Search in sources :

Example 6 with UnmodifiableReplicaException

use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.

the class DatabaseReplicatorTest method prepareTransferPingPong.

@Test
public void prepareTransferPingPong() throws Exception {
    // Prepared transaction should be transferred to replica, back to old leader, and then
    // finish.
    var dbQueue = new LinkedBlockingQueue<Database>();
    var txnQueue = new LinkedBlockingQueue<Transaction>();
    var msgQueue = new LinkedBlockingQueue<byte[]>();
    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);
            msgQueue.add(message);
        }

        @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();
    byte[] k1 = "k1".getBytes();
    byte[] v1 = "v1".getBytes();
    leaderIx.store(txn1, k1, v1);
    PrepareHandler handler = leaderDb.prepareWriter("TestHandler");
    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());
    fastAssertArrayEquals(v1, replicaIx.load(txn2, k1));
    byte[] k2 = "k2".getBytes();
    byte[] v2 = "v2".getBytes();
    replicaIx.store(txn2, k2, v2);
    handler = replicaDb.prepareWriter("TestHandler");
    try {
        handler.prepare(txn2, null);
        fail();
    } catch (IllegalStateException e) {
    // Already prepared.
    }
    replicaDb.failover();
    try {
        txn2.commit();
        fail();
    } catch (UnmodifiableReplicaException e) {
    // This will unstick the transaction.
    }
    // Now the old leader is the leader again.
    assertEquals(leaderDb, dbQueue.take());
    Transaction txn3 = txnQueue.take();
    assertNotEquals(txn1, txn3);
    assertNotEquals(txn2, txn3);
    assertEquals(txnId, txn3.id());
    fastAssertArrayEquals("message".getBytes(), msgQueue.take());
    fastAssertArrayEquals(v1, leaderIx.load(txn3, k1));
    assertNull(leaderIx.load(txn3, k2));
    byte[] k3 = "k3".getBytes();
    byte[] v3 = "v3".getBytes();
    leaderIx.store(txn3, k3, v3);
    txn3.commit();
    fence(leaderDb, replicaDb);
    // Verify that leader and replica observe the committed changes. Note that v2 was
    // rolled back, because when the replica was acting as leader, it was unable to commit
    // v2. It tried to prepare the transaction again, but that's currently illegal.
    fastAssertArrayEquals(v1, leaderIx.load(null, k1));
    assertNull(leaderIx.load(null, k2));
    fastAssertArrayEquals(v3, leaderIx.load(null, k3));
    fastAssertArrayEquals(v1, replicaIx.load(null, k1));
    assertNull(replicaIx.load(null, k2));
    fastAssertArrayEquals(v3, replicaIx.load(null, k3));
}
Also used : UnmodifiableReplicaException(org.cojen.tupl.UnmodifiableReplicaException) Transaction(org.cojen.tupl.Transaction) PrepareHandler(org.cojen.tupl.ext.PrepareHandler) Database(org.cojen.tupl.Database) Index(org.cojen.tupl.Index) LinkedBlockingQueue(java.util.concurrent.LinkedBlockingQueue)

Example 7 with UnmodifiableReplicaException

use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.

the class DatabaseReplicatorTest method standbyLeader.

@Test
public void standbyLeader() throws Exception {
    // Test that a standby member can become an interim leader and prevent data loss.
    Database[] dbs = startGroup(2, Role.STANDBY, null);
    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");
    leaderDb.suspendCheckpoints();
    replicaDb.suspendCheckpoints();
    leaderDb.checkpoint();
    replicaDb.checkpoint();
    byte[] key = "key".getBytes();
    byte[] value = "value".getBytes();
    leaderIx.store(null, key, value);
    // Wait for replica to catch up.
    fence(leaderDb, replicaDb);
    fastAssertArrayEquals(value, replicaIx.load(null, key));
    // Close and re-open leader, which doesn't have the key in the log. Must explicitly
    // close the replicator, to ensure that closing the database doesn't flush the log.
    mReplicators[0].close();
    leaderDb = closeAndReopen(0);
    leaderIx = leaderDb.openIndex("test");
    byte[] found = null;
    for (int i = 0; i < 100; i++) {
        found = leaderIx.load(null, key);
        if (found != null) {
            break;
        }
        TestUtils.sleep(1000);
    }
    fastAssertArrayEquals(value, found);
    value = "value!".getBytes();
    for (int i = 0; i < 10; i++) {
        try {
            leaderIx.store(null, key, value);
            break;
        } catch (UnmodifiableReplicaException e) {
            TestUtils.sleep(1000);
        }
    }
    // Wait for replica to catch up.
    fence(leaderDb, replicaDb);
    fastAssertArrayEquals(value, replicaIx.load(null, key));
    leaderDb.close();
}
Also used : UnmodifiableReplicaException(org.cojen.tupl.UnmodifiableReplicaException) Database(org.cojen.tupl.Database) Index(org.cojen.tupl.Index)

Example 8 with UnmodifiableReplicaException

use of org.cojen.tupl.UnmodifiableReplicaException in project Tupl by cojen.

the class DatabaseReplicatorTest method startGroup.

/**
 * @return first is the leader
 */
private Database[] startGroup(int members, Role replicaRole, Supplier<PrepareHandler> handlerSupplier) throws Exception {
    if (members < 1) {
        throw new IllegalArgumentException();
    }
    mSockets = new ServerSocket[members];
    for (int i = 0; i < members; i++) {
        mSockets[i] = TestUtils.newServerSocket();
    }
    mReplBaseFiles = new File[members];
    mReplConfigs = new ReplicatorConfig[members];
    mReplicators = new StreamReplicator[members];
    mDbConfigs = new DatabaseConfig[members];
    mDatabases = new Database[members];
    for (int i = 0; i < members; i++) {
        mReplBaseFiles[i] = TestUtils.newTempBaseFile(getClass());
        EventListener listener = false ? EventListener.printTo(System.out) : null;
        mReplConfigs[i] = new ReplicatorConfig().groupToken(1).localSocket(mSockets[i]).baseFile(mReplBaseFiles[i]).eventListener(listener);
        if (i > 0) {
            mReplConfigs[i].addSeed(mSockets[0].getLocalSocketAddress());
            mReplConfigs[i].localRole(replicaRole);
        }
        mReplicators[i] = StreamReplicator.open(mReplConfigs[i]);
        ((Controller) mReplicators[i]).keepServerSocket();
        mDbConfigs[i] = new DatabaseConfig().baseFile(mReplBaseFiles[i]).replicate(mReplicators[i]).eventListener(listener).lockTimeout(5, TimeUnit.SECONDS).directPageAccess(false);
        if (handlerSupplier != null) {
            mDbConfigs[i].prepareHandlers(Map.of("TestHandler", handlerSupplier.get()));
        }
        Database db = Database.open(mDbConfigs[i]);
        mDatabases[i] = db;
        readyCheck: {
            for (int trial = 0; trial < 100; trial++) {
                TestUtils.sleep(100);
                if (i == 0) {
                    try {
                        db.openIndex("control");
                        // Ensure that replicas obtain the index in the snapshot.
                        db.checkpoint();
                        break readyCheck;
                    } catch (UnmodifiableReplicaException e) {
                    // Not leader yet.
                    }
                } else {
                    assertNotNull(db.openIndex("control"));
                    break readyCheck;
                }
            }
            throw new AssertionError(i == 0 ? "No leader" : "Not joined");
        }
    }
    return mDatabases;
}
Also used : UnmodifiableReplicaException(org.cojen.tupl.UnmodifiableReplicaException) Database(org.cojen.tupl.Database) EventListener(org.cojen.tupl.diag.EventListener) DatabaseConfig(org.cojen.tupl.DatabaseConfig)

Aggregations

UnmodifiableReplicaException (org.cojen.tupl.UnmodifiableReplicaException)8 Database (org.cojen.tupl.Database)7 Index (org.cojen.tupl.Index)6 LinkedBlockingQueue (java.util.concurrent.LinkedBlockingQueue)3 Transaction (org.cojen.tupl.Transaction)3 PrepareHandler (org.cojen.tupl.ext.PrepareHandler)3 IOException (java.io.IOException)1 DatabaseConfig (org.cojen.tupl.DatabaseConfig)1 DatabaseException (org.cojen.tupl.DatabaseException)1 LockTimeoutException (org.cojen.tupl.LockTimeoutException)1 EventListener (org.cojen.tupl.diag.EventListener)1 LatchCondition (org.cojen.tupl.util.LatchCondition)1