Merge pull request #4456 from keszybz/stored-fds

Preserve stored fds over service restart
This commit is contained in:
Lennart Poettering
2016-11-02 16:29:04 -06:00
committed by GitHub
6 changed files with 71 additions and 51 deletions
+19 -22
View File
@@ -205,28 +205,25 @@
<varlistentry>
<term>FDSTORE=1</term>
<listitem><para>Stores additional file descriptors in the
service manager. File descriptors sent this way will be
maintained per-service by the service manager and be passed
again using the usual file descriptor passing logic on the
next invocation of the service (see
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>).
This is useful for implementing service restart schemes where
services serialize their state to <filename>/run</filename>,
push their file descriptors to the system manager, and are
then restarted, retrieving their state again via socket
passing and <filename>/run</filename>. Note that the service
manager will accept messages for a service only if
<varname>FileDescriptorStoreMax=</varname> is set to non-zero
for it (defaults to zero). See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. Multiple arrays of file descriptors may be sent
in separate messages, in which case the arrays are combined.
Note that the service manager removes duplicate file
descriptors before passing them to the service. Use
<function>sd_pid_notify_with_fds()</function> to send messages
with <literal>FDSTORE=1</literal>, see
below.</para></listitem>
<listitem><para>Stores additional file descriptors in the service manager. File
descriptors sent this way will be maintained per-service by the service manager
and will be passed again using the usual file descriptor passing logic on the next
invocation of the service, see
<citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
This is useful for implementing service restart schemes where services serialize
their state to <filename>/run</filename>, push their file descriptors to the
system manager, and are then restarted, retrieving their state again via socket
passing and <filename>/run</filename>. Note that the service manager will accept
messages for a service only if <varname>FileDescriptorStoreMax=</varname> is set
to non-zero for it (defaults to zero, see
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
File descriptors must be pollable, see
<citerefentry><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
Multiple arrays of file descriptors may be sent in separate messages, in which
case the arrays are combined. Note that the service manager removes duplicate
file descriptors before passing them to the service. Use
<function>sd_pid_notify_with_fds()</function> to send messages with
<literal>FDSTORE=1</literal>, see below.</para></listitem>
</varlistentry>
<varlistentry>
+2 -2
View File
@@ -852,13 +852,13 @@
serialized to <filename>/run</filename> and the file
descriptors passed to the service manager, to allow restarts
without losing state. Defaults to 0, i.e. no file descriptors
may be stored in the service manager by default. All file
may be stored in the service manager. All file
descriptors passed to the service manager from a specific
service are passed back to the service's main process on the
next service restart. Any file descriptors passed to the
service manager are automatically closed when POLLHUP or
POLLERR is seen on them, or when the service is fully stopped
and no job queued or being executed for it.</para></listitem>
and no job is queued or being executed for it.</para></listitem>
</varlistentry>
<varlistentry>
+37 -24
View File
@@ -289,7 +289,17 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
free(fs);
}
static void service_release_resources(Unit *u) {
static void service_release_fd_store(Service *s) {
assert(s);
log_unit_debug(UNIT(s), "Releasing all stored fds");
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
assert(s->n_fd_store == 0);
}
static void service_release_resources(Unit *u, bool inactive) {
Service *s = SERVICE(u);
assert(s);
@@ -297,16 +307,14 @@ static void service_release_resources(Unit *u) {
if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0)
return;
log_unit_debug(u, "Releasing all resources.");
log_unit_debug(u, "Releasing resources.");
s->stdin_fd = safe_close(s->stdin_fd);
s->stdout_fd = safe_close(s->stdout_fd);
s->stderr_fd = safe_close(s->stderr_fd);
while (s->fd_store)
service_fd_store_unlink(s->fd_store);
assert(s->n_fd_store == 0);
if (inactive)
service_release_fd_store(s);
}
static void service_done(Unit *u) {
@@ -350,7 +358,7 @@ static void service_done(Unit *u) {
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
service_release_resources(u);
service_release_resources(u, true);
}
static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
@@ -360,6 +368,10 @@ static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *us
assert(fs);
/* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */
log_unit_debug(UNIT(fs->service),
"Received %s on stored fd %d (%s), closing.",
revents & EPOLLERR ? "EPOLLERR" : "EPOLLHUP",
fs->fd, strna(fs->fdname));
service_fd_store_unlink(fs);
return 0;
}
@@ -368,20 +380,23 @@ static int service_add_fd_store(Service *s, int fd, const char *name) {
ServiceFDStore *fs;
int r;
/* fd is always consumed if we return >= 0 */
assert(s);
assert(fd >= 0);
if (s->n_fd_store >= s->n_fd_store_max)
return 0;
return -EXFULL; /* Our store is full.
* Use this errno rather than E[NM]FILE to distinguish from
* the case where systemd itself hits the file limit. */
LIST_FOREACH(fd_store, fs, s->fd_store) {
r = same_fd(fs->fd, fd);
if (r < 0)
return r;
if (r > 0) {
/* Already included */
safe_close(fd);
return 1;
return 0; /* fd already included */
}
}
@@ -409,7 +424,7 @@ static int service_add_fd_store(Service *s, int fd, const char *name) {
LIST_PREPEND(fd_store, s->fd_store, fs);
s->n_fd_store++;
return 1;
return 1; /* fd newly stored */
}
static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
@@ -417,10 +432,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
assert(s);
if (fdset_size(fds) <= 0)
return 0;
while (s->n_fd_store < s->n_fd_store_max) {
while (fdset_size(fds) > 0) {
_cleanup_close_ int fd = -1;
fd = fdset_steal_first(fds);
@@ -428,17 +440,17 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) {
break;
r = service_add_fd_store(s, fd, name);
if (r == -EXFULL)
return log_unit_warning_errno(UNIT(s), r,
"Cannot store more fds than FileDescriptorStoreMax=%u, closing remaining.",
s->n_fd_store_max);
if (r < 0)
return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m");
if (r > 0) {
log_unit_debug(UNIT(s), "Added fd to fd store.");
fd = -1;
}
return log_unit_error_errno(UNIT(s), r, "Failed to add fd to store: %m");
if (r > 0)
log_unit_debug(UNIT(s), "Added fd %u (%s) to fd store.", fd, strna(name));
fd = -1;
}
if (fdset_size(fds) > 0)
log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max);
return 0;
}
@@ -1225,6 +1237,7 @@ static int service_spawn(
return r;
n_fds = r;
log_unit_debug(UNIT(s), "Passing %i fds to service", n_fds);
}
r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout));
@@ -2336,7 +2349,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
r = service_add_fd_store(s, fd, t);
if (r < 0)
log_unit_error_errno(u, r, "Failed to add fd to store: %m");
else if (r > 0)
else
fdset_remove(fds, fd);
}
+4 -2
View File
@@ -302,6 +302,7 @@ int unit_set_description(Unit *u, const char *description) {
bool unit_check_gc(Unit *u) {
UnitActiveState state;
bool inactive;
assert(u);
if (u->job)
@@ -311,16 +312,17 @@ bool unit_check_gc(Unit *u) {
return true;
state = unit_active_state(u);
inactive = state == UNIT_INACTIVE;
/* If the unit is inactive and failed and no job is queued for
* it, then release its runtime resources */
if (UNIT_IS_INACTIVE_OR_FAILED(state) &&
UNIT_VTABLE(u)->release_resources)
UNIT_VTABLE(u)->release_resources(u);
UNIT_VTABLE(u)->release_resources(u, inactive);
/* But we keep the unit object around for longer when it is
* referenced or configured to not be gc'ed */
if (state != UNIT_INACTIVE)
if (!inactive)
return true;
if (u->no_gc)
+1 -1
View File
@@ -372,7 +372,7 @@ struct UnitVTable {
/* When the unit is not running and no job for it queued we
* shall release its runtime resources */
void (*release_resources)(Unit *u);
void (*release_resources)(Unit *u, bool inactive);
/* Invoked on every child that died */
void (*sigchld_event)(Unit *u, pid_t pid, int code, int status);
+8
View File
@@ -59,4 +59,12 @@ sleep 3
systemctl stop forever-print-hola
[[ ! -f "/i-lose-my-logs" ]]
# https://github.com/systemd/systemd/issues/4408
rm -f /i-lose-my-logs
systemctl start forever-print-hola
sleep 3
systemctl kill --signal=SIGKILL systemd-journald
sleep 3
[[ ! -f "/i-lose-my-logs" ]]
touch /testok