mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-23 10:15:08 +00:00
3f585cddfc
This is a cleanup of job groups, rationalizing a bunch of stuff. Some notable changes (none user-visible hopefully): 1. Previously, if a job group wanted a pgid, then we would assign it to the first process to run in the job group. Now we deliberately mark which process will own the pgroup, via a new `leads_pgrp` flag in process_t. This eliminates a source of ambiguity. 2. Previously, if a job were run inside fish's pgroup, we would set fish's pgroup as the group of the job. But this meant we had to check if the job had fish's pgroup in lots of places, for example when calling tcsetpgrp. Now a job group only has a pgrp if that pgrp is external (i.e. the job is under job control).
145 lines
6 KiB
C++
145 lines
6 KiB
C++
#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;
|
|
|
|
/// 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>;
|
|
|
|
/// 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:
|
|
/// \return whether this group wants job control.
|
|
bool wants_job_control() const { return job_control_; }
|
|
|
|
/// \return if this job group should own the terminal when it runs.
|
|
bool wants_terminal() const { return wants_terminal_ && is_foreground(); }
|
|
|
|
/// \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; }
|
|
|
|
/// \return the command which produced this job tree.
|
|
const wcstring &get_command() const { return command_; }
|
|
|
|
/// \return the job ID, or -1 if none.
|
|
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; }
|
|
|
|
/// Get the cancel signal, or 0 if none.
|
|
int get_cancel_signal() const { return cancel_group->get_cancel_signal(); }
|
|
|
|
/// Mark that a process in this group got a signal, and so should cancel.
|
|
void cancel_with_signal(int sig) { cancel_group->cancel_with_signal(sig); }
|
|
|
|
/// The cancellation group. This is never null.
|
|
const cancellation_group_ref_t cancel_group{};
|
|
|
|
/// If set, the saved terminal modes of this job. This needs to be saved so that we can restore
|
|
/// the terminal to the same state when resuming a stopped job.
|
|
maybe_t<struct termios> tmodes{};
|
|
|
|
/// 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);
|
|
|
|
/// 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);
|
|
|
|
/// 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);
|
|
|
|
~job_group_t();
|
|
|
|
private:
|
|
job_group_t(wcstring command, cancellation_group_ref_t cg, job_id_t job_id,
|
|
bool job_control = false, bool wants_terminal = false);
|
|
|
|
// 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_;
|
|
|
|
// Whether we should tcsetpgrp to the job when it runs in the foreground.
|
|
const bool wants_terminal_;
|
|
|
|
// Whether we are in the foreground, meaning that the user is waiting for this.
|
|
relaxed_atomic_bool_t is_foreground_{};
|
|
|
|
// 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_{};
|
|
|
|
// The original command which produced this job tree.
|
|
const wcstring command_;
|
|
|
|
/// Our job ID. -1 if none.
|
|
const job_id_t job_id_;
|
|
};
|
|
|
|
#endif
|