package edu.berkeley.thebes.twopl.common; import org.apache.thrift.TException; import org.apache.thrift.transport.TTransportException; import org.slf4j.LoggerFactory; import javax.naming.ConfigurationException; import com.google.common.collect.Sets; import edu.berkeley.thebes.common.config.Config; import edu.berkeley.thebes.common.data.DataItem; import edu.berkeley.thebes.common.data.Version; import edu.berkeley.thebes.common.interfaces.IThebesClient; import edu.berkeley.thebes.twopl.common.thrift.TwoPLMasterReplicaService.Client; import java.io.FileNotFoundException; import java.nio.ByteBuffer; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Provides a layer of abstraction that manages getting the actual locks from a set of * GET/PUT requests. * Communicates directly with the master replicas via a {@link TwoPLMasterRouter}. * * Note: This is used by Thebes clients to talk directly with the 2PL servers; * alternatively, Thebes clients use {@link ThebesTwoPLClient} to talk to TMS, * which use this class to talk to the 2PL servers. */ public class ThebesTwoPLTransactionClient implements IThebesClient { private static org.slf4j.Logger logger = LoggerFactory.getLogger(ThebesTwoPLTransactionClient.class); private static AtomicInteger NEXT_SEQUENCE_NUMBER = new AtomicInteger(0); private short clientId = Config.getClientID(); private long sessionId; private boolean inTransaction; private Set<String> writeLocks; private Set<String> readLocks; private TwoPLMasterRouter masterRouter; public ThebesTwoPLTransactionClient() { } @Override public void open() throws TTransportException, ConfigurationException, FileNotFoundException { masterRouter = new TwoPLMasterRouter(); } @Override public void beginTransaction() throws TException { if (inTransaction) { throw new TException("Currently in a transaction."); } sessionId = (clientId*100000) + NEXT_SEQUENCE_NUMBER.getAndIncrement(); logger.trace("Starting transaction with seqno " + sessionId); inTransaction = true; writeLocks = Sets.newHashSet(); readLocks = Sets.newHashSet(); } @Override public void abortTransaction() throws TException { throw new TException("abort not supported by ThebesTwoPLTransactionClient"); } @Override public boolean commitTransaction() throws TException { inTransaction = false; for (String key : readLocks) { masterRouter.getMasterByKey(key).unlock(sessionId, key); } for (String key : writeLocks) { masterRouter.getMasterByKey(key).unlock(sessionId, key); } return true; } @Override public boolean put(String key, ByteBuffer value) throws TException { if (!inTransaction) { throw new TException("Must be in a transaction!"); } writeLock(key); long timestamp = System.currentTimeMillis(); DataItem dataItem = new DataItem(value, new Version(clientId, sessionId, timestamp)); return masterRouter.getMasterByKey(key).put(sessionId, key, dataItem.toThrift()); } /** Same as put, but does not acquire or need a lock. */ public boolean unsafe_load(String key, ByteBuffer value) throws TException { long timestamp = System.currentTimeMillis(); DataItem dataItem = new DataItem(value, new Version(clientId, sessionId, timestamp)); return masterRouter.getMasterByKey(key).unsafe_load(key, dataItem.toThrift()); } @Override public ByteBuffer get(String key) throws TException { if (!inTransaction) { throw new TException("Must be in a transaction!"); } readLock(key); DataItem dataItem = new DataItem(masterRouter.getMasterByKey(key).get(sessionId, key)); // Null is returned by 0-length data if (dataItem.getData().limit() == 0) { return null; } return dataItem.getData(); } public void writeLock(String key) throws TException { if (!writeLocks.contains(key)) { if (readLocks.contains(key)) { throw new TException("Cannot upgrade locks right now."); } writeLocks.add(key); for (int i = 0; i < 1; i ++) { try { logger.trace("Session " + sessionId + " attempting W lock on key " + key + " @" + System.currentTimeMillis()); masterRouter.getMasterByKey(key).write_lock(sessionId, key); return; } catch (TException e) { // TODO: if this helps, make it more universal masterRouter.refreshMasterForKey(key); logger.warn("Session " + sessionId + " failed in obtaining W lock on key " + key + " @ " + System.currentTimeMillis()); e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } logger.warn("! Session " + sessionId + " could not obtain W key " + key); throw new TException("Obtaining write lock timed out."); } } public void readLock(String key) throws TException { if (!readLocks.contains(key) && !writeLocks.contains(key)) { readLocks.add(key); for (int i = 0; i < 1; i ++) { try { logger.trace("Session " + sessionId + " attempting R lock on key " + key + " @" + System.currentTimeMillis()); masterRouter.getMasterByKey(key).read_lock(sessionId, key); return; } catch (TException e) { masterRouter.refreshMasterForKey(key); logger.warn("Session " + sessionId + " failed in obtaining R lock on key " + key + " @" + System.currentTimeMillis()); e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } logger.warn("! Session " + sessionId + " could not obtain R key " + key); throw new TException("Obtaining read lock timed out."); } } @Override public void sendCommand(String cmd) throws TException { throw new UnsupportedOperationException(); } public void close() { return; } }