2020-07-20 07:41:58 +08: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;
|
|
|
|
|
|
|
|
/// 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-20 02:05:50 +08:00
|
|
|
/// \return whether this group wants job control.
|
|
|
|
bool wants_job_control() const { return job_control_; }
|
2020-07-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
/// \return if this job group should own the terminal when it runs.
|
|
|
|
bool wants_terminal() const { return wants_terminal_ && is_foreground(); }
|
2020-07-20 07:41:58 +08: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-20 02:05:50 +08:00
|
|
|
/// \return the command which produced this job tree.
|
|
|
|
const wcstring &get_command() const { return command_; }
|
2020-07-20 07:41:58 +08:00
|
|
|
|
|
|
|
/// \return the job ID, or -1 if none.
|
2022-02-20 02:05:50 +08: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-20 07:41:58 +08:00
|
|
|
|
|
|
|
/// Get the cancel signal, or 0 if none.
|
2022-03-21 05:32:18 +08:00
|
|
|
int get_cancel_signal() const { return signal_; }
|
2020-07-20 07:41:58 +08:00
|
|
|
|
|
|
|
/// Mark that a process in this group got a signal, and so should cancel.
|
2022-03-21 05:32:18 +08:00
|
|
|
void cancel_with_signal(int signal) {
|
|
|
|
assert(signal > 0 && "Invalid cancel signal");
|
|
|
|
signal_.compare_exchange(0, signal);
|
|
|
|
}
|
2020-07-20 07:41:58 +08:00
|
|
|
|
|
|
|
/// If set, the saved terminal modes of this job. This needs to be saved so that we can restore
|
2022-02-20 02:05:50 +08:00
|
|
|
/// the terminal to the same state when resuming a stopped job.
|
2020-07-20 07:41:58 +08:00
|
|
|
maybe_t<struct termios> tmodes{};
|
|
|
|
|
2022-02-20 02:05:50 +08: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-03 06:06:05 +08:00
|
|
|
|
2022-02-20 02:05:50 +08: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.
|
2022-03-21 05:32:18 +08:00
|
|
|
static job_group_ref_t create(wcstring command, bool wants_job_id);
|
2020-07-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
/// Construct a group for a job which will assign its first process as pgroup leader.
|
2022-03-21 05:32:18 +08:00
|
|
|
static job_group_ref_t create_with_job_control(wcstring command, bool wants_terminal);
|
2020-07-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
~job_group_t();
|
2020-07-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
private:
|
2022-03-21 05:32:18 +08:00
|
|
|
job_group_t(wcstring command, job_id_t job_id, bool job_control = false,
|
|
|
|
bool wants_terminal = false);
|
2020-07-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08: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-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
// Whether we should tcsetpgrp to the job when it runs in the foreground.
|
|
|
|
const bool wants_terminal_;
|
2020-07-20 07:41:58 +08:00
|
|
|
|
|
|
|
// Whether we are in the foreground, meaning that the user is waiting for this.
|
|
|
|
relaxed_atomic_bool_t is_foreground_{};
|
|
|
|
|
2022-02-20 02:05:50 +08: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-20 07:41:58 +08:00
|
|
|
|
2022-02-20 02:05:50 +08:00
|
|
|
// The original command which produced this job tree.
|
|
|
|
const wcstring command_;
|
|
|
|
|
|
|
|
/// Our job ID. -1 if none.
|
|
|
|
const job_id_t job_id_;
|
2022-03-21 05:32:18 +08:00
|
|
|
|
|
|
|
/// The signal causing us the group to cancel, or 0.
|
|
|
|
relaxed_atomic_t<int> signal_{0};
|
2020-07-20 07:41:58 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|