@@ -12,7 +12,7 @@ use core::{
12
12
mem:: MaybeUninit ,
13
13
ops:: Deref ,
14
14
ptr:: drop_in_place,
15
- sync:: atomic:: { AtomicBool , Ordering } ,
15
+ sync:: atomic:: { fence , AtomicBool , AtomicU32 , Ordering } ,
16
16
} ;
17
17
18
18
/// An object that can become inaccessible at runtime.
@@ -191,3 +191,235 @@ impl<T> Deref for RevocableGuard<'_, T> {
191
191
unsafe { & * self . data_ref }
192
192
}
193
193
}
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