Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
53 changes: 53 additions & 0 deletions lib/wasi-types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,60 @@ pub mod net {
}

pub mod signal {
use core::time::Duration;

use wasmer::ValueType;

pub use crate::wasi::Signal;
use crate::wasi::{Errno, Timestamp};

#[derive(Debug, Copy, Clone, ValueType)]
#[repr(C)]
pub struct __wasi_timeval_t {
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<Duration> 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<Self, Self::Error> {
// `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)]
#[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
15 changes: 9 additions & 6 deletions lib/wasix/src/syscalls/wasi/proc_raise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ pub fn proc_raise(mut ctx: FunctionEnvMut<'_, WasiEnv>, sig: Signal) -> Result<E
Ok(Errno::Success)
}

/// ### `proc_raise()`
/// Send a signal to the process of the calling thread.
/// Note: This is similar to `raise` in POSIX.
/// ### `proc_raise_interval()`
/// Send a signal to the process of the calling thread with an interval.
/// Note: This is similar to `setitimer` in POSIX.
/// Inputs:
/// - `Signal`
/// Signal to be raised for this process
/// - `sig`: Signal to be raised for this process
/// - `interval`: The time interval in milliseconds
/// - `repeat`: Whether repeat the `sig` with `interval` or not
pub fn proc_raise_interval(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
sig: Signal,
Expand All @@ -35,7 +36,9 @@ pub fn proc_raise_interval(
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)?;

Expand Down
2 changes: 2 additions & 0 deletions lib/wasix/src/syscalls/wasix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down
81 changes: 81 additions & 0 deletions lib/wasix/src/syscalls/wasix/proc_raise_interval2.rs
Original file line number Diff line number Diff line change
@@ -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<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 = 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<Option<Duration>, Errno> {
let dur: Duration = tv.try_into()?;

if dur == Duration::ZERO {
Ok(None)
} else {
Ok(Some(dur))
}
}
14 changes: 14 additions & 0 deletions lib/wasix/tests/wasm_tests/libc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions lib/wasix/tests/wasm_tests/libc_tests/alarm/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -ex

$CC main.c -o main
Loading
Loading