1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-11-21 07:15:49 +00:00

daemon: stop rebuilding the kqueue every restart of the child

We populate the kqueue with all of four kevents: three signal handlers and
one for read of the child pipe.  Every time we start the child, we rebuild
this kqueue from scratch for the child and tear it down before we exit and
check if we need to restart the child.  As a consequence, we effectively
drop any of the signals we're interested in between restarts.

Push the kqueue out into the daemon state to avoid losing any signal events
in the process, and reimplement the restart timer in terms of kqueue timers.
The pipe read event will be automatically deleted upon last close, which
leaves us with only the signal events that really get retained between
restarts of the child.

PR:		277959
Reviewed by:	des, markj
Differential Revision:	https://reviews.freebsd.org/D47004
This commit is contained in:
Kyle Evans 2024-11-19 13:51:27 -06:00
parent aa8722cc18
commit bc1dfc316a

View File

@ -86,6 +86,7 @@ struct daemon_state {
int pipe_rd;
int pipe_wr;
int keep_cur_workdir;
int kqueue_fd;
int restart_delay;
int stdmask;
int syslog_priority;
@ -113,6 +114,7 @@ static void daemon_terminate(struct daemon_state *);
static void daemon_exec(struct daemon_state *);
static bool daemon_is_child_dead(struct daemon_state *);
static void daemon_set_child_pipe(struct daemon_state *);
static int daemon_setup_kqueue(void);
static int pidfile_truncate(struct pidfh *);
@ -344,6 +346,8 @@ main(int argc, char *argv[])
/* Write out parent pidfile if needed. */
pidfile_write(state.parent_pidfh);
state.kqueue_fd = daemon_setup_kqueue();
do {
state.mode = MODE_SUPERVISE;
daemon_eventloop(&state);
@ -408,27 +412,13 @@ daemon_eventloop(struct daemon_state *state)
state->pipe_rd = pipe_fd[0];
state->pipe_wr = pipe_fd[1];
kq = kqueuex(KQUEUE_CLOEXEC);
kq = state->kqueue_fd;
EV_SET(&event, state->pipe_rd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0,
NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
EV_SET(&event, SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
EV_SET(&event, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
EV_SET(&event, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
memset(&event, 0, sizeof(struct kevent));
/* Spawn a child to exec the command. */
@ -521,11 +511,12 @@ daemon_eventloop(struct daemon_state *state)
}
continue;
default:
assert(0 && "Unexpected kevent filter type");
continue;
}
}
close(kq);
/* EVFILT_READ kqueue filter goes away here. */
close(state->pipe_rd);
state->pipe_rd = -1;
@ -539,19 +530,76 @@ daemon_eventloop(struct daemon_state *state)
}
}
/*
* Note that daemon_sleep() should not be called with anything but the signal
* events in the kqueue without further consideration.
*/
static void
daemon_sleep(struct daemon_state *state)
{
struct timespec ts = { state->restart_delay, 0 };
struct kevent event = { 0 };
int ret;
assert(state->pipe_rd == -1);
assert(state->pipe_wr == -1);
if (!state->restart_enabled) {
return;
}
while (nanosleep(&ts, &ts) == -1) {
if (errno != EINTR) {
err(1, "nanosleep");
EV_SET(&event, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS,
state->restart_delay, NULL);
if (kevent(state->kqueue_fd, &event, 1, NULL, 0, NULL) == -1) {
err(1, "failed to register timer");
}
for (;;) {
ret = kevent(state->kqueue_fd, NULL, 0, &event, 1, NULL);
if (ret == -1) {
if (errno != EINTR) {
err(1, "kevent");
}
continue;
}
/*
* Any other events being raised are indicative of a problem
* that we need to investigate. Most likely being that
* something was not cleaned up from the eventloop.
*/
assert(event.filter == EVFILT_TIMER ||
event.filter == EVFILT_SIGNAL);
if (event.filter == EVFILT_TIMER) {
/* Break's over, back to work. */
break;
}
/* Process any pending signals. */
switch (event.ident) {
case SIGTERM:
/*
* We could disarm the timer, but we'll be terminating
* promptly anyways.
*/
state->restart_enabled = false;
return;
case SIGHUP:
if (state->log_reopen && state->output_fd >= 0) {
reopen_log(state);
}
break;
case SIGCHLD:
default:
/* Discard */
break;
}
}
/* SIGTERM should've returned immediately. */
assert(state->restart_enabled);
}
static void
@ -745,6 +793,7 @@ daemon_state_init(struct daemon_state *state)
.pipe_rd = -1,
.pipe_wr = -1,
.keep_cur_workdir = 1,
.kqueue_fd = -1,
.restart_delay = 1,
.stdmask = STDOUT_FILENO | STDERR_FILENO,
.syslog_enabled = false,
@ -765,6 +814,9 @@ daemon_terminate(struct daemon_state *state)
{
assert(state != NULL);
if (state->kqueue_fd >= 0) {
close(state->kqueue_fd);
}
if (state->output_fd >= 0) {
close(state->output_fd);
}
@ -835,6 +887,35 @@ daemon_set_child_pipe(struct daemon_state *state)
close(state->pipe_rd);
}
static int
daemon_setup_kqueue(void)
{
int kq;
struct kevent event = { 0 };
kq = kqueuex(KQUEUE_CLOEXEC);
if (kq == -1) {
err(EXIT_FAILURE, "kqueue");
}
EV_SET(&event, SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
EV_SET(&event, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
EV_SET(&event, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) {
err(EXIT_FAILURE, "failed to register kevent");
}
return (kq);
}
static int
pidfile_truncate(struct pidfh *pfh)
{