/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2010, 2015 Oracle and/or its affiliates. All rights reserved.
*
*/
package rep;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.sleepycat.db.*;
import repmgrtests.Util;
import org.junit.Test;
import java.util.*;
public class TestMirandaTimeout {
class /* struct */ Message {
DatabaseEntry ctl, rec;
int sourceEID;
boolean perm;
Message(DatabaseEntry ctl, DatabaseEntry rec, int eid, boolean p) {
this.ctl = ctl;
this.rec = rec;
sourceEID = eid;
perm = p;
}
}
List<Queue<Message>> queues = new ArrayList<Queue<Message>>();
Environment[] envs = new Environment[2];
public static final int SELF = Integer.MAX_VALUE;
@Test public void deferredArrival() throws Exception {
EnvironmentConfig ec = new EnvironmentConfig();
ec.setAllowCreate(true);
ec.setInitializeCache(true);
ec.setInitializeLocking(true);
ec.setInitializeLogging(true);
ec.setInitializeReplication(true);
ec.setTransactional(true);
ec.setThreaded(true);
ec.setReplicationNumSites(2);
if (Boolean.getBoolean("VERB_REPLICATION"))
ec.setVerbose(VerboseConfig.REPLICATION, true);
// The EID serves double duty as an index into the array of
// ArrayLists which are used as the message queues. Master is
// EID 0 and client is EID 1.
//
queues.add(new LinkedList<Message>());
queues.add(new LinkedList<Message>());
ec.setReplicationTransport(SELF, new Transport(0));
Environment master = new Environment(Util.mkdir("master"), ec);
envs[0] = master;
ec.setReplicationTransport(SELF, new Transport(1));
Environment client = new Environment(Util.mkdir("client"), ec);
envs[1] = client;
master.startReplication(null, true);
client.startReplication(null, false);
processMessages();
DatabaseConfig dc = new DatabaseConfig();
dc.setTransactional(true);
dc.setAllowCreate(true);
dc.setType(DatabaseType.BTREE);
Database db = master.openDatabase(null, "test.db", null, dc);
processMessages();
Transaction txn = master.beginTransaction(null, null);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry value = new DatabaseEntry();
key.setData("mykey".getBytes());
value.setData("myvalue".getBytes());
db.put(txn, key, value);
txn.commit();
byte[] token1 = txn.getCommitToken();
txn = master.beginTransaction(null, null);
key = new DatabaseEntry();
value = new DatabaseEntry();
key.setData("one,two".getBytes());
value.setData("buckle my shoe".getBytes());
db.put(txn, key, value);
txn.commit();
byte[] token2 = txn.getCommitToken();
// Since we haven't sent pending msgs to the client yet, the
// transaction shouldn't be there yet.
//
assertEquals(TransactionStatus.TIMEOUT,
client.isTransactionApplied(token1, 0));
// Start 2 client threads to wait for the transactions in
// reverse order, to make sure the waiting completes in the
// correct order.
//
Client clientTh2 = new Client(client, token2);
Thread t2 = new Thread(clientTh2, "clientThread2");
t2.start();
Thread.sleep(5000);
Client clientTh1 = new Client(client, token1);
Thread t1 = new Thread(clientTh1, "clientThread1");
t1.start();
Thread.sleep(5000);
processOnePerm();
Thread.sleep(5000);
processMessages();
t1.join();
t2.join();
assertEquals(TransactionStatus.APPLIED, clientTh1.getResult());
assertTrue(clientTh1.getDuration() > 1000 &&
clientTh1.getDuration() < 100000);
assertEquals(TransactionStatus.APPLIED, clientTh2.getResult());
// We started thread2 (way) before starting thread1, so its
// start time should be less (assuming the system isn't
// ridiculously busy). But thread1 still should have
// completed (way) before thread2, because its transaction was
// replicated first (before a pause).
//
assertTrue(clientTh2.getStartTime() < clientTh1.getStartTime());
assertTrue(clientTh1.getEndTime() < clientTh2.getEndTime());
db.close();
client.close();
master.close();
}
class Client implements Runnable {
private Environment env;
private byte[] token;
private TransactionStatus result;
private long duration, endTime, startTime;
public void run() {
try {
startTime = System.currentTimeMillis();
result = env.isTransactionApplied(token, 100000000);
endTime = System.currentTimeMillis();
duration = endTime - startTime;
} catch (Exception e) {
// if an exception happens, we leave "result"
// unset, which will eventually cause a test failure
// in the parent thread.
e.printStackTrace();
}
}
Client(Environment env, byte[] token) {
this.env = env;
this.token = token;
}
long getDuration() { return duration; }
long getStartTime() { return startTime; }
long getEndTime() { return endTime; }
TransactionStatus getResult() { return result; }
}
class Transport implements ReplicationTransport {
private int myEID;
Transport(int eid) { myEID = eid; }
public int send(Environment env, DatabaseEntry ctl, DatabaseEntry rec,
LogSequenceNumber lsn, int eid, boolean noBuf,
boolean perm, boolean anywhere, boolean isRetry) {
int target = 1 - myEID;
queues.get(target).add(new Message(ctl, rec, myEID, perm));
return (0);
}
}
void processOnePerm() throws Exception {
Queue<Message> q = queues.get(1); // the client is site 1
while (!q.isEmpty()) {
Message m = q.remove();
envs[1].processReplicationMessage(m.ctl, m.rec, m.sourceEID);
if (m.perm)
return;
}
}
void processMessages() throws Exception {
boolean done = false;
while (!done) {
boolean got = false;
for (int eid = 0; eid < 2; eid++) {
Queue<Message> q = queues.get(eid);
while (!q.isEmpty()) {
got = true;
Message m = q.remove();
envs[eid].processReplicationMessage(m.ctl, m.rec, m.sourceEID);
}
}
if (!got)
done = true;
}
}
}