Skip to content

Commit 370bf52

Browse files
authored
Merge pull request #791 from wedsonaf/async-revocable
rust: add `AsyncRevocable`
2 parents 9f4510e + 968b3f1 commit 370bf52

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)