pub struct MvccClock { /* private fields */ }Expand description
A mutex-guarded clock for concurrent MVCC use.
The lock is held across the f callback in [get_timestamp], ensuring
that a commit timestamp is published (e.g. stored as Preparing(ts))
before any other transaction can generate a higher timestamp. This closes
the TOCTOU window between timestamp generation and Preparing state
publication in the commit protocol.
§Speculative reads
We have speculative reads (and speculative ignores). That is, an active transaction can see changes of another transaction which is in the preparing phase. Assuming the other transaction successfully commits, the active transaction continues to make progress. If the other transaction gets aborted, then the active transaction needs to be aborted as well.
So, say tx2 starts at begin_ts(11) and another transaction tx1,
started earlier, is now in its preparing phase with end_ts(10). Once the
end_ts is assigned, that will be the final commit timestamp of that
transaction. So tx2 should see changes made by tx1, since tx1 was
committed (in logical time) before tx2 started.
Whether tx2 can see tx1’s changes depends on when tx1 acquired the
end_ts timestamp during the preparing phase.
Note: We need speculative reads, otherwise it’s difficult to make the MVCC model work without blocking. I made an attempt in turso#5198 but this introduced a subtle bug which violated snapshot isolation. So without speculative reads in the previous example,
tx2needs to wait tilltx1is committed or aborted.
§Need for atomicity
We want to atomically generate end_ts and publish Preparing(end_ts)
while the clock lock is held. This closes the TOCTOU window.
Consider the example:
tx1 (Active): generates end_ts = 10
tx2 (Active): gets begin_ts = 11
tx2 (Active): does queries but does not see changes by tx1 (tx1 is still Active)
tx1 (Preparing): stores Preparing(end_ts=10)
tx2 (Active): queries again, now it can see changes by tx1 (tx1 is now Preparing)This is a snapshot isolation violation — tx2 observes different
values for the same rows within the same transaction.
So we want the following two operations to be atomic:
let ts = get_timestamp()
store Preparing(ts)tx2 must get its begin timestamp either before or after these
two operations. If it interleaves, the above bug happens.
§Note on the Hekaton paper
The Hekaton paper doesn’t mention this “gotcha”. The paper says:
“When the transaction has completed its normal processing and requests to commit, it acquires an end timestamp and switches to the Preparing state.”
But it doesn’t go into more detail about atomicity here.
Implementations§
Source§impl MvccClock
impl MvccClock
pub fn new() -> Self
Sourcepub fn get_begin_timestamp(&self) -> u64
pub fn get_begin_timestamp(&self) -> u64
Generate a begin timestamp. No side-effect needed alongside generation.
Sourcepub fn get_commit_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64
pub fn get_commit_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64
Generate a commit timestamp and call f with it while the lock is
held, atomically publishing the timestamp before releasing.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for MvccClock
impl !RefUnwindSafe for MvccClock
impl Send for MvccClock
impl Sync for MvccClock
impl Unpin for MvccClock
impl UnsafeUnpin for MvccClock
impl UnwindSafe for MvccClock
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more