com.persistit.TimestampAllocator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of akiban-persistit Show documentation
Show all versions of akiban-persistit Show documentation
Java B+Tree Key-Value Store Library
/**
* Copyright © 2011-2012 Akiban Technologies, Inc. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This program may also be available under different license terms.
* For more information, see www.akiban.com or contact [email protected].
*
* Contributors:
* Akiban Technologies, Inc.
*/
package com.persistit;
import java.util.concurrent.atomic.AtomicLong;
import com.persistit.exception.PersistitInterruptedException;
import com.persistit.util.Util;
class TimestampAllocator {
private final static int CHECKPOINT_TIMESTAMP_MARKER_INTERVAL = 100;
private final static long UNAVAILABLE_CHECKPOINT_TIMESTAMP = -1;
/**
* Default interval in nanoseconds between checkpoints - two minutes.
*/
private final AtomicLong _timestamp = new AtomicLong();
private volatile long _checkpointTimestamp;
public long updateTimestamp() {
return _timestamp.incrementAndGet();
}
long bumpTimestamp(final long delta) {
return _timestamp.addAndGet(delta);
}
public long updateTimestamp(final long timestamp) {
_timestamp.incrementAndGet();
while (true) {
final long expected = _timestamp.get();
if (expected < timestamp) {
if (_timestamp.compareAndSet(expected, timestamp)) {
return timestamp;
}
} else {
return expected;
}
}
}
/**
*
* Atomically allocate a new checkpoint timestamp. This method temporarily
* marks the checkpoint timestamp field as "unavailable" until a new value
* is computed. This prevents another thread executing
* {@link Buffer#writePageOnCheckpoint(long)} from trusting the old
* checkpoint timestamp while a new one is being allocated.
*
*
* Here's the execution sequence to avoid:
*
* - Let the initial checkpoint timestamp be c0.
*
- Thread A (the checkpoint thread invoking this method) gets an updated
* timestamp c1 which will become the new checkpoint timestamp.
* - Thread B (a thread attempting to modify a page that is already dirty
* with timestamp t0) gets an updated timestamp t1 greater than c1
* - Thread B in writePageOnCheckpoint determines that t0 is larger than
* the current checkpoint c0 (because the checkpoint timestamp field has not
* been updated yet) and therefore continues on to further modify the page
* without writing its state at t0.
* - Thread A now updates the checkpoint timestamp field to contain c1.
* In this scenario the version of the page at t0 was never written to
* the journal even though t0 < c1 and t1 > c1. This violates the recovery
* safety requirement.
*
*
* The solution is to stall the {@link #getProposedCheckpointTimestamp()}
* method called by writePageOnCheckpoint before c1 is allocated until after
* the checkpointTimestamp field containing c1 is visible.
*
*
* @return the allocated timestamp
*/
long allocateCheckpointTimestamp() {
/*
* Add a gap to the timestamp counter - this is useful only for humans
* trying to decipher timestamps in the journal - is not necessary for
* correct function.
*/
bumpTimestamp(CHECKPOINT_TIMESTAMP_MARKER_INTERVAL);
/*
* Prevents Buffer#writePageOnCheckpoint from seeing the old checkpoint
* while we are assigning the new one. The
* getProposedCheckpointTimestamp method below spins until this has been
* settled.
*/
_checkpointTimestamp = UNAVAILABLE_CHECKPOINT_TIMESTAMP;
final long timestamp = updateTimestamp();
_checkpointTimestamp = timestamp;
return timestamp;
}
public long getCurrentTimestamp() {
return _timestamp.get();
}
long getProposedCheckpointTimestamp() throws PersistitInterruptedException {
long timestamp;
/*
* Spin until stable. This value must not be observed until set.
*/
for (int iterations = 0; (timestamp = _checkpointTimestamp) == UNAVAILABLE_CHECKPOINT_TIMESTAMP; iterations++) {
if (iterations > 10) {
Util.spinSleep();
}
}
return timestamp;
}
}