From f28fe4a3875c784534dd486d879a38a1067ca8ee Mon Sep 17 00:00:00 2001
From: 4lDO2 <4lDO2@protonmail.com>
Date: Thu, 27 Jun 2024 21:20:56 +0200
Subject: [PATCH] Use fetch_add instead of CAS when setting sigmask.

This makes sigprocmask, the way it is currently implemented, wait-free
(technically I think x86 only guarantees LOCK XADD is lock-free and
fair, but still).
---
 redox-rt/src/signal.rs | 55 ++++++++++--------------------------------
 1 file changed, 13 insertions(+), 42 deletions(-)

diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs
index 8b7bf0ff..a651e341 100644
--- a/redox-rt/src/signal.rs
+++ b/redox-rt/src/signal.rs
@@ -79,30 +79,18 @@ unsafe fn inner(stack: &mut SigStack) {
     if !sigaction.flags.contains(SigactionFlags::NODEFER) {
         sigallow_inside &= !sig_bit(stack.sig_num);
     }
+    let sigallow_inside_lo = sigallow_inside & 0xffff_ffff;
+    let sigallow_inside_hi = sigallow_inside >> 32;
 
     // Set sigmask to sa_mask and unmark the signal as pending.
-    let lo = os.control.word[0].load(Ordering::Relaxed) >> 32;
-    let hi = os.control.word[1].load(Ordering::Relaxed) >> 32;
-    let prev_sigallow = lo | (hi << 32);
+    let prev_sigallow_lo = os.control.word[0].load(Ordering::Relaxed) >> 32;
+    let prev_sigallow_hi = os.control.word[1].load(Ordering::Relaxed) >> 32;
+    let prev_sigallow = prev_sigallow_lo | (prev_sigallow_hi << 32);
 
     let sig_group = stack.sig_num / 32;
 
-    let prev_w0 = os.control.word[0].fetch_update(Ordering::Relaxed, Ordering::Relaxed, |mut prev| {
-        prev &= 0xffff_ffff;
-        if sig_group == 0 {
-            prev &= !sig_bit(stack.sig_num);
-        }
-        prev |= (sigallow_inside & 0xffff_ffff) << 32;
-        Some(prev)
-    }).unwrap();
-    let prev_w1 = os.control.word[1].fetch_update(Ordering::Relaxed, Ordering::Relaxed, |mut prev| {
-        prev &= 0xffff_ffff;
-        if sig_group == 1 {
-            prev &= !sig_bit(stack.sig_num);
-        }
-        prev |= (sigallow_inside >> 32) << 32;
-        Some(prev)
-    }).unwrap();
+    let prev_w0 = os.control.word[0].fetch_add(sigallow_inside_lo.wrapping_sub(prev_sigallow_lo), Ordering::Relaxed);
+    let prev_w1 = os.control.word[1].fetch_add(sigallow_inside_hi.wrapping_sub(prev_sigallow_hi), Ordering::Relaxed);
 
     // TODO: If sa_mask caused signals to be unblocked, deliver one or all of those first?
 
@@ -123,16 +111,8 @@ unsafe fn inner(stack: &mut SigStack) {
     core::sync::atomic::compiler_fence(Ordering::Acquire);
 
     // Update allowset again.
-    let prev_w0 = os.control.word[0].fetch_update(Ordering::Relaxed, Ordering::Relaxed, |mut prev| {
-        prev &= 0xffff_ffff;
-        prev |= lo << 32;
-        Some(prev)
-    }).unwrap();
-    let prev_w1 = os.control.word[1].fetch_update(Ordering::Relaxed, Ordering::Relaxed, |mut prev| {
-        prev &= 0xffff_ffff;
-        prev |= hi << 32;
-        Some(prev)
-    }).unwrap();
+    let prev_w0 = os.control.word[0].fetch_add(prev_sigallow_lo.wrapping_sub(sigallow_inside_lo), Ordering::Relaxed);
+    let prev_w1 = os.control.word[1].fetch_add(prev_sigallow_hi.wrapping_sub(sigallow_inside_hi), Ordering::Relaxed);
 
     // TODO: If resetting the sigmask caused signals to be unblocked, then should they be delivered
     // here? And would it be possible to tail-call-optimize that?
@@ -183,19 +163,10 @@ fn modify_sigmask(old: Option<&mut u64>, op: Option<impl FnMut(u32, bool) -> u32
 
     for i in 0..2 {
         let pending_bits = words[i] & 0xffff_ffff;
-        let allow_bits = (words[i] >> 32) as u32;
-        let new_allow_bits = !op(!allow_bits, i == 1);
-
-        while let Err(changed) = ctl.word[i].compare_exchange(words[i], pending_bits | (u64::from(new_allow_bits) << 32), Ordering::Relaxed, Ordering::Relaxed) {
-            // If kernel observed a signal being unblocked and pending simultaneously, it will have
-            // set a flag causing it to check for the INHIBIT_SIGNALS flag every time the context
-            // is switched to. To avoid race conditions, we should NOT auto-raise those signals in
-            // userspace as a result of unblocking it. The kernel will instead take care of that later.
-            can_raise |= (changed & (changed >> 32)) << (32 * i);
-            cant_raise |= (changed & !(changed >> 32)) << (32 * i);
-
-            words[i] = changed;
-        }
+        let old_allow_bits = words[i] & 0xffff_ffff_0000_0000;
+        let new_allow_bits = u64::from(!op(!((old_allow_bits >> 32) as u32), i == 1)) << 32;
+
+        ctl.word[i].fetch_add(new_allow_bits.wrapping_sub(old_allow_bits), Ordering::Relaxed);
     }
 
     // TODO: Prioritize cant_raise realtime signals?
-- 
GitLab