package edu.brown.hstore.callbacks; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.voltdb.ClientResponseImpl; import com.google.protobuf.RpcCallback; import edu.brown.hstore.HStoreSite; import edu.brown.hstore.Hstoreservice.Status; import edu.brown.hstore.Hstoreservice.TransactionPrepareResponse; import edu.brown.hstore.txns.LocalTransaction; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.utils.PartitionSet; /** * This callback is invoked at the base partition once the txn gets all of * the 2PC:PREPARE acknowledgments. This is where we will invoke the HStoreSite * to send the ClientResponse back to the client. * @author pavlo */ public class LocalPrepareCallback extends PartitionCountingCallback<LocalTransaction> implements RpcCallback<TransactionPrepareResponse> { private static final Logger LOG = Logger.getLogger(LocalPrepareCallback.class); private static final LoggerBoolean debug = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug); } private final List<TransactionPrepareResponse> responses = new ArrayList<TransactionPrepareResponse>(); /** * Constructor * @param hstore_site */ public LocalPrepareCallback(HStoreSite hstore_site) { super(hstore_site); } public void init(LocalTransaction ts, PartitionSet partitions) { this.responses.clear(); super.init(ts, partitions); } // ---------------------------------------------------------------------------- // CALLBACK METHODS // ---------------------------------------------------------------------------- @Override protected void unblockCallback() { if (debug.val) LOG.debug(String.format("%s - Unblocking callback and sending back ClientResponse", this.ts)); if (hstore_conf.site.txn_profiling && this.ts.profiler != null) { if (debug.val) LOG.debug(ts + " - TransactionProfiler.stopPostPrepare() / " + Status.OK); this.ts.profiler.stopPostPrepare(); this.ts.profiler.startPostFinish(); } // Everybody returned ok, so we'll tell them to all commit right now // so that they can start executing other things this.finishTransaction(Status.OK); // At this point all of our HStoreSites came back with an OK on the 2PC PREPARE // So that means we can send back the result to the client and then // send the 2PC COMMIT message to all of our friends. // We want to do this first because the transaction state could get // cleaned-up right away when we call HStoreCoordinator.transactionFinish() ClientResponseImpl cresponse = this.ts.getClientResponse(); assert(cresponse.isInitialized()) : "Trying to send back ClientResponse for " + ts + " before it was set!"; this.hstore_site.responseSend(this.ts, cresponse); } @Override protected void abortCallback(int partition, Status status) { if (debug.val) LOG.debug(String.format("%s - Aborting callback [status=%s]", this.ts, status)); if (hstore_conf.site.txn_profiling && this.ts.profiler != null) { if (debug.val) LOG.debug(ts + " - TransactionProfiler.stopPostPrepare() / " + status); this.ts.profiler.stopPostPrepare(); this.ts.profiler.startPostFinish(); } // We don't care whether our transaction was rejected or not because we // know that we still need to call TransactionFinish, which will delete // the final transaction state if (status == Status.ABORT_RESTART || status == Status.ABORT_SPECULATIVE) { hstore_site.getTransactionQueueManager().restartTransaction(this.ts, status); } // Change the response's status and send back the result to the client else { ClientResponseImpl cresponse = this.ts.getClientResponse(); cresponse.setStatus(status); if (debug.val) LOG.debug(String.format("%s - Sending back %s %s", this.ts, status, cresponse.getClass().getSimpleName())); this.hstore_site.responseSend(this.ts, cresponse); } } // ---------------------------------------------------------------------------- // RPC CALLBACK // ---------------------------------------------------------------------------- @Override public void run(TransactionPrepareResponse response) { if (debug.val) LOG.debug(String.format("Got %s with %d partitions for %s", response.getClass().getSimpleName(), response.getPartitionsCount(), this.ts)); assert(this.ts != null) : String.format("Missing LocalTransaction handle for txn #%d [status=%s]", response.getTransactionId(), response.getStatus()); assert(this.ts.getTransactionId().longValue() == response.getTransactionId()) : String.format("Unexpected %s for a different transaction %s != #%d", response.getClass().getSimpleName(), this.ts, response.getTransactionId()); // If any TransactionPrepareResponse comes back with anything but an OK, // then the we need to abort the transaction immediately // TODO: Instead of OK, we should have different status types for what the // remote partition did. It should be PREPARE_OK or PREPARE_COMMIT // If it's a PREPARE_COMMIT then we know that we don't need to send // a COMMIT message to it in the next round. this.responses.add(response); if (response.getStatus() != Status.OK) { boolean first = true; for (int partition : response.getPartitionsList()) { if (first) { this.abort(partition, response.getStatus()); first = false; } else { this.decrementCounter(partition); } } // FOR } else { for (Integer partition : response.getPartitionsList()) { this.run(partition.intValue()); } // FOR } } } // END CLASS