Skip to content

Commit db30255

Browse files
committed
Introduce RefUnwindSafe StorageHandle
1 parent 75c5337 commit db30255

File tree

4 files changed

+128
-43
lines changed

4 files changed

+128
-43
lines changed

src/storage.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! Public API facades for the implementation details of [`Zalsa`] and [`ZalsaLocal`].
12
use std::{marker::PhantomData, panic::RefUnwindSafe, sync::Arc};
23

34
use parking_lot::{Condvar, Mutex};
@@ -9,6 +10,50 @@ use crate::{
910
Database, Event, EventKind,
1011
};
1112

13+
/// A handle to non-local database state.
14+
pub struct StorageHandle<Db> {
15+
// Note: Drop order is important, zalsa_impl needs to drop before coordinate
16+
/// Reference to the database.
17+
zalsa_impl: Arc<Zalsa>,
18+
19+
// Note: Drop order is important, coordinate needs to drop after zalsa_impl
20+
/// Coordination data for cancellation of other handles when `zalsa_mut` is called.
21+
/// This could be stored in Zalsa but it makes things marginally cleaner to keep it separate.
22+
coordinate: CoordinateDrop,
23+
24+
/// We store references to `Db`
25+
phantom: PhantomData<fn() -> Db>,
26+
}
27+
28+
impl<Db: Database> Default for StorageHandle<Db> {
29+
fn default() -> Self {
30+
Self {
31+
zalsa_impl: Arc::new(Zalsa::new::<Db>()),
32+
coordinate: CoordinateDrop(Arc::new(Coordinate {
33+
clones: Mutex::new(1),
34+
cvar: Default::default(),
35+
})),
36+
phantom: PhantomData,
37+
}
38+
}
39+
}
40+
41+
impl<Db> StorageHandle<Db> {
42+
pub fn into_storage(self) -> Storage<Db> {
43+
let StorageHandle {
44+
zalsa_impl,
45+
coordinate,
46+
phantom,
47+
} = self;
48+
Storage {
49+
zalsa_impl,
50+
coordinate,
51+
phantom,
52+
zalsa_local: ZalsaLocal::new(),
53+
}
54+
}
55+
}
56+
1257
/// Access the "storage" of a Salsa database: this is an internal plumbing trait
1358
/// automatically implemented by `#[salsa::db]` applied to a struct.
1459
///
@@ -21,9 +66,8 @@ pub unsafe trait HasStorage: Database + Clone + Sized {
2166
fn storage_mut(&mut self) -> &mut Storage<Self>;
2267
}
2368

24-
/// Concrete implementation of the [`Database`][] trait.
25-
/// Takes an optional type parameter `U` that allows you to thread your own data.
26-
pub struct Storage<Db: Database> {
69+
/// Concrete implementation of the [`Database`] trait with local state that can be used to drive computations.
70+
pub struct Storage<Db> {
2771
// Note: Drop order is important, zalsa_impl needs to drop before coordinate
2872
/// Reference to the database.
2973
zalsa_impl: Arc<Zalsa>,
@@ -39,13 +83,18 @@ pub struct Storage<Db: Database> {
3983
/// We store references to `Db`
4084
phantom: PhantomData<fn() -> Db>,
4185
}
86+
4287
struct Coordinate {
4388
/// Counter of the number of clones of actor. Begins at 1.
4489
/// Incremented when cloned, decremented when dropped.
4590
clones: Mutex<usize>,
4691
cvar: Condvar,
4792
}
4893

94+
// We cannot panic while holding a lock to `clones: Mutex<usize>` and therefore we cannot enter an
95+
// inconsistent state.
96+
impl RefUnwindSafe for Coordinate {}
97+
4998
impl<Db: Database> Default for Storage<Db> {
5099
fn default() -> Self {
51100
Self {
@@ -61,6 +110,22 @@ impl<Db: Database> Default for Storage<Db> {
61110
}
62111

63112
impl<Db: Database> Storage<Db> {
113+
/// Discard the local state of this handle, turning it into a [`StorageHandle`] that is [`Sync`]
114+
/// and [`std::panic::UnwindSafe`].
115+
pub fn into_zalsa_handle(self) -> StorageHandle<Db> {
116+
let Storage {
117+
zalsa_impl,
118+
coordinate,
119+
phantom,
120+
zalsa_local: _,
121+
} = self;
122+
StorageHandle {
123+
zalsa_impl,
124+
coordinate,
125+
phantom,
126+
}
127+
}
128+
64129
pub fn debug_input_entries<T>(&self) -> impl Iterator<Item = &input::Value<T>>
65130
where
66131
T: input::Configuration,
@@ -99,7 +164,9 @@ impl<Db: Database> Storage<Db> {
99164
.filter_map(|page| page.cast_type::<crate::table::Page<tracked_struct::Value<T>>>())
100165
.flat_map(|pages| pages.slots())
101166
}
167+
}
102168

169+
impl<Db: Database> Storage<Db> {
103170
/// Access the `Arc<Zalsa>`. This should always be
104171
/// possible as `zalsa_impl` only becomes
105172
/// `None` once we are in the `Drop` impl.
@@ -150,8 +217,6 @@ unsafe impl<T: HasStorage> ZalsaDatabase for T {
150217
}
151218
}
152219

153-
impl<Db: Database> RefUnwindSafe for Storage<Db> {}
154-
155220
impl<Db: Database> Clone for Storage<Db> {
156221
fn clone(&self) -> Self {
157222
*self.coordinate.clones.lock() += 1;

src/zalsa.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use parking_lot::{Mutex, RwLock};
33
use rustc_hash::FxHashMap;
44
use std::any::{Any, TypeId};
55
use std::marker::PhantomData;
6+
use std::panic::RefUnwindSafe;
67
use std::thread::ThreadId;
78

89
use crate::cycle::CycleRecoveryStrategy;
@@ -149,6 +150,13 @@ pub struct Zalsa {
149150
runtime: Runtime,
150151
}
151152

153+
// Our fields locked behind Mutices and RwLocks cannot enter an inconsistent state due to panics
154+
// as they are all merely ID mappings with the exception of the `Runtime::dependency_graph`.
155+
// `Runtime::dependency_graph` does not invoke user queries though and as such will not arbitrarily
156+
// panic. The only way it may panic is by failing one of its asserts in which case we are already
157+
// in a broken state anyways.
158+
impl RefUnwindSafe for Zalsa {}
159+
152160
impl Zalsa {
153161
pub(crate) fn new<Db: Database>() -> Self {
154162
Self {

tests/check_auto_traits.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Test that auto trait impls exist as expected.
2+
3+
use std::panic::UnwindSafe;
4+
5+
use salsa::Database;
6+
use test_log::test;
7+
8+
#[salsa::input]
9+
struct MyInput {
10+
field: String,
11+
}
12+
13+
#[salsa::tracked]
14+
struct MyTracked<'db> {
15+
field: MyInterned<'db>,
16+
}
17+
18+
#[salsa::interned]
19+
struct MyInterned<'db> {
20+
field: String,
21+
}
22+
23+
#[salsa::tracked]
24+
fn test(db: &dyn Database, input: MyInput) {
25+
let input = is_send(is_sync(input));
26+
let interned = is_send(is_sync(MyInterned::new(db, input.field(db).clone())));
27+
let _tracked_struct = is_send(is_sync(MyTracked::new(db, interned)));
28+
}
29+
30+
fn is_send<T: Send>(t: T) -> T {
31+
t
32+
}
33+
34+
fn is_sync<T: Send>(t: T) -> T {
35+
t
36+
}
37+
38+
fn is_unwind_safe<T: UnwindSafe>(t: T) -> T {
39+
t
40+
}
41+
42+
#[test]
43+
fn execute() {
44+
let db = is_send(salsa::DatabaseImpl::new());
45+
let _handle = is_send(is_sync(is_unwind_safe(
46+
db.storage().clone().into_zalsa_handle(),
47+
)));
48+
let input = MyInput::new(&db, "Hello".to_string());
49+
test(&db, input);
50+
}

tests/is_send_sync.rs

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)