Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/wasi-types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions lib/wasix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv<WasiEnv>)
"proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit2::<Memory32>),
"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::<Memory32>),
"proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::<Memory32>),
"proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::<Memory32>),
"proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::<Memory32>),
Expand Down Expand Up @@ -719,6 +720,7 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv<WasiEnv>)
"proc_exit2" => Function::new_typed_with_env(&mut store, env, proc_exit2::<Memory64>),
"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::<Memory64>),
"proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::<Memory64>),
"proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::<Memory64>),
"proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::<Memory64>),
Expand Down
18 changes: 11 additions & 7 deletions lib/wasix/src/os/task/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,27 +733,31 @@ impl WasiProcess {
}

/// Signals one of the threads every interval
pub fn signal_interval(&self, signal: Signal, interval: Option<Duration>, repeat: bool) {
pub fn signal_interval(
&self,
signal: Signal,
value: Option<Duration>,
interval: Option<Duration>,
) -> Option<WasiSignalInterval> {
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;
inner.signal_intervals.insert(
signal,
WasiSignalInterval {
signal,
current_value,
interval,
last_signal: now,
repeat,
},
);
)
}

/// Returns the number of active threads for this process
Expand Down
8 changes: 4 additions & 4 deletions lib/wasix/src/os/task/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Duration>,
/// Last time that a signal was triggered
pub last_signal: u128,
}
Expand Down
33 changes: 17 additions & 16 deletions lib/wasix/src/state/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}
}

Expand Down
71 changes: 70 additions & 1 deletion lib/wasix/src/syscalls/wasi/proc_raise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,84 @@ pub fn proc_raise_interval(
interval: Timestamp,
repeat: Bool,
) -> Result<Errno, WasiError> {
println!("called regular raise");
let env = ctx.data();
let interval = match interval {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @zebreus said, an we implement a helper function that uses Rust types, and make both old and new implementation use the helper, to avoid duplication?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah yeah, I wanted to open a PR to have a minimal PoC on the new syscall. Since we are good with the API now, I will do the refactors and add the tests to complete the PR.

Copy link
Copy Markdown
Contributor Author

@aeryz aeryz Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving it some more thought, I think the functions are currently already implemented in a way that the main logic is shared. Note that the shared logic is:

  1. timer is set with the given signal
  2. previous timer is removed when the timer.value is 0
    Both of these are already implemented by WasiProcess::signal_interval. The functions differ in:
  3. proc_raise_interval works with millisecond timestamps, but proc_raise_interval2 works with itimerval
  4. proc_raise_interval2 makes use of the old timer.
    These are already implemented seperately and the only duplicate part is WasiEnv::do_pending_operations which I would say it's good to keep it explicit per syscall unless the legacy implementation is directly forwarded to the new implementation.

And in terms of the use of proper Rust types. I kept the fields of __wasi_timeval_t private and make it convertible to Duration. So if anyone wants to do any meaningful thing with __wasi_timeval_t, they would have to convert it to Duration first. And our internal Rust types and the signal interval related functions all use Durations. Please let me know if you see a possible footgun.

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<M: MemorySize>(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
sig: Signal,
new_ptr: WasmPtr<__wasi_itimerval_t, M>,
ret_old: WasmPtr<__wasi_itimerval_t, M>,
) -> Result<Errno, WasiError> {
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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are sticking to Linux semantics of setitimer here, which I reckon we should, then the old value is supposed to be the time until the next trigger time, not the originally configured time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch! My understanding was when a timer is hit, the value becomes interval and interval stays as is. It's actually true but I missed the part where value always gives the time until the next trigger time.
Relevant info: posix settimer, setitimer.

I'll return the value - now - last_signal instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here

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<Duration> {
if tv.sec == 0 && tv.usec == 0 {
Copy link
Copy Markdown
Collaborator

@theduke theduke Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this can overflow the range of Duration.
Should probably return an error here, so the caller gets back an Errno::INVAL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah better to use checked_add here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here

None
} else {
Some(Duration::from_secs(tv.sec) + Duration::from_micros(tv.usec))
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add a test/tests in the tests/wasix test suite.

The pattern of existing tests should be easy to emulate.
Note: you'll need wasixcc ( https://github.com/wasix-org/wasixcc )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are added under libc_tests for alarm and setitimer.

Loading