Skip to content

Commit 2071c7f

Browse files
committed
More docs
1 parent 6c5495a commit 2071c7f

File tree

2 files changed

+58
-25
lines changed

2 files changed

+58
-25
lines changed

src/function/execute.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,13 @@ where
248248
// Did the new result we got depend on our own provisional value, in a cycle?
249249
// If not, return because this query is not a cycle head.
250250
if !depends_on_self {
251-
if let Some(outer) = outer_cycle {
252-
claim_guard.set_release_mode(ReleaseMode::TransferTo(outer));
253-
} else {
254-
claim_guard.set_release_mode(ReleaseMode::SelfOnly);
255-
}
251+
// For as long as this query participates in any cycle, don't release its lock, instead
252+
// transfer it to the outermost cycle head (if any). This prevents any other thread
253+
// from claiming this query (all cycle heads are potential entry points to the same cycle),
254+
// which would result in them competing for the same locks (we want the locks to converge to a single cycle head).
255+
claim_guard.set_release_mode(ReleaseMode::TransferTo(
256+
outer_cycle.expect("query to of an outer cycle."),
257+
));
256258

257259
completed_query.revisions.set_cycle_heads(cycle_heads);
258260
break (new_value, completed_query);
@@ -324,7 +326,7 @@ where
324326
}
325327

326328
if let Some(outer_cycle) = outer_cycle {
327-
tracing::debug!(
329+
tracing::info!(
328330
"Detected nested cycle {database_key_index:?}, iterate it as part of the outer cycle {outer_cycle:?}"
329331
);
330332

src/function/sync.rs

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,18 @@ pub(crate) struct SyncState {
3535
/// waiting for this query to complete.
3636
anyone_waiting: bool,
3737

38+
/// Whether any other query has transferred its lock ownership to this query.
39+
/// This is only an optimization so that the expensive unblocking of transferred queries
40+
/// can be skipped if `false`. This field might be `true` in cases where queries *were* transferred
41+
/// to this query, but have since then been transferred to another query (in a later iteration).
3842
is_transfer_target: bool,
39-
claimed_twice: bool,
43+
44+
/// Whether this query has been claimed by the query that currently owns it.
45+
///
46+
/// If `a` has been transferred to `b` and the stack for t1 is `b -> a`, then `a` can be claimed
47+
/// and `claimed_transferred` is set to `true`. However, t2 won't be able to claim `a` because
48+
/// it doesn't own `b`.
49+
claimed_transferred: bool,
4050
}
4151

4252
impl SyncTable {
@@ -47,8 +57,12 @@ impl SyncTable {
4757
}
4858
}
4959

60+
/// Claims the given key index, or blocks if it is running on another thread.
61+
///
62+
/// `REENTRANT` controls whether claiming a query whose ownership has been transferred to another query
63+
/// should result in a cycle (`false`) or can be claimed (`true`), if not already done so.
5064
#[inline]
51-
pub(crate) fn try_claim<'me, const REENTRANT: bool>(
65+
pub(crate) fn try_claim<'me, const TRANSFERRED: bool>(
5266
&'me self,
5367
zalsa: &'me Zalsa,
5468
key_index: Id,
@@ -59,7 +73,8 @@ impl SyncTable {
5973
let id = match occupied_entry.get().id {
6074
SyncOwnerId::Thread(id) => id,
6175
SyncOwnerId::Transferred => {
62-
return match self.try_claim_transferred::<REENTRANT>(zalsa, occupied_entry)
76+
return match self
77+
.try_claim_transferred::<TRANSFERRED>(zalsa, occupied_entry)
6378
{
6479
Ok(claimed) => claimed,
6580
Err(other_thread) => match other_thread.block(write) {
@@ -96,7 +111,7 @@ impl SyncTable {
96111
id: SyncOwnerId::Thread(thread::current().id()),
97112
anyone_waiting: false,
98113
is_transfer_target: false,
99-
claimed_twice: false,
114+
claimed_transferred: false,
100115
});
101116
ClaimResult::Claimed(ClaimGuard {
102117
key_index,
@@ -128,7 +143,9 @@ impl SyncTable {
128143
}
129144
ClaimTransferredResult::Reentrant => {
130145
let SyncState {
131-
id, claimed_twice, ..
146+
id,
147+
claimed_transferred: claimed_twice,
148+
..
132149
} = entry.into_mut();
133150
debug_assert!(!*claimed_twice);
134151

@@ -150,7 +167,7 @@ impl SyncTable {
150167
id: SyncOwnerId::Thread(thread::current().id()),
151168
anyone_waiting: false,
152169
is_transfer_target: false,
153-
claimed_twice: false,
170+
claimed_transferred: false,
154171
});
155172
Ok(ClaimResult::Claimed(ClaimGuard {
156173
key_index,
@@ -175,10 +192,18 @@ impl SyncTable {
175192

176193
#[derive(Copy, Clone, Debug)]
177194
pub(crate) enum SyncOwnerId {
178-
/// Entry is owned by this thread
195+
/// Query is owned by this thread
179196
Thread(thread::ThreadId),
180-
/// Entry has been transferred and is owned by another thread.
181-
/// The id is known by the `DependencyGraph`.
197+
198+
/// The query's lock ownership has been transferred to another query.
199+
/// E.g. if `a` transfers its ownership to `b`, then only the thread in the critical path
200+
/// to complete b` can claim `a` (in most instances, only the thread owning `b` can claim `a`).
201+
///
202+
/// The thread owning `a` is stored in the `DependencyGraph`.
203+
///
204+
/// A query can be marked as `Transferred` even if it has since then been released by the owning query.
205+
/// In that case, the query is effectively unclaimed and the `Transferred` state is stale. The reason
206+
/// for this is that it avoids the need for locking each sync table when releasing the transferred queries.
182207
Transferred,
183208
}
184209

@@ -220,7 +245,7 @@ impl<'me> ClaimGuard<'me> {
220245
let SyncState {
221246
anyone_waiting,
222247
is_transfer_target,
223-
claimed_twice,
248+
claimed_transferred: claimed_twice,
224249
..
225250
} = state;
226251

@@ -250,8 +275,8 @@ impl<'me> ClaimGuard<'me> {
250275
panic!("key claimed twice?");
251276
};
252277

253-
if state.get().claimed_twice {
254-
state.get_mut().claimed_twice = false;
278+
if state.get().claimed_transferred {
279+
state.get_mut().claimed_transferred = false;
255280
state.get_mut().id = SyncOwnerId::Transferred;
256281
} else {
257282
self.release(WaitResult::Completed, state.remove());
@@ -261,6 +286,7 @@ impl<'me> ClaimGuard<'me> {
261286
#[cold]
262287
#[inline(never)]
263288
pub(crate) fn transfer(&self, new_owner: DatabaseKeyIndex) {
289+
tracing::info!("transfer");
264290
let self_key = self.database_key_index();
265291

266292
let owner_ingredient = self.zalsa.lookup_ingredient(new_owner.ingredient_index());
@@ -283,7 +309,7 @@ impl<'me> ClaimGuard<'me> {
283309
let SyncState {
284310
anyone_waiting,
285311
id,
286-
claimed_twice,
312+
claimed_transferred: claimed_twice,
287313
..
288314
} = syncs.get_mut(&self.key_index).expect("key claimed twice?");
289315

@@ -326,23 +352,28 @@ impl std::fmt::Debug for SyncTable {
326352
}
327353
}
328354

355+
/// Controls how the lock is released when the `ClaimGuard` is dropped.
329356
#[derive(Copy, Clone, Debug, Default)]
330357
pub(crate) enum ReleaseMode {
331358
/// The default release mode.
332359
///
333-
/// Releases the lock of the current query for claims that are not transferred. Queries who's ownership
334-
/// were transferred to this query will be transitively unlocked.
335-
///
336-
/// If this lock is owned by another query (because it was transferred), then releasing is a no-op.
360+
/// Releases the query for which this claim guard holds the lock and any queries that have
361+
/// transferred ownership to this query.
337362
#[default]
338363
Default,
339364

365+
/// Only releases the lock for this query. Any query that has transferred ownership to this query
366+
/// will remain locked.
367+
///
368+
/// If this thread panics, the query will be released as normal (default mode).
340369
SelfOnly,
341370

342371
/// Transfers the ownership of the lock to the specified query.
343372
///
344-
/// All waiting queries will be awakened so that they can retry and block on the new owner thread.
345-
/// The new owner thread (or any thread it blocks on) will be able to acquire the lock (reentrant).
373+
/// The query will remain locked except the query that's currently blocking this query from completing
374+
/// (to avoid deadlocks).
375+
///
376+
/// If this thread panics, the query will be released as normal (default mode).
346377
TransferTo(DatabaseKeyIndex),
347378
}
348379

0 commit comments

Comments
 (0)