job: Fix process cleanup using SIGCHLD/waitpid

Add a SIGCHLD handler for cleaning up pty processes passing the WNOHANG flag. It
may also be used to cleanup processes spawned with uv_spawn.
This commit is contained in:
Thiago de Arruda 2015-02-26 23:29:08 -03:00
parent 0c6fdd5251
commit a6fb511c7f
2 changed files with 64 additions and 43 deletions

View File

@ -1,6 +1,8 @@
#include <stdint.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <uv.h>
#include "nvim/os/uv_helpers.h"
@ -43,6 +45,7 @@
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
@ -56,6 +59,8 @@ 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
@ -73,6 +78,8 @@ void job_teardown(void)
// 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);
}
@ -200,10 +207,16 @@ void job_stop(Job *job)
}
job->stopped_time = os_hrtime();
// Close the job's stdin. If the job doesn't close it's own stdout/stderr,
// they will be closed when the job exits(possibly due to being terminated
// after a timeout)
close_job_in(job);
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
@ -312,6 +325,12 @@ 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
@ -331,6 +350,13 @@ bool job_resize(Job *job, uint16_t width, uint16_t height)
return true;
}
void job_close_streams(Job *job)
{
close_job_in(job);
close_job_out(job);
close_job_err(job);
}
/// 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)
@ -375,14 +401,41 @@ static void read_cb(RStream *rstream, void *data, bool eof)
}
}
void job_close_streams(Job *job)
{
close_job_in(job);
close_job_out(job);
close_job_err(job);
}
static void close_cb(uv_handle_t *handle)
{
job_decref(handle_get_job(handle));
}
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;
}
if (WIFSTOPPED(stat) || WIFCONTINUED(stat)) {
// Only care for processes that exited
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);
}
process_close(job);
break;
}
}
}

View File

@ -34,7 +34,6 @@
typedef struct {
struct winsize winsize;
uv_pipe_t proc_stdin, proc_stdout, proc_stderr;
uv_signal_t schld;
int tty_fd;
} PtyProcess;
@ -136,9 +135,6 @@ bool pty_process_spawn(Job *job) FUNC_ATTR_NONNULL_ALL
goto error;
}
uv_signal_init(uv_default_loop(), &ptyproc->schld);
uv_signal_start(&ptyproc->schld, chld_handler, SIGCHLD);
ptyproc->schld.data = job;
ptyproc->tty_fd = master;
job->pid = pid;
return true;
@ -162,9 +158,6 @@ error:
void pty_process_close(Job *job) FUNC_ATTR_NONNULL_ALL
{
PtyProcess *ptyproc = job->process;
uv_signal_stop(&ptyproc->schld);
uv_close((uv_handle_t *)&ptyproc->schld, NULL);
pty_process_close_master(job);
job_close_streams(job);
job_decref(job);
@ -207,31 +200,6 @@ static void init_child(Job *job) FUNC_ATTR_NONNULL_ALL
fprintf(stderr, "execvp failed: %s\n", strerror(errno));
}
static void chld_handler(uv_signal_t *handle, int signum) FUNC_ATTR_NONNULL_ALL
{
Job *job = handle->data;
int stat = 0;
if (waitpid(job->pid, &stat, 0) < 0) {
fprintf(stderr, "Waiting for pid %d failed: %s\n", job->pid,
strerror(errno));
return;
}
if (WIFSTOPPED(stat) || WIFCONTINUED(stat)) {
// Did not exit
return;
}
if (WIFEXITED(stat)) {
job->status = WEXITSTATUS(stat);
} else if (WIFSIGNALED(stat)) {
job->status = WTERMSIG(stat);
}
pty_process_close(job);
}
static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
{
memset(termios, 0, sizeof(struct termios));