Skip to main content

MvccClock

Struct MvccClock 

Source
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, tx2 needs to wait till tx1 is 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 violationtx2 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

Source

pub fn new() -> Self

Source

pub fn get_begin_timestamp(&self) -> u64

Generate a begin timestamp. No side-effect needed alongside generation.

Source

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§

Source§

impl Debug for MvccClock

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for MvccClock

Source§

fn default() -> MvccClock

Returns the “default value” for a type. Read more
Source§

impl LogicalClock for MvccClock

Source§

fn get_timestamp<F: FnOnce(u64)>(&self, f: F) -> u64

Generates the next timestamp, calls f with it, then returns it. Read more
Source§

fn reset(&self, ts: u64)

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more