Skip to content

Commit 1270569

Browse files
committed
Introduce sd_notify_barrier
This adds the sd_notify_barrier function, to allow users to synchronize against the reception of sd_notify(3) status messages. It acts as a synchronization point, and a successful return gurantees that all previous messages have been consumed by the manager. This can be used to eliminate race conditions where the sending process exits too early for systemd to associate its PID to a cgroup and attribute the status message to a unit correctly. systemd-notify now uses this function for proper notification delivery and be useful for NotifyAccess=all units again in user mode, or in cases where it doesn't have a control process as parent. Fixes: systemd#2739
1 parent 9dcd43b commit 1270569

File tree

6 files changed

+130
-1
lines changed

6 files changed

+130
-1
lines changed

man/rules/meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ manpages = [
718718
['sd_machine_get_class', '3', ['sd_machine_get_ifindices'], ''],
719719
['sd_notify',
720720
'3',
721-
['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf'],
721+
['sd_notifyf', 'sd_pid_notify', 'sd_pid_notify_with_fds', 'sd_pid_notifyf', 'sd_notify_barrier'],
722722
''],
723723
['sd_path_lookup', '3', ['sd_path_lookup_strv'], ''],
724724
['sd_pid_get_owner_uid',

man/sd_notify.xml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<refname>sd_pid_notify</refname>
2323
<refname>sd_pid_notifyf</refname>
2424
<refname>sd_pid_notify_with_fds</refname>
25+
<refname>sd_notify_barrier</refname>
2526
<refpurpose>Notify service manager about start-up completion and other service status changes</refpurpose>
2627
</refnamediv>
2728

@@ -65,6 +66,12 @@
6566
<paramdef>const int *<parameter>fds</parameter></paramdef>
6667
<paramdef>unsigned <parameter>n_fds</parameter></paramdef>
6768
</funcprototype>
69+
70+
<funcprototype>
71+
<funcdef>int <function>sd_notify_barrier</function></funcdef>
72+
<paramdef>int <parameter>unset_environment</parameter></paramdef>
73+
<paramdef>uint64_t <parameter>timeout</parameter></paramdef>
74+
</funcprototype>
6875
</funcsynopsis>
6976
</refsynopsisdiv>
7077

@@ -251,6 +258,17 @@
251258
submitted name does not follow these restrictions, it is ignored.</para></listitem>
252259
</varlistentry>
253260

261+
<varlistentry>
262+
<term>BARRIER=1</term>
263+
264+
<listitem><para>Tells the service manager that the client is explicitly requesting synchronization by means of
265+
closing the file descriptor sent with this command. The service manager gurantees that the processing of a <varname>
266+
BARRIER=1</varname> command will only happen after all previous notification messages sent before this command
267+
have been processed. Hence, this command accompanied with a single file descriptor can be used to synchronize
268+
against reception of all previous status messages. Note that this command cannot be mixed with other notifications,
269+
and has to be sent in a separate message to the service manager, otherwise all assignments will be ignored. Note that
270+
sending 0 or more than 1 file descriptor with this command is a violation of the protocol.</para></listitem>
271+
</varlistentry>
254272
</variablelist>
255273

256274
<para>It is recommended to prefix variable names that are not
@@ -272,6 +290,13 @@
272290
attribute the message to the unit, and thus will ignore it, even if
273291
<varname>NotifyAccess=</varname><option>all</option> is set for it.</para>
274292

293+
<para>Hence, to eliminate all race conditions involving lookup of the client's unit and attribution of notifications
294+
to units correctly, <function>sd_notify_barrier()</function> may be used. This call acts as a synchronization point
295+
and ensures all notifications sent before this call have been picked up by the service manager when it returns
296+
successfully. Use of <function>sd_notify_barrier()</function> is needed for clients which are not invoked by the
297+
service manager, otherwise this synchronization mechanism is unnecessary for attribution of notifications to the
298+
unit.</para>
299+
275300
<para><function>sd_notifyf()</function> is similar to
276301
<function>sd_notify()</function> but takes a
277302
<function>printf()</function>-like format string plus
@@ -302,6 +327,14 @@
302327
to the service manager on messages that do not expect them (i.e.
303328
without <literal>FDSTORE=1</literal>) they are immediately closed
304329
on reception.</para>
330+
331+
<para><function>sd_notify_barrier()</function> allows the caller to
332+
synchronize against reception of previously sent notification messages
333+
and uses the <literal>BARRIER=1</literal> command. It takes a relative
334+
<varname>timeout</varname> value in microseconds which is passed to
335+
<citerefentry><refentrytitle>ppoll</refentrytitle><manvolnum>2</manvolnum>
336+
</citerefentry>. A value of UINT64_MAX is interpreted as infinite timeout.
337+
</para>
305338
</refsect1>
306339

307340
<refsect1>
@@ -392,6 +425,22 @@
392425

393426
<programlisting>sd_pid_notify_with_fds(0, 0, "FDSTORE=1\nFDNAME=foobar", &amp;fd, 1);</programlisting>
394427
</example>
428+
429+
<example>
430+
<title>Eliminating race conditions</title>
431+
432+
<para>When the client sending the notifications is not spawned
433+
by the service manager, it may exit too quickly and the service
434+
manager may fail to attribute them correctly to the unit. To
435+
prevent such races, use <function>sd_notify_barrier()</function>
436+
to synchronize against reception of all notifications sent before
437+
this call is made.</para>
438+
439+
<programlisting>sd_notify(0, "READY=1");
440+
/* set timeout to 5 seconds */
441+
sd_notify_barrier(0, 5 * 1000000);
442+
</programlisting>
443+
</example>
395444
</refsect1>
396445

397446
<refsect1>

src/core/manager.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2284,6 +2284,20 @@ static int manager_dispatch_cgroups_agent_fd(sd_event_source *source, int fd, ui
22842284
return 0;
22852285
}
22862286

2287+
static bool manager_process_barrier_fd(const char *buf, FDSet *fds) {
2288+
assert(buf);
2289+
2290+
/* nothing else must be sent when using BARRIER=1 */
2291+
if (STR_IN_SET(buf, "BARRIER=1", "BARRIER=1\n")) {
2292+
if (fdset_size(fds) != 1)
2293+
log_warning("Got incorrect number of fds with BARRIER=1, closing them.");
2294+
return true;
2295+
} else if (startswith(buf, "BARRIER=1\n") || strstr(buf, "\nBARRIER=1\n") || endswith(buf, "\nBARRIER=1"))
2296+
log_warning("Extra notification messages sent with BARRIER=1, ignoring everything.");
2297+
2298+
return false;
2299+
}
2300+
22872301
static void manager_invoke_notify_message(
22882302
Manager *m,
22892303
Unit *u,
@@ -2417,6 +2431,10 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
24172431
/* Make sure it's NUL-terminated. */
24182432
buf[n] = 0;
24192433

2434+
/* possibly a barrier fd, let's see */
2435+
if (manager_process_barrier_fd(buf, fds))
2436+
return 0;
2437+
24202438
/* Increase the generation counter used for filtering out duplicate unit invocations. */
24212439
m->notifygen++;
24222440

src/libsystemd/sd-daemon/sd-daemon.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <limits.h>
55
#include <mqueue.h>
66
#include <netinet/in.h>
7+
#include <poll.h>
78
#include <stdarg.h>
89
#include <stddef.h>
910
#include <stdio.h>
@@ -23,6 +24,7 @@
2324
#include "process-util.h"
2425
#include "socket-util.h"
2526
#include "strv.h"
27+
#include "time-util.h"
2628
#include "util.h"
2729

2830
#define SNDBUF_SIZE (8*1024*1024)
@@ -551,6 +553,34 @@ _public_ int sd_pid_notify_with_fds(
551553
return r;
552554
}
553555

556+
_public_ int sd_notify_barrier(int unset_environment, uint64_t timeout) {
557+
_cleanup_close_pair_ int pipe_fd[2] = { -1, -1 };
558+
struct timespec ts;
559+
int r;
560+
561+
if (pipe2(pipe_fd, O_CLOEXEC) < 0)
562+
return -errno;
563+
564+
r = sd_pid_notify_with_fds(0, unset_environment, "BARRIER=1", &pipe_fd[1], 1);
565+
if (r <= 0)
566+
return r;
567+
568+
pipe_fd[1] = safe_close(pipe_fd[1]);
569+
570+
struct pollfd pfd = {
571+
.fd = pipe_fd[0],
572+
/* POLLHUP is implicit */
573+
.events = 0,
574+
};
575+
r = ppoll(&pfd, 1, timeout == UINT64_MAX ? NULL : timespec_store(&ts, timeout), NULL);
576+
if (r < 0)
577+
return -errno;
578+
if (r == 0)
579+
return -ETIMEDOUT;
580+
581+
return 1;
582+
}
583+
554584
_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) {
555585
return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0);
556586
}

src/notify/notify.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "string-util.h"
1919
#include "strv.h"
2020
#include "terminal-util.h"
21+
#include "time-util.h"
2122
#include "user-util.h"
2223
#include "util.h"
2324

@@ -27,6 +28,7 @@ static const char *arg_status = NULL;
2728
static bool arg_booted = false;
2829
static uid_t arg_uid = UID_INVALID;
2930
static gid_t arg_gid = GID_INVALID;
31+
static bool arg_no_block = false;
3032

3133
static int help(void) {
3234
_cleanup_free_ char *link = NULL;
@@ -45,6 +47,7 @@ static int help(void) {
4547
" --uid=USER Set user to send from\n"
4648
" --status=TEXT Set status text\n"
4749
" --booted Check if the system was booted up with systemd\n"
50+
" --no-block Do not wait until operation finished\n"
4851
"\nSee the %s for details.\n"
4952
, program_invocation_short_name
5053
, ansi_highlight(), ansi_normal()
@@ -83,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) {
8386
ARG_STATUS,
8487
ARG_BOOTED,
8588
ARG_UID,
89+
ARG_NO_BLOCK
8690
};
8791

8892
static const struct option options[] = {
@@ -93,6 +97,7 @@ static int parse_argv(int argc, char *argv[]) {
9397
{ "status", required_argument, NULL, ARG_STATUS },
9498
{ "booted", no_argument, NULL, ARG_BOOTED },
9599
{ "uid", required_argument, NULL, ARG_UID },
100+
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
96101
{}
97102
};
98103

@@ -157,6 +162,10 @@ static int parse_argv(int argc, char *argv[]) {
157162
break;
158163
}
159164

165+
case ARG_NO_BLOCK:
166+
arg_no_block = true;
167+
break;
168+
160169
case '?':
161170
return -EINVAL;
162171

@@ -256,6 +265,16 @@ static int run(int argc, char* argv[]) {
256265
if (r == 0)
257266
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
258267
"No status data could be sent: $NOTIFY_SOCKET was not set");
268+
269+
if (!arg_no_block) {
270+
r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
271+
if (r < 0)
272+
return log_error_errno(r, "Failed to invoke barrier: %m");
273+
if (r == 0)
274+
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
275+
"No status data could be sent: $NOTIFY_SOCKET was not set");
276+
}
277+
259278
return 0;
260279
}
261280

src/systemd/sd-daemon.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _s
286286
*/
287287
int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds);
288288

289+
/*
290+
Returns > 0 if synchronization with systemd succeeded. Returns < 0
291+
on error. Returns 0 if $NOTIFY_SOCKET was not set. Note that the
292+
timeout parameter of this function call takes the timeout in µs, and
293+
will be passed to ppoll(2), hence the behaviour will be similar to
294+
ppoll(2). This function can be called after sending a status message
295+
to systemd, if one needs to synchronize against reception of the
296+
status messages sent before this call is made. Therefore, this
297+
cannot be used to know if the status message was processed
298+
successfully, but to only synchronize against its consumption.
299+
*/
300+
int sd_notify_barrier(int unset_environment, uint64_t timeout);
301+
289302
/*
290303
Returns > 0 if the system was booted with systemd. Returns < 0 on
291304
error. Returns 0 if the system was not booted with systemd. Note

0 commit comments

Comments
 (0)