2020-07-19 23:41:58 +00:00
|
|
|
#ifndef FISH_JOB_GROUP_H
|
|
|
|
#define FISH_JOB_GROUP_H
|
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
|
|
|
#include <termios.h>
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "global_safety.h"
|
|
|
|
|
|
|
|
/// A job ID, corresponding to what is printed in 'jobs'.
|
|
|
|
/// 1 is the first valid job ID.
|
|
|
|
using job_id_t = int;
|
|
|
|
|
Implement cancel groups
This concerns how "internal job groups" know to stop executing when an
external command receives a "cancel signal" (SIGINT or SIGQUIT). For
example:
while true
sleep 1
end
The intent is that if any 'sleep' exits from a cancel signal, then so would
the while loop. This is why you can hit control-C to end the loop even
if the SIGINT is delivered to sleep and not fish.
Here the 'while' loop is considered an "internal job group" (no separate
pgid, bash would not fork) while each 'sleep' is a separate external
command with its own job group, pgroup, etc. Prior to this change, after
running each 'sleep', parse_execution_context_t would check to see if its
exit status was a cancel signal, and if so, stash it into an int that the
cancel checker would check. But this became unwieldy: now there were three
sources of cancellation signals (that int, the job group, and fish itself).
Introduce the notion of a "cancellation group" which is a set of job
groups that should cancel together. Even though the while loop and sleep
are in different job groups, they are in the same cancellation group. When
any job gets a SIGINT or SIGQUIT, it marks that signal in its cancellation
group, which prevents running new jobs in that group.
This reduces the number of signals to check from 3 to 2; eventually we can
teach cancellation groups how to check fish's own signals and then it will
just be 1.
2020-09-02 22:06:05 +00:00
|
|
|
/// A cancellation group is "a set of jobs that should cancel together." It's effectively just a
|
|
|
|
/// shared pointer to a bool which latches to true on cancel.
|
|
|
|
/// For example, in `begin ; true ; end | false`, we have two jobs: the outer pipline and the inner
|
|
|
|
/// 'true'. These share a cancellation group.
|
|
|
|
/// Note this is almost but not quite a job group. A job group is a "a set of jobs which share a
|
|
|
|
/// pgid" but cancellation groups may be bigger. For example in `begin ; sleep 1; sleep 2; end` we
|
|
|
|
/// have that 'begin' is an internal group (a simple function/block execution) without a pgid,
|
|
|
|
/// while each 'sleep' will be a different job, with its own pgid, and so be in a different job
|
|
|
|
/// group. But all share a cancellation group.
|
|
|
|
/// Note that a background job will always get a new cancellation group.
|
|
|
|
/// Cancellation groups must be thread safe.
|
|
|
|
class cancellation_group_t {
|
|
|
|
public:
|
|
|
|
/// \return true if we should cancel.
|
|
|
|
bool should_cancel() const { return get_cancel_signal() != 0; }
|
|
|
|
|
|
|
|
/// \return the signal indicating cancellation, or 0 if none.
|
|
|
|
int get_cancel_signal() const { return signal_; }
|
|
|
|
|
|
|
|
/// If we have not already cancelled, then trigger cancellation with the given signal.
|
|
|
|
void cancel_with_signal(int signal) {
|
|
|
|
assert(signal > 0 && "Invalid cancel signal");
|
|
|
|
signal_.compare_exchange(0, signal);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper to return a new group.
|
|
|
|
static std::shared_ptr<cancellation_group_t> create() {
|
|
|
|
return std::make_shared<cancellation_group_t>();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// If we cancelled from a signal, return that signal, else 0.
|
|
|
|
relaxed_atomic_t<int> signal_{0};
|
|
|
|
};
|
|
|
|
using cancellation_group_ref_t = std::shared_ptr<cancellation_group_t>;
|
|
|
|
|
2020-07-19 23:41:58 +00:00
|
|
|
/// job_group_t is conceptually similar to the idea of a process group. It represents data which
|
|
|
|
/// is shared among all of the "subjobs" that may be spawned by a single job.
|
|
|
|
/// For example, two fish functions in a pipeline may themselves spawn multiple jobs, but all will
|
|
|
|
/// share the same job group.
|
|
|
|
/// There is also a notion of a "internal" job group. Internal groups are used when executing a
|
|
|
|
/// foreground function or block with no pipeline. These are not jobs as the user understands them -
|
|
|
|
/// they do not consume a job ID, they do not show up in job lists, and they do not have a pgid
|
|
|
|
/// because they contain no external procs. Note that job_group_t is intended to eventually be
|
|
|
|
/// shared between threads, and so must be thread safe.
|
|
|
|
class job_t;
|
|
|
|
class job_group_t;
|
|
|
|
using job_group_ref_t = std::shared_ptr<job_group_t>;
|
|
|
|
|
|
|
|
class job_group_t {
|
|
|
|
public:
|
2022-02-19 18:05:50 +00:00
|
|
|
/// \return whether this group wants job control.
|
|
|
|
bool wants_job_control() const { return job_control_; }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// \return if this job group should own the terminal when it runs.
|
|
|
|
bool wants_terminal() const { return wants_terminal_ && is_foreground(); }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
/// \return whether we are currently the foreground group.
|
|
|
|
bool is_foreground() const { return is_foreground_; }
|
|
|
|
|
|
|
|
/// Mark whether we are in the foreground.
|
|
|
|
void set_is_foreground(bool flag) { is_foreground_ = flag; }
|
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// \return the command which produced this job tree.
|
|
|
|
const wcstring &get_command() const { return command_; }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
/// \return the job ID, or -1 if none.
|
2022-02-19 18:05:50 +00:00
|
|
|
job_id_t get_job_id() const { return job_id_; }
|
|
|
|
|
|
|
|
/// \return whether we have a valid job ID. "Simple block" groups like function calls do not.
|
|
|
|
bool has_job_id() const { return job_id_ > 0; }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
/// Get the cancel signal, or 0 if none.
|
Implement cancel groups
This concerns how "internal job groups" know to stop executing when an
external command receives a "cancel signal" (SIGINT or SIGQUIT). For
example:
while true
sleep 1
end
The intent is that if any 'sleep' exits from a cancel signal, then so would
the while loop. This is why you can hit control-C to end the loop even
if the SIGINT is delivered to sleep and not fish.
Here the 'while' loop is considered an "internal job group" (no separate
pgid, bash would not fork) while each 'sleep' is a separate external
command with its own job group, pgroup, etc. Prior to this change, after
running each 'sleep', parse_execution_context_t would check to see if its
exit status was a cancel signal, and if so, stash it into an int that the
cancel checker would check. But this became unwieldy: now there were three
sources of cancellation signals (that int, the job group, and fish itself).
Introduce the notion of a "cancellation group" which is a set of job
groups that should cancel together. Even though the while loop and sleep
are in different job groups, they are in the same cancellation group. When
any job gets a SIGINT or SIGQUIT, it marks that signal in its cancellation
group, which prevents running new jobs in that group.
This reduces the number of signals to check from 3 to 2; eventually we can
teach cancellation groups how to check fish's own signals and then it will
just be 1.
2020-09-02 22:06:05 +00:00
|
|
|
int get_cancel_signal() const { return cancel_group->get_cancel_signal(); }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
/// Mark that a process in this group got a signal, and so should cancel.
|
Implement cancel groups
This concerns how "internal job groups" know to stop executing when an
external command receives a "cancel signal" (SIGINT or SIGQUIT). For
example:
while true
sleep 1
end
The intent is that if any 'sleep' exits from a cancel signal, then so would
the while loop. This is why you can hit control-C to end the loop even
if the SIGINT is delivered to sleep and not fish.
Here the 'while' loop is considered an "internal job group" (no separate
pgid, bash would not fork) while each 'sleep' is a separate external
command with its own job group, pgroup, etc. Prior to this change, after
running each 'sleep', parse_execution_context_t would check to see if its
exit status was a cancel signal, and if so, stash it into an int that the
cancel checker would check. But this became unwieldy: now there were three
sources of cancellation signals (that int, the job group, and fish itself).
Introduce the notion of a "cancellation group" which is a set of job
groups that should cancel together. Even though the while loop and sleep
are in different job groups, they are in the same cancellation group. When
any job gets a SIGINT or SIGQUIT, it marks that signal in its cancellation
group, which prevents running new jobs in that group.
This reduces the number of signals to check from 3 to 2; eventually we can
teach cancellation groups how to check fish's own signals and then it will
just be 1.
2020-09-02 22:06:05 +00:00
|
|
|
void cancel_with_signal(int sig) { cancel_group->cancel_with_signal(sig); }
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// The cancellation group. This is never null.
|
|
|
|
const cancellation_group_ref_t cancel_group{};
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
/// If set, the saved terminal modes of this job. This needs to be saved so that we can restore
|
2022-02-19 18:05:50 +00:00
|
|
|
/// the terminal to the same state when resuming a stopped job.
|
2020-07-19 23:41:58 +00:00
|
|
|
maybe_t<struct termios> tmodes{};
|
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// Set the pgid for this job group, latching it to this value.
|
|
|
|
/// This should only be called if job control is active for this group.
|
|
|
|
/// The pgid should not already have been set, and should be different from fish's pgid.
|
|
|
|
/// Of course this does not keep the pgid alive by itself.
|
|
|
|
void set_pgid(pid_t pgid);
|
Implement cancel groups
This concerns how "internal job groups" know to stop executing when an
external command receives a "cancel signal" (SIGINT or SIGQUIT). For
example:
while true
sleep 1
end
The intent is that if any 'sleep' exits from a cancel signal, then so would
the while loop. This is why you can hit control-C to end the loop even
if the SIGINT is delivered to sleep and not fish.
Here the 'while' loop is considered an "internal job group" (no separate
pgid, bash would not fork) while each 'sleep' is a separate external
command with its own job group, pgroup, etc. Prior to this change, after
running each 'sleep', parse_execution_context_t would check to see if its
exit status was a cancel signal, and if so, stash it into an int that the
cancel checker would check. But this became unwieldy: now there were three
sources of cancellation signals (that int, the job group, and fish itself).
Introduce the notion of a "cancellation group" which is a set of job
groups that should cancel together. Even though the while loop and sleep
are in different job groups, they are in the same cancellation group. When
any job gets a SIGINT or SIGQUIT, it marks that signal in its cancellation
group, which prevents running new jobs in that group.
This reduces the number of signals to check from 3 to 2; eventually we can
teach cancellation groups how to check fish's own signals and then it will
just be 1.
2020-09-02 22:06:05 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// Get the pgid. This never returns fish's pgid.
|
|
|
|
maybe_t<pid_t> get_pgid() const { return pgid_; }
|
|
|
|
|
|
|
|
/// Construct a group for a job that will live internal to fish, optionally claiming a job ID.
|
|
|
|
static job_group_ref_t create(wcstring command, cancellation_group_ref_t cg, bool wants_job_id);
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
/// Construct a group for a job which will assign its first process as pgroup leader.
|
|
|
|
static job_group_ref_t create_with_job_control(wcstring command, cancellation_group_ref_t cg,
|
|
|
|
bool wants_terminal);
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
~job_group_t();
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
private:
|
|
|
|
job_group_t(wcstring command, cancellation_group_ref_t cg, job_id_t job_id,
|
|
|
|
bool job_control = false, bool wants_terminal = false);
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
// Whether job control is enabled.
|
|
|
|
// If this is set, then the first process in the root job must be external.
|
|
|
|
// It will become the process group leader.
|
|
|
|
const bool job_control_;
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
// Whether we should tcsetpgrp to the job when it runs in the foreground.
|
|
|
|
const bool wants_terminal_;
|
2020-07-19 23:41:58 +00:00
|
|
|
|
|
|
|
// Whether we are in the foreground, meaning that the user is waiting for this.
|
|
|
|
relaxed_atomic_bool_t is_foreground_{};
|
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
// The pgid leading our group. This is only ever set if job_control_ is true.
|
|
|
|
// This is never fish's pgid.
|
|
|
|
maybe_t<pid_t> pgid_{};
|
2020-07-19 23:41:58 +00:00
|
|
|
|
2022-02-19 18:05:50 +00:00
|
|
|
// The original command which produced this job tree.
|
|
|
|
const wcstring command_;
|
|
|
|
|
|
|
|
/// Our job ID. -1 if none.
|
|
|
|
const job_id_t job_id_;
|
2020-07-19 23:41:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|