Commit ae6eba54f24b
tarball1 file changed+139-17
@@ -19,12 +19,21 @@
1919
//!2020
//! [`RoomEventCache`]: super::super::RoomEventCache2121
22
-use std::ops::{Deref, DerefMut};22
+use std::{23
+ future::Future,24
+ ops::{Deref, DerefMut},25
+ panic::Location,26
+ sync::Mutex as StdMutex,27
+ time::Duration,28
+};2329
2430
use matrix_sdk_base::event_cache::store::{2531
EventCacheStoreLock, EventCacheStoreLockGuard, EventCacheStoreLockState,2632
};33
+use matrix_sdk_common::timeout::timeout;34
+use ruma::time::Instant;2735
use tokio::sync::{Mutex, RwLock, RwLockWriteGuard};36
+use tracing::warn;2837
2938
use super::super::Result;3039
@@ -42,12 +51,95 @@ pub struct StateLock<S> {
4251
/// Please see inline comment of [`Self::read`] to understand why it4352
/// exists.4453
state_lock_upgrade_mutex: Mutex<()>,54
+55
+ holders: StdMutex<Holders>,56
+}57
+58
+/// How long a wait on a state lock runs before it names whoever holds the lock.59
+const SLOW_WAIT: Duration = Duration::from_secs(5);60
+61
+#[derive(Default)]62
+struct Holders {63
+ next: u64,64
+ held: Vec<(u64, &'static Location<'static>, &'static str, Instant)>,65
+}66
+67
+struct Holder<'a> {68
+ id: u64,69
+ holders: &'a StdMutex<Holders>,70
+}71
+72
+impl Drop for Holder<'_> {73
+ fn drop(&mut self) {74
+ let mut holders = self.holders.lock().unwrap_or_else(|poisoned| poisoned.into_inner());75
+ holders.held.retain(|(id, ..)| *id != self.id);76
+ }4577
}4678
4779
impl<S> StateLock<S> {4880
/// Construct a new [`StateLock`].4981
pub fn new_inner(state: S) -> Self {50
- Self { locked_state: RwLock::new(state), state_lock_upgrade_mutex: Mutex::new(()) }82
+ Self {83
+ locked_state: RwLock::new(state),84
+ state_lock_upgrade_mutex: Mutex::new(()),85
+ holders: Default::default(),86
+ }87
+ }88
+89
+ fn hold(&self, caller: &'static Location<'static>, kind: &'static str) -> Holder<'_> {90
+ let mut holders = self.holders.lock().unwrap_or_else(|poisoned| poisoned.into_inner());91
+ holders.next += 1;92
+ let id = holders.next;93
+ holders.held.push((id, caller, kind, Instant::now()));94
+ Holder { id, holders: &self.holders }95
+ }96
+97
+ async fn waiting<T>(98
+ &self,99
+ caller: &'static Location<'static>,100
+ kind: &'static str,101
+ acquire: impl Future<Output = T>,102
+ ) -> T {103
+ let mut acquire = std::pin::pin!(acquire);104
+ match timeout(acquire.as_mut(), SLOW_WAIT).await {105
+ Ok(acquired) => acquired,106
+ Err(_) => {107
+ let held = {108
+ let holders =109
+ self.holders.lock().unwrap_or_else(|poisoned| poisoned.into_inner());110
+ holders111
+ .held112
+ .iter()113
+ .map(|(_, at, kind, since)| {114
+ format!("{kind} at {at} for {:?}", since.elapsed())115
+ })116
+ .collect::<Vec<_>>()117
+ .join(", ")118
+ };119
+ warn!(%caller, kind, %held, "still waiting on a cache state lock");120
+ acquire.await121
+ }122
+ }123
+ }124
+125
+ /// Lock this [`StateLock`] with per-thread shared access.126
+ #[track_caller]127
+ pub fn read<'a>(&'a self) -> impl Future<Output = Result<StateLockReadGuard<'a, S>>> + 'a128
+ where129
+ S: Store,130
+ StateLockWriteGuard<'a, S>: Reload,131
+ {132
+ self.read_at(Location::caller())133
+ }134
+135
+ /// Lock this [`StateLock`] with exclusive per-thread write access.136
+ #[track_caller]137
+ pub fn write<'a>(&'a self) -> impl Future<Output = Result<StateLockWriteGuard<'a, S>>> + 'a138
+ where139
+ S: Store,140
+ StateLockWriteGuard<'a, S>: Reload,141
+ {142
+ self.write_at(Location::caller())51143
}52144
53145
/// Lock this [`StateLock`] with per-thread shared access.@@ -59,7 +151,10 @@ impl<S> StateLock<S> {
59151
///60152
/// If the cross-process lock over the store is dirty (see61153
/// [`EventCacheStoreLockState`]), the state is reset to the last chunk.62
- pub async fn read<'a>(&'a self) -> Result<StateLockReadGuard<'a, S>>154
+ async fn read_at<'a>(155
+ &'a self,156
+ caller: &'static Location<'static>,157
+ ) -> Result<StateLockReadGuard<'a, S>>63158
where64159
S: Store,65160
StateLockWriteGuard<'a, S>: Reload,@@ -110,15 +205,21 @@ impl<S> StateLock<S> {
110205
//111206
// [^1]: https://docs.rs/lock_api/0.4.14/lock_api/struct.RwLock.html#method.upgradable_read112207
// [^2]: https://docs.rs/async-lock/3.4.1/async_lock/struct.RwLock.html#method.upgradable_read113
- let _state_lock_upgrade_guard = self.state_lock_upgrade_mutex.lock().await;208
+ let (_state_lock_upgrade_guard, state_guard) = self209
+ .waiting(caller, "read", async {210
+ let upgrade = self.state_lock_upgrade_mutex.lock().await;114211
115
- // Obtain a read lock.116
- let state_guard = self.locked_state.read().await;212
+ // Obtain a read lock.213
+ (upgrade, self.locked_state.read().await)214
+ })215
+ .await;117216
118217
match state_guard.store().lock().await? {119
- EventCacheStoreLockState::Clean(store_guard) => {120
- Ok(StateLockReadGuard { state: state_guard, store: store_guard })121
- }218
+ EventCacheStoreLockState::Clean(store_guard) => Ok(StateLockReadGuard {219
+ state: state_guard,220
+ store: store_guard,221
+ _holder: self.hold(caller, "read"),222
+ }),122223
EventCacheStoreLockState::Dirty(store_guard) => {123224
// Drop the read lock, and take a write lock to modify the state.124225
// This is safe because only one reader at a time (see@@ -126,7 +227,11 @@ impl<S> StateLock<S> {
126227
drop(state_guard);127228
let state_guard = self.locked_state.write().await;128229
129
- let mut guard = StateLockWriteGuard { state: state_guard, store: store_guard };230
+ let mut guard = StateLockWriteGuard {231
+ state: state_guard,232
+ store: store_guard,233
+ _holder: self.hold(caller, "read"),234
+ };130235
131236
// Reload the state.132237
guard.reload().await?;@@ -151,19 +256,28 @@ impl<S> StateLock<S> {
151256
///152257
/// If the cross-process lock over the store is dirty (see153258
/// [`EventCacheStoreLockState`]), the state is reset to the last chunk.154
- pub async fn write<'a>(&'a self) -> Result<StateLockWriteGuard<'a, S>>259
+ async fn write_at<'a>(260
+ &'a self,261
+ caller: &'static Location<'static>,262
+ ) -> Result<StateLockWriteGuard<'a, S>>155263
where156264
S: Store,157265
StateLockWriteGuard<'a, S>: Reload,158266
{159
- let state_guard = self.locked_state.write().await;267
+ let state_guard = self.waiting(caller, "write", self.locked_state.write()).await;160268
161269
match state_guard.store().lock().await? {162
- EventCacheStoreLockState::Clean(store_guard) => {163
- Ok(StateLockWriteGuard { state: state_guard, store: store_guard })164
- }270
+ EventCacheStoreLockState::Clean(store_guard) => Ok(StateLockWriteGuard {271
+ state: state_guard,272
+ store: store_guard,273
+ _holder: self.hold(caller, "write"),274
+ }),165275
EventCacheStoreLockState::Dirty(store_guard) => {166
- let mut guard = StateLockWriteGuard { state: state_guard, store: store_guard };276
+ let mut guard = StateLockWriteGuard {277
+ state: state_guard,278
+ store: store_guard,279
+ _holder: self.hold(caller, "write"),280
+ };167281
168282
// Reload the state.169283
guard.reload().await?;@@ -184,6 +298,8 @@ pub struct StateLockReadGuard<'a, S> {
184298
185299
/// The cross-process lock guard over the store.186300
pub store: EventCacheStoreLockGuard,301
+302
+ _holder: Holder<'a>,187303
}188304
189305
impl<'a, S> Deref for StateLockReadGuard<'a, S> {@@ -201,6 +317,8 @@ pub struct StateLockWriteGuard<'a, S> {
201317
202318
/// The cross-process lock guard over the store.203319
pub store: EventCacheStoreLockGuard,320
+321
+ _holder: Holder<'a>,204322
}205323
206324
impl<'a, S> Deref for StateLockWriteGuard<'a, S> {@@ -226,7 +344,11 @@ impl<'a, S> StateLockWriteGuard<'a, S> {
226344
/// It returns an RAII guard which will drop the read access to the227345
/// state and to the store when dropped.228346
fn downgrade(self) -> StateLockReadGuard<'a, S> {229
- StateLockReadGuard { state: self.state.downgrade(), store: self.store }347
+ StateLockReadGuard {348
+ state: self.state.downgrade(),349
+ store: self.store,350
+ _holder: self._holder,351
+ }230352
}231353
}