Skip to content

Commit 3233fd5

Browse files
committed
Improve WorkspaceStore to prevent deadlocks (including runtime dealock detector \o/)
1 parent 5cf90d8 commit 3233fd5

File tree

8 files changed

+726
-428
lines changed

8 files changed

+726
-428
lines changed

libparsec/crates/client/src/workspace/store/cache.rs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,20 @@ pub(super) async fn populate_cache_from_local_storage(
193193
) -> Result<ArcLocalChildManifest, PopulateCacheFromLocalStorageError> {
194194
// 1) Local storage lookup
195195

196-
let maybe_found = {
197-
let mut maybe_storage = store.storage.lock().await;
198-
let storage = match &mut *maybe_storage {
199-
None => return Err(PopulateCacheFromLocalStorageError::Stopped),
200-
Some(storage) => storage,
201-
};
202-
203-
storage.get_manifest(entry_id).await.map_err(|err| {
204-
PopulateCacheFromLocalStorageError::Internal(err.context("cannot access local storage"))
205-
})?
206-
};
196+
let maybe_found = store
197+
.data
198+
.with_storage(|maybe_storage| async move {
199+
let storage = maybe_storage
200+
.as_mut()
201+
.ok_or_else(|| PopulateCacheFromLocalStorageError::Stopped)?;
202+
203+
storage.get_manifest(entry_id).await.map_err(|err| {
204+
PopulateCacheFromLocalStorageError::Internal(
205+
err.context("cannot access local storage"),
206+
)
207+
})
208+
})
209+
.await?;
207210

208211
let manifest = match maybe_found {
209212
Some(encrypted) => {
@@ -230,8 +233,9 @@ pub(super) async fn populate_cache_from_local_storage(
230233

231234
// 2) We got our manifest, don't forget to update the cache before returning it
232235

233-
let mut cache = store.current_view_cache.lock().expect("Mutex is poisoned");
234-
let manifest = cache.manifests.insert_if_missing(manifest);
236+
let manifest = store
237+
.data
238+
.with_current_view_cache(|cache| cache.manifests.insert_if_missing(manifest));
235239

236240
Ok(manifest)
237241
}
@@ -368,21 +372,23 @@ pub(super) async fn populate_cache_from_local_storage_or_server(
368372
encrypted: manifest.dump_and_encrypt(&store.device.local_symkey),
369373
},
370374
};
371-
let outcome = {
372-
let mut maybe_storage = store.storage.lock().await;
373-
let storage = match &mut *maybe_storage {
374-
None => return Err(PopulateCacheFromLocalStorageOrServerError::Stopped),
375-
Some(storage) => storage,
376-
};
377-
storage
378-
.populate_manifest(&update_data)
379-
.await
380-
.map_err(|err| {
381-
PopulateCacheFromLocalStorageOrServerError::Internal(
382-
err.context("cannot populate local storage with manifest"),
383-
)
384-
})?
385-
};
375+
let outcome = store
376+
.data
377+
.with_storage(|maybe_storage| async move {
378+
let storage = maybe_storage
379+
.as_mut()
380+
.ok_or_else(|| PopulateCacheFromLocalStorageOrServerError::Stopped)?;
381+
382+
storage
383+
.populate_manifest(&update_data)
384+
.await
385+
.map_err(|err| {
386+
PopulateCacheFromLocalStorageOrServerError::Internal(
387+
err.context("cannot populate local storage with manifest"),
388+
)
389+
})
390+
})
391+
.await?;
386392
match outcome {
387393
PopulateManifestOutcome::Stored => break manifest,
388394
// A concurrent operation has populated the local storage !
@@ -424,8 +430,9 @@ pub(super) async fn populate_cache_from_local_storage_or_server(
424430

425431
// 4) Also update the cache
426432

427-
let mut cache = store.current_view_cache.lock().expect("Mutex is poisoned");
428-
let manifest = cache.manifests.insert_if_missing(manifest);
433+
let manifest = store
434+
.data
435+
.with_current_view_cache(|cache| cache.manifests.insert_if_missing(manifest));
429436

430437
Ok(manifest)
431438
}

libparsec/crates/client/src/workspace/store/file_updater.rs

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use std::sync::Arc;
44

5+
use libparsec_platform_async::event::EventListener;
56
use libparsec_types::prelude::*;
67

78
use crate::certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError};
@@ -49,8 +50,9 @@ pub(super) async fn for_update_file(
4950
// Guard's drop will panic if the lock is not released
5051
macro_rules! release_guard_on_error {
5152
($update_guard:expr) => {
52-
let mut cache_guard = store.current_view_cache.lock().expect("Mutex is poisoned");
53-
cache_guard.lock_update_manifests.release($update_guard);
53+
store.data.with_current_view_cache(|cache| {
54+
cache.lock_update_manifests.release($update_guard);
55+
});
5456
};
5557
}
5658

@@ -62,36 +64,49 @@ pub(super) async fn for_update_file(
6264
listener.await;
6365
}
6466

65-
let update_guard = {
66-
let mut cache_guard = store.current_view_cache.lock().expect("Mutex is poisoned");
67-
68-
// 1) Lock for update
67+
// 1) Lock for update
6968

70-
let outcome = cache_guard.lock_update_manifests.take(entry_id);
71-
match outcome {
69+
enum LockForUpdateOutcome {
70+
WaitAndRetryStep1(EventListener),
71+
// There is no GoToStep2 because it's the dispatching step
72+
GoToStep3(ManifestUpdateLockGuard),
73+
GoToStep4((ManifestUpdateLockGuard, ArcLocalChildManifest)),
74+
}
75+
let outcome = store.data.with_current_view_cache(|cache| {
76+
match cache.lock_update_manifests.take(entry_id) {
7277
ManifestUpdateLockTakeOutcome::Taken(update_guard) => {
7378
// 2) Cache lookup for entry...
7479

75-
let found = cache_guard.manifests.get(&entry_id);
80+
let found = cache.manifests.get(&entry_id);
7681
if let Some(manifest) = found {
7782
// Cache hit ! We go to step 4.
78-
break (update_guard, manifest.clone());
83+
LockForUpdateOutcome::GoToStep4((update_guard, manifest.clone()))
84+
} else {
85+
// The entry is not in cache, go to step 3 for a lookup in the local storage.
86+
// Note we keep the update lock: this has no impact on read operation, and
87+
// any other write operation taking the lock will have no choice but to try
88+
// to populate the cache just like we are going to do.
89+
LockForUpdateOutcome::GoToStep3(update_guard)
7990
}
80-
// The entry is not in cache, go to step 3 for a lookup in the local storage.
81-
// Note we keep the update lock: this has no impact on read operation, and
82-
// any other write operation taking the lock will have no choice but to try
83-
// to populate the cache just like we are going to do.
84-
update_guard
8591
}
8692

8793
ManifestUpdateLockTakeOutcome::NeedWait(listener) => {
88-
if !wait {
89-
return Err(ForUpdateFileError::WouldBlock);
90-
}
91-
maybe_need_wait = Some(listener);
92-
continue;
94+
LockForUpdateOutcome::WaitAndRetryStep1(listener)
9395
}
9496
}
97+
});
98+
let update_guard = match outcome {
99+
LockForUpdateOutcome::GoToStep3(update_guard) => update_guard,
100+
LockForUpdateOutcome::GoToStep4((update_guard, manifest)) => {
101+
break (update_guard, manifest)
102+
}
103+
LockForUpdateOutcome::WaitAndRetryStep1(listener) => {
104+
if !wait {
105+
return Err(ForUpdateFileError::WouldBlock);
106+
}
107+
maybe_need_wait = Some(listener);
108+
continue;
109+
}
95110
};
96111

97112
// Be careful here: `update_guard` must be manually released in case of error !
@@ -143,7 +158,7 @@ pub(super) async fn for_update_file(
143158
};
144159

145160
let updater = FileUpdater {
146-
_update_guard: update_guard,
161+
update_guard,
147162
#[cfg(debug_assertions)]
148163
entry_id: manifest.base.id,
149164
};
@@ -159,7 +174,7 @@ pub(super) async fn for_update_file(
159174
/// keep hold on the store.
160175
#[derive(Debug)]
161176
pub(crate) struct FileUpdater {
162-
_update_guard: ManifestUpdateLockGuard,
177+
update_guard: ManifestUpdateLockGuard,
163178
#[cfg(debug_assertions)]
164179
entry_id: VlobID,
165180
}
@@ -176,39 +191,41 @@ impl FileUpdater {
176191
#[cfg(debug_assertions)]
177192
assert_eq!(manifest.base.id, self.entry_id);
178193

179-
let mut storage_guard = store.storage.lock().await;
180-
let storage = storage_guard
181-
.as_mut()
182-
.ok_or_else(|| UpdateFileManifestAndContinueError::Stopped)?;
183-
184194
let update_data = UpdateManifestData {
185195
entry_id: manifest.base.id,
186196
base_version: manifest.base.version,
187197
need_sync: manifest.need_sync,
188198
encrypted: manifest.dump_and_encrypt(&store.device.local_symkey),
189199
};
190-
191200
let new_chunks = new_chunks
192201
.map(|(chunk_id, cleartext)| (chunk_id, store.device.local_symkey.encrypt(cleartext)));
193-
storage
194-
.update_manifest_and_chunks(&update_data, new_chunks, removed_chunks)
202+
store
203+
.data
204+
.with_storage(|maybe_storage| async move {
205+
let storage = maybe_storage
206+
.as_mut()
207+
.ok_or_else(|| UpdateFileManifestAndContinueError::Stopped)?;
208+
209+
storage
210+
.update_manifest_and_chunks(&update_data, new_chunks, removed_chunks)
211+
.await
212+
.map_err(UpdateFileManifestAndContinueError::Internal)
213+
})
195214
.await?;
196215

197216
// Finally update cache
198-
let mut cache = store.current_view_cache.lock().expect("Mutex is poisoned");
199-
cache
200-
.manifests
201-
.insert(ArcLocalChildManifest::File(manifest));
217+
store.data.with_current_view_cache(|cache| {
218+
cache
219+
.manifests
220+
.insert(ArcLocalChildManifest::File(manifest));
221+
});
202222

203223
Ok(())
204224
}
205225

206226
pub fn close(self, store: &super::WorkspaceStore) {
207-
store
208-
.current_view_cache
209-
.lock()
210-
.expect("Mutex is poisoned")
211-
.lock_update_manifests
212-
.release(self._update_guard);
227+
store.data.with_current_view_cache(|cache| {
228+
cache.lock_update_manifests.release(self.update_guard);
229+
});
213230
}
214231
}

0 commit comments

Comments
 (0)