Skip to content

Commit ff639ef

Browse files
authored
add monotonic clock "pause" feature to p1->p2 adapter (#13563)
This adds a new `adapter_monotonic_clock_set_paused` export to the adapter which toggles whether `clock_time_get` will call `monotonic_clock::now` when called with `CLOCKID_MONOTONIC` versus returning a cached value instead. This is a workaround for guest language runtimes whose `cabi_realloc` implementations may call `clock_time_get`. Since calling imports from `cabi_realloc` is disallowed, this will trap if the adapter calls `monotonic_clock::now`. We can avoid the trap by using a cached value instead. This helps us address bytecodealliance/componentize-go#56. In that case, `cabi_realloc` is implemented by calling [unsafe.SliceData](https://pkg.go.dev/unsafe#SliceData) to allocate a segment of the appropriate size and alignment from the GC-managed heap and then pinning it using [pinner.Pin](https://pkg.go.dev/runtime#Pinner.Pin) until it is no longer needed. However, such an allocation may trigger a GC under memory pressure, and as of this writing the Go collector calls `clock_time_get` to measure time spent in various stages of the GC process. An alternative fix for the `componentize-go` issue would be to modify the Go runtime to avoid such calls on WASI during GC, but getting that upstream is likely to be significantly more difficult than working around it in the adapter. If and when Go supports WASIp3 or later natively, the adapter will no longer be used, in which case we will certainly address this upstream.
1 parent eb98ce9 commit ff639ef

1 file changed

Lines changed: 58 additions & 3 deletions

File tree

  • crates/wasi-preview1-component-adapter/src

crates/wasi-preview1-component-adapter/src/lib.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,25 @@ pub unsafe extern "C" fn reset_adapter_state() {
199199
}
200200
}
201201

202+
/// Toggle whether `clock_time_get` calls `monotonic_clock::now` or uses a
203+
/// cached value.
204+
///
205+
/// When `paused` is true, subsequent calls to `clock_time_get` will return a
206+
/// cached value instead of calling `monotonic_clock::now`. This is useful in
207+
/// cases where the module's `cabi_realloc` function might call `clock_time_get`
208+
/// with `CLOCKID_MONOTONIC`. Since `cabi_realloc` is forbidden to call
209+
/// imports, we can avoid trapping by using the cached value.
210+
///
211+
/// This should be set back to false as soon as it is safe to call imports from
212+
/// `clock_time_get` again.
213+
#[unsafe(no_mangle)]
214+
pub unsafe extern "C" fn adapter_monotonic_clock_set_paused(paused: bool) {
215+
State::with(|state| {
216+
state.monotonic_clock_paused.set(paused);
217+
Ok(())
218+
});
219+
}
220+
202221
#[unsafe(no_mangle)]
203222
pub unsafe extern "C" fn cabi_import_realloc(
204223
old_ptr: *mut u8,
@@ -685,8 +704,32 @@ pub unsafe extern "C" fn clock_time_get(
685704
) -> Errno {
686705
match id {
687706
CLOCKID_MONOTONIC => {
688-
*time = monotonic_clock::now();
689-
ERRNO_SUCCESS
707+
// See `adapter_monotonic_clock_set_paused` for details on why we
708+
// sometimes use a cached value instead of calling
709+
// `monotonic_clock::now` here.
710+
if matches!(
711+
unsafe { get_allocation_state() },
712+
AllocationState::StackAllocated | AllocationState::StateAllocated
713+
) {
714+
State::with(|state| {
715+
if state.monotonic_clock_paused.get() {
716+
*time = state.monotonic_clock_cached.get();
717+
} else {
718+
let now = monotonic_clock::now();
719+
*time = now;
720+
state.monotonic_clock_cached.set(now);
721+
}
722+
723+
Ok(())
724+
})
725+
} else {
726+
// If the `State` has not yet been allocated, return a zero
727+
// value. This ensures that the clock won't go backwards once
728+
// the `State` is allocated and we start calling
729+
// `monotonic_clock::now`.
730+
*time = 0;
731+
ERRNO_SUCCESS
732+
}
690733
}
691734
CLOCKID_REALTIME => {
692735
let res = wall_clock::now();
@@ -2704,6 +2747,16 @@ struct State {
27042747
#[cfg(not(feature = "proxy"))]
27052748
dotdot: [UnsafeCell<u8>; 2],
27062749

2750+
/// Cached copy of the most recent value returned by `monotonic_clock::now`
2751+
monotonic_clock_cached: Cell<Timestamp>,
2752+
2753+
/// If true, skip calling `monotonic_clock::now` in `clock_time_get` and
2754+
/// instead return the value in `monotonic_clock_cached`.
2755+
///
2756+
/// See `wasi_snapshot_preview1 adapter_monotonic_clock_set_paused` for
2757+
/// details.
2758+
monotonic_clock_paused: Cell<bool>,
2759+
27072760
/// Another canary constant located at the end of the structure to catch
27082761
/// memory corruption coming from the bottom.
27092762
magic2: u32,
@@ -2765,7 +2818,7 @@ const fn temporary_data_size() -> usize {
27652818
}
27662819

27672820
// Remove miscellaneous metadata also stored in state.
2768-
let misc = if cfg!(feature = "proxy") { 8 } else { 10 };
2821+
let misc = if cfg!(feature = "proxy") { 12 } else { 14 };
27692822
start -= misc * size_of::<usize>();
27702823

27712824
// Everything else is the `command_data` allocation.
@@ -2882,6 +2935,8 @@ impl State {
28822935
},
28832936
#[cfg(not(feature = "proxy"))]
28842937
dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')],
2938+
monotonic_clock_cached: Cell::new(0),
2939+
monotonic_clock_paused: Cell::new(false),
28852940
});
28862941
}
28872942
}

0 commit comments

Comments
 (0)