Skip to content

Commit

Permalink
journald: never block when sending messages on NOTIFY_SOCKET socket
Browse files Browse the repository at this point in the history
Otherwise we might run into deadlocks, when journald blocks on the
notify socket on PID 1, and PID 1 blocks on IPC to dbus-daemon and
dbus-daemon blocks on logging to journald. Break this cycle by making
sure that journald never ever blocks on PID 1.

Note that this change disables support for event loop watchdog support,
as these messages are sent in blocking style by sd-event. That should
not be a big loss though, as people reported frequent problems with the
watchdog hitting journald on excessively slow IO.

Fixes: systemd#1505.
  • Loading branch information
poettering authored and ssahani committed Nov 3, 2015
1 parent bd92648 commit e87e164
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 22 deletions.
130 changes: 127 additions & 3 deletions src/journal/journald-server.c
Expand Up @@ -78,6 +78,8 @@

#define RECHECK_SPACE_USEC (30*USEC_PER_SEC)

#define NOTIFY_SNDBUF_SIZE (8*1024*1024)

static int determine_space_for(
Server *s,
JournalMetrics *metrics,
Expand Down Expand Up @@ -1457,6 +1459,126 @@ static int server_open_hostname(Server *s) {
return 0;
}

static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
Server *s = userdata;
int r;

assert(s);
assert(s->notify_event_source == es);
assert(s->notify_fd == fd);

if (revents != EPOLLOUT) {
log_error("Invalid events on notify file descriptor.");
return -EINVAL;
}

/* The $NOTIFY_SOCKET is writable again, now send exactly one
* message on it. Either it's the initial READY=1 event or an
* stdout stream event. If there's nothing to write anymore,
* turn our event source off. The next time there's something
* to send it will be turned on again. */

if (!s->sent_notify_ready) {
static const char p[] =
"READY=1\n"
"STATUS=Processing requests...";
ssize_t l;

l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT);
if (l < 0) {
if (errno == EAGAIN)
return 0;

return log_error_errno(errno, "Failed to send READY=1 notification message: %m");
}

s->sent_notify_ready = true;
log_debug("Sent READY=1 notification.");

} else if (s->stdout_streams_notify_queue)
/* Dispatch one stream notification event */
stdout_stream_send_notify(s->stdout_streams_notify_queue);

/* Leave us enabled if there's still more to to do. */
if (s->stdout_streams_notify_queue)
return 0;

/* There was nothing to do anymore, let's turn ourselves off. */
r = sd_event_source_set_enabled(es, SD_EVENT_OFF);
if (r < 0)
return log_error_errno(r, "Failed to turn off notify event source: %m");

return 0;
}

static int server_connect_notify(Server *s) {
union sockaddr_union sa = {
.un.sun_family = AF_UNIX,
};
const char *e;
int r;

assert(s);
assert(s->notify_fd < 0);
assert(!s->notify_event_source);

/*
So here's the problem: we'd like to send notification
messages to PID 1, but we cannot do that via sd_notify(),
since that's synchronous, and we might end up blocking on
it. Specifically: given that PID 1 might block on
dbus-daemon during IPC, and dbus-daemon is logging to us,
and might hence block on us, we might end up in a deadlock
if we block on sending PID 1 notification messages -- by
generating a full blocking circle. To avoid this, let's
create a non-blocking socket, and connect it to the
notification socket, and then wait for POLLOUT before we
send anything. This should efficiently avoid any deadlocks,
as we'll never block on PID 1, hence PID 1 can safely block
on dbus-daemon which can safely block on us again.
Don't think that this issue is real? It is, see:
https://github.com/systemd/systemd/issues/1505
*/

e = getenv("NOTIFY_SOCKET");
if (!e)
return 0;

if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
log_error("NOTIFY_SOCKET set to an invalid value: %s", e);
return -EINVAL;
}

if (strlen(e) > sizeof(sa.un.sun_path)) {
log_error("NOTIFY_SOCKET path too long: %s", e);
return -EINVAL;
}

s->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (s->notify_fd < 0)
return log_error_errno(errno, "Failed to create notify socket: %m");

(void) fd_inc_sndbuf(s->notify_fd, NOTIFY_SNDBUF_SIZE);

strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path));
if (sa.un.sun_path[0] == '@')
sa.un.sun_path[0] = 0;

r = connect(s->notify_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(e));
if (r < 0)
return log_error_errno(errno, "Failed to connect to notify socket: %m");

r = sd_event_add_io(s->event, &s->notify_event_source, s->notify_fd, EPOLLOUT, dispatch_notify_event, s);
if (r < 0)
return log_error_errno(r, "Failed to watch notification socket: %m");

/* This should fire pretty soon, which we'll use to send the
* READY=1 event. */

return 0;
}

int server_init(Server *s) {
_cleanup_fdset_free_ FDSet *fds = NULL;
int n, r, fd;
Expand All @@ -1465,7 +1587,7 @@ int server_init(Server *s) {
assert(s);

zero(*s);
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = -1;
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = s->notify_fd = -1;
s->compress = true;
s->seal = true;

Expand Down Expand Up @@ -1511,8 +1633,6 @@ int server_init(Server *s) {
if (r < 0)
return log_error_errno(r, "Failed to create event loop: %m");

sd_event_set_watchdog(s->event, true);

n = sd_listen_fds(true);
if (n < 0)
return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
Expand Down Expand Up @@ -1637,6 +1757,8 @@ int server_init(Server *s) {
server_cache_boot_id(s);
server_cache_machine_id(s);

(void) server_connect_notify(s);

return system_journal_open(s, false);
}

Expand Down Expand Up @@ -1685,6 +1807,7 @@ void server_done(Server *s) {
sd_event_source_unref(s->sigterm_event_source);
sd_event_source_unref(s->sigint_event_source);
sd_event_source_unref(s->hostname_event_source);
sd_event_source_unref(s->notify_event_source);
sd_event_unref(s->event);

safe_close(s->syslog_fd);
Expand All @@ -1693,6 +1816,7 @@ void server_done(Server *s) {
safe_close(s->dev_kmsg_fd);
safe_close(s->audit_fd);
safe_close(s->hostname_fd);
safe_close(s->notify_fd);

if (s->rate_limit)
journal_rate_limit_free(s->rate_limit);
Expand Down
13 changes: 9 additions & 4 deletions src/journal/journald-server.h
Expand Up @@ -26,9 +26,12 @@

#include "sd-event.h"

typedef struct Server Server;

#include "hashmap.h"
#include "journal-file.h"
#include "journald-rate-limit.h"
#include "journald-stream.h"
#include "list.h"

typedef enum Storage {
Expand All @@ -48,15 +51,14 @@ typedef enum SplitMode {
_SPLIT_INVALID = -1
} SplitMode;

typedef struct StdoutStream StdoutStream;

typedef struct Server {
struct Server {
int syslog_fd;
int native_fd;
int stdout_fd;
int dev_kmsg_fd;
int audit_fd;
int hostname_fd;
int notify_fd;

sd_event *event;

Expand All @@ -71,6 +73,7 @@ typedef struct Server {
sd_event_source *sigterm_event_source;
sd_event_source *sigint_event_source;
sd_event_source *hostname_event_source;
sd_event_source *notify_event_source;

JournalFile *runtime_journal;
JournalFile *system_journal;
Expand Down Expand Up @@ -111,6 +114,7 @@ typedef struct Server {
usec_t oldest_file_usec;

LIST_HEAD(StdoutStream, stdout_streams);
LIST_HEAD(StdoutStream, stdout_streams_notify_queue);
unsigned n_stdout_streams;

char *tty_path;
Expand All @@ -132,6 +136,7 @@ typedef struct Server {

struct udev *udev;

bool sent_notify_ready;
bool sync_scheduled;

char machine_id_field[sizeof("_MACHINE_ID=") + 32];
Expand All @@ -140,7 +145,7 @@ typedef struct Server {

/* Cached cgroup root, so that we don't have to query that all the time */
char *cgroup_root;
} Server;
};

#define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID="))

Expand Down
68 changes: 62 additions & 6 deletions src/journal/journald-stream.c
Expand Up @@ -79,6 +79,7 @@ struct StdoutStream {
bool forward_to_console:1;

bool fdstore:1;
bool in_notify_queue:1;

char buffer[LINE_MAX+1];
size_t length;
Expand All @@ -88,6 +89,7 @@ struct StdoutStream {
char *state_file;

LIST_FIELDS(StdoutStream, stdout_stream);
LIST_FIELDS(StdoutStream, stdout_stream_notify_queue);
};

void stdout_stream_free(StdoutStream *s) {
Expand All @@ -98,6 +100,9 @@ void stdout_stream_free(StdoutStream *s) {
assert(s->server->n_stdout_streams > 0);
s->server->n_stdout_streams --;
LIST_REMOVE(stdout_stream, s->server->stdout_streams, s);

if (s->in_notify_queue)
LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
}

if (s->event_source) {
Expand All @@ -121,7 +126,7 @@ static void stdout_stream_destroy(StdoutStream *s) {
return;

if (s->state_file)
unlink(s->state_file);
(void) unlink(s->state_file);

stdout_stream_free(s);
}
Expand Down Expand Up @@ -200,11 +205,15 @@ static int stdout_stream_save(StdoutStream *s) {
goto fail;
}

/* Store the connection fd in PID 1, so that we get it passed
* in again on next start */
if (!s->fdstore) {
sd_pid_notify_with_fds(0, false, "FDSTORE=1", &s->fd, 1);
s->fdstore = true;
if (!s->fdstore && !s->in_notify_queue) {
LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
s->in_notify_queue = true;

if (s->server->notify_event_source) {
r = sd_event_source_set_enabled(s->server->notify_event_source, SD_EVENT_ON);
if (r < 0)
log_warning_errno(r, "Failed to enable notify event source: %m");
}
}

return 0;
Expand Down Expand Up @@ -729,3 +738,50 @@ int server_open_stdout_socket(Server *s) {

return 0;
}

void stdout_stream_send_notify(StdoutStream *s) {
struct iovec iovec = {
.iov_base = (char*) "FDSTORE=1",
.iov_len = strlen("FDSTORE=1"),
};
struct msghdr msghdr = {
.msg_iov = &iovec,
.msg_iovlen = 1,
};
struct cmsghdr *cmsg;
ssize_t l;

assert(s);
assert(!s->fdstore);
assert(s->in_notify_queue);
assert(s->server);
assert(s->server->notify_fd >= 0);

/* Store the connection fd in PID 1, so that we get it passed
* in again on next start */

msghdr.msg_controllen = CMSG_SPACE(sizeof(int));
msghdr.msg_control = alloca0(msghdr.msg_controllen);

cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));

memcpy(CMSG_DATA(cmsg), &s->fd, sizeof(int));

l = sendmsg(s->server->notify_fd, &msghdr, MSG_DONTWAIT|MSG_NOSIGNAL);
if (l < 0) {
if (errno == EAGAIN)
return;

log_error_errno(errno, "Failed to send stream file descriptor to service manager: %m");
} else {
log_debug("Successfully sent stream file descriptor to service manager.");
s->fdstore = 1;
}

LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
s->in_notify_queue = false;

}
4 changes: 4 additions & 0 deletions src/journal/journald-stream.h
Expand Up @@ -21,9 +21,13 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

typedef struct StdoutStream StdoutStream;

#include "fdset.h"
#include "journald-server.h"

int server_open_stdout_socket(Server *s);
int server_restore_streams(Server *s, FDSet *fds);

void stdout_stream_free(StdoutStream *s);
void stdout_stream_send_notify(StdoutStream *s);
8 changes: 0 additions & 8 deletions src/journal/journald.c
Expand Up @@ -61,10 +61,6 @@ int main(int argc, char *argv[]) {
log_debug("systemd-journald running as pid "PID_FMT, getpid());
server_driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started");

sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");

for (;;) {
usec_t t = USEC_INFINITY, n;

Expand Down Expand Up @@ -117,10 +113,6 @@ int main(int argc, char *argv[]) {
server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, "Journal stopped");

finish:
sd_notify(false,
"STOPPING=1\n"
"STATUS=Shutting down...");

server_done(&server);

return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
Expand Down
1 change: 0 additions & 1 deletion units/systemd-journald.service.in
Expand Up @@ -22,7 +22,6 @@ RestartSec=0
NotifyAccess=all
StandardOutput=null
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
WatchdogSec=3min
FileDescriptorStoreMax=1024

# Increase the default a bit in order to allow many simultaneous
Expand Down

0 comments on commit e87e164

Please sign in to comment.