mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-03-13 12:58:20 +03:00
LLVM 13 introduced `-Wunused-but-set-variable` diagnostic flag, which trips over some intentionally set-but-not-used variables or variables attached to cleanup handlers with side effects (`_cleanup_umask_`, `_cleanup_(notify_on_cleanup)`, `_cleanup_(restore_sigsetp)`, etc.): ``` ../src/basic/process-util.c:1257:46: error: variable 'saved_ssp' set but not used [-Werror,-Wunused-but-set-variable] _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; ^ 1 error generated. ```
396 lines
14 KiB
C
396 lines
14 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <sys/eventfd.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "barrier.h"
|
|
#include "fd-util.h"
|
|
#include "io-util.h"
|
|
#include "macro.h"
|
|
|
|
/**
|
|
* Barriers
|
|
* This barrier implementation provides a simple synchronization method based
|
|
* on file-descriptors that can safely be used between threads and processes. A
|
|
* barrier object contains 2 shared counters based on eventfd. Both processes
|
|
* can now place barriers and wait for the other end to reach a random or
|
|
* specific barrier.
|
|
* Barriers are numbered, so you can either wait for the other end to reach any
|
|
* barrier or the last barrier that you placed. This way, you can use barriers
|
|
* for one-way *and* full synchronization. Note that even-though barriers are
|
|
* numbered, these numbers are internal and recycled once both sides reached the
|
|
* same barrier (implemented as a simple signed counter). It is thus not
|
|
* possible to address barriers by their ID.
|
|
*
|
|
* Barrier-API: Both ends can place as many barriers via barrier_place() as
|
|
* they want and each pair of barriers on both sides will be implicitly linked.
|
|
* Each side can use the barrier_wait/sync_*() family of calls to wait for the
|
|
* other side to place a specific barrier. barrier_wait_next() waits until the
|
|
* other side calls barrier_place(). No links between the barriers are
|
|
* considered and this simply serves as most basic asynchronous barrier.
|
|
* barrier_sync_next() is like barrier_wait_next() and waits for the other side
|
|
* to place their next barrier via barrier_place(). However, it only waits for
|
|
* barriers that are linked to a barrier we already placed. If the other side
|
|
* already placed more barriers than we did, barrier_sync_next() returns
|
|
* immediately.
|
|
* barrier_sync() extends barrier_sync_next() and waits until the other end
|
|
* placed as many barriers via barrier_place() as we did. If they already placed
|
|
* as many as we did (or more), it returns immediately.
|
|
*
|
|
* Additionally to basic barriers, an abortion event is available.
|
|
* barrier_abort() places an abortion event that cannot be undone. An abortion
|
|
* immediately cancels all placed barriers and replaces them. Any running and
|
|
* following wait/sync call besides barrier_wait_abortion() will immediately
|
|
* return false on both sides (otherwise, they always return true).
|
|
* barrier_abort() can be called multiple times on both ends and will be a
|
|
* no-op if already called on this side.
|
|
* barrier_wait_abortion() can be used to wait for the other side to call
|
|
* barrier_abort() and is the only wait/sync call that does not return
|
|
* immediately if we aborted outself. It only returns once the other side
|
|
* called barrier_abort().
|
|
*
|
|
* Barriers can be used for in-process and inter-process synchronization.
|
|
* However, for in-process synchronization you could just use mutexes.
|
|
* Therefore, main target is IPC and we require both sides to *not* share the FD
|
|
* table. If that's given, barriers provide target tracking: If the remote side
|
|
* exit()s, an abortion event is implicitly queued on the other side. This way,
|
|
* a sync/wait call will be woken up if the remote side crashed or exited
|
|
* unexpectedly. However, note that these abortion events are only queued if the
|
|
* barrier-queue has been drained. Therefore, it is safe to place a barrier and
|
|
* exit. The other side can safely wait on the barrier even though the exit
|
|
* queued an abortion event. Usually, the abortion event would overwrite the
|
|
* barrier, however, that's not true for exit-abortion events. Those are only
|
|
* queued if the barrier-queue is drained (thus, the receiving side has placed
|
|
* more barriers than the remote side).
|
|
*/
|
|
|
|
/**
|
|
* barrier_create() - Initialize a barrier object
|
|
* @obj: barrier to initialize
|
|
*
|
|
* This initializes a barrier object. The caller is responsible of allocating
|
|
* the memory and keeping it valid. The memory does not have to be zeroed
|
|
* beforehand.
|
|
* Two eventfd objects are allocated for each barrier. If allocation fails, an
|
|
* error is returned.
|
|
*
|
|
* If this function fails, the barrier is reset to an invalid state so it is
|
|
* safe to call barrier_destroy() on the object regardless whether the
|
|
* initialization succeeded or not.
|
|
*
|
|
* The caller is responsible to destroy the object via barrier_destroy() before
|
|
* releasing the underlying memory.
|
|
*
|
|
* Returns: 0 on success, negative error code on failure.
|
|
*/
|
|
int barrier_create(Barrier *b) {
|
|
_unused_ _cleanup_(barrier_destroyp) Barrier *staging = b;
|
|
int r;
|
|
|
|
assert(b);
|
|
|
|
b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
|
if (b->me < 0)
|
|
return -errno;
|
|
|
|
b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
|
if (b->them < 0)
|
|
return -errno;
|
|
|
|
r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK);
|
|
if (r < 0)
|
|
return -errno;
|
|
|
|
staging = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* barrier_destroy() - Destroy a barrier object
|
|
* @b: barrier to destroy or NULL
|
|
*
|
|
* This destroys a barrier object that has previously been passed to
|
|
* barrier_create(). The object is released and reset to invalid
|
|
* state. Therefore, it is safe to call barrier_destroy() multiple
|
|
* times or even if barrier_create() failed. However, barrier must be
|
|
* always initialized with BARRIER_NULL.
|
|
*
|
|
* If @b is NULL, this is a no-op.
|
|
*/
|
|
Barrier* barrier_destroy(Barrier *b) {
|
|
if (!b)
|
|
return NULL;
|
|
|
|
b->me = safe_close(b->me);
|
|
b->them = safe_close(b->them);
|
|
safe_close_pair(b->pipe);
|
|
b->barriers = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* barrier_set_role() - Set the local role of the barrier
|
|
* @b: barrier to operate on
|
|
* @role: role to set on the barrier
|
|
*
|
|
* This sets the roles on a barrier object. This is needed to know
|
|
* which side of the barrier you're on. Usually, the parent creates
|
|
* the barrier via barrier_create() and then calls fork() or clone().
|
|
* Therefore, the FDs are duplicated and the child retains the same
|
|
* barrier object.
|
|
*
|
|
* Both sides need to call barrier_set_role() after fork() or clone()
|
|
* are done. If this is not done, barriers will not work correctly.
|
|
*
|
|
* Note that barriers could be supported without fork() or clone(). However,
|
|
* this is currently not needed so it hasn't been implemented.
|
|
*/
|
|
void barrier_set_role(Barrier *b, unsigned role) {
|
|
assert(b);
|
|
assert(IN_SET(role, BARRIER_PARENT, BARRIER_CHILD));
|
|
/* make sure this is only called once */
|
|
assert(b->pipe[0] >= 0 && b->pipe[1] >= 0);
|
|
|
|
if (role == BARRIER_PARENT)
|
|
b->pipe[1] = safe_close(b->pipe[1]);
|
|
else {
|
|
b->pipe[0] = safe_close(b->pipe[0]);
|
|
|
|
/* swap me/them for children */
|
|
SWAP_TWO(b->me, b->them);
|
|
}
|
|
}
|
|
|
|
/* places barrier; returns false if we aborted, otherwise true */
|
|
static bool barrier_write(Barrier *b, uint64_t buf) {
|
|
ssize_t len;
|
|
|
|
/* prevent new sync-points if we already aborted */
|
|
if (barrier_i_aborted(b))
|
|
return false;
|
|
|
|
assert(b->me >= 0);
|
|
do {
|
|
len = write(b->me, &buf, sizeof(buf));
|
|
} while (len < 0 && IN_SET(errno, EAGAIN, EINTR));
|
|
|
|
if (len != sizeof(buf))
|
|
goto error;
|
|
|
|
/* lock if we aborted */
|
|
if (buf >= (uint64_t)BARRIER_ABORTION) {
|
|
if (barrier_they_aborted(b))
|
|
b->barriers = BARRIER_WE_ABORTED;
|
|
else
|
|
b->barriers = BARRIER_I_ABORTED;
|
|
} else if (!barrier_is_aborted(b))
|
|
b->barriers += buf;
|
|
|
|
return !barrier_i_aborted(b);
|
|
|
|
error:
|
|
/* If there is an unexpected error, we have to make this fatal. There
|
|
* is no way we can recover from sync-errors. Therefore, we close the
|
|
* pipe-ends and treat this as abortion. The other end will notice the
|
|
* pipe-close and treat it as abortion, too. */
|
|
|
|
safe_close_pair(b->pipe);
|
|
b->barriers = BARRIER_WE_ABORTED;
|
|
return false;
|
|
}
|
|
|
|
/* waits for barriers; returns false if they aborted, otherwise true */
|
|
static bool barrier_read(Barrier *b, int64_t comp) {
|
|
if (barrier_they_aborted(b))
|
|
return false;
|
|
|
|
while (b->barriers > comp) {
|
|
struct pollfd pfd[2] = {
|
|
{ .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1],
|
|
.events = POLLHUP },
|
|
{ .fd = b->them,
|
|
.events = POLLIN }};
|
|
uint64_t buf;
|
|
int r;
|
|
|
|
r = ppoll_usec(pfd, ELEMENTSOF(pfd), USEC_INFINITY);
|
|
if (r == -EINTR)
|
|
continue;
|
|
if (r < 0)
|
|
goto error;
|
|
|
|
if (pfd[1].revents) {
|
|
ssize_t len;
|
|
|
|
/* events on @them signal new data for us */
|
|
len = read(b->them, &buf, sizeof(buf));
|
|
if (len < 0 && IN_SET(errno, EAGAIN, EINTR))
|
|
continue;
|
|
|
|
if (len != sizeof(buf))
|
|
goto error;
|
|
} else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL))
|
|
/* POLLHUP on the pipe tells us the other side exited.
|
|
* We treat this as implicit abortion. But we only
|
|
* handle it if there's no event on the eventfd. This
|
|
* guarantees that exit-abortions do not overwrite real
|
|
* barriers. */
|
|
buf = BARRIER_ABORTION;
|
|
else
|
|
continue;
|
|
|
|
/* lock if they aborted */
|
|
if (buf >= (uint64_t)BARRIER_ABORTION) {
|
|
if (barrier_i_aborted(b))
|
|
b->barriers = BARRIER_WE_ABORTED;
|
|
else
|
|
b->barriers = BARRIER_THEY_ABORTED;
|
|
} else if (!barrier_is_aborted(b))
|
|
b->barriers -= buf;
|
|
}
|
|
|
|
return !barrier_they_aborted(b);
|
|
|
|
error:
|
|
/* If there is an unexpected error, we have to make this fatal. There
|
|
* is no way we can recover from sync-errors. Therefore, we close the
|
|
* pipe-ends and treat this as abortion. The other end will notice the
|
|
* pipe-close and treat it as abortion, too. */
|
|
|
|
safe_close_pair(b->pipe);
|
|
b->barriers = BARRIER_WE_ABORTED;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* barrier_place() - Place a new barrier
|
|
* @b: barrier object
|
|
*
|
|
* This places a new barrier on the barrier object. If either side already
|
|
* aborted, this is a no-op and returns "false". Otherwise, the barrier is
|
|
* placed and this returns "true".
|
|
*
|
|
* Returns: true if barrier was placed, false if either side aborted.
|
|
*/
|
|
bool barrier_place(Barrier *b) {
|
|
assert(b);
|
|
|
|
if (barrier_is_aborted(b))
|
|
return false;
|
|
|
|
barrier_write(b, BARRIER_SINGLE);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* barrier_abort() - Abort the synchronization
|
|
* @b: barrier object to abort
|
|
*
|
|
* This aborts the barrier-synchronization. If barrier_abort() was already
|
|
* called on this side, this is a no-op. Otherwise, the barrier is put into the
|
|
* ABORT-state and will stay there. The other side is notified about the
|
|
* abortion. Any following attempt to place normal barriers or to wait on normal
|
|
* barriers will return immediately as "false".
|
|
*
|
|
* You can wait for the other side to call barrier_abort(), too. Use
|
|
* barrier_wait_abortion() for that.
|
|
*
|
|
* Returns: false if the other side already aborted, true otherwise.
|
|
*/
|
|
bool barrier_abort(Barrier *b) {
|
|
assert(b);
|
|
|
|
barrier_write(b, BARRIER_ABORTION);
|
|
return !barrier_they_aborted(b);
|
|
}
|
|
|
|
/**
|
|
* barrier_wait_next() - Wait for the next barrier of the other side
|
|
* @b: barrier to operate on
|
|
*
|
|
* This waits until the other side places its next barrier. This is independent
|
|
* of any barrier-links and just waits for any next barrier of the other side.
|
|
*
|
|
* If either side aborted, this returns false.
|
|
*
|
|
* Returns: false if either side aborted, true otherwise.
|
|
*/
|
|
bool barrier_wait_next(Barrier *b) {
|
|
assert(b);
|
|
|
|
if (barrier_is_aborted(b))
|
|
return false;
|
|
|
|
barrier_read(b, b->barriers - 1);
|
|
return !barrier_is_aborted(b);
|
|
}
|
|
|
|
/**
|
|
* barrier_wait_abortion() - Wait for the other side to abort
|
|
* @b: barrier to operate on
|
|
*
|
|
* This waits until the other side called barrier_abort(). This can be called
|
|
* regardless whether the local side already called barrier_abort() or not.
|
|
*
|
|
* If the other side has already aborted, this returns immediately.
|
|
*
|
|
* Returns: false if the local side aborted, true otherwise.
|
|
*/
|
|
bool barrier_wait_abortion(Barrier *b) {
|
|
assert(b);
|
|
|
|
barrier_read(b, BARRIER_THEY_ABORTED);
|
|
return !barrier_i_aborted(b);
|
|
}
|
|
|
|
/**
|
|
* barrier_sync_next() - Wait for the other side to place a next linked barrier
|
|
* @b: barrier to operate on
|
|
*
|
|
* This is like barrier_wait_next() and waits for the other side to call
|
|
* barrier_place(). However, this only waits for linked barriers. That means, if
|
|
* the other side already placed more barriers than (or as much as) we did, this
|
|
* returns immediately instead of waiting.
|
|
*
|
|
* If either side aborted, this returns false.
|
|
*
|
|
* Returns: false if either side aborted, true otherwise.
|
|
*/
|
|
bool barrier_sync_next(Barrier *b) {
|
|
assert(b);
|
|
|
|
if (barrier_is_aborted(b))
|
|
return false;
|
|
|
|
barrier_read(b, MAX((int64_t)0, b->barriers - 1));
|
|
return !barrier_is_aborted(b);
|
|
}
|
|
|
|
/**
|
|
* barrier_sync() - Wait for the other side to place as many barriers as we did
|
|
* @b: barrier to operate on
|
|
*
|
|
* This is like barrier_sync_next() but waits for the other side to call
|
|
* barrier_place() as often as we did (in total). If they already placed as much
|
|
* as we did (or more), this returns immediately instead of waiting.
|
|
*
|
|
* If either side aborted, this returns false.
|
|
*
|
|
* Returns: false if either side aborted, true otherwise.
|
|
*/
|
|
bool barrier_sync(Barrier *b) {
|
|
assert(b);
|
|
|
|
if (barrier_is_aborted(b))
|
|
return false;
|
|
|
|
barrier_read(b, 0);
|
|
return !barrier_is_aborted(b);
|
|
}
|