use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.
the class ReplController method ready.
public void ready(long initialPosition, long initialTxnId) throws IOException {
acquireExclusive();
try {
mLeaderNotifyCondition = new LatchCondition();
// Init for the shouldCheckpoint method. Without this, an initial checkpoint is
// performed even if it's not necessary.
cCheckpointPosHandle.setOpaque(this, initialPosition | (1L << 63));
} finally {
releaseExclusive();
}
ReplDecoder decoder = mEngine.startReceiving(initialPosition, initialTxnId);
if (decoder == null) {
// Failed to start, and database has been closed with an exception.
return;
}
CoreDatabase db = mEngine.mDatabase;
// Can now send control messages.
mRepl.controlMessageAcceptor(message -> {
try {
db.writeControlMessage(message);
} catch (UnmodifiableReplicaException e) {
// Drop it.
} catch (Throwable e) {
Utils.uncaught(e);
}
});
// Can now accept snapshot requests.
mRepl.snapshotRequestAcceptor(sender -> {
try {
ReplUtils.sendSnapshot(db, sender);
} catch (Throwable e) {
Utils.closeQuietly(sender);
if (e instanceof DatabaseException || !(e instanceof IOException)) {
Utils.uncaught(e);
}
}
});
// Update the local member role.
mRepl.start();
// Wait until replication has "caught up" before returning.
boolean isLeader = decoder.catchup();
// We're not truly caught up until all outstanding redo operations have been applied.
// Suspend and resume does the trick.
mEngine.suspend();
mEngine.resume();
// Wait for leaderNotify method to be called. The local member might be the leader now,
// or the new leadership might have been immediately revoked. Either case is detected.
acquireExclusive();
try {
if (isLeader && mLeaderNotifyCondition != null) {
mLeaderNotifyCondition.await(this);
}
} finally {
mLeaderNotifyCondition = null;
releaseExclusive();
}
}
use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.
the class DatabaseReplicatorTest method prepareTransfer.
private void prepareTransfer(boolean prepareCommit) throws Exception {
// Prepared transaction should be transferred to replica and finish.
var dbQueue = new LinkedBlockingQueue<Database>();
var txnQueue = new LinkedBlockingQueue<Transaction>();
Supplier<PrepareHandler> supplier = () -> new PrepareHandler() {
private Database mDb;
@Override
public void init(Database db) {
mDb = db;
}
@Override
public void prepare(Transaction txn, byte[] message) throws IOException {
dbQueue.add(mDb);
txnQueue.add(txn);
}
@Override
public void prepareCommit(Transaction txn, byte[] message) throws IOException {
prepare(txn, message);
}
};
Database[] dbs = startGroup(2, Role.NORMAL, supplier);
Database leaderDb = dbs[0];
Database replicaDb = dbs[1];
Index leaderIx = leaderDb.openIndex("test");
// Wait for replica to catch up.
fence(leaderDb, replicaDb);
Index replicaIx = replicaDb.openIndex("test");
Transaction txn1 = leaderDb.newTransaction();
PrepareHandler handler = leaderDb.prepareWriter("TestHandler");
byte[] k1 = "k1".getBytes();
byte[] v1 = "v1".getBytes();
leaderIx.store(txn1, k1, v1);
if (prepareCommit) {
handler.prepareCommit(txn1, null);
fastAssertArrayEquals(v1, leaderIx.load(null, k1));
} else {
handler.prepare(txn1, null);
try {
leaderIx.load(null, k1);
fail();
} catch (LockTimeoutException e) {
}
}
leaderDb.failover();
// Replica is now the leader and should have the transaction.
assertEquals(replicaDb, dbQueue.take());
Transaction txn2 = txnQueue.take();
assertNotEquals(txn1, txn2);
assertEquals(txn1.id(), txn2.id());
fastAssertArrayEquals(v1, replicaIx.load(txn2, k1));
byte[] k2 = "k2".getBytes();
byte[] v2 = "v2".getBytes();
replicaIx.store(txn2, k2, v2);
if (prepareCommit) {
fastAssertArrayEquals(v1, replicaIx.load(null, k1));
} else {
try {
replicaIx.load(null, k1);
fail();
} catch (LockTimeoutException e) {
}
}
txn2.commit();
// Wait for old leader to catch up. This will fail at first because the old leader
// transaction is stuck.
boolean pass = true;
try {
fence(replicaDb, leaderDb, true);
pass = false;
} catch (AssertionError e) {
}
assertTrue(pass);
try {
txn1.commit();
fail();
} catch (UnmodifiableReplicaException e) {
// This will unstick the transaction.
}
fence(replicaDb, leaderDb);
// Verify that the old leader observes the committed changes.
fastAssertArrayEquals(v1, leaderIx.load(null, k1));
fastAssertArrayEquals(v2, leaderIx.load(null, k2));
}
use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.
the class DatabaseReplicatorTest method prepareBlank.
private void prepareBlank(boolean prepareCommit) throws Exception {
// Test against a prepared transaction that has no changes. It should still ensure that
// the transaction is created properly on the replica.
var dbQueue = new LinkedBlockingQueue<Database>();
var txnQueue = new LinkedBlockingQueue<Transaction>();
var msgQueue = new LinkedBlockingQueue<byte[]>();
var pcQueue = new LinkedBlockingQueue<Boolean>();
Supplier<PrepareHandler> supplier = () -> new PrepareHandler() {
private Database mDb;
@Override
public void init(Database db) {
mDb = db;
}
@Override
public void prepare(Transaction txn, byte[] message) throws IOException {
doPrepare(txn, message, false);
}
@Override
public void prepareCommit(Transaction txn, byte[] message) throws IOException {
doPrepare(txn, message, true);
}
private void doPrepare(Transaction txn, byte[] message, boolean commit) throws IOException {
dbQueue.add(mDb);
txnQueue.add(txn);
msgQueue.add(message);
pcQueue.add(commit);
}
};
Database[] dbs = startGroup(2, Role.NORMAL, supplier);
Database leaderDb = dbs[0];
Database replicaDb = dbs[1];
Index leaderIx = leaderDb.openIndex("test");
// Wait for replica to catch up.
fence(leaderDb, replicaDb);
Index replicaIx = replicaDb.openIndex("test");
Transaction txn1 = leaderDb.newTransaction();
PrepareHandler handler = leaderDb.prepareWriter("TestHandler");
if (prepareCommit) {
handler.prepareCommit(txn1, "message".getBytes());
} else {
handler.prepare(txn1, "message".getBytes());
}
leaderDb.failover();
// Must capture the id before it gets replaced.
long txnId = txn1.id();
try {
txn1.commit();
fail();
} catch (UnmodifiableReplicaException e) {
// This will unstick the transaction.
}
// Replica is now the leader and should have the transaction.
assertEquals(replicaDb, dbQueue.take());
Transaction txn2 = txnQueue.take();
assertNotEquals(txn1, txn2);
assertEquals(txnId, txn2.id());
fastAssertArrayEquals("message".getBytes(), msgQueue.take());
assertEquals(prepareCommit, pcQueue.take());
byte[] k1 = "k1".getBytes();
byte[] v1 = "v1".getBytes();
replicaIx.store(txn2, k1, v1);
txn2.commit();
fence(replicaDb, leaderDb);
// Verify that the old leader observes the committed changes.
fastAssertArrayEquals(v1, leaderIx.load(null, k1));
}
use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.
the class DatabaseReplicatorTest method basicTest.
private void basicTest(int memberCount) throws Exception {
Database[] dbs = startGroup(memberCount);
Index ix0 = dbs[0].openIndex("test");
for (int t = 0; t < 10; t++) {
byte[] key = ("hello-" + t).getBytes();
byte[] value = ("world-" + t).getBytes();
ix0.store(null, key, value);
fastAssertArrayEquals(value, ix0.load(null, key));
for (int i = 0; i < dbs.length; i++) {
for (int q = 100; --q >= 0; ) {
byte[] actual = null;
try {
Index ix = dbs[i].openIndex("test");
actual = ix.load(null, key);
fastAssertArrayEquals(value, actual);
break;
} catch (UnmodifiableReplicaException e) {
// Index doesn't exist yet due to replication delay.
if (q == 0) {
throw e;
}
} catch (AssertionError e) {
// Value doesn't exist yet due to replication delay.
if (q == 0 || actual != null) {
throw e;
}
}
TestUtils.sleep(100);
}
}
}
}
use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.
the class DatabaseReplicatorTest method explicitFailover.
@Test
public void explicitFailover() throws Exception {
Database[] dbs = startGroup(2);
Database leaderDb = dbs[0];
Database replicaDb = dbs[1];
Index leaderIx = leaderDb.openIndex("test");
// Wait for replica to catch up.
fence(leaderDb, replicaDb);
Index replicaIx = replicaDb.openIndex("test");
byte[] key = "hello".getBytes();
byte[] value = "world".getBytes();
leaderDb.failover();
try {
leaderIx.store(null, key, value);
fail();
} catch (UnmodifiableReplicaException e) {
}
boolean success = false;
for (int i = 0; i < 10; i++) {
try {
replicaIx.store(null, key, value);
success = true;
break;
} catch (UnmodifiableReplicaException e) {
}
TestUtils.sleep(1000);
}
assertTrue(success);
// Wait for old leader to catch up.
fence(replicaDb, leaderDb);
fastAssertArrayEquals(value, leaderIx.load(null, key));
}
Aggregations