+// This tiny ad-hoc read/write lock is needed because of the very specific
+// synchronization needed between default_handler and teardown_pipes:
+// - Many default_handlers can be running at once
+// - The signals_mutex already ensures only one teardown_pipes runs at once
+// - If teardown_pipes starts while a default_handler is ongoing, it must block
+// - If default_handler starts while a teardown_pipes is ongoing, it must *not* block
+// Locks are implemented as ints.
+
+// The lock is split into a teardown bit and a handler count (sign bit unused).
+// There is a teardown running or waiting to run if the teardown bit is set.
+// There is a handler running if the handler count is nonzero.
+#define PIPELOCK_TEARDOWN_BIT ( (int)0x40000000 )
+#define PIPELOCK_COUNT_MASK (~((int)0xC0000000))
+#define PIPELOCK_GET_COUNT(x) ((x) & PIPELOCK_COUNT_MASK)
+#define PIPELOCK_INCR_COUNT(x, by) (((x) & PIPELOCK_TEARDOWN_BIT) | (PIPELOCK_GET_COUNT (PIPELOCK_GET_COUNT (x) + (by))))
+
+static inline void
+acquire_pipelock_teardown (int *lock)
+{
+ int lockvalue_draining;
+ // First mark that a teardown is occurring, so handlers will stop entering the lock.
+ while (1) {
+ int lockvalue = mph_int_get (lock);
+ lockvalue_draining = lockvalue | PIPELOCK_TEARDOWN_BIT;
+ if (mph_int_test_and_set (lock, lockvalue, lockvalue_draining))
+ break;
+ }
+ // Now wait for all handlers to complete.
+ while (1) {
+ if (0 == PIPELOCK_GET_COUNT (lockvalue_draining))
+ break; // We now hold the lock.
+ // Handler is still running, spin until it completes.
+ sched_yield (); // We can call this because !defined(HOST_WIN32)
+ lockvalue_draining = mph_int_get (lock);
+ }
+}
+
+static inline void
+release_pipelock_teardown (int *lock)
+{
+ while (1) {
+ int lockvalue = mph_int_get (lock);
+ int lockvalue_new = lockvalue & ~PIPELOCK_TEARDOWN_BIT;
+ // Technically this can't fail, because we hold both the pipelock and the mutex, but
+ if (mph_int_test_and_set (lock, lockvalue, lockvalue_new))
+ return;
+ }
+}
+
+// Return 1 for success
+static inline int
+acquire_pipelock_handler (int *lock)
+{
+ while (1) {
+ int lockvalue = mph_int_get (lock);
+ if (lockvalue & PIPELOCK_TEARDOWN_BIT) // Final lock is being torn down
+ return 0;
+ int lockvalue_new = PIPELOCK_INCR_COUNT (lockvalue, 1);
+ if (mph_int_test_and_set (lock, lockvalue, lockvalue_new))
+ return 1;
+ }
+}
+
+static inline void
+release_pipelock_handler (int *lock)
+{
+ while (1) {
+ int lockvalue = mph_int_get (lock);
+ int lockvalue_new = PIPELOCK_INCR_COUNT (lockvalue, -1);
+ if (mph_int_test_and_set (lock, lockvalue, lockvalue_new))
+ return;
+ }
+}
+
+// This handler is registered once for each UnixSignal object. A pipe is maintained
+// for each one; Wait users read at one end of this pipe, and default_handler sends
+// a write on the pipe for each signal received while the Wait is ongoing.
+//
+// Notice a fairly unlikely race condition exists here: Because we synchronize with
+// pipe teardown, but not install/uninstall (in other words, we are only trying to
+// protect against writing on a closed pipe) it is technically possible a full
+// uninstall and then an install could complete after signum is checked but before
+// the remaining instructions execute. In this unlikely case count could be
+// incremented or a byte written on the wrong signal handler.