Skip to content

Commit 6171c5f

Browse files
committed
Add a TryLock primitive
1 parent 2709c3d commit 6171c5f

4 files changed

Lines changed: 103 additions & 48 deletions

File tree

crates/guest-rust/src/rt/async_support.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ mod futures_stream;
6868
mod inter_task_wakeup;
6969
mod stream_support;
7070
mod subtask;
71+
mod try_lock;
7172
#[cfg(feature = "inter-task-wakeup")]
7273
mod unit_stream;
7374
mod waitable;

crates/guest-rust/src/rt/async_support/future_support.rs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,23 +111,20 @@
111111
//! alive until that write completes but otherwise shouldn't hinder anything
112112
//! else.
113113
114-
use crate::rt::Cleanup;
115-
use crate::rt::async_support::ReturnCode;
114+
use crate::rt::async_support::try_lock::TryLock;
116115
use crate::rt::async_support::waitable::{WaitableOp, WaitableOperation};
116+
use crate::rt::async_support::ReturnCode;
117+
use crate::rt::Cleanup;
117118
use alloc::sync::Arc;
118119
use alloc::task::Wake;
119120
use core::alloc::Layout;
120-
use core::cell::UnsafeCell;
121121
use core::fmt;
122122
use core::future::{Future, IntoFuture};
123123
use core::marker;
124124
use core::mem::{self, ManuallyDrop};
125125
use core::pin::Pin;
126126
use core::ptr;
127-
use core::sync::atomic::{
128-
AtomicBool, AtomicU32,
129-
Ordering::{Acquire, Relaxed, Release},
130-
};
127+
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
131128
use core::task::{Context, Poll, Waker};
132129

133130
/// Helper trait which encapsulates the various operations which can happen
@@ -506,8 +503,7 @@ impl<O: FutureOps> RawFutureWriter<O> {
506503
O: 'static,
507504
{
508505
return Arc::new(DeferredWrite {
509-
write: UnsafeCell::new(self.write(value)),
510-
in_use: AtomicBool::new(false),
506+
write: TryLock::new(self.write(value)),
511507
})
512508
.wake();
513509

@@ -525,8 +521,7 @@ impl<O: FutureOps> RawFutureWriter<O> {
525521
/// doesn't require the `async-spawn` feature and instead works with the
526522
/// `wasip3_task` C ABI structures (which spawn doesn't support).
527523
struct DeferredWrite<O: FutureOps> {
528-
write: UnsafeCell<RawFutureWrite<O>>,
529-
in_use: AtomicBool,
524+
write: TryLock<RawFutureWrite<O>>,
530525
}
531526

532527
// SAFETY: Needed to satisfy `Waker::from` but otherwise should be ok
@@ -536,10 +531,6 @@ impl<O: FutureOps> RawFutureWriter<O> {
536531

537532
impl<O: FutureOps + 'static> Wake for DeferredWrite<O> {
538533
fn wake(self: Arc<Self>) {
539-
if self.in_use.swap(true, Acquire) {
540-
panic!("recursive wake detected");
541-
}
542-
543534
// When a `wake` signal comes in that should happen in two
544535
// locations:
545536
//
@@ -561,16 +552,15 @@ impl<O: FutureOps> RawFutureWriter<O> {
561552
let poll = {
562553
let waker = Waker::from(self.clone());
563554
let mut cx = Context::from_waker(&waker);
564-
unsafe { Pin::new_unchecked(&mut *self.write.get()).poll(&mut cx) }
555+
let mut write = self.write.try_lock().unwrap();
556+
unsafe { Pin::new_unchecked(&mut *write).poll(&mut cx) }
565557
};
566558
if poll.is_ready() {
567559
assert_eq!(Arc::strong_count(&self), 1);
568560
} else {
569561
assert!(Arc::strong_count(&self) > 1);
570562
}
571563
assert_eq!(Arc::weak_count(&self), 0);
572-
573-
self.in_use.store(false, Release);
574564
}
575565
}
576566
}

crates/guest-rust/src/rt/async_support/inter_task_wakeup.rs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
use super::FutureState;
2+
use crate::rt::async_support::try_lock::TryLock;
23
use crate::rt::async_support::{BLOCKED, COMPLETED};
34
use crate::{RawStreamReader, RawStreamWriter, StreamOps, UnitStreamOps};
4-
use core::cell::UnsafeCell;
55
use core::ptr;
6-
use core::sync::atomic::{
7-
AtomicBool,
8-
Ordering::{Acquire, Release},
9-
};
106

117
#[derive(Default)]
128
pub struct State {
@@ -31,7 +27,9 @@ impl FutureState<'_> {
3127
assert!(!self.inter_task_wakeup.stream_reading);
3228
let (writer, reader) = UnitStreamOps::new();
3329
self.inter_task_wakeup.stream = Some(reader);
34-
self.waker.inter_task_stream.set(writer);
30+
let mut waker_stream = self.waker.inter_task_stream.lock.try_lock().unwrap();
31+
assert!(waker_stream.is_none());
32+
*waker_stream = Some(writer);
3533
}
3634

3735
// If there's not already a pending read then schedule a new read here.
@@ -83,37 +81,18 @@ impl State {
8381

8482
#[derive(Default)]
8583
pub struct WakerState {
86-
in_use: AtomicBool,
87-
lock: UnsafeCell<Option<RawStreamWriter<UnitStreamOps>>>,
84+
lock: TryLock<Option<RawStreamWriter<UnitStreamOps>>>,
8885
}
8986

90-
unsafe impl Send for WakerState {}
91-
unsafe impl Sync for WakerState {}
92-
9387
impl WakerState {
94-
fn set(&self, writer: RawStreamWriter<UnitStreamOps>) {
95-
self.with(|waker_stream| {
96-
assert!(waker_stream.is_none());
97-
*waker_stream = Some(writer);
98-
})
99-
}
100-
101-
fn with<R>(&self, f: impl FnOnce(&mut Option<RawStreamWriter<UnitStreamOps>>) -> R) -> R {
102-
assert!(!self.in_use.swap(true, Acquire));
103-
let ret = unsafe { f(&mut *self.lock.get()) };
104-
self.in_use.store(false, Release);
105-
ret
106-
}
107-
10888
pub fn wake(&self) {
10989
// Here the wakeup stream should already have been filled in by the
11090
// original future itself. The stream should also have an active read
11191
// while the future is sleeping. This means that this write should
11292
// succeed immediately.
113-
self.with(|inter_task_stream| {
114-
let stream = inter_task_stream.as_mut().unwrap();
115-
let rc = unsafe { UnitStreamOps.start_write(stream.handle(), ptr::null_mut(), 1) };
116-
assert_eq!(rc, COMPLETED | (1 << 4));
117-
})
93+
let mut inter_task_stream = self.lock.try_lock().unwrap();
94+
let stream = inter_task_stream.as_mut().unwrap();
95+
let rc = unsafe { UnitStreamOps.start_write(stream.handle(), ptr::null_mut(), 1) };
96+
assert_eq!(rc, COMPLETED | (1 << 4));
11897
}
11998
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use core::cell::UnsafeCell;
2+
use core::ops::{Deref, DerefMut};
3+
use core::sync::atomic::{
4+
AtomicBool,
5+
Ordering::{Acquire, Release},
6+
};
7+
8+
/// Small helper type to wrap `T` in a lock-like primitive which only supports
9+
/// the `try_lock` operation.
10+
///
11+
/// This is useful on wasm right now where threads aren't actually a thing so
12+
/// there shouldn't ever be contention, but the Rust type system still requires
13+
/// Send/Sync.
14+
#[derive(Default)]
15+
pub struct TryLock<T> {
16+
locked: AtomicBool,
17+
data: UnsafeCell<T>,
18+
}
19+
20+
unsafe impl<T: Send> Send for TryLock<T> {}
21+
unsafe impl<T: Send> Sync for TryLock<T> {}
22+
23+
impl<T> TryLock<T> {
24+
pub fn new(data: T) -> Self {
25+
TryLock {
26+
locked: AtomicBool::new(false),
27+
data: UnsafeCell::new(data),
28+
}
29+
}
30+
31+
pub fn try_lock(&self) -> Option<TryLockGuard<'_, T>> {
32+
if self.locked.swap(true, Acquire) {
33+
None
34+
} else {
35+
Some(TryLockGuard { lock: self })
36+
}
37+
}
38+
}
39+
40+
pub struct TryLockGuard<'a, T> {
41+
lock: &'a TryLock<T>,
42+
}
43+
44+
impl<T> Deref for TryLockGuard<'_, T> {
45+
type Target = T;
46+
47+
fn deref(&self) -> &Self::Target {
48+
unsafe { &*self.lock.data.get() }
49+
}
50+
}
51+
52+
impl<T> DerefMut for TryLockGuard<'_, T> {
53+
fn deref_mut(&mut self) -> &mut Self::Target {
54+
unsafe { &mut *self.lock.data.get() }
55+
}
56+
}
57+
58+
impl<T> Drop for TryLockGuard<'_, T> {
59+
fn drop(&mut self) {
60+
self.lock.locked.store(false, Release);
61+
}
62+
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use super::*;
67+
68+
#[test]
69+
fn smoke() {
70+
let lock = TryLock::new(1);
71+
assert!(lock.try_lock().is_some());
72+
assert!(lock.try_lock().is_some());
73+
74+
let mut guard = lock.try_lock().unwrap();
75+
assert_eq!(*guard, 1);
76+
*guard = 2;
77+
assert_eq!(*guard, 2);
78+
assert!(lock.try_lock().is_none());
79+
drop(guard);
80+
assert!(lock.try_lock().is_some());
81+
82+
let guard = lock.try_lock().unwrap();
83+
assert_eq!(*guard, 2);
84+
}
85+
}

0 commit comments

Comments
 (0)