diff options
Diffstat (limited to 'src/nvim/os/job.c')
-rw-r--r-- | src/nvim/os/job.c | 467 |
1 files changed, 0 insertions, 467 deletions
diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c deleted file mode 100644 index f9bde21361..0000000000 --- a/src/nvim/os/job.c +++ /dev/null @@ -1,467 +0,0 @@ -#include <stdint.h> -#include <stdbool.h> - -#include <uv.h> - -#include "nvim/os/uv_helpers.h" -#include "nvim/os/job.h" -#include "nvim/os/job_defs.h" -#include "nvim/os/job_private.h" -#include "nvim/os/pty_process.h" -#include "nvim/os/rstream.h" -#include "nvim/os/rstream_defs.h" -#include "nvim/os/wstream.h" -#include "nvim/os/wstream_defs.h" -#include "nvim/os/event.h" -#include "nvim/os/event_defs.h" -#include "nvim/os/time.h" -#include "nvim/vim.h" -#include "nvim/memory.h" - -#ifdef HAVE_SYS_WAIT_H -# include <sys/wait.h> -#endif - -// {SIGNAL}_TIMEOUT is the time (in nanoseconds) that a job has to cleanly exit -// before we send SIGNAL to it -#define TERM_TIMEOUT 1000000000 -#define KILL_TIMEOUT (TERM_TIMEOUT * 2) -#define JOB_BUFFER_SIZE 0xFFFF - -#define close_job_stream(job, stream, type) \ - do { \ - if (job->stream) { \ - type##stream_free(job->stream); \ - job->stream = NULL; \ - if (!uv_is_closing((uv_handle_t *)job->proc_std##stream)) { \ - uv_close((uv_handle_t *)job->proc_std##stream, close_cb); \ - } \ - } \ - } while (0) - -#define close_job_in(job) close_job_stream(job, in, w) -#define close_job_out(job) close_job_stream(job, out, r) -#define close_job_err(job) close_job_stream(job, err, r) - -Job *table[MAX_RUNNING_JOBS] = {NULL}; -size_t stop_requests = 0; -uv_timer_t job_stop_timer; -uv_signal_t schld; - -// Some helpers shared in this module - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/job.c.generated.h" -#endif -// Callbacks for libuv - -/// Initializes job control resources -void job_init(void) -{ - uv_disable_stdio_inheritance(); - uv_timer_init(uv_default_loop(), &job_stop_timer); - uv_signal_init(uv_default_loop(), &schld); - uv_signal_start(&schld, chld_handler, SIGCHLD); -} - -/// Releases job control resources and terminates running jobs -void job_teardown(void) -{ - // Stop all jobs - for (int i = 0; i < MAX_RUNNING_JOBS; i++) { - Job *job; - if ((job = table[i]) != NULL) { - uv_kill(job->pid, SIGTERM); - job->term_sent = true; - job_stop(job); - } - } - - // Wait until all jobs are closed - event_poll_until(-1, !stop_requests); - uv_signal_stop(&schld); - uv_close((uv_handle_t *)&schld, NULL); - // Close the timer - uv_close((uv_handle_t *)&job_stop_timer, NULL); -} - -/// Tries to start a new job. -/// -/// @param[out] status The job id if the job started successfully, 0 if the job -/// table is full, -1 if the program could not be executed. -/// @return The job pointer if the job started successfully, NULL otherwise -Job *job_start(JobOptions opts, int *status) -{ - int i; - Job *job; - - // Search for a free slot in the table - for (i = 0; i < MAX_RUNNING_JOBS; i++) { - if (table[i] == NULL) { - break; - } - } - - if (i == MAX_RUNNING_JOBS) { - // No free slots - shell_free_argv(opts.argv); - *status = 0; - return NULL; - } - - job = xmalloc(sizeof(Job)); - // Initialize - job->id = i + 1; - *status = job->id; - job->status = -1; - job->refcount = 1; - job->stopped_time = 0; - job->term_sent = false; - job->in = NULL; - job->out = NULL; - job->err = NULL; - job->opts = opts; - job->closed = false; - - process_init(job); - - if (opts.writable) { - handle_set_job((uv_handle_t *)job->proc_stdin, job); - job->refcount++; - } - - if (opts.stdout_cb) { - handle_set_job((uv_handle_t *)job->proc_stdout, job); - job->refcount++; - } - - if (opts.stderr_cb) { - handle_set_job((uv_handle_t *)job->proc_stderr, job); - job->refcount++; - } - - // Spawn the job - if (!process_spawn(job)) { - if (opts.writable) { - uv_close((uv_handle_t *)job->proc_stdin, close_cb); - } - if (opts.stdout_cb) { - uv_close((uv_handle_t *)job->proc_stdout, close_cb); - } - if (opts.stderr_cb) { - uv_close((uv_handle_t *)job->proc_stderr, close_cb); - } - process_close(job); - event_poll(0); - // Manually invoke the close_cb to free the job resources - *status = -1; - return NULL; - } - - if (opts.writable) { - job->in = wstream_new(opts.maxmem); - wstream_set_stream(job->in, job->proc_stdin); - } - - // Start the readable streams - if (opts.stdout_cb) { - job->out = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job); - rstream_set_stream(job->out, job->proc_stdout); - rstream_start(job->out); - } - - if (opts.stderr_cb) { - job->err = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job); - rstream_set_stream(job->err, job->proc_stderr); - rstream_start(job->err); - } - // Save the job to the table - table[i] = job; - - return job; -} - -/// Finds a job instance by id -/// -/// @param id The job id -/// @return the Job instance -Job *job_find(int id) -{ - Job *job; - - if (id <= 0 || id > MAX_RUNNING_JOBS || !(job = table[id - 1]) - || job->stopped_time) { - return NULL; - } - - return job; -} - -/// Terminates a job. This is a non-blocking operation, but if the job exists -/// it's guaranteed to succeed(SIGKILL will eventually be sent) -/// -/// @param job The Job instance -void job_stop(Job *job) -{ - if (job->stopped_time) { - return; - } - - job->stopped_time = os_hrtime(); - if (job->opts.pty) { - // close all streams for pty jobs to send SIGHUP to the process - job_close_streams(job); - pty_process_close_master(job); - } else { - // Close the job's stdin. If the job doesn't close its own stdout/stderr, - // they will be closed when the job exits(possibly due to being terminated - // after a timeout) - close_job_in(job); - } - - if (!stop_requests++) { - // When there's at least one stop request pending, start a timer that - // will periodically check if a signal should be send to a to the job - DLOG("Starting job kill timer"); - uv_timer_start(&job_stop_timer, job_stop_timer_cb, 100, 100); - } -} - -/// job_wait - synchronously wait for a job to finish -/// -/// @param job The job instance -/// @param ms Number of milliseconds to wait, 0 for not waiting, -1 for -/// waiting until the job quits. -/// @return returns the status code of the exited job. -1 if the job is -/// still running and the `timeout` has expired. Note that this is -/// indistinguishable from the process returning -1 by itself. Which -/// is possible on some OS. Returns -2 if the job was interrupted. -int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL -{ - // The default status is -1, which represents a timeout - int status = -1; - bool interrupted = false; - - // Increase refcount to stop the job from being freed before we have a - // chance to get the status. - job->refcount++; - event_poll_until(ms, - // Until... - got_int || // interrupted by the user - job->refcount == 1); // job exited - - // we'll assume that a user frantically hitting interrupt doesn't like - // the current job. Signal that it has to be killed. - if (got_int) { - interrupted = true; - got_int = false; - job_stop(job); - if (ms == -1) { - // We can only return, if all streams/handles are closed and the job - // exited. - event_poll_until(-1, job->refcount == 1); - } else { - event_poll(0); - } - } - - if (job->refcount == 1) { - // Job exited, collect status and manually invoke close_cb to free the job - // resources - status = interrupted ? -2 : job->status; - job_close_streams(job); - job_decref(job); - } else { - job->refcount--; - } - - return status; -} - -/// Close the pipe used to write to the job. -/// -/// This can be used for example to indicate to the job process that no more -/// input is coming, and that it should shut down cleanly. -/// -/// It has no effect when the input pipe doesn't exist or was already -/// closed. -/// -/// @param job The job instance -void job_close_in(Job *job) FUNC_ATTR_NONNULL_ALL -{ - close_job_in(job); -} - -// Close the job stdout stream. -void job_close_out(Job *job) FUNC_ATTR_NONNULL_ALL -{ - close_job_out(job); -} - -// Close the job stderr stream. -void job_close_err(Job *job) FUNC_ATTR_NONNULL_ALL -{ - close_job_out(job); -} - -/// All writes that complete after calling this function will be reported -/// to `cb`. -/// -/// Use this function to be notified about the status of an in-flight write. -/// -/// @see {wstream_set_write_cb} -/// -/// @param job The job instance -/// @param cb The function that will be called on write completion or -/// failure. It will be called with the job as the `data` argument. -void job_write_cb(Job *job, wstream_cb cb) FUNC_ATTR_NONNULL_ALL -{ - wstream_set_write_cb(job->in, cb, job); -} - -/// Writes data to the job's stdin. This is a non-blocking operation, it -/// returns when the write request was sent. -/// -/// @param job The Job instance -/// @param buffer The buffer which contains the data to be written -/// @return true if the write request was successfully sent, false if writing -/// to the job stream failed (possibly because the OS buffer is full) -bool job_write(Job *job, WBuffer *buffer) -{ - return wstream_write(job->in, buffer); -} - -/// Get the job id -/// -/// @param job A pointer to the job -/// @return The job id -int job_id(Job *job) -{ - return job->id; -} - -// Get the job pid -int job_pid(Job *job) -{ - return job->pid; -} - -/// Get data associated with a job -/// -/// @param job A pointer to the job -/// @return The job data -void *job_data(Job *job) -{ - return job->opts.data; -} - -/// Resize the window for a pty job -bool job_resize(Job *job, uint16_t width, uint16_t height) -{ - if (!job->opts.pty) { - return false; - } - pty_process_resize(job, width, height); - return true; -} - -void job_close_streams(Job *job) -{ - close_job_in(job); - close_job_out(job); - close_job_err(job); -} - -JobOptions *job_opts(Job *job) -{ - return &job->opts; -} - -/// Iterates the table, sending SIGTERM to stopped jobs and SIGKILL to those -/// that didn't die from SIGTERM after a while(exit_timeout is 0). -static void job_stop_timer_cb(uv_timer_t *handle) -{ - Job *job; - uint64_t now = os_hrtime(); - - for (size_t i = 0; i < MAX_RUNNING_JOBS; i++) { - if ((job = table[i]) == NULL || !job->stopped_time) { - continue; - } - - uint64_t elapsed = now - job->stopped_time; - - if (!job->term_sent && elapsed >= TERM_TIMEOUT) { - ILOG("Sending SIGTERM to job(id: %d)", job->id); - uv_kill(job->pid, SIGTERM); - job->term_sent = true; - } else if (elapsed >= KILL_TIMEOUT) { - ILOG("Sending SIGKILL to job(id: %d)", job->id); - uv_kill(job->pid, SIGKILL); - process_close(job); - } - } -} - -// Wraps the call to std{out,err}_cb and emits a JobExit event if necessary. -static void read_cb(RStream *rstream, RBuffer *buf, void *data, bool eof) -{ - Job *job = data; - - if (rstream == job->out) { - job->opts.stdout_cb(rstream, buf, data, eof); - if (eof) { - close_job_out(job); - } - } else { - job->opts.stderr_cb(rstream, buf, data, eof); - if (eof) { - close_job_err(job); - } - } -} - -static void close_cb(uv_handle_t *handle) -{ - job_decref(handle_get_job(handle)); -} - -static void job_exited(Event event) -{ - Job *job = event.data; - process_close(job); -} - -static void chld_handler(uv_signal_t *handle, int signum) -{ - int stat = 0; - int pid; - - do { - pid = waitpid(-1, &stat, WNOHANG); - } while (pid < 0 && errno == EINTR); - - if (pid <= 0) { - return; - } - - Job *job = NULL; - // find the job corresponding to the exited pid - for (int i = 0; i < MAX_RUNNING_JOBS; i++) { - if ((job = table[i]) != NULL && job->pid == pid) { - if (WIFEXITED(stat)) { - job->status = WEXITSTATUS(stat); - } else if (WIFSIGNALED(stat)) { - job->status = WTERMSIG(stat); - } - if (exiting) { - // don't enqueue more events when exiting - process_close(job); - } else { - event_push((Event) {.handler = job_exited, .data = job}, false); - } - break; - } - } -} - |