-
Notifications
You must be signed in to change notification settings - Fork 103
refactor signal infrastructure and support SIGINT (ctrl-c) #690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,6 +95,34 @@ pub trait ThreadProvider: RawPointerProvider { | |
| /// [`EnterShim`]: crate::shim::EnterShim | ||
| /// [`EnterShim::interrupt`]: crate::shim::EnterShim::interrupt | ||
| fn interrupt_thread(&self, thread: &Self::ThreadHandle); | ||
|
|
||
| /// Runs `f` on the current thread after performing any platform-specific | ||
| /// thread registration needed for [`current_thread`](Self::current_thread) | ||
| /// and related functionality to work. | ||
| /// | ||
| /// This is intended for test threads that do not go through the normal | ||
| /// [`spawn_thread`](Self::spawn_thread) / guest entry path. The platform | ||
| /// sets up thread state before calling `f` and tears it down afterward. | ||
| /// | ||
| /// The default implementation simply calls `f()` with no additional setup. | ||
| /// Platforms that require explicit thread registration should override this. | ||
| #[cfg(debug_assertions)] | ||
| fn run_test_thread<R>(f: impl FnOnce() -> R) -> R { | ||
| f() | ||
| } | ||
| } | ||
|
|
||
| /// Provider for consuming platform-originating signals. | ||
| /// | ||
| /// Platforms can record signals (e.g., `SIGINT`) and the shim should call | ||
| /// [`SignalProvider::take_pending_signals`] to consume them. | ||
| pub trait SignalProvider { | ||
| /// Atomically take all pending asynchronous signals (e.g., SIGINT and SIGALRM) | ||
| /// for the current thread, passing each one to `f`. | ||
| /// | ||
| /// Platforms that support asynchronous signals should override this method. | ||
| #[allow(unused_variables, reason = "no-op by default")] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: prefer |
||
| fn take_pending_signals(&self, f: impl FnMut(crate::shim::Signal)) {} | ||
|
Comment on lines
+115
to
+125
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am quite surprised by this provider. There are some surprising inversions happening here, for example, it depends on I don't think the Also, "shim should call" implies that a shim that does not call stuff is incorrect. This sort of requirement is exactly what traits are for, and thus the way the design should be is to actually have the shim implement a trait that probably looks like the following: Then the platform can always invoke this trait, and the shim can make a decision of it wants to buffer things or not. If you need/want the "all in a group" behavior, then you can make the trait (instead of the above): |
||
| } | ||
|
|
||
| /// Punch through any functionality for a particular platform that is not explicitly part of the | ||
|
|
@@ -220,6 +248,25 @@ where | |
| /// A provider of raw mutexes | ||
| pub trait RawMutexProvider { | ||
| type RawMutex: RawMutex; | ||
|
|
||
| /// Called when a thread enters an interruptible wait. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Called by whom? |
||
| /// | ||
| /// The passed `waker` should live at least until the thread leaves the interruptible | ||
| /// wait (i.e., [`on_interruptible_wait_end`](Self::on_interruptible_wait_end) is called). | ||
|
Comment on lines
+254
to
+255
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a somewhat surprising requirement; we can simplify this by updating the signature in this to take a |
||
| /// The platform can use the `waker` to wake up the thread while it is in the interruptible wait. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "can use" -> "uses"? |
||
| /// | ||
| /// This is a no-op by default. | ||
| #[allow(unused_variables)] | ||
| fn on_interruptible_wait_start(&self, waker: &crate::event::wait::Waker<Self>) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name of this function is somewhat weird. It is not actually beginning the wait, right? Maybe something like Also, what is the expected behavior if someone runs "start" multiple times in a row? |
||
| where | ||
| Self: crate::sync::RawSyncPrimitivesProvider + Sized, | ||
| { | ||
| } | ||
|
|
||
| /// Called when a thread leaves an interruptible wait. | ||
| /// | ||
| /// This is a no-op by default. | ||
| fn on_interruptible_wait_end(&self) {} | ||
|
Comment on lines
+266
to
+269
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can merge with the above one by taking in a |
||
| } | ||
|
|
||
| /// A raw mutex/lock API; expected to roughly match (or even be implemented using) a Linux futex. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -133,3 +133,108 @@ impl Exception { | |
| /// #PF | ||
| pub const PAGE_FAULT: Self = Self(14); | ||
| } | ||
|
|
||
| /// A signal number. | ||
| /// | ||
| /// Signal numbers are 1-based and must be in the range 1–63. | ||
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||
| pub struct Signal(u32); | ||
|
Comment on lines
+139
to
+141
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: store as a |
||
|
|
||
| impl Signal { | ||
| /// SIGINT (signal 2) — interrupt from keyboard (Ctrl+C). | ||
| pub const SIGINT: Self = Self(2); | ||
| /// SIGALRM (signal 14) — timer signal from `alarm`. | ||
| pub const SIGALRM: Self = Self(14); | ||
|
|
||
| /// Create a `Signal` from a raw signal number. | ||
| /// | ||
| /// Returns `None` if `signum` is outside the valid range 1–63. | ||
| pub const fn from_raw(signum: u32) -> Option<Self> { | ||
| if signum >= 1 && signum <= 63 { | ||
| Some(Self(signum)) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
|
|
||
| /// Returns the raw signal number. | ||
| pub const fn as_raw(self) -> u32 { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| /// A set of [`Signal`]s, stored as a 64-bit bitmask. | ||
| /// | ||
| /// Bit `(signum - 1)` is set when signal `signum` is present in the set. | ||
| /// Because signal numbers are 1-based and capped at 63, all 63 possible | ||
| /// signals fit in a single `u64`. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| pub struct SigSet(u64); | ||
|
Comment on lines
+166
to
+172
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are explicitly defining the internal details of how things are stored, you can just expose the value inside it via |
||
|
|
||
| impl SigSet { | ||
| /// An empty signal set. | ||
| pub const fn empty() -> Self { | ||
| Self(0) | ||
| } | ||
|
|
||
| /// Returns `true` if the set contains no signals. | ||
| pub const fn is_empty(&self) -> bool { | ||
| self.0 == 0 | ||
| } | ||
|
|
||
| /// Adds `signal` to the set. | ||
| pub const fn add(&mut self, signal: Signal) { | ||
| self.0 |= 1 << (signal.0 - 1); | ||
| } | ||
|
|
||
| /// Returns a new set that is `self` with `signal` added. | ||
| #[must_use] | ||
| pub const fn with(self, signal: Signal) -> Self { | ||
| Self(self.0 | (1 << (signal.0 - 1))) | ||
| } | ||
|
|
||
| /// Removes `signal` from the set. | ||
| pub const fn remove(&mut self, signal: Signal) { | ||
| self.0 &= !(1 << (signal.0 - 1)); | ||
| } | ||
|
|
||
| /// Returns `true` if the set contains `signal`. | ||
| pub const fn contains(&self, signal: Signal) -> bool { | ||
| (self.0 & (1 << (signal.0 - 1))) != 0 | ||
| } | ||
|
|
||
| /// Removes and returns the lowest-numbered signal in the set, or `None` | ||
| /// if empty. | ||
| pub fn pop_lowest(&mut self) -> Option<Signal> { | ||
| if self.0 == 0 { | ||
| return None; | ||
| } | ||
| let bit = self.0.trailing_zeros(); | ||
| self.0 &= !(1u64 << bit); | ||
| // bit is 0–62, so bit + 1 is 1–63 — always valid. | ||
| Some(Signal(bit + 1)) | ||
| } | ||
|
|
||
| /// Creates a `SigSet` from a raw `u64` bitmask. | ||
| pub const fn from_u64(bits: u64) -> Self { | ||
| Self(bits) | ||
| } | ||
|
|
||
| /// Returns the underlying `u64` bitmask. | ||
| pub const fn as_u64(&self) -> u64 { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| impl Iterator for SigSet { | ||
| type Item = Signal; | ||
|
|
||
| fn next(&mut self) -> Option<Signal> { | ||
| self.pop_lowest() | ||
| } | ||
|
|
||
| fn size_hint(&self) -> (usize, Option<usize>) { | ||
| let count = self.0.count_ones() as usize; | ||
| (count, Some(count)) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,17 @@ impl TryFrom<i32> for Signal { | |
| } | ||
| } | ||
| } | ||
| impl TryFrom<Signal> for litebox::shim::Signal { | ||
| type Error = Signal; | ||
|
|
||
| fn try_from(value: Signal) -> Result<Self, Self::Error> { | ||
| match value { | ||
| Signal::SIGINT => Ok(Self::SIGINT), | ||
| Signal::SIGALRM => Ok(Self::SIGALRM), | ||
| _ => Err(value), | ||
|
Comment on lines
+105
to
+112
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is quite surprising, haven't you designed the I do think that the long-term move is for |
||
| } | ||
| } | ||
| } | ||
|
|
||
| /// The default disposition of a signal. | ||
| pub enum SignalDisposition { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we scope this function to tests only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it. Using
cfg(test)does not work here because the tests reside in a different crate (litebox_shim_linux). Test functions cannot be used by a different crate. One possible solution is adding a new feature, but that adds complexity.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, we can use
cfg(debug_assertions).