Skip to content

Commit 968b3f1

Browse files
committed
rust: add AsyncRevocable
This allows access to objects to be revoked without having to wait for existing users to complete. This will be used to drop futures in tasks when executors are being torn down. Signed-off-by: Wedson Almeida Filho <wedsonaf@google.com>
1 parent 9f4510e commit 968b3f1

File tree

1 file changed

+233
-1
lines changed

1 file changed

+233
-1
lines changed

rust/kernel/revocable.rs

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use core::{
1212
mem::MaybeUninit,
1313
ops::Deref,
1414
ptr::drop_in_place,
15-
sync::atomic::{AtomicBool, Ordering},
15+
sync::atomic::{fence, AtomicBool, AtomicU32, Ordering},
1616
};
1717

1818
/// An object that can become inaccessible at runtime.
@@ -191,3 +191,235 @@ impl<T> Deref for RevocableGuard<'_, T> {
191191
unsafe { &*self.data_ref }
192192
}
193193
}
194+
195+
/// An object that can become inaccessible at runtime.
196+
///
197+
/// Once access is revoked and all concurrent users complete (i.e., all existing instances of
198+
/// [`AsyncRevocableGuard`] are dropped), the wrapped object is also dropped.
199+
///
200+
/// Unlike [`Revocable`], [`AsyncRevocable`] does not wait for concurrent users of the wrapped
201+
/// object to finish before [`AsyncRevocable::revoke`] completes -- thus the async qualifier. This
202+
/// has the advantage of not requiring RCU locks or waits of any kind.
203+
///
204+
/// # Examples
205+
///
206+
/// ```
207+
/// # use kernel::revocable::AsyncRevocable;
208+
///
209+
/// struct Example {
210+
/// a: u32,
211+
/// b: u32,
212+
/// }
213+
///
214+
/// fn add_two(v: &AsyncRevocable<Example>) -> Option<u32> {
215+
/// let guard = v.try_access()?;
216+
/// Some(guard.a + guard.b)
217+
/// }
218+
///
219+
/// let v = AsyncRevocable::new(Example { a: 10, b: 20 });
220+
/// assert_eq!(add_two(&v), Some(30));
221+
/// v.revoke();
222+
/// assert_eq!(add_two(&v), None);
223+
/// ```
224+
///
225+
/// Example where revocation happens while there is a user:
226+
///
227+
/// ```
228+
/// # use kernel::revocable::AsyncRevocable;
229+
/// use core::sync::atomic::{AtomicBool, Ordering};
230+
///
231+
/// struct Example {
232+
/// a: u32,
233+
/// b: u32,
234+
/// }
235+
///
236+
/// static DROPPED: AtomicBool = AtomicBool::new(false);
237+
///
238+
/// impl Drop for Example {
239+
/// fn drop(&mut self) {
240+
/// DROPPED.store(true, Ordering::Relaxed);
241+
/// }
242+
/// }
243+
///
244+
/// fn add_two(v: &AsyncRevocable<Example>) -> Option<u32> {
245+
/// let guard = v.try_access()?;
246+
/// Some(guard.a + guard.b)
247+
/// }
248+
///
249+
/// let v = AsyncRevocable::new(Example { a: 10, b: 20 });
250+
/// assert_eq!(add_two(&v), Some(30));
251+
///
252+
/// let guard = v.try_access().unwrap();
253+
/// assert!(!v.is_revoked());
254+
/// assert!(!DROPPED.load(Ordering::Relaxed));
255+
/// v.revoke();
256+
/// assert!(!DROPPED.load(Ordering::Relaxed));
257+
/// assert!(v.is_revoked());
258+
/// assert!(v.try_access().is_none());
259+
/// assert_eq!(guard.a + guard.b, 30);
260+
/// drop(guard);
261+
/// assert!(DROPPED.load(Ordering::Relaxed));
262+
/// ```
263+
pub struct AsyncRevocable<T> {
264+
usage_count: AtomicU32,
265+
data: MaybeUninit<UnsafeCell<T>>,
266+
}
267+
268+
// SAFETY: `AsyncRevocable` is `Send` if the wrapped object is also `Send`. This is because while
269+
// the functionality exposed by `AsyncRevocable` can be accessed from any thread/CPU, it is
270+
// possible that this isn't supported by the wrapped object.
271+
unsafe impl<T: Send> Send for AsyncRevocable<T> {}
272+
273+
// SAFETY: `AsyncRevocable` is `Sync` if the wrapped object is both `Send` and `Sync`. We require
274+
// `Send` from the wrapped object as well because of `AsyncRevocable::revoke`, which can trigger
275+
// the `Drop` implementation of the wrapped object from an arbitrary thread.
276+
unsafe impl<T: Sync + Send> Sync for AsyncRevocable<T> {}
277+
278+
const REVOKED: u32 = 0x80000000;
279+
const COUNT_MASK: u32 = !REVOKED;
280+
const SATURATED_COUNT: u32 = REVOKED - 1;
281+
282+
impl<T> AsyncRevocable<T> {
283+
/// Creates a new asynchronously revocable instance of the given data.
284+
pub fn new(data: T) -> Self {
285+
Self {
286+
usage_count: AtomicU32::new(0),
287+
data: MaybeUninit::new(UnsafeCell::new(data)),
288+
}
289+
}
290+
291+
/// Tries to access the \[revocable\] wrapped object.
292+
///
293+
/// Returns `None` if the object has been revoked and is therefore no longer accessible.
294+
///
295+
/// Returns a guard that gives access to the object otherwise; the object is guaranteed to
296+
/// remain accessible while the guard is alive.
297+
pub fn try_access(&self) -> Option<AsyncRevocableGuard<'_, T>> {
298+
loop {
299+
let count = self.usage_count.load(Ordering::Relaxed);
300+
301+
// Fail attempt to access if the object is already revoked.
302+
if count & REVOKED != 0 {
303+
return None;
304+
}
305+
306+
// No need to increment if the count is saturated.
307+
if count == SATURATED_COUNT
308+
|| self
309+
.usage_count
310+
.compare_exchange(count, count + 1, Ordering::Relaxed, Ordering::Relaxed)
311+
.is_ok()
312+
{
313+
return Some(AsyncRevocableGuard { revocable: self });
314+
}
315+
}
316+
}
317+
318+
/// Revokes access to the protected object.
319+
///
320+
/// Returns `true` if access has been revoked, or `false` when the object has already been
321+
/// revoked by a previous call to [`AsyncRevocable::revoke`].
322+
///
323+
/// This call is non-blocking, that is, no new users of the revocable object will be allowed,
324+
/// but potential current users are able to continue to use it and the thread won't wait for
325+
/// them to finish. In such cases, the object will be dropped when the last user completes.
326+
pub fn revoke(&self) -> bool {
327+
// Set the `REVOKED` bit.
328+
//
329+
// The acquire barrier matches up with the release when decrementing the usage count.
330+
let prev = self.usage_count.fetch_or(REVOKED, Ordering::Acquire);
331+
if prev & REVOKED != 0 {
332+
// Another thread already revoked this object.
333+
return false;
334+
}
335+
336+
if prev == 0 {
337+
// SAFETY: This thread just revoked the object and the usage count is zero, so the
338+
// object is valid and there will be no future users.
339+
unsafe { drop_in_place(UnsafeCell::raw_get(self.data.as_ptr())) };
340+
}
341+
342+
true
343+
}
344+
345+
/// Returns whether access to the object has been revoked.
346+
pub fn is_revoked(&self) -> bool {
347+
self.usage_count.load(Ordering::Relaxed) & REVOKED != 0
348+
}
349+
}
350+
351+
impl<T> Drop for AsyncRevocable<T> {
352+
fn drop(&mut self) {
353+
let count = *self.usage_count.get_mut();
354+
if count != REVOKED {
355+
// The object hasn't been dropped yet, so we do it now.
356+
357+
// This matches with the release when decrementing the usage count.
358+
fence(Ordering::Acquire);
359+
360+
// SAFETY: Since `count` is does not indicate a count of 0 and the REVOKED bit set, the
361+
// object is still valid.
362+
unsafe { drop_in_place(UnsafeCell::raw_get(self.data.as_ptr())) };
363+
}
364+
}
365+
}
366+
367+
/// A guard that allows access to a revocable object and keeps it alive.
368+
///
369+
/// # Invariants
370+
///
371+
/// The owner owns an increment on the usage count (which may have saturated it), which keeps the
372+
/// revocable object alive.
373+
pub struct AsyncRevocableGuard<'a, T> {
374+
revocable: &'a AsyncRevocable<T>,
375+
}
376+
377+
impl<T> Deref for AsyncRevocableGuard<'_, T> {
378+
type Target = T;
379+
380+
fn deref(&self) -> &Self::Target {
381+
// SAFETY: The type invariants guarantee that the caller owns an increment.
382+
unsafe { &*self.revocable.data.assume_init_ref().get() }
383+
}
384+
}
385+
386+
impl<T> Drop for AsyncRevocableGuard<'_, T> {
387+
fn drop(&mut self) {
388+
loop {
389+
let count = self.revocable.usage_count.load(Ordering::Relaxed);
390+
let actual_count = count & COUNT_MASK;
391+
if actual_count == SATURATED_COUNT {
392+
// The count is saturated, so we won't decrement (nor do we drop the object).
393+
return;
394+
}
395+
396+
if actual_count == 0 {
397+
// Trying to underflow the count.
398+
panic!("actual_count is zero");
399+
}
400+
401+
// On success, we use release ordering, which matches with the acquire in one of the
402+
// places where we drop the object, namely: below, in `AsyncRevocable::revoke`, or in
403+
// `AsyncRevocable::drop`.
404+
if self
405+
.revocable
406+
.usage_count
407+
.compare_exchange(count, count - 1, Ordering::Release, Ordering::Relaxed)
408+
.is_ok()
409+
{
410+
if count == 1 | REVOKED {
411+
// `count` is now zero and it is revoked, so free it now.
412+
413+
// This matches with the release above (which may have happened in other
414+
// threads concurrently).
415+
fence(Ordering::Acquire);
416+
417+
// SAFETY: Since `count` was 1, the object is still alive.
418+
unsafe { drop_in_place(UnsafeCell::raw_get(self.revocable.data.as_ptr())) };
419+
}
420+
421+
return;
422+
}
423+
}
424+
}
425+
}

0 commit comments

Comments
 (0)