Skip to content

Commit 4d2ccff

Browse files
committed
return to the database-wrapping-storage setup
1 parent 12e0741 commit 4d2ccff

36 files changed

+415
-347
lines changed

components/salsa-macros/src/db.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ struct DbMacro {
3232
impl DbMacro {
3333
fn try_db(self, input: syn::Item) -> syn::Result<TokenStream> {
3434
match input {
35+
syn::Item::Struct(input) => {
36+
let has_storage_impl = self.has_storage_impl(&input)?;
37+
Ok(quote! {
38+
#has_storage_impl
39+
#input
40+
})
41+
}
3542
syn::Item::Trait(mut input) => {
3643
self.add_salsa_view_method(&mut input)?;
3744
Ok(quote! {
@@ -46,11 +53,54 @@ impl DbMacro {
4653
}
4754
_ => Err(syn::Error::new_spanned(
4855
input,
49-
"`db` must be applied to a trait or impl",
56+
"`db` must be applied to a struct, trait, or impl",
5057
)),
5158
}
5259
}
5360

61+
fn find_storage_field(&self, input: &syn::ItemStruct) -> syn::Result<syn::Ident> {
62+
let storage = "storage";
63+
for field in input.fields.iter() {
64+
if let Some(i) = &field.ident {
65+
if i == storage {
66+
return Ok(i.clone());
67+
}
68+
} else {
69+
return Err(syn::Error::new_spanned(
70+
field,
71+
"database struct must be a braced struct (`{}`) with a field named `storage`",
72+
));
73+
}
74+
}
75+
76+
Err(syn::Error::new_spanned(
77+
&input.ident,
78+
"database struct must be a braced struct (`{}`) with a field named `storage`",
79+
))
80+
}
81+
82+
fn has_storage_impl(&self, input: &syn::ItemStruct) -> syn::Result<TokenStream> {
83+
let storage = self.find_storage_field(input)?;
84+
let db = &input.ident;
85+
let zalsa = self.hygiene.ident("zalsa");
86+
87+
Ok(quote! {
88+
const _: () = {
89+
use salsa::plumbing as #zalsa;
90+
91+
unsafe impl #zalsa::HasStorage for #db {
92+
fn storage(&self) -> &#zalsa::Storage<Self> {
93+
&self.#storage
94+
}
95+
96+
fn storage_mut(&mut self) -> &mut #zalsa::Storage<Self> {
97+
&mut self.#storage
98+
}
99+
}
100+
};
101+
})
102+
}
103+
54104
fn add_salsa_view_method(&self, input: &mut syn::ItemTrait) -> syn::Result<()> {
55105
input.items.push(parse_quote! {
56106
#[doc(hidden)]

examples/calc/db.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
use std::sync::{Arc, Mutex};
22

3-
use salsa::UserData;
4-
5-
pub type CalcDatabaseImpl = salsa::DatabaseImpl<Calc>;
6-
73
// ANCHOR: db_struct
4+
#[salsa::db]
85
#[derive(Default)]
9-
pub struct Calc {
6+
pub struct CalcDatabaseImpl {
7+
storage: salsa::Storage<Self>,
8+
109
// The logs are only used for testing and demonstrating reuse:
1110
logs: Arc<Mutex<Option<Vec<String>>>>,
1211
}
1312
// ANCHOR_END: db_struct
1413

15-
impl Calc {
14+
impl CalcDatabaseImpl {
1615
/// Enable logging of each salsa event.
1716
#[cfg(test)]
1817
pub fn enable_logging(&self) {
@@ -34,12 +33,13 @@ impl Calc {
3433
}
3534

3635
// ANCHOR: db_impl
37-
impl UserData for Calc {
38-
fn salsa_event(db: &CalcDatabaseImpl, event: &dyn Fn() -> salsa::Event) {
36+
#[salsa::db]
37+
impl salsa::Database for CalcDatabaseImpl {
38+
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
3939
let event = event();
4040
eprintln!("Event: {event:?}");
4141
// Log interesting events, if logging is enabled
42-
if let Some(logs) = &mut *db.logs.lock().unwrap() {
42+
if let Some(logs) = &mut *self.logs.lock().unwrap() {
4343
// only log interesting events
4444
if let salsa::EventKind::WillExecute { .. } = event.kind {
4545
logs.push(format!("Event: {event:?}"));

examples/lazy-input/main.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ use notify_debouncer_mini::{
88
notify::{RecommendedWatcher, RecursiveMode},
99
DebounceEventResult, Debouncer,
1010
};
11-
use salsa::{Accumulator, DatabaseImpl, Setter, UserData};
11+
use salsa::{Accumulator, Setter, Storage};
1212

1313
// ANCHOR: main
1414
fn main() -> Result<()> {
1515
// Create the channel to receive file change events.
1616
let (tx, rx) = unbounded();
17-
let mut db = DatabaseImpl::with(LazyInput::new(tx));
17+
let mut db = LazyInputDatabase::new(tx);
1818

1919
let initial_file_path = std::env::args_os()
2020
.nth(1)
@@ -74,34 +74,38 @@ trait Db: salsa::Database {
7474
fn input(&self, path: PathBuf) -> Result<File>;
7575
}
7676

77-
struct LazyInput {
77+
#[salsa::db]
78+
struct LazyInputDatabase {
79+
storage: Storage<Self>,
7880
logs: Mutex<Vec<String>>,
7981
files: DashMap<PathBuf, File>,
8082
file_watcher: Mutex<Debouncer<RecommendedWatcher>>,
8183
}
8284

83-
impl LazyInput {
85+
impl LazyInputDatabase {
8486
fn new(tx: Sender<DebounceEventResult>) -> Self {
8587
Self {
88+
storage: Default::default(),
8689
logs: Default::default(),
8790
files: DashMap::new(),
8891
file_watcher: Mutex::new(new_debouncer(Duration::from_secs(1), tx).unwrap()),
8992
}
9093
}
9194
}
9295

93-
impl UserData for LazyInput {
94-
fn salsa_event(db: &DatabaseImpl<Self>, event: &dyn Fn() -> salsa::Event) {
96+
#[salsa::db]
97+
impl salsa::Database for LazyInputDatabase {
98+
fn salsa_event(&self, event: &dyn Fn() -> salsa::Event) {
9599
// don't log boring events
96100
let event = event();
97101
if let salsa::EventKind::WillExecute { .. } = event.kind {
98-
db.logs.lock().unwrap().push(format!("{:?}", event));
102+
self.logs.lock().unwrap().push(format!("{:?}", event));
99103
}
100104
}
101105
}
102106

103107
#[salsa::db]
104-
impl Db for DatabaseImpl<LazyInput> {
108+
impl Db for LazyInputDatabase {
105109
fn input(&self, path: PathBuf) -> Result<File> {
106110
let path = path
107111
.canonicalize()

src/database.rs

+5-204
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
1-
use std::{any::Any, marker::PhantomData, panic::RefUnwindSafe, sync::Arc};
1+
use std::any::Any;
22

3-
use parking_lot::{Condvar, Mutex};
4-
5-
use crate::{
6-
self as salsa,
7-
zalsa::Zalsa,
8-
zalsa_local::{self, ZalsaLocal},
9-
Durability, Event, EventKind, Revision,
10-
};
3+
use crate::{zalsa::ZalsaDatabase, Durability, Event, Revision};
114

125
/// The trait implemented by all Salsa databases.
13-
/// You can create your own subtraits of this trait using the `#[salsa::db]` procedural macro.
14-
///
15-
/// # Safety
16-
///
17-
/// This trait can only safely be implemented by Salsa's [`DatabaseImpl`][] type.
18-
///
19-
/// FIXME: Document better the unsafety conditions we require.
20-
#[salsa_macros::db]
21-
pub unsafe trait Database: Send + AsDynDatabase + Any {
6+
/// You can create your own subtraits of this trait using the `#[salsa::db]`(`crate::db`) procedural macro.
7+
#[crate::db]
8+
pub trait Database: Send + AsDynDatabase + Any + ZalsaDatabase {
229
/// This function is invoked by the salsa runtime at various points during execution.
2310
/// You can customize what happens by implementing the [`UserData`][] trait.
2411
/// By default, the event is logged at level debug using tracing facade.
@@ -58,21 +45,6 @@ pub unsafe trait Database: Send + AsDynDatabase + Any {
5845
{
5946
crate::attach::attach(self, || op(self))
6047
}
61-
62-
/// Plumbing method: Access the internal salsa methods.
63-
#[doc(hidden)]
64-
fn zalsa(&self) -> &Zalsa;
65-
66-
/// Plumbing method: Access the internal salsa methods for mutating the database.
67-
///
68-
/// **WARNING:** Triggers a new revision, canceling other database handles.
69-
/// This can lead to deadlock!
70-
#[doc(hidden)]
71-
fn zalsa_mut(&mut self) -> &mut Zalsa;
72-
73-
/// Access the thread-local state associated with this database
74-
#[doc(hidden)]
75-
fn zalsa_local(&self) -> &ZalsaLocal;
7648
}
7749

7850
/// Upcast to a `dyn Database`.
@@ -108,174 +80,3 @@ impl dyn Database {
10880
self.zalsa().views().try_view_as(self).unwrap()
10981
}
11082
}
111-
112-
/// Concrete implementation of the [`Database`][] trait.
113-
/// Takes an optional type parameter `U` that allows you to thread your own data.
114-
pub struct DatabaseImpl<U: UserData = ()> {
115-
/// Reference to the database. This is always `Some` except during destruction.
116-
zalsa_impl: Option<Arc<Zalsa>>,
117-
118-
/// Coordination data for cancellation of other handles when `zalsa_mut` is called.
119-
/// This could be stored in Zalsa but it makes things marginally cleaner to keep it separate.
120-
coordinate: Arc<Coordinate>,
121-
122-
/// Per-thread state
123-
zalsa_local: zalsa_local::ZalsaLocal,
124-
125-
/// The `U` is stored as a `dyn Any` in `zalsa_impl`
126-
phantom: PhantomData<U>,
127-
}
128-
129-
impl<U: UserData + Default> Default for DatabaseImpl<U> {
130-
fn default() -> Self {
131-
Self::with(U::default())
132-
}
133-
}
134-
135-
impl DatabaseImpl<()> {
136-
/// Create a new database with the given user data.
137-
///
138-
/// You can also use the [`Default`][] trait if your userdata implements it.
139-
pub fn new() -> Self {
140-
Self::with(())
141-
}
142-
}
143-
144-
impl<U: UserData> DatabaseImpl<U> {
145-
/// Create a new database with the given user data.
146-
///
147-
/// You can also use the [`Default`][] trait if your userdata implements it.
148-
pub fn with(u: U) -> Self {
149-
Self {
150-
zalsa_impl: Some(Arc::new(Zalsa::with(u))),
151-
coordinate: Arc::new(Coordinate {
152-
clones: Mutex::new(1),
153-
cvar: Default::default(),
154-
}),
155-
zalsa_local: ZalsaLocal::new(),
156-
phantom: PhantomData::<U>,
157-
}
158-
}
159-
160-
/// Access the `Arc<Zalsa>`. This should always be
161-
/// possible as `zalsa_impl` only becomes
162-
/// `None` once we are in the `Drop` impl.
163-
fn zalsa_impl(&self) -> &Arc<Zalsa> {
164-
self.zalsa_impl.as_ref().unwrap()
165-
}
166-
167-
// ANCHOR: cancel_other_workers
168-
/// Sets cancellation flag and blocks until all other workers with access
169-
/// to this storage have completed.
170-
///
171-
/// This could deadlock if there is a single worker with two handles to the
172-
/// same database!
173-
fn cancel_others(&mut self) {
174-
let zalsa = self.zalsa_impl();
175-
zalsa.set_cancellation_flag();
176-
177-
self.salsa_event(&|| Event {
178-
thread_id: std::thread::current().id(),
179-
180-
kind: EventKind::DidSetCancellationFlag,
181-
});
182-
183-
let mut clones = self.coordinate.clones.lock();
184-
while *clones != 1 {
185-
self.coordinate.cvar.wait(&mut clones);
186-
}
187-
}
188-
// ANCHOR_END: cancel_other_workers
189-
}
190-
191-
impl<U: UserData> std::ops::Deref for DatabaseImpl<U> {
192-
type Target = U;
193-
194-
fn deref(&self) -> &U {
195-
self.zalsa_impl().user_data().downcast_ref::<U>().unwrap()
196-
}
197-
}
198-
199-
impl<U: UserData> std::ops::DerefMut for DatabaseImpl<U> {
200-
fn deref_mut(&mut self) -> &mut U {
201-
self.zalsa_mut()
202-
.user_data_mut()
203-
.downcast_mut::<U>()
204-
.unwrap()
205-
}
206-
}
207-
208-
impl<U: UserData + RefUnwindSafe> RefUnwindSafe for DatabaseImpl<U> {}
209-
210-
#[salsa_macros::db]
211-
unsafe impl<U: UserData> Database for DatabaseImpl<U> {
212-
fn zalsa(&self) -> &Zalsa {
213-
&**self.zalsa_impl()
214-
}
215-
216-
fn zalsa_mut(&mut self) -> &mut Zalsa {
217-
self.cancel_others();
218-
219-
// The ref count on the `Arc` should now be 1
220-
let arc_zalsa_mut = self.zalsa_impl.as_mut().unwrap();
221-
let zalsa_mut = Arc::get_mut(arc_zalsa_mut).unwrap();
222-
zalsa_mut.new_revision();
223-
zalsa_mut
224-
}
225-
226-
fn zalsa_local(&self) -> &ZalsaLocal {
227-
&self.zalsa_local
228-
}
229-
230-
// Report a salsa event.
231-
fn salsa_event(&self, event: &dyn Fn() -> Event) {
232-
U::salsa_event(self, event)
233-
}
234-
}
235-
236-
impl<U: UserData> Clone for DatabaseImpl<U> {
237-
fn clone(&self) -> Self {
238-
*self.coordinate.clones.lock() += 1;
239-
240-
Self {
241-
zalsa_impl: self.zalsa_impl.clone(),
242-
coordinate: Arc::clone(&self.coordinate),
243-
zalsa_local: ZalsaLocal::new(),
244-
phantom: PhantomData::<U>,
245-
}
246-
}
247-
}
248-
249-
impl<U: UserData> Drop for DatabaseImpl<U> {
250-
fn drop(&mut self) {
251-
// Drop the database handle *first*
252-
self.zalsa_impl.take();
253-
254-
// *Now* decrement the number of clones and notify once we have completed
255-
*self.coordinate.clones.lock() -= 1;
256-
self.coordinate.cvar.notify_all();
257-
}
258-
}
259-
260-
pub trait UserData: Any + Sized + Send + Sync {
261-
/// Callback invoked by the [`Database`][] at key points during salsa execution.
262-
/// By overriding this method, you can inject logging or other custom behavior.
263-
///
264-
/// By default, the event is logged at level debug using the `tracing` crate.
265-
///
266-
/// # Parameters
267-
///
268-
/// * `event` a fn that, if called, will return the event that occurred
269-
fn salsa_event(_db: &DatabaseImpl<Self>, event: &dyn Fn() -> Event) {
270-
tracing::debug!("salsa_event: {:?}", event())
271-
}
272-
}
273-
274-
impl UserData for () {}
275-
276-
struct Coordinate {
277-
/// Counter of the number of clones of actor. Begins at 1.
278-
/// Incremented when cloned, decremented when dropped.
279-
clones: Mutex<usize>,
280-
cvar: Condvar,
281-
}

0 commit comments

Comments
 (0)