Skip to content

Commit 5af9e0d

Browse files
sync: add blocking lock methods to RwLock (#4425)
1 parent 8f77ee8 commit 5af9e0d

File tree

2 files changed

+129
-5
lines changed

2 files changed

+129
-5
lines changed

tokio/src/sync/mutex.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,22 @@ impl<T: ?Sized> Mutex<T> {
364364
}
365365
}
366366

367-
/// Blocking lock this mutex. When the lock has been acquired, function returns a
367+
/// Blockingly locks this `Mutex`. When the lock has been acquired, function returns a
368368
/// [`MutexGuard`].
369369
///
370370
/// This method is intended for use cases where you
371371
/// need to use this mutex in asynchronous code as well as in synchronous code.
372372
///
373+
/// # Panics
374+
///
375+
/// This function panics if called within an asynchronous execution context.
376+
///
377+
/// - If you find yourself in an asynchronous execution context and needing
378+
/// to call some (synchronous) function which performs one of these
379+
/// `blocking_` operations, then consider wrapping that call inside
380+
/// [`spawn_blocking()`][crate::runtime::Handle::spawn_blocking]
381+
/// (or [`block_in_place()`][crate::task::block_in_place]).
382+
///
373383
/// # Examples
374384
///
375385
/// ```
@@ -379,16 +389,24 @@ impl<T: ?Sized> Mutex<T> {
379389
/// #[tokio::main]
380390
/// async fn main() {
381391
/// let mutex = Arc::new(Mutex::new(1));
392+
/// let lock = mutex.lock().await;
382393
///
383394
/// let mutex1 = Arc::clone(&mutex);
384-
/// let sync_code = tokio::task::spawn_blocking(move || {
395+
/// let blocking_task = tokio::task::spawn_blocking(move || {
396+
/// // This shall block until the `lock` is released.
385397
/// let mut n = mutex1.blocking_lock();
386398
/// *n = 2;
387399
/// });
388400
///
389-
/// sync_code.await.unwrap();
401+
/// assert_eq!(*lock, 1);
402+
/// // Release the lock.
403+
/// drop(lock);
404+
///
405+
/// // Await the completion of the blocking task.
406+
/// blocking_task.await.unwrap();
390407
///
391-
/// let n = mutex.lock().await;
408+
/// // Assert uncontended.
409+
/// let n = mutex.try_lock().unwrap();
392410
/// assert_eq!(*n, 2);
393411
/// }
394412
///

tokio/src/sync/rwlock.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ impl<T: ?Sized> RwLock<T> {
420420
///
421421
/// // Drop the guard after the spawned task finishes.
422422
/// drop(n);
423-
///}
423+
/// }
424424
/// ```
425425
pub async fn read(&self) -> RwLockReadGuard<'_, T> {
426426
#[cfg(all(tokio_unstable, feature = "tracing"))]
@@ -459,6 +459,58 @@ impl<T: ?Sized> RwLock<T> {
459459
}
460460
}
461461

462+
/// Blockingly locks this `RwLock` with shared read access.
463+
///
464+
/// This method is intended for use cases where you
465+
/// need to use this rwlock in asynchronous code as well as in synchronous code.
466+
///
467+
/// Returns an RAII guard which will drop the read access of this `RwLock` when dropped.
468+
///
469+
/// # Panics
470+
///
471+
/// This function panics if called within an asynchronous execution context.
472+
///
473+
/// - If you find yourself in an asynchronous execution context and needing
474+
/// to call some (synchronous) function which performs one of these
475+
/// `blocking_` operations, then consider wrapping that call inside
476+
/// [`spawn_blocking()`][crate::runtime::Handle::spawn_blocking]
477+
/// (or [`block_in_place()`][crate::task::block_in_place]).
478+
///
479+
/// # Examples
480+
///
481+
/// ```
482+
/// use std::sync::Arc;
483+
/// use tokio::sync::RwLock;
484+
///
485+
/// #[tokio::main]
486+
/// async fn main() {
487+
/// let rwlock = Arc::new(RwLock::new(1));
488+
/// let mut write_lock = rwlock.write().await;
489+
///
490+
/// let blocking_task = tokio::task::spawn_blocking({
491+
/// let rwlock = Arc::clone(&rwlock);
492+
/// move || {
493+
/// // This shall block until the `write_lock` is released.
494+
/// let read_lock = rwlock.blocking_read();
495+
/// assert_eq!(*read_lock, 0);
496+
/// }
497+
/// });
498+
///
499+
/// *write_lock -= 1;
500+
/// drop(write_lock); // release the lock.
501+
///
502+
/// // Await the completion of the blocking task.
503+
/// blocking_task.await.unwrap();
504+
///
505+
/// // Assert uncontended.
506+
/// assert!(rwlock.try_write().is_ok());
507+
/// }
508+
/// ```
509+
#[cfg(feature = "sync")]
510+
pub fn blocking_read(&self) -> RwLockReadGuard<'_, T> {
511+
crate::future::block_on(self.read())
512+
}
513+
462514
/// Locks this `RwLock` with shared read access, causing the current task
463515
/// to yield until the lock has been acquired.
464516
///
@@ -739,6 +791,60 @@ impl<T: ?Sized> RwLock<T> {
739791
}
740792
}
741793

794+
/// Blockingly locks this `RwLock` with exclusive write access.
795+
///
796+
/// This method is intended for use cases where you
797+
/// need to use this rwlock in asynchronous code as well as in synchronous code.
798+
///
799+
/// Returns an RAII guard which will drop the write access of this `RwLock` when dropped.
800+
///
801+
/// # Panics
802+
///
803+
/// This function panics if called within an asynchronous execution context.
804+
///
805+
/// - If you find yourself in an asynchronous execution context and needing
806+
/// to call some (synchronous) function which performs one of these
807+
/// `blocking_` operations, then consider wrapping that call inside
808+
/// [`spawn_blocking()`][crate::runtime::Handle::spawn_blocking]
809+
/// (or [`block_in_place()`][crate::task::block_in_place]).
810+
///
811+
/// # Examples
812+
///
813+
/// ```
814+
/// use std::sync::Arc;
815+
/// use tokio::{sync::RwLock};
816+
///
817+
/// #[tokio::main]
818+
/// async fn main() {
819+
/// let rwlock = Arc::new(RwLock::new(1));
820+
/// let read_lock = rwlock.read().await;
821+
///
822+
/// let blocking_task = tokio::task::spawn_blocking({
823+
/// let rwlock = Arc::clone(&rwlock);
824+
/// move || {
825+
/// // This shall block until the `read_lock` is released.
826+
/// let mut write_lock = rwlock.blocking_write();
827+
/// *write_lock = 2;
828+
/// }
829+
/// });
830+
///
831+
/// assert_eq!(*read_lock, 1);
832+
/// // Release the last outstanding read lock.
833+
/// drop(read_lock);
834+
///
835+
/// // Await the completion of the blocking task.
836+
/// blocking_task.await.unwrap();
837+
///
838+
/// // Assert uncontended.
839+
/// let read_lock = rwlock.try_read().unwrap();
840+
/// assert_eq!(*read_lock, 2);
841+
/// }
842+
/// ```
843+
#[cfg(feature = "sync")]
844+
pub fn blocking_write(&self) -> RwLockWriteGuard<'_, T> {
845+
crate::future::block_on(self.write())
846+
}
847+
742848
/// Locks this `RwLock` with exclusive write access, causing the current
743849
/// task to yield until the lock has been acquired.
744850
///

0 commit comments

Comments
 (0)