From c586f4766f6c49618e0aa74b24cc94f5a8bac8a6 Mon Sep 17 00:00:00 2001 From: aeryz Date: Tue, 17 Mar 2026 02:20:03 +0300 Subject: [PATCH 1/2] Add `proc_raise_interval2` syscall Signed-off-by: aeryz --- lib/wasi-types/src/types.rs | 17 ++++++ lib/wasix/src/lib.rs | 2 + lib/wasix/src/os/task/process.rs | 18 +++--- lib/wasix/src/os/task/signal.rs | 8 +-- lib/wasix/src/state/env.rs | 33 ++++++----- lib/wasix/src/syscalls/wasi/proc_raise.rs | 71 ++++++++++++++++++++++- 6 files changed, 121 insertions(+), 28 deletions(-) diff --git a/lib/wasi-types/src/types.rs b/lib/wasi-types/src/types.rs index 5c4a646f65df..463e3fccdebe 100644 --- a/lib/wasi-types/src/types.rs +++ b/lib/wasi-types/src/types.rs @@ -291,7 +291,24 @@ pub mod net { } pub mod signal { + use wasmer::ValueType; + pub use crate::wasi::Signal; + use crate::wasi::Timestamp; + + #[derive(Debug, Copy, Clone, ValueType)] + #[repr(C)] + pub struct __wasi_timeval_t { + pub sec: Timestamp, + pub usec: Timestamp, + } + + #[derive(Debug, Copy, Clone, ValueType)] + #[repr(C)] + pub struct __wasi_itimerval_t { + pub interval: __wasi_timeval_t, + pub value: __wasi_timeval_t, + } } pub mod subscription { diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index 31c4ff159911..f54ca9fc0a88 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -572,6 +572,7 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit2::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), + "proc_raise_interval2" => Function::new_typed_with_env(&mut store, env, proc_raise_interval2::), "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), "proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::), @@ -719,6 +720,7 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit2::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), + "proc_raise_interval2" => Function::new_typed_with_env(&mut store, env, proc_raise_interval2::), "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), "proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::), diff --git a/lib/wasix/src/os/task/process.rs b/lib/wasix/src/os/task/process.rs index f1d0f1244818..9482d99e2e9a 100644 --- a/lib/wasix/src/os/task/process.rs +++ b/lib/wasix/src/os/task/process.rs @@ -733,15 +733,19 @@ impl WasiProcess { } /// Signals one of the threads every interval - pub fn signal_interval(&self, signal: Signal, interval: Option, repeat: bool) { + pub fn signal_interval( + &self, + signal: Signal, + value: Option, + interval: Option, + ) -> Option { let mut inner = self.inner.0.lock().unwrap(); - let interval = match interval { + let current_value = match value { None => { - inner.signal_intervals.remove(&signal); - return; + return inner.signal_intervals.remove(&signal); } - Some(a) => a, + Some(value) => value, }; let now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() as u128; @@ -749,11 +753,11 @@ impl WasiProcess { signal, WasiSignalInterval { signal, + current_value, interval, last_signal: now, - repeat, }, - ); + ) } /// Returns the number of active threads for this process diff --git a/lib/wasix/src/os/task/signal.rs b/lib/wasix/src/os/task/signal.rs index d6b729b888fc..54b6c5f4d488 100644 --- a/lib/wasix/src/os/task/signal.rs +++ b/lib/wasix/src/os/task/signal.rs @@ -21,10 +21,10 @@ pub type DynSignalHandlerAbi = dyn SignalHandlerAbi + Send + Sync + 'static; pub struct WasiSignalInterval { /// Signal that will be raised pub signal: Signal, - /// Time between the signals - pub interval: Duration, - /// Flag that indicates if the signal should repeat - pub repeat: bool, + /// The current interval value + pub current_value: Duration, + /// The next interval value if any + pub interval: Option, /// Last time that a signal was triggered pub last_signal: u128, } diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 12ce05bcf78a..88f0634bdf86 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -748,29 +748,30 @@ impl WasiEnv { let inner = env_inner.main_module_instance_handles(); if let Some(handler) = inner.signal.clone() { // We might also have signals that trigger on timers - let mut now = 0; { - let mut has_signal_interval = false; let mut inner = env.process.inner.0.lock().unwrap(); + if !inner.signal_intervals.is_empty() { - now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000).unwrap() - as u128; - for signal in inner.signal_intervals.values() { + let now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000) + .unwrap() as u128; + inner.signal_intervals.retain(|_, signal| { + let mut should_retain = true; let elapsed = now - signal.last_signal; - if elapsed >= signal.interval.as_nanos() { - has_signal_interval = true; - break; - } - } - } - if has_signal_interval { - for signal in inner.signal_intervals.values_mut() { - let elapsed = now - signal.last_signal; - if elapsed >= signal.interval.as_nanos() { + if elapsed >= signal.current_value.as_nanos() { signal.last_signal = now; signals.push(signal.signal); + match signal.interval { + Some(interval) => { + signal.current_value = interval; + } + None => { + // interval is None, so we can remove the timer now. + should_retain = false; + } + } } - } + should_retain + }); } } diff --git a/lib/wasix/src/syscalls/wasi/proc_raise.rs b/lib/wasix/src/syscalls/wasi/proc_raise.rs index fec04deee205..1ca7403eea46 100644 --- a/lib/wasix/src/syscalls/wasi/proc_raise.rs +++ b/lib/wasix/src/syscalls/wasi/proc_raise.rs @@ -29,15 +29,84 @@ pub fn proc_raise_interval( interval: Timestamp, repeat: Bool, ) -> Result { + println!("called regular raise"); let env = ctx.data(); let interval = match interval { 0 => None, a => Some(Duration::from_millis(a)), }; let repeat = matches!(repeat, Bool::True); - env.process.signal_interval(sig, interval, repeat); + let _ = env + .process + .signal_interval(sig, interval, if repeat { interval } else { None }); WasiEnv::do_pending_operations(&mut ctx)?; Ok(Errno::Success) } + +/// ### `proc_raise_interval2()` +/// Send a delayed signal to the process of the calling thread with an optional interval for repeated signaling. +/// Note: This is similar to `setitimer` in POSIX. +/// Inputs: +/// - `sig`: Signal to be raised for this process +/// - `new_ptr`: Pointer to the new value +/// - `ret_old`: Output pointer to the old value. If `null`, it's ignored. +pub fn proc_raise_interval2( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sig: Signal, + new_ptr: WasmPtr<__wasi_itimerval_t, M>, + ret_old: WasmPtr<__wasi_itimerval_t, M>, +) -> Result { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + + let new_ptr = new_ptr.deref(&memory); + let new = wasi_try_ok!(new_ptr.read().map_err(crate::mem_error_to_wasi)); + + let value = timeval_to_duration(new.value); + let interval = timeval_to_duration(new.interval); + + println!("duration: {value:?} {interval:?}"); + + let old_value = env.process.signal_interval(sig, value, interval); + + if !ret_old.is_null() { + let ret_ptr = ret_old.deref(&memory); + let zero = __wasi_timeval_t { sec: 0, usec: 0 }; + let ret_timer = match old_value { + Some(old_value) => __wasi_itimerval_t { + interval: if let Some(interval) = old_value.interval { + __wasi_timeval_t { + sec: interval.as_secs(), + usec: interval.subsec_micros().into(), + } + } else { + zero + }, + value: __wasi_timeval_t { + sec: old_value.current_value.as_secs(), + usec: old_value.current_value.subsec_micros().into(), + }, + }, + None => __wasi_itimerval_t { + interval: zero, + value: zero, + }, + }; + + wasi_try_ok!(ret_ptr.write(ret_timer).map_err(crate::mem_error_to_wasi)); + } + + WasiEnv::do_pending_operations(&mut ctx)?; + + Ok(Errno::Success) +} + +fn timeval_to_duration(tv: __wasi_timeval_t) -> Option { + if tv.sec == 0 && tv.usec == 0 { + None + } else { + Some(Duration::from_secs(tv.sec) + Duration::from_micros(tv.usec)) + } +} From 1431b8d0ec638ecfb52870622a70e75983d6367f Mon Sep 17 00:00:00 2001 From: aeryz Date: Fri, 27 Mar 2026 18:11:57 +0300 Subject: [PATCH 2/2] refactor proc_raise_interval2 and add tests --- lib/wasi-types/src/types.rs | 42 +++++- lib/wasix/src/syscalls/wasi/proc_raise.rs | 78 +---------- lib/wasix/src/syscalls/wasix/mod.rs | 2 + .../syscalls/wasix/proc_raise_interval2.rs | 81 +++++++++++ lib/wasix/tests/wasm_tests/libc_tests.rs | 14 ++ .../wasm_tests/libc_tests/alarm/build.sh | 4 + .../tests/wasm_tests/libc_tests/alarm/main.c | 91 ++++++++++++ .../wasm_tests/libc_tests/setitimer/build.sh | 4 + .../wasm_tests/libc_tests/setitimer/main.c | 129 ++++++++++++++++++ 9 files changed, 370 insertions(+), 75 deletions(-) create mode 100644 lib/wasix/src/syscalls/wasix/proc_raise_interval2.rs create mode 100644 lib/wasix/tests/wasm_tests/libc_tests/alarm/build.sh create mode 100644 lib/wasix/tests/wasm_tests/libc_tests/alarm/main.c create mode 100644 lib/wasix/tests/wasm_tests/libc_tests/setitimer/build.sh create mode 100644 lib/wasix/tests/wasm_tests/libc_tests/setitimer/main.c diff --git a/lib/wasi-types/src/types.rs b/lib/wasi-types/src/types.rs index 463e3fccdebe..ae1b04271a5d 100644 --- a/lib/wasi-types/src/types.rs +++ b/lib/wasi-types/src/types.rs @@ -291,16 +291,52 @@ pub mod net { } pub mod signal { + use core::time::Duration; + use wasmer::ValueType; pub use crate::wasi::Signal; - use crate::wasi::Timestamp; + use crate::wasi::{Errno, Timestamp}; #[derive(Debug, Copy, Clone, ValueType)] #[repr(C)] pub struct __wasi_timeval_t { - pub sec: Timestamp, - pub usec: Timestamp, + sec: Timestamp, + usec: Timestamp, + } + + impl __wasi_timeval_t { + pub const ZERO: Self = Self { sec: 0, usec: 0 }; + + #[must_use] + pub const fn from_duration(duration: Duration) -> Self { + Self { + sec: duration.as_secs(), + usec: duration.subsec_micros() as u64, + } + } + } + + impl From for __wasi_timeval_t { + fn from(value: Duration) -> Self { + Self::from_duration(value) + } + } + + impl TryFrom<__wasi_timeval_t> for Duration { + type Error = Errno; + + fn try_from(value: __wasi_timeval_t) -> Result { + // `usec` cannot be >= 1sec + // https://github.com/torvalds/linux/blob/46b513250491a7bfc97d98791dbe6a10bcc8129d/include/linux/time64.h#L103 + if value.usec >= 1_000_000 { + return Err(Errno::Inval); + } + + Duration::from_secs(value.sec) + .checked_add(Duration::from_micros(value.usec)) + .ok_or(Errno::Inval) + } } #[derive(Debug, Copy, Clone, ValueType)] diff --git a/lib/wasix/src/syscalls/wasi/proc_raise.rs b/lib/wasix/src/syscalls/wasi/proc_raise.rs index 1ca7403eea46..1a54b36e6a2f 100644 --- a/lib/wasix/src/syscalls/wasi/proc_raise.rs +++ b/lib/wasix/src/syscalls/wasi/proc_raise.rs @@ -17,19 +17,19 @@ pub fn proc_raise(mut ctx: FunctionEnvMut<'_, WasiEnv>, sig: Signal) -> Result, sig: Signal, interval: Timestamp, repeat: Bool, ) -> Result { - println!("called regular raise"); let env = ctx.data(); let interval = match interval { 0 => None, @@ -44,69 +44,3 @@ pub fn proc_raise_interval( Ok(Errno::Success) } - -/// ### `proc_raise_interval2()` -/// Send a delayed signal to the process of the calling thread with an optional interval for repeated signaling. -/// Note: This is similar to `setitimer` in POSIX. -/// Inputs: -/// - `sig`: Signal to be raised for this process -/// - `new_ptr`: Pointer to the new value -/// - `ret_old`: Output pointer to the old value. If `null`, it's ignored. -pub fn proc_raise_interval2( - mut ctx: FunctionEnvMut<'_, WasiEnv>, - sig: Signal, - new_ptr: WasmPtr<__wasi_itimerval_t, M>, - ret_old: WasmPtr<__wasi_itimerval_t, M>, -) -> Result { - let env = ctx.data(); - let memory = unsafe { env.memory_view(&ctx) }; - - let new_ptr = new_ptr.deref(&memory); - let new = wasi_try_ok!(new_ptr.read().map_err(crate::mem_error_to_wasi)); - - let value = timeval_to_duration(new.value); - let interval = timeval_to_duration(new.interval); - - println!("duration: {value:?} {interval:?}"); - - let old_value = env.process.signal_interval(sig, value, interval); - - if !ret_old.is_null() { - let ret_ptr = ret_old.deref(&memory); - let zero = __wasi_timeval_t { sec: 0, usec: 0 }; - let ret_timer = match old_value { - Some(old_value) => __wasi_itimerval_t { - interval: if let Some(interval) = old_value.interval { - __wasi_timeval_t { - sec: interval.as_secs(), - usec: interval.subsec_micros().into(), - } - } else { - zero - }, - value: __wasi_timeval_t { - sec: old_value.current_value.as_secs(), - usec: old_value.current_value.subsec_micros().into(), - }, - }, - None => __wasi_itimerval_t { - interval: zero, - value: zero, - }, - }; - - wasi_try_ok!(ret_ptr.write(ret_timer).map_err(crate::mem_error_to_wasi)); - } - - WasiEnv::do_pending_operations(&mut ctx)?; - - Ok(Errno::Success) -} - -fn timeval_to_duration(tv: __wasi_timeval_t) -> Option { - if tv.sec == 0 && tv.usec == 0 { - None - } else { - Some(Duration::from_secs(tv.sec) + Duration::from_micros(tv.usec)) - } -} diff --git a/lib/wasix/src/syscalls/wasix/mod.rs b/lib/wasix/src/syscalls/wasix/mod.rs index bc2bf061f5fb..b8d616ac13d8 100644 --- a/lib/wasix/src/syscalls/wasix/mod.rs +++ b/lib/wasix/src/syscalls/wasix/mod.rs @@ -44,6 +44,7 @@ mod proc_fork_env; mod proc_id; mod proc_join; mod proc_parent; +mod proc_raise_interval2; mod proc_signal; mod proc_signals_get; mod proc_signals_sizes_get; @@ -136,6 +137,7 @@ pub use proc_fork_env::*; pub use proc_id::*; pub use proc_join::*; pub use proc_parent::*; +pub use proc_raise_interval2::*; pub use proc_signal::*; pub use proc_signals_get::*; pub use proc_signals_sizes_get::*; diff --git a/lib/wasix/src/syscalls/wasix/proc_raise_interval2.rs b/lib/wasix/src/syscalls/wasix/proc_raise_interval2.rs new file mode 100644 index 000000000000..5eb638026a9d --- /dev/null +++ b/lib/wasix/src/syscalls/wasix/proc_raise_interval2.rs @@ -0,0 +1,81 @@ +use super::*; +use crate::syscalls::*; + +/// ### `proc_raise_interval2()` +/// Send a delayed signal to the process of the calling thread with an optional interval for repeated signaling. +/// Note: This is similar to `setitimer` in POSIX. +/// Inputs: +/// - `sig`: Signal to be raised for this process +/// - `new_ptr`: Pointer to the new value +/// - `ret_old`: Output pointer to the old value. If `null`, it's ignored. +pub fn proc_raise_interval2( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + sig: Signal, + new_ptr: WasmPtr<__wasi_itimerval_t, M>, + ret_old: WasmPtr<__wasi_itimerval_t, M>, +) -> Result { + let env = ctx.data(); + let memory = unsafe { env.memory_view(&ctx) }; + + let new_ptr = new_ptr.deref(&memory); + let new = wasi_try_ok!(new_ptr.read().map_err(crate::mem_error_to_wasi)); + + let value = wasi_try_ok!(timeval_to_duration(new.value)); + let interval = wasi_try_ok!(timeval_to_duration(new.interval)); + + let old_value = env.process.signal_interval(sig, value, interval); + + if !ret_old.is_null() { + let ret_ptr = ret_old.deref(&memory); + + let ret_timer = match old_value { + Some(old_value) => { + let now = Duration::from_nanos(wasi_try_ok!(platform_clock_time_get( + Snapshot0Clockid::Monotonic, + 1_000_000 + )) as u64); + + // IMPORTANT: Unlike Linux, the signal handlers run whenever there is a call to the host. + // This means there is a chance that the timer is handled with some delay. When this is the + // case, the remaining time before the timer ticks becomes negative. We treat it as `0` and + // set the old timer's value to `0`. + let old_remaining = old_value + .current_value + .checked_sub( + now.checked_sub(Duration::from_nanos(old_value.last_signal as u64)) + .expect("current time is greater than a previously set time"), + ) + .unwrap_or(Duration::ZERO); + + __wasi_itimerval_t { + interval: if let Some(interval) = old_value.interval { + interval.into() + } else { + __wasi_timeval_t::ZERO + }, + value: old_remaining.into(), + } + } + None => __wasi_itimerval_t { + interval: __wasi_timeval_t::ZERO, + value: __wasi_timeval_t::ZERO, + }, + }; + + wasi_try_ok!(ret_ptr.write(ret_timer).map_err(crate::mem_error_to_wasi)); + } + + WasiEnv::do_pending_operations(&mut ctx)?; + + Ok(Errno::Success) +} + +fn timeval_to_duration(tv: __wasi_timeval_t) -> Result, Errno> { + let dur: Duration = tv.try_into()?; + + if dur == Duration::ZERO { + Ok(None) + } else { + Ok(Some(dur)) + } +} diff --git a/lib/wasix/tests/wasm_tests/libc_tests.rs b/lib/wasix/tests/wasm_tests/libc_tests.rs index 76645d3d4cdd..f0c1bbf9160a 100644 --- a/lib/wasix/tests/wasm_tests/libc_tests.rs +++ b/lib/wasix/tests/wasm_tests/libc_tests.rs @@ -38,3 +38,17 @@ fn test_variadic_args() { let output = String::from_utf8_lossy(&result.stdout); assert_eq!(output.trim(), "Printing 5, 6, 0, 42"); } + +#[test] +fn test_libc_setitimer() { + let wasm = run_build_script(file!(), "setitimer").unwrap(); + let result = run_wasm_with_result(&wasm, wasm.parent().unwrap()).unwrap(); + assert_eq!(result.exit_code.unwrap(), libc::EXIT_SUCCESS); +} + +#[test] +fn test_libc_alarm() { + let wasm = run_build_script(file!(), "alarm").unwrap(); + let result = run_wasm_with_result(&wasm, wasm.parent().unwrap()).unwrap(); + assert_eq!(result.exit_code.unwrap(), libc::EXIT_SUCCESS); +} diff --git a/lib/wasix/tests/wasm_tests/libc_tests/alarm/build.sh b/lib/wasix/tests/wasm_tests/libc_tests/alarm/build.sh new file mode 100644 index 000000000000..d768288564b3 --- /dev/null +++ b/lib/wasix/tests/wasm_tests/libc_tests/alarm/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex + +$CC main.c -o main diff --git a/lib/wasix/tests/wasm_tests/libc_tests/alarm/main.c b/lib/wasix/tests/wasm_tests/libc_tests/alarm/main.c new file mode 100644 index 000000000000..d630f62c55f6 --- /dev/null +++ b/lib/wasix/tests/wasm_tests/libc_tests/alarm/main.c @@ -0,0 +1,91 @@ +/* +Tests: +- Alarm triggers the signal handler after the given time. +- Signal handler gets the correct signal after the `handler` hits. +- The signal is raised only once. +*/ + +#include +#include +#include +#include + +#define NS_PER_SEC 1000000000LL +#define NS_PER_MS 1000000LL + +#define TIME_DIFF_PRECISION (20 * NS_PER_MS) + +#define ALARM_SEC 1 + +static struct timespec armed_at; + +static volatile sig_atomic_t signal_count = 0; +static volatile sig_atomic_t alarm_fired = 0; + +long long ts_to_ns(struct timespec t) { + return (long long)t.tv_sec * NS_PER_SEC + t.tv_nsec; +} + +void handler(int signum) { + struct timespec fired_at; + long long base_diff; + + if (signum != SIGALRM) { + _exit(1); + } + + signal_count++; + if (signal_count > 1) { + _exit(1); + } + + clock_gettime(CLOCK_MONOTONIC, &fired_at); + long long diff = ts_to_ns(fired_at) - ts_to_ns(armed_at); + + base_diff = ALARM_SEC * NS_PER_SEC; + + // Check if the time difference is within bounds + if (diff > (base_diff + TIME_DIFF_PRECISION) || diff < (base_diff - TIME_DIFF_PRECISION)) { + fprintf(stderr, "the time difference is invalid %lld %lld", diff, base_diff); + _exit(1); + } + + alarm_fired = 1; +} + +int main() { + struct sigaction sa; + + // setup the signal handler for SIGALRM + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); + + clock_gettime(CLOCK_MONOTONIC, &armed_at); + if (alarm(ALARM_SEC) != 0) { + fprintf(stderr, "alarm unexpectedly replaced an existing alarm"); + return 1; + } + + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 1 * NS_PER_MS; // 1ms + + int i = 3000; // 3 seconds total wait before failure/success + while (i--) { + nanosleep(&ts, NULL); + } + + if (!alarm_fired) { + fprintf(stderr, "alarm did not fire\n"); + return 1; + } + + if (signal_count != 1) { + fprintf(stderr, "handler ran unexpected number of times: %d\n", signal_count); + return 1; + } + + return 0; +} diff --git a/lib/wasix/tests/wasm_tests/libc_tests/setitimer/build.sh b/lib/wasix/tests/wasm_tests/libc_tests/setitimer/build.sh new file mode 100644 index 000000000000..d768288564b3 --- /dev/null +++ b/lib/wasix/tests/wasm_tests/libc_tests/setitimer/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex + +$CC main.c -o main diff --git a/lib/wasix/tests/wasm_tests/libc_tests/setitimer/main.c b/lib/wasix/tests/wasm_tests/libc_tests/setitimer/main.c new file mode 100644 index 000000000000..2d8efde0b657 --- /dev/null +++ b/lib/wasix/tests/wasm_tests/libc_tests/setitimer/main.c @@ -0,0 +1,129 @@ +/* +Tests: +- Setitimer creates the correct timer with the given value + interval. +- The signal handler gets the correct signal. +- Reseting the timer returns the time until the next signal. +- Setting the old timer pointer to `NULL` do not cause any issues. +- After the `value` timer is hit, the timer switches to the provided `interval`. +*/ + +#include +#include +#include +#include +#include + +#define NS_PER_SEC 1000000000LL +#define NS_PER_MS 1000000LL +#define US_PER_MS 1000LL + +// 20 ms precision +#define TIME_DIFF_PRECISION 20 * NS_PER_MS + +#define VALUE_SEC 2 +#define VALUE_MS 500 + +#define INTERVAL_SEC 1 +#define INTERVAL_MS 200 + +static struct timespec armed_at; + +long long ts_to_ns(struct timespec t) { + return (long long)t.tv_sec * 1000000000LL + t.tv_nsec; +} + +void handler(int signum) { + static int count = 0; + struct timespec fired_at; + long long base_diff; + + // Make sure the given signal is the correct one + if (signum != SIGALRM) { + _exit(1); + } + + count++; + + clock_gettime(CLOCK_MONOTONIC, &fired_at); + long long diff = ts_to_ns(fired_at) - ts_to_ns(armed_at); + + armed_at.tv_sec = fired_at.tv_sec; + armed_at.tv_nsec = fired_at.tv_nsec; + + // If we hit here the first time, we check `it_value`, otherwise we check `it_interval` + if (count == 1) { + base_diff = VALUE_SEC * NS_PER_SEC + VALUE_MS * NS_PER_MS; + } else { + base_diff = INTERVAL_SEC * NS_PER_SEC + INTERVAL_MS * NS_PER_MS; + } + + // Check if the time difference is within bounds + if (diff > (base_diff + TIME_DIFF_PRECISION) || diff < (base_diff - TIME_DIFF_PRECISION)) { + fprintf(stderr, "the time difference is invalid %lld %lld", diff, base_diff); + _exit(1); + } + + if (count == 3) { + _exit(0); + }; +} + +int main() { + struct sigaction sa; + struct itimerval timer; + struct itimerval old_timer; + + // setup the signal handler for SIGALRM + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); + + timer.it_value.tv_sec = 5; + timer.it_value.tv_usec = 0; + + timer.it_interval.tv_sec = 2; + timer.it_interval.tv_usec = 0; + + if (setitimer(ITIMER_REAL, &timer, NULL) == -1) { + fprintf(stderr, "setitimer"); + return 1; + } + + // The initial time interval is set to 2.5 seconds + timer.it_value.tv_sec = VALUE_SEC; + timer.it_value.tv_usec = VALUE_MS * US_PER_MS; + + // Later, we expect the timer to be set to 1.2 seconds + timer.it_interval.tv_sec = INTERVAL_SEC; + timer.it_interval.tv_usec = INTERVAL_MS * US_PER_MS; + + // We save the time at when we call `setitimer` to later compute the rough time spent until the sighandler + // is executed. + clock_gettime(CLOCK_MONOTONIC, &armed_at); + + if (setitimer(ITIMER_REAL, &timer, &old_timer) == -1) { + fprintf(stderr, "setitimer"); + return 1; + } + + // We initially set the timer to 5 seconds, and then immediately call `setitimer` again. + // This should result in getting some value back like 4.999... seconds. + if (old_timer.it_value.tv_sec != 4 || old_timer.it_value.tv_usec > 900 * NS_PER_MS) { + fprintf(stderr, "the timer changed unexpectedly (%lld, %lld)", old_timer.it_value.tv_sec, old_timer.it_value.tv_usec); + return 1; + } + + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 1 * NS_PER_MS; // 1ms + int i = 100000; // 10 seconds wait total before failure + while (i--) { + // We are sleeping for 1ms to make the Wasmer signal handler run every 1ms + // so that we can compute the time difference between the timer set + // and the sighandler call accurately. + nanosleep(&ts, NULL); + } + + return 1; +}