+2
.gitignore
+2
.gitignore
+4
Cargo.lock
+4
Cargo.lock
+1
-1
Cargo.toml
+1
-1
Cargo.toml
···
1
1
[workspace]
2
2
resolver = "3"
3
-
members = [ "futures", "futures-core", "futures-combinators", "futures-compat", "futures-derive", "futures-util"]
3
+
members = [ "futures", "futures-core", "futures-combinators", "futures-compat", "futures-derive", "futures-util", "lifetime-guard"]
4
4
5
5
[workspace.package]
6
6
version = "0.1.0"
+162
-9
README.md
+162
-9
README.md
···
82
82
83
83
New async primitives that disallow intra-task concurrency, clone of `futures` and `futures-concurrency` for the new primitives.
84
84
85
-
## TODO:
86
-
- [x] ScopedFuture
87
-
- [ ] static combinators (Join Race etc), see futures-concurrency
88
-
- [ ] `#[bsync]` or some compiler ScopedFuture generation
89
-
- [ ] growable combinators (eg. `FutureGroup`, `FuturesUnordered`) (require alloc?)
90
-
- [ ] unsound (needs `Forget`) multithreading
91
-
- [ ] "rethinking async rust"
92
-
- [ ] all of the above for streams
93
-
- [ ] rfc?
94
85
95
86
channels: need lifetimed receievers, probably needs `Forget` (arc-like channels would be unsafe)
96
87
···
141
132
- need tons of interior mutability, since immutable/can't move means `poll` cannot take `&mut self`, cells everywhere
142
133
- nvm lots of unsafe code, but nothing really unsound
143
134
- potentially bad error messages? stuff like `join!` will have to output code that manually sets up the waker self ref
135
+
136
+
## TODO:
137
+
- [x] ScopedFuture
138
+
- [x] static combinators (Join Race etc), see futures-concurrency
139
+
- [x] `#[async_scoped]` or some compiler ScopedFuture generation
140
+
- [ ] doubly linked list waker registration
141
+
- [ ] repeating static time reactors - eg. make event poll every N seconds
142
+
- [ ] io uring reactors
143
+
- [ ] growable combinators (eg. `FutureGroup`, `FuturesUnordered`) (require alloc?)
144
+
- [ ] unsound (needs `Forget`) multithreading
145
+
- [ ] "rethinking async rust"
146
+
- [ ] all of the above for streams
147
+
- [ ] rfc?
148
+
149
+
# Chapter 3
150
+
151
+
man this really sucks
152
+
153
+
i need better things to do
154
+
155
+
issues from ch 2
156
+
- works great[*]
157
+
- *: incompatible with event loop - something still has to poll
158
+
- we're back to doubly linked list of waker registration in event loop
159
+
- this requires Forget
160
+
- ScopedFuture - Future interop sucks
161
+
162
+
163
+
structured concurrency with regular combinators:
164
+
- scope holds tasks
165
+
- scope cancels tasks when dropped
166
+
- tasks are ran by central executor
167
+
168
+
pure structured concurrency with borrow checker:
169
+
- high level block_on(), any task wake wakes every level up
170
+
- tasks have events
171
+
172
+
how do the tasks register to an event loop? they don't fuck
173
+
174
+
175
+
```rust
176
+
struct Task<F: Future> {
177
+
inner: F,
178
+
prev: *const Task,
179
+
next: *const Task,
180
+
waker: Waker,
181
+
}
182
+
```
183
+
184
+
&waker is passed into all sub-tasks, calling wake clone etc panics!!!
185
+
this is pretty jank
186
+
187
+
also waker doesn't have a lifetime so a safe code could easily register to external source that outlives the task
188
+
this is unsound
189
+
190
+
we need
191
+
```rust
192
+
struct WakerRegister {
193
+
prev: *const WakerRegister,
194
+
next: *const WakerRegister,
195
+
}
196
+
```
197
+
198
+
# Borrow Checked Structured Concurrency
199
+
200
+
An async system needs to have the following components:
201
+
202
+
- event loop : polls events and schedules tasks when they are ready
203
+
- tasks : state machines that progress and can await events from the event loop
204
+
- task combinators : tasks that compose other tasks into useful logic structures
205
+
206
+
Tasks will register themselves to events on the event loop, which will need to outlive tasks and register pointers to wake the tasks, so that they can again be polled.
207
+
This is incompatible with the borrow checker because the task pointers (wakers) are being stored inside an event loop with a lifetime that may exceed the tasks'.
208
+
209
+
`Waker` is effectively a `*const dyn Wake`. It is implemented using a custom `RawWakerVTable` rather than a `dyn` ptr to allow for `Wake::wake(self)`, which is not object safe.
210
+
This method is necessary for runtime implementations that rely on the wakers to be effectively `Arc<Task>`, since `wake(self)` consumes `self` and decrements the reference count.
211
+
212
+
There are two types of sound async runtimes that currently exist in rust:
213
+
214
+
[tokio](https://github.com/tokio-rs) and [smol](https://github.com/smol-rs) work using the afformentioned reference counting system to ensure wakers aren't dangling pointers to tasks that no longer exist.
215
+
216
+
[embassy](https://github.com/embassy-rs/embassy) and [rtic](https://github.com/rtic-rs/rtic) work by ensuring tasks are stored in `static` task pools for `N` tasks. Scheduled tasks are represented by an intrusively linked list to avoid allocation, and wakers can't be dangling pointers because completed tasks will refuse to add themselves back to the linked list, or will be replaced by a new task. This is useful in environments where it is desirable to avoid heap allocation, but requires the user annotate the maximum number of a specific task that can exist at one time, and fails to spawn tasks when they exceed that limit.
217
+
218
+
An async runtime where futures are allocated to the stack cannot be sound under this model because `Future::poll` allows any safe `Future` implementation to store or clone wakers wherever they want, which become dangling pointers after the stack allocated future goes out of scope. In order to prevent this, we must have a circular reference, where the task (`Task<'scope>: Wake`) contains a `&'scope Waker` and the `Waker` contains `*const dyn Wake`. For that to be safe, the `Waker` must never be moved. This cannot be possible because something needs to register the waker:
219
+
220
+
```rust
221
+
// waker is moved
222
+
poll(self: Pin<&mut Self>, cx: &mut Context<'_> /* '_ is the duration of the poll fn */) -> Poll<Self::Output> {
223
+
// waker is moved!
224
+
// can't register &Waker bc we run into the same problem,
225
+
// and the waker only lives for '_
226
+
store.register(cx.waker())
227
+
}
228
+
```
229
+
230
+
Effectively, by saying our waker can't move, we are saying it must be stored by the task, which means it can't be a useful waker. Instead, what we could do is have a waker-register (verb, not noun) that facilitates the binding of an immovable waker to an immovable task, where the waker is guaranteed to outlive the task:
231
+
232
+
```rust
233
+
pub trait Wake<'task> {
234
+
fn wake(&self);
235
+
fn register_waker(&mut self, waker: &'task Waker);
236
+
}
237
+
238
+
pub struct Waker {
239
+
task: *const dyn Wake,
240
+
valid: bool, // task is in charge of invalidating when it goes out of scope
241
+
}
242
+
243
+
pub struct WakerRegistration<'poll> {
244
+
task: &'poll mut dyn Wake,
245
+
}
246
+
247
+
impl<'poll> WakerRegistration<'poll> {
248
+
pub fn register<'task>(self, slot: &'task Waker)
249
+
where
250
+
'task: 'poll,
251
+
Self: 'task
252
+
{
253
+
*slot = Waker::new(self.task as *const dyn Wake);
254
+
*task.register_waker(slot)
255
+
}
256
+
}
257
+
```
258
+
259
+
This system works better because `WakerRegistration` only lives
260
+
261
+
262
+
263
+
Experienced rust programmers might be reading this and thinking I am stupid (true) because `Forget`
264
+
265
+
An astute (soon to be disappointed) reader might be thinking, as I did when I first learned about this sytem, "what if we ensured that there was only one `Waker` per `Task`, and gave the task a pointer to the waker, so that it could disable the waker when dropped?"
266
+
267
+
Unfortunately, there are a multitude of issues with this system
268
+
269
+
- In order to hold a pointer to the Waker from the
270
+
- Preventing a `Waker` from moving means panicking on the
271
+
272
+
Even if it was guaranteed that wakers could not be moved or `cloned` (by panicking on `clone`), and registration occured via `&Waker`, the task would still be unable to
273
+
274
+
https://conradludgate.com/posts/async-stack
275
+
276
+
## Structured Concurrency
277
+
278
+
https://trio.discourse.group/t/discussion-notes-on-structured-concurrency-or-go-statement-considered-harmful/25
279
+
https://kotlinlang.org/docs/coroutines-basics.html
280
+
281
+
Notably, the structured concurrency pattern fits very nicely with our hypothetical unsound stack based async runtime.
282
+
283
+
## WeakCell Pattern & Forget trait
284
+
285
+
https://github.com/rust-lang/rfcs/pull/3782
286
+
287
+
288
+
There are two solutions:
289
+
290
+
- `Wakers` panic on `clone()`
291
+
292
+
## Waker allocation problem & intra task concurrency
293
+
294
+
we can't do intra task concurrency because WeakRegistrations
295
+
296
+
+4
-10
futures-compat/src/lib.rs
+4
-10
futures-compat/src/lib.rs
···
13
13
//! that expects it to live for 'scope, and then the ScopedFutureWrapper is
14
14
//! dropped
15
15
//!
16
-
//!
17
-
//! ## TRIGGER WARNING
18
-
//!
19
16
//! This code is not for the faint of heart. Read at your own risk.
20
17
21
18
use std::{
···
34
31
/// can transmute between them, but the waker will be completely invalid!
35
32
36
33
/// wraps an internal ScopedFuture, implements Future
37
-
pub struct ScopedFutureWrapper<'scope, F: ScopedFuture<'scope> + 'scope> {
34
+
pub struct ScopedFutureWrapper<'scope, F: ScopedFuture<'scope>> {
38
35
inner: UnsafeCell<F>,
39
36
marker: PhantomData<&'scope ()>,
40
37
}
···
44
41
{
45
42
type Output = F::Output;
46
43
47
-
fn poll(
48
-
self: std::pin::Pin<&mut Self>,
49
-
cx: &mut std::task::Context<'_>,
50
-
) -> Poll<Self::Output> {
44
+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
51
45
// # Safety
52
46
//
53
47
// Transmutes `Waker` into `&'scope dyn Wake`.
···
102
96
marker: PhantomData<&'scope ()>,
103
97
}
104
98
105
-
impl<'scope, F: Future> ScopedFuture<'scope>
99
+
impl<'scope, F: Future + 'scope> ScopedFuture<'scope>
106
100
for UnscopedFutureWrapper<'scope, F>
107
101
{
108
102
type Output = F::Output;
···
140
134
}
141
135
}
142
136
143
-
impl<'scope, F: Future> UnscopedFutureWrapper<'scope, F> {
137
+
impl<'scope, F: Future + 'scope> UnscopedFutureWrapper<'scope, F> {
144
138
pub unsafe fn from_future(f: F) -> Self {
145
139
Self {
146
140
inner: f.into(),
+3
-2
futures-core/src/lib.rs
+3
-2
futures-core/src/lib.rs
···
35
35
/// The waker is no longer tied to the actual future's lifetime, making it
36
36
/// unsound to not have either static tasks or reference counting.
37
37
/// To avoid this, we want to use a &'scope waker instead, with 1 waker / task.
38
+
#[must_use = "futures do nothing unless you `.await` or poll them"]
38
39
#[diagnostic::on_unimplemented(
39
-
message = "The type `{Self}` must implement the `ScopedFuture` trait.",
40
-
label = "Missing `ScopedFuture` implementation",
40
+
message = "`{Self}` is not a `ScopedFuture`",
41
+
label = "`{Self}` is not a `ScopedFuture`",
41
42
note = "If you are trying to await a `task::Future` from within a `ScopedFuture`, note that the systems are incompatible."
42
43
)]
43
44
pub trait ScopedFuture<'scope> {
+49
-5
futures-core/src/task.rs
+49
-5
futures-core/src/task.rs
···
1
-
/// A task that can be woken.
2
-
///
3
-
/// This acts as a handle for a reactor to indicate when a `ScopedFuture` is
4
-
/// once again ready to be polled.
5
-
pub trait Wake<'scope> {
1
+
use std::{mem, ptr::NonNull, sync::atomic::AtomicPtr};
2
+
3
+
// Task: Wake
4
+
//
5
+
// Wake must not outlive event loop/storage
6
+
pub trait Wake<'events> {
6
7
fn wake(&self);
8
+
// task can't outlive event loop -> holds &'events
9
+
fn register_waker(&self, waker: &'events Waker);
10
+
}
11
+
12
+
// type Waker<'events> = Option<NonNull<dyn Wake<'events>>>;
13
+
14
+
pub struct Waker<'events> {
15
+
task: *const dyn Wake<'events>,
16
+
}
17
+
18
+
// wakers must outlive 'task
19
+
impl<'events> Waker<'events> {
20
+
pub fn new(task: *const dyn Wake<'events>) -> Self {
21
+
Self { task: task.into() }
22
+
}
23
+
24
+
pub fn wake(self) {
25
+
unsafe { self.task.as_ref() }.inspect(|task| task.wake());
26
+
}
27
+
}
28
+
29
+
pub struct WakerRegistration<'events> {
30
+
task: &'events dyn Wake<'events>, // valid for all of 'events
31
+
}
32
+
33
+
impl<'events> WakerRegistration<'events> {
34
+
// slot is valid for all 'events
35
+
pub fn register(self, slot: &'events mut Waker<'events>) {
36
+
// Cast from 'events to 'static
37
+
//
38
+
// # Safety
39
+
//
40
+
// This is safe because the drop guard guarantees that the task ptr (which lives for static)
41
+
// becomes null when the wake is dropped, ensuring the dangling pointer is never dereferenced.
42
+
let dangling_task = unsafe {
43
+
mem::transmute::<&'events dyn Wake<'events>, *const dyn Wake<'events>>(
44
+
self.task,
45
+
)
46
+
};
47
+
slot.task = dangling_task;
48
+
49
+
(*self.task).register_waker(slot);
50
+
}
7
51
}
+107
-56
futures-derive/src/lib.rs
+107
-56
futures-derive/src/lib.rs
···
1
1
use proc_macro::TokenStream;
2
2
use quote::{ToTokens, quote};
3
3
use syn::{
4
-
Expr, ExprAwait, FnArg, GenericArgument, ItemFn, Pat, ReturnType,
5
-
Signature, parse_macro_input, visit_mut::VisitMut,
4
+
Expr, ExprAwait, FnArg, GenericArgument, ItemFn, ReturnType,
5
+
parse_macro_input, parse_quote, parse2, visit_mut::VisitMut,
6
6
};
7
7
8
+
/// Takes async fn that returns anonymous `Future` impl.
9
+
/// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl.
10
+
///
11
+
/// ```rust
12
+
/// fn my_func<'a, 'b>(a: &'a A, b: &'b B) -> impl ScopedFuture<'a + 'b, Output = T> + 'a + 'b {
13
+
/// let output = async move { [body] } // compilers turns this into -> impl Future<Output = T> + 'a + 'b
14
+
/// unsafe { UnscopedFutureWrapper::from_future(output) }
15
+
/// }
16
+
/// ```
17
+
///
18
+
/// see https://rust-lang.github.io/rfcs/2394-async_await.html#lifetime-capture-in-the-anonymous-future
19
+
/// for more context on lifetime capture
20
+
/// - resulting ScopedFuture needs to be constrained to not outlive the lifetimes of any references
21
+
///
22
+
/// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer
23
+
/// lifetimes from the anonymous future impl returned by the actual inner async fn
8
24
#[proc_macro_attribute]
9
25
pub fn async_scoped(_: TokenStream, item: TokenStream) -> TokenStream {
10
-
let mut input = parse_macro_input!(item as ItemFn);
11
-
26
+
let mut item_fn = parse_macro_input!(item as ItemFn);
12
27
// Wraps *every* async expression within the function block with
13
28
// `ScopedFutureWrapper`, allowing them to be treated as regular `Future`
14
29
// impls.
···
16
31
// This will cause a compiler error if any expression being awaited is not
17
32
// a `ScopedFuture`, which is intentional because the `Future` and
18
33
// `ScopedFuture` systems are incompatible.
19
-
ScopedFutureWrappingVisitor.visit_item_fn_mut(&mut input);
34
+
ScopedFutureWrappingVisitor.visit_item_fn_mut(&mut item_fn);
20
35
21
-
// Wrap the function with `UnscopedFutureWrapper` to convert it back into
22
-
// a `ScopedFuture`.
23
-
wrap_async_with_scoped(&input).into()
36
+
// disable async since it is moved to the block
37
+
item_fn.sig.asyncness = None;
38
+
39
+
// wrap block with UnscopedFutureWrapper
40
+
let block = *item_fn.block;
41
+
*item_fn.block = parse_quote! {
42
+
{
43
+
let future = async move #block;
44
+
unsafe { futures_compat::UnscopedFutureWrapper::from_future(future) }
45
+
}
46
+
};
47
+
48
+
let output = match &item_fn.sig.output {
49
+
ReturnType::Default => quote! { () },
50
+
ReturnType::Type(_, ty) => quote! { #ty },
51
+
};
52
+
53
+
let has_lifetime_dependency =
54
+
item_fn.sig.inputs.iter().any(|param| match param {
55
+
FnArg::Receiver(receiver) => receiver.reference.is_some(),
56
+
FnArg::Typed(pat) => has_lifetime_dependency(&pat.ty),
57
+
});
58
+
59
+
// set outer fn output to ScopedFuture<'_/'static, Output = #output>
60
+
item_fn.sig.output = if has_lifetime_dependency {
61
+
parse_quote! { -> impl futures_core::ScopedFuture<'_, Output = #output> + '_ }
62
+
} else {
63
+
parse_quote! { -> impl futures_core::ScopedFuture<'static, Output = #output> }
64
+
};
65
+
66
+
item_fn.to_token_stream().into()
24
67
}
25
68
69
+
/// This currently is impossible to do the `futures_compat` workarounds not
70
+
/// being compatible with closures.
71
+
///
26
72
/// Takes async fn that returns anonymous `Future` impl.
27
73
/// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl.
28
74
///
···
39
85
///
40
86
/// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer
41
87
/// lifetimes from the anonymous future impl returned by the actual inner async fn
42
-
fn wrap_async_with_scoped(
43
-
ItemFn {
44
-
attrs,
45
-
vis,
46
-
sig:
47
-
Signature {
48
-
constness,
49
-
unsafety,
50
-
ident,
51
-
generics,
52
-
inputs,
53
-
output,
54
-
..
55
-
},
56
-
block,
57
-
}: &ItemFn,
58
-
) -> proc_macro2::TokenStream {
59
-
let output = match output {
60
-
ReturnType::Default => quote! { () },
61
-
ReturnType::Type(_, ty) => quote! { #ty },
62
-
};
88
+
// #[proc_macro]
89
+
// pub fn closure(input: TokenStream) -> TokenStream {
90
+
// // let ExprClosure {
91
+
// // attrs,
92
+
// // lifetimes,
93
+
// // constness,
94
+
// // movability,
95
+
// // capture,
96
+
// // inputs,
97
+
// // output,
98
+
// // body,
99
+
// // ..
100
+
// // } = parse_macro_input!(input as ExprClosure);
101
+
// let mut closure = parse_macro_input!(input as ExprClosure);
102
+
// // disable async because we move it to inner
103
+
// closure.asyncness = None;
104
+
// let body = closure.body;
105
+
106
+
// // let output = match closure.output {
107
+
// // ReturnType::Default => parse_quote! { () },
108
+
// // ReturnType::Type(_, ty) => parse_quote! { #ty },
109
+
// // };
63
110
64
-
let inner_args: Vec<syn::Ident> = inputs
65
-
.iter()
66
-
.filter_map(|param| match param {
67
-
FnArg::Receiver(_) => Some(quote::format_ident!("self")),
68
-
FnArg::Typed(typed) => {
69
-
if let Pat::Ident(ident) = &*typed.pat {
70
-
Some(ident.ident.to_owned())
71
-
} else {
72
-
None
73
-
}
74
-
}
75
-
})
76
-
.collect();
111
+
// // let outer_output =
112
+
// // parse_quote! { futures_core::ScopedFuture<'_, Output = #output> + '_ };
77
113
78
-
let has_lifetime_dependency = inputs.iter().any(|param| match param {
79
-
FnArg::Receiver(receiver) => receiver.reference.is_some(),
80
-
FnArg::Typed(pat) => has_lifetime_dependency(&pat.ty),
81
-
});
114
+
// closure.body = parse_quote! {{
115
+
// let output = async move { #body };
116
+
// unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) }
117
+
// }};
118
+
// // closure.output = outer_output;
119
+
// closure.to_token_stream().into()
120
+
// }
82
121
83
-
let outer_output = if has_lifetime_dependency {
84
-
quote! { futures_core::ScopedFuture<'_, Output = #output> + '_ }
85
-
} else {
86
-
quote! { futures_core::ScopedFuture<'static, Output = #output> }
87
-
};
122
+
/// Wraps a block of optionally async statements and expressions in an anonymous `ScopedFuture` impl.
123
+
///
124
+
/// This generates a modified block of the form:
125
+
///
126
+
/// ```rust
127
+
/// {
128
+
/// let output = async { <original block, mapped to convert all `ScopedFuture` to `Future`> };
129
+
/// unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) }
130
+
/// }
131
+
/// ```
132
+
#[proc_macro]
133
+
pub fn block(input: TokenStream) -> TokenStream {
134
+
// block is formed { **expr/stmt }, so we need to surround the inputs in {}
135
+
let input = proc_macro2::TokenStream::from(input);
136
+
let block_input = quote! { { #input } };
88
137
89
-
quote! {
90
-
#(#attrs)* #vis #constness #unsafety fn #ident #generics (#inputs) -> impl #outer_output {
91
-
async #constness #unsafety fn __inner (#inputs) -> #output #block
138
+
let mut block = parse2(block_input).expect("Failed to parse as block.");
92
139
93
-
let future = __inner(#(#inner_args),*);
140
+
ScopedFutureWrappingVisitor.visit_block_mut(&mut block);
94
141
95
-
unsafe { futures_compat::UnscopedFutureWrapper::from_future(future) }
142
+
quote! {
143
+
{
144
+
let output = async #block;
145
+
unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) }
96
146
}
97
147
}
148
+
.into()
98
149
}
99
150
100
151
/// Determines if typed pattern contains a reference or dependency on a
+2
-14
futures/src/lib.rs
+2
-14
futures/src/lib.rs
···
1
1
#![no_std]
2
2
3
3
pub use futures_combinators;
4
-
use futures_compat::{ScopedFutureWrapper, UnscopedFutureWrapper};
5
4
pub use futures_core;
6
5
use futures_core::ScopedFuture;
7
6
pub use futures_derive::async_scoped;
···
16
15
}
17
16
18
17
#[async_scoped]
19
-
fn test(a: i32, b: &i32) -> () {
20
-
// evil().await;
21
-
let x = inner(a, &b).await;
22
-
// async {}.await;
23
-
24
-
let test_block = futures_derive::block! { 1 + 1; 2 }.await;
25
-
26
-
// let test_closure = futures_derive::closure! { |&ab, &cd| ab + cd };
27
-
28
-
// let asdf = futures_derive::closure! { |a: &i32| {
29
-
// *a + b
30
-
// }};
31
-
// let x = asdf(&a).await;
18
+
fn test(a: i32, b: &i32) -> i32 {
19
+
futures_derive::block! { 1 + *b; 2 }.await
32
20
}
33
21
34
22
fn test2<'a>(a: i32) {}
+11
lifetime-guard/Cargo.toml
+11
lifetime-guard/Cargo.toml
+153
lifetime-guard/src/lib.rs
+153
lifetime-guard/src/lib.rs
···
1
+
/// Unsound Drop based guard API
2
+
use std::{
3
+
cell::Cell,
4
+
mem,
5
+
pin::{Pin, pin},
6
+
ptr::{self, NonNull, dangling},
7
+
};
8
+
9
+
/// must not outlive strong guard
10
+
///
11
+
/// enforces strong guard doesn't move with ptr
12
+
pub struct WeakGuard<'weak, T> {
13
+
strong: Option<&'weak StrongGuard<'weak, T>>,
14
+
}
15
+
16
+
impl<'weak, T> WeakGuard<'weak, T> {
17
+
pub fn register_strong(&mut self, weak: &'weak T) {
18
+
self.strong = Some(weak);
19
+
}
20
+
}
21
+
22
+
impl<'weak, T> Drop for WeakGuard<'weak, StrongGuard<'weak, T>> {
23
+
fn drop(&mut self) {
24
+
// self.weak.
25
+
}
26
+
}
27
+
28
+
/// outlives strong guard
29
+
pub struct StrongGuard<'weak, T> {
30
+
strong: *const WeakGuard<'weak, T>,
31
+
}
32
+
33
+
// wakers must outlive 'task
34
+
impl<'weak, T> StrongGuard<'weak, T> {
35
+
pub fn new(strong: *const WeakGuard<'weak, T>) -> Self {
36
+
Self {
37
+
strong: task.into(),
38
+
}
39
+
}
40
+
}
41
+
42
+
// pub struct GuardRegistration<'weak, T> {
43
+
// task: &'weak StrongGuard<'weak, T>, // valid for all of 'weak
44
+
// }
45
+
46
+
// impl<'weak> GuardRegistration<'weak> {
47
+
// // slot is valid for all 'weak
48
+
// pub fn register(self, slot: &'weak mut StrongGuard<'weak>) {
49
+
// // Cast from 'weak to 'static
50
+
// //
51
+
// // # Safety
52
+
// //
53
+
// // This is safe because the drop guard guarantees that the task ptr (which lives for static)
54
+
// // becomes null when the wake is dropped, ensuring the dangling pointer is never dereferenced.
55
+
// let dangling_task = unsafe {
56
+
// mem::transmute::<
57
+
// &'weak dyn StrongGuard<'weak>,
58
+
// *const dyn StrongGuard<'weak>,
59
+
// >(self.task)
60
+
// };
61
+
// slot.strong = dangling_task;
62
+
63
+
// (*self.task).register_waker(slot);
64
+
// }
65
+
// }
66
+
67
+
pub struct ValueGuard<T> {
68
+
data: T,
69
+
ref_guard: Cell<*const RefGuard<T>>,
70
+
}
71
+
72
+
impl<T> ValueGuard<T> {
73
+
pub fn new(data: T) -> Self {
74
+
Self {
75
+
data,
76
+
ref_guard: Cell::new(ptr::null()),
77
+
}
78
+
}
79
+
80
+
pub fn set(&mut self, value: T) {
81
+
self.data = value;
82
+
}
83
+
}
84
+
85
+
impl<T: Copy> ValueGuard<T> {
86
+
pub fn get(&self) -> T {
87
+
self.data
88
+
}
89
+
}
90
+
91
+
impl<T> Drop for ValueGuard<T> {
92
+
fn drop(&mut self) {
93
+
unsafe {
94
+
self.ref_guard
95
+
.get()
96
+
.as_ref()
97
+
.inspect(|guard| guard.value_guard.set(ptr::null()))
98
+
};
99
+
}
100
+
}
101
+
102
+
pub struct RefGuard<T> {
103
+
value_guard: Cell<*const ValueGuard<T>>,
104
+
}
105
+
106
+
impl<T> RefGuard<T> {
107
+
pub fn new() -> Self {
108
+
Self {
109
+
value_guard: Cell::new(ptr::null()),
110
+
}
111
+
}
112
+
}
113
+
114
+
impl<T: Copy> RefGuard<T> {
115
+
pub fn get(&self) -> Option<T> {
116
+
unsafe { self.value_guard.get().as_ref().map(|ptr| ptr.data) }
117
+
}
118
+
}
119
+
120
+
impl<T> Drop for RefGuard<T> {
121
+
fn drop(&mut self) {
122
+
unsafe { self.value_guard.get().as_ref() }
123
+
.inspect(|guard| guard.ref_guard.set(ptr::null()));
124
+
}
125
+
}
126
+
127
+
pub struct GuardRegistration<'a, T> {
128
+
value_guard: Pin<&'a ValueGuard<T>>,
129
+
}
130
+
131
+
impl<'a, T> GuardRegistration<'a, T> {
132
+
pub fn from_guard(value_guard: Pin<&'a ValueGuard<T>>) -> Self {
133
+
Self { value_guard }
134
+
}
135
+
136
+
pub fn register(self, slot: Pin<&'a mut RefGuard<T>>) {
137
+
// register new ptrs
138
+
let old_value_guard = slot
139
+
.value_guard
140
+
.replace(self.value_guard.get_ref() as *const ValueGuard<T>);
141
+
142
+
let old_ref_guard = self
143
+
.value_guard
144
+
.ref_guard
145
+
.replace(slot.into_ref().get_ref());
146
+
147
+
// annul old ptrs
148
+
unsafe { old_value_guard.as_ref() }
149
+
.inspect(|guard| guard.ref_guard.set(ptr::null()));
150
+
unsafe { old_ref_guard.as_ref() }
151
+
.inspect(|guard| guard.value_guard.set(ptr::null()));
152
+
}
153
+
}