From 279da1e3f99b9c767a69849b5445e3cfd8d83376 Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Thu, 10 Jul 2014 15:25:47 +0200 Subject: shared: add generic IPC barrier The "Barrier" object is a simple inter-process barrier implementation. It allows placing synchronization points and waiting for the other side to reach it. Additionally, it has an abortion-mechanism as second-layer synchronization to send abortion-events asynchronously to the other side. The API is usually used to synchronize processes during fork(). However, it can be extended to pass state through execve() so you could synchronize beyond execve(). Usually, it's used like this (error-handling replaced by assert() for simplicity): Barrier b; r = barrier_init(&b); assert_se(r >= 0); pid = fork(); assert_se(pid >= 0); if (pid == 0) { barrier_set_role(&b, BARRIER_CHILD); ...do child post-setup... if (CHILD_SETUP_FAILED) exit(1); ...child setup done... barrier_place(&b); if (!barrier_sync(&b)) { /* parent setup failed */ exit(1); } barrier_destroy(&b); /* redundant as execve() and exit() imply this */ /* parent & child setup successful */ execve(...); } barrier_set_role(&b, BARRIER_PARENT); ...do parent post-setup... if (PARENT_SETUP_FAILED) { barrier_abort(&b); /* send abortion event */ barrier_wait_abortion(&b); /* wait for child to abort (exit() implies abortion) */ barrier_destroy(&b); ...bail out... } ...parent setup done... barrier_place(&b); if (!barrier_sync(&b)) { ...child setup failed... ; barrier_destroy(&b); ...bail out... } barrier_destroy(&b); ...child setup successfull... This is the most basic API. Using barrier_place() to place barriers and barrier_sync() to perform a full synchronization between both processes. barrier_abort() places an abortion barrier which superceeds any other barriers, exit() (or barrier_destroy()) places an abortion-barrier that queues behind existing barriers (thus *not* replacing existing barriers unlike barrier_abort()). This example uses hard-synchronization with wait_abortion(), sync() and friends. These are all optional. Barriers are highly dynamic and can be used for one-way synchronization or even no synchronization at all (postponing it for later). The sync() call performs a full two-way synchronization. The API is documented and should be fairly self-explanatory. A test-suite shows some special semantics regarding abortion, wait_next() and exit(). Internally, barriers use two eventfds and a pipe. The pipe is used to detect exit()s of the remote side as eventfds do not allow that. The eventfds are used to place barriers, one for each side. Barriers itself are numbered, but the numbers are reused once both sides reached the same barrier, thus you cannot address barriers by the index. Moreover, the numbering is implicit and we only store a counter. This makes the implementation itself very lightweight, which is probably negligible considering that we need 3 FDs for a barrier.. Last but not least: This barrier implementation is quite heavy. It's definitely not meant for fast IPC synchronization. However, it's very easy to use. And given the *HUGE* overhead of fork(), the barrier-overhead should be negligible. --- .gitignore | 1 + 1 file changed, 1 insertion(+) (limited to '.gitignore') diff --git a/.gitignore b/.gitignore index bf53064105..48606313cc 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,7 @@ /tags /test-architecture /test-async +/test-barrier /test-boot-timestamp /test-bus-chat /test-bus-cleanup -- cgit v1.2.3-54-g00ecf