mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
gex-native: a timed futex wait past its deadline times out instead of panicking
perform_block tested start.elapsed() against the deadline and then subtracted a second reading from it; a thread descheduled between the two (a loaded machine) read past the deadline and panicked with "overflow when subtracting durations", killing the guest thread (Baba Is You's, on gooner). time_left reads the clock once and uses checked_sub; the futex and PI-lock waits use it. Test: time_left_tests. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_461fe3ff06374ecba693831148697ba6
This commit is contained in:
1 parent
8d03296efd
commit
824808854c
1 file changed
+28
-4
+28
-4
@@ -1358,6 +1358,32 @@ fn store_limits() -> wasmtime::StoreLimits {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// How much of a timed wait's `deadline` is left once `elapsed` has gone by,
|
||||
/// or `None` when it is up. Read the clock once and pass it here: testing
|
||||
/// `start.elapsed() >= d` and then computing `d - start.elapsed()` reads it
|
||||
/// twice, and a thread descheduled between the two (a loaded machine) got a
|
||||
/// second reading past the deadline and panicked subtracting durations,
|
||||
/// taking the guest thread with it (Baba Is You's, on gooner).
|
||||
fn time_left(deadline: std::time::Duration, elapsed: std::time::Duration) -> Option<std::time::Duration> {
|
||||
deadline.checked_sub(elapsed).filter(|d| !d.is_zero())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod time_left_tests {
|
||||
use super::time_left;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn a_deadline_already_past_is_up_rather_than_a_panic() {
|
||||
let ms = Duration::from_millis;
|
||||
assert_eq!(time_left(ms(50), ms(20)), Some(ms(30)));
|
||||
assert_eq!(time_left(ms(50), ms(50)), None);
|
||||
// The case that panicked: the clock read past the deadline.
|
||||
assert_eq!(time_left(ms(50), ms(51)), None);
|
||||
assert_eq!(time_left(Duration::ZERO, ms(1)), None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a syscall's blocking half outside the kernel lock.
|
||||
///
|
||||
/// `service` runs on every turn so host devices keep working while the guest
|
||||
@@ -1406,8 +1432,7 @@ fn perform_block(
|
||||
let slice = std::time::Duration::from_millis(20);
|
||||
loop {
|
||||
let left = match deadline {
|
||||
Some(d) if start.elapsed() >= d => return Err(Errno::ETIMEDOUT),
|
||||
Some(d) => (d - start.elapsed()).min(slice),
|
||||
Some(d) => time_left(d, start.elapsed()).ok_or(Errno::ETIMEDOUT)?.min(slice),
|
||||
None => slice,
|
||||
};
|
||||
match table.wait(uaddr, val, Some(left), &|| mem.read_u32(uaddr).ok()) {
|
||||
@@ -1447,8 +1472,7 @@ fn perform_block(
|
||||
continue;
|
||||
}
|
||||
let left = match deadline {
|
||||
Some(d) if start.elapsed() >= d => return Err(Errno::ETIMEDOUT),
|
||||
Some(d) => d - start.elapsed(),
|
||||
Some(d) => time_left(d, start.elapsed()).ok_or(Errno::ETIMEDOUT)?,
|
||||
None => std::time::Duration::from_millis(100),
|
||||
};
|
||||
// In slices: the owner's UNLOCK_PI wakes this, and the word
|
||||
|
||||
Reference in new issue
Block a user