/** * Only return transaction state objects that are ready to run. This is non-blocking. If the txn * is not ready, then this will return null. <B>Note:</B> This should only be allowed to be called * by one thread. */ @Override public AbstractTransaction poll() { AbstractTransaction retval = null; if (trace.val) LOG.trace(String.format("Partition %d :: Attempting to acquire lock", this.partitionId)); if (this.state == QueueState.UNBLOCKED) { this.lock.lock(); try { // if (this.state != QueueState.UNBLOCKED) { // this.checkQueueState(false); // } if (this.state == QueueState.UNBLOCKED) { // 2012-12-21 // So this is allow to be null because there is a race condition // if another thread removes the txn from the queue. retval = super.poll(); if (retval != null) { if (debug.val) LOG.debug(String.format("Partition %d :: poll() -> %s", this.partitionId, retval)); this.lastTxnPopped = retval.getTransactionId(); this.txnsPopped++; } // call this again to prime the next txn this.checkQueueState(true); } } finally { if (trace.val) LOG.trace(String.format("Partition %d :: Releasing lock", this.partitionId)); this.lock.unlock(); } } return (retval); }
@Override public boolean remove(Object obj) { AbstractTransaction txn = (AbstractTransaction) obj; boolean retval; if (trace.val) LOG.trace(String.format("Partition %d :: Attempting to acquire lock", this.partitionId)); this.lock.lock(); try { // We have to check whether we are the first txn in the queue, // because we will need to reset the blockTimestamp after // delete ourselves so that the next guy can get executed // This is not thread-safe... boolean reset = txn.equals(super.peek()); retval = super.remove(txn); if (debug.val) { LOG.debug(String.format("Partition %d :: remove(%s) -> %s", this.partitionId, txn, retval)); // Sanity Check assert (super.contains(txn) == false) : "Failed to remove " + txn + "???\n" + this.debug(); } if (retval) this.checkQueueState(reset); } finally { if (trace.val) LOG.trace(String.format("Partition %d :: Releasing lock", this.partitionId)); this.lock.unlock(); } return (retval); }
/** Only return transaction state objects that are ready to run. */ @Override public AbstractTransaction poll() { AbstractTransaction retval = null; // These invocations of poll() can return null if the next // txn was speculatively executed if (this.nextTxn == null) this.checkQueueState(); if (this.state == QueueState.BLOCKED_SAFETY) { if (EstTime.currentTimeMillis() >= this.blockTime) { retval = super.poll(); } } else if (this.state == QueueState.UNBLOCKED) { // assert(checkQueueState() == QueueState.UNBLOCKED); retval = super.poll(); } if (retval != null) { assert (this.nextTxn.equals(retval)) : String.format( "Partition %d :: Next txn is %s but our poll returned %s\n" + StringUtil.SINGLE_LINE + "%s", this.partitionId, this.nextTxn, retval, this.debug()); this.nextTxn = null; this.lastTxnPopped = retval.getTransactionId(); } if (d && retval != null) LOG.debug(String.format("Partition %d :: poll() -> %s", this.partitionId, retval)); this.checkQueueState(); return retval; }
/** * Update the information stored about the latest transaction seen from each initiator. Compute * the newest safe transaction id. */ public Long noteTransactionRecievedAndReturnLastSeen(AbstractTransaction ts) { // this doesn't exclude dummy txnid but is also a sanity check assert (ts != null); // we've decided that this can happen, and it's fine... just ignore it if (d) { if (this.lastTxnPopped != null && this.lastTxnPopped.compareTo(ts.getTransactionId()) > 0) { LOG.warn( String.format( "Txn ordering deadlock at Partition %d ::> LastTxn: %s / NewTxn: %s", this.partitionId, this.lastTxnPopped, ts)); LOG.warn("LAST: " + this.lastTxnPopped); LOG.warn("NEW: " + ts); } } // update the latest transaction for the specified initiator if (this.lastSeenTxn == null || ts.getTransactionId().compareTo(this.lastSeenTxn) < 0) { if (t) LOG.trace("SET lastSeenTxnId = " + ts); this.lastSeenTxn = ts.getTransactionId(); } // this minimum is the newest safe transaction to run // but you still need to check if a transaction has been confirmed // by its initiator // (note: this check is done when peeking/polling from the queue) // if (t) LOG.trace("SET this.newestCandidateTransaction = " + this.lastSeenTxn); // this.newestCandidateTransaction = this.lastSeenTxn; // this will update the state of the queue if needed this.checkQueueState(); // return the last seen id for the originating initiator return this.lastSeenTxn; }
@Override public final boolean equals(Object obj) { if (obj instanceof AbstractTransaction) { AbstractTransaction other = (AbstractTransaction) obj; if (this.txn_id == null) { return (this.hashCode() != other.hashCode()); } return (this.txn_id.equals(other.txn_id)); } return (false); }
protected QueueState checkQueueState() { QueueState newState = QueueState.UNBLOCKED; AbstractTransaction ts = super.peek(); if (ts == null) { if (t) LOG.trace(String.format("Partition %d :: Queue is empty.", this.partitionId)); newState = QueueState.BLOCKED_EMPTY; } // Check whether can unblock now else if (ts == this.nextTxn && this.state != QueueState.UNBLOCKED) { if (EstTime.currentTimeMillis() < this.blockTime) { newState = QueueState.BLOCKED_SAFETY; } else if (d) { LOG.debug( String.format( "Partition %d :: Wait time for %s has passed. Unblocking...", this.partitionId, this.nextTxn)); } } // This is a new txn and we should wait... else if (this.nextTxn != ts) { long txnTimestamp = TransactionIdManager.getTimestampFromTransactionId(ts.getTransactionId().longValue()); long timestamp = EstTime.currentTimeMillis(); long waitTime = Math.max(0, this.waitTime - (timestamp - txnTimestamp)); newState = (waitTime > 0 ? QueueState.BLOCKED_SAFETY : QueueState.UNBLOCKED); this.blockTime = timestamp + waitTime; this.nextTxn = ts; if (d) { String debug = ""; if (t) { Map<String, Object> m = new LinkedHashMap<String, Object>(); m.put("Txn Init Timestamp", txnTimestamp); m.put("Current Timestamp", timestamp); m.put("Block Time Remaining", (this.blockTime - timestamp)); debug = "\n" + StringUtil.formatMaps(m); } LOG.debug( String.format( "Partition %d :: Blocking %s for %d ms [maxWait=%d]%s", this.partitionId, ts, (this.blockTime - timestamp), this.waitTime, debug)); } } if (newState != this.state) { this.state = newState; if (d) LOG.debug( String.format( "Partition %d :: State:%s / NextTxn:%s", this.partitionId, this.state, this.nextTxn)); } return this.state; }
/** Drop data for unknown initiators. This is the only valid add interface. */ @Override public boolean offer(AbstractTransaction ts) { assert (ts != null); // Check whether this new txn is less than the current this.nextTxn // If it is and there is still time remaining before it is released, // then we'll switch and become the new next this.nextTxn if (this.nextTxn != null && ts.compareTo(this.nextTxn) < 0) { this.checkQueueState(); if (this.state != QueueState.UNBLOCKED) { if (d) LOG.debug( String.format( "Partition %d :: Switching %s as new next txn [old=%s]", this.partitionId, ts, this.nextTxn)); this.nextTxn = ts; } else { if (d) LOG.warn( String.format( "Partition %d :: offer(%s) -> %s [next=%s]", this.partitionId, ts, "REJECTED", this.nextTxn)); return (false); } } boolean retval = super.offer(ts); if (d) LOG.debug(String.format("Partition %d :: offer(%s) -> %s", this.partitionId, ts, retval)); if (retval) this.checkQueueState(); return retval; }
/** Add in a transaction to the queue. It is safe to call this from any thread if you need to */ @Override public boolean offer(AbstractTransaction ts, boolean force) { assert (ts != null); assert (ts.isInitialized()) : String.format( "Unexpected uninitialized transaction %s [partition=%d]", ts, this.partitionId); boolean retval = super.offer(ts, force); if (debug.val) LOG.debug(String.format("Partition %d :: offer(%s) -> %s", this.partitionId, ts, retval)); if (retval) { if (trace.val) LOG.trace(String.format("Partition %d :: Attempting to acquire lock", this.partitionId)); this.lock.lock(); try { if (retval) this.checkQueueState(false); } finally { if (trace.val) LOG.trace(String.format("Partition %d :: Releasing lock", this.partitionId)); this.lock.unlock(); } } return (retval); }
/** * This is the most important method of the queue. This will figure out the next state and how * long we must wait until we can release the next transaction. <B>Note:</B> I believe that this * is the only thing that needs to be synchronized * * @param afterRemoval If this flag is set to true, then it means that who ever is calling this * method just removed something from the queue. That means that we need to go and check * whether the lastSafeTxnId should change. * @return */ private QueueState checkQueueState(boolean afterRemoval) { if (trace.val && super.isEmpty() == false) LOG.trace( String.format( "Partition %d :: checkQueueState(afterPoll=%s) [current=%s]", this.partitionId, afterRemoval, this.state)); QueueState newState = (afterRemoval ? QueueState.BLOCKED_SAFETY : QueueState.UNBLOCKED); long currentTimestamp = -1l; AbstractTransaction ts = super.peek(); // BLOCKING Long txnId = null; if (ts == null) { // if (trace.val) // LOG.trace(String.format("Partition %d :: Queue is empty.", // this.partitionId)); newState = QueueState.BLOCKED_EMPTY; } // Check whether can unblock now else { assert (ts.isInitialized()) : String.format( "Unexpected uninitialized transaction %s [partition=%d]", ts, this.partitionId); txnId = ts.getTransactionId(); // HACK: Ignore null txnIds if (txnId == null) { LOG.warn( String.format( "Partition %d :: Uninitialized transaction handle %s", this.partitionId, ts)); return (this.state); } assert (txnId != null) : "Null transaction id from " + txnId; // If this txnId is greater than the last safe one that we've seen, then we know // that the lastSafeTxnId has been polled. That means that we need to // wait for an appropriate amount of time before we're allow to be executed. if (txnId.compareTo(this.lastSafeTxnId) > 0 && afterRemoval == false) { newState = QueueState.BLOCKED_ORDERING; if (debug.val) LOG.debug( String.format( "Partition %d :: txnId[%d] > lastSafeTxnId[%d]", this.partitionId, txnId, this.lastSafeTxnId)); } // If our current block time is negative, then we know that we're the first txnId // that's been in the system. We'll also want to wait a bit before we're // allowed to be executed. else if (this.blockTimestamp == NULL_BLOCK_TIMESTAMP) { newState = QueueState.BLOCKED_SAFETY; if (debug.val) LOG.debug( String.format( "Partition %d :: txnId[%d] ==> %s (blockTime=%d)", this.partitionId, txnId, newState, this.blockTimestamp)); } // Check whether it's safe to unblock this mofo else if ((currentTimestamp = System.currentTimeMillis()) < this.blockTimestamp) { newState = QueueState.BLOCKED_SAFETY; if (debug.val) LOG.debug( String.format( "Partition %d :: txnId[%d] ==> %s (blockTime[%d] - current[%d] = %d)", this.partitionId, txnId, newState, this.blockTimestamp, currentTimestamp, Math.max(0, this.blockTimestamp - currentTimestamp))); } // We didn't find any reason to block this txn, so it's sail yo for it... else if (debug.val) { LOG.debug( String.format( "Partition %d :: Safe to Execute %d [currentTime=%d]", this.partitionId, txnId, System.currentTimeMillis())); } } if (newState != this.state) { // note if we get non-empty but blocked if ((newState == QueueState.BLOCKED_ORDERING) || (newState == QueueState.BLOCKED_SAFETY)) { if (trace.val) LOG.trace( String.format("Partition %d :: NewState=%s --> %s", this.partitionId, newState, ts)); long txnTimestamp = TransactionIdManager.getTimestampFromTransactionId(txnId.longValue()); if (currentTimestamp == -1) currentTimestamp = System.currentTimeMillis(); // Calculate how long we need to wait before this txn is safe to run // If we're blocking on "safety", then we can use an offset based // on when the txnId was created. If we're blocking for "ordering", // then we'll want to wait for the full wait time. int waitTime = this.waitTime; if (newState == QueueState.BLOCKED_SAFETY) { waitTime = (int) Math.max(0, this.waitTime - (currentTimestamp - txnTimestamp)); } this.blockTimestamp = currentTimestamp + waitTime; if (trace.val) LOG.trace( String.format( "Partition %d :: SET blockTimestamp = %d --> %s", this.partitionId, this.blockTimestamp, ts)); if (this.blockTimestamp <= currentTimestamp) { newState = QueueState.UNBLOCKED; } if (this.profiler != null && this.lastSafeTxnId.equals(txnId) == false) this.profiler.waitTimes.put(newState == QueueState.UNBLOCKED ? 0 : waitTime); if (debug.val) LOG.debug( String.format( "Partition %d :: SET lastSafeTxnId = %d --> %s", this.partitionId, this.lastSafeTxnId, ts)); if (trace.val) { LOG.trace( String.format( "Partition %d :: SET lastSafeTxnId = %d --> %s", this.partitionId, this.lastSafeTxnId, ts)); String debug = ""; if (trace.val) { Map<String, Object> m = new LinkedHashMap<String, Object>(); m.put("Txn Init Timestamp", txnTimestamp); m.put("Current Timestamp", currentTimestamp); m.put("Block Time Remaining", (this.blockTimestamp - currentTimestamp)); debug = "\n" + StringUtil.formatMaps(m); } LOG.trace( String.format( "Partition %d :: Blocking %s for %d ms " + "[maxWait=%d, origState=%s, newState=%s]\n%s%s", this.partitionId, ts, (this.blockTimestamp - currentTimestamp), this.waitTime, this.state, newState, this.debug(), debug)); } } else if (newState == QueueState.UNBLOCKED) { if (currentTimestamp == -1) currentTimestamp = System.currentTimeMillis(); if (this.blockTimestamp > currentTimestamp) { newState = QueueState.BLOCKED_SAFETY; } } } // IF // This txn should always becomes our next safeTxnId. // This is essentially the next txn // that should be executed, but somebody *could* come along and add in // a new txn with a lower id. But that's ok because we've synchronized setting // the id up above. This is actually probably the only part of this entire method // that needs to be protected... if (txnId != null) this.lastSafeTxnId = txnId; // Set the new state if (newState != this.state) { if (trace.val) LOG.trace( String.format( "Partition %d :: ORIG[%s]->NEW[%s] / LastSafeTxn:%d", this.partitionId, this.state, newState, this.lastSafeTxnId)); if (this.profiler != null) { this.profiler.queueStates.get(this.state).stopIfStarted(); this.profiler.queueStates.get(newState).start(); } this.state = newState; // Always poke anybody that is blocking on this queue. // The txn may not be ready to run just yet, but at least they'll be // able to recompute a new sleep time. this.isReady.signal(); } else if (this.profiler != null) { this.profiler.queueStates.get(this.state).restart(); } // Sanity Check if ((this.state == QueueState.BLOCKED_ORDERING) || (this.state == QueueState.BLOCKED_SAFETY)) { assert (this.state != QueueState.BLOCKED_EMPTY); } // Make sure that we're always in a valid state to avoid livelock problems assert (this.state != QueueState.BLOCKED_SAFETY || (this.state == QueueState.BLOCKED_SAFETY && this.blockTimestamp != NULL_BLOCK_TIMESTAMP)) : String.format("Invalid state %s with NULL blocked timestamp", this.state); assert (this.state != QueueState.BLOCKED_ORDERING || (this.state == QueueState.BLOCKED_ORDERING && this.blockTimestamp != NULL_BLOCK_TIMESTAMP)) : String.format("Invalid state %s with NULL blocked timestamp", this.state); return this.state; }
/** * Only return transaction state objects that are ready to run. This method will wait until the * transaction's block time has passed. * * @return * @throws InterruptedException */ public AbstractTransaction take() throws InterruptedException { AbstractTransaction retval = null; // Ok now here is the tricky part. We don't have a txn that is // ready to run, so we need to block ourselves until we get one. // This could be for two reasons: // (1) The queue is empty. // (2) The waiting period for the next txn hasn't passed yet. // // Note that we can't simply attach ourselves to our inner queue because // we don't want to get back the txn right when it gets added. // We want to wait until the time period has passed. if (trace.val) LOG.trace(String.format("Partition %d :: Attempting to acquire lock", this.partitionId)); this.lock.lockInterruptibly(); try { if (debug.val && this.state != QueueState.UNBLOCKED) LOG.debug( String.format( "Partition %d :: take() -> " + "Current state is %s. Blocking until ready", this.partitionId, this.state)); while (this.state != QueueState.UNBLOCKED) { if (trace.val) LOG.trace( String.format( "Partition %d :: take() -> Calculating how long to block", this.partitionId)); long waitTime = -1; boolean isEmpty = (this.state == QueueState.BLOCKED_EMPTY); boolean needsUpdateQueue = false; // If the queue isn't empty, then we need to figure out // how long we should sleep for if (isEmpty == false) { // If we're blocked because of an ordering issue (i.e., we have a new txn // in the system that is less than our current head of the queue, but we // haven't inserted it yet), then we will want to wait for the full timeout // period. We won't actually have to wait this long because somebody will poke // us after the new txn is added to the queue. if (this.state == QueueState.BLOCKED_ORDERING) { waitTime = this.waitTime; } else { waitTime = this.blockTimestamp - System.currentTimeMillis(); } } try { // If we're empty, then we need to block indefinitely until we're poked if (isEmpty) { if (debug.val) LOG.debug( String.format( "Partition %d :: take() -> " + "Blocking because queue is empty", this.partitionId)); this.isReady.await(); } // Otherwise, we'll sleep until our time out and then // check the queue status for ourselves else if (waitTime > 0) { // We are going to wait for the specified time // The Condition will return true if somebody poked us, which // means that somebody changed the queue state to UNBLOCKED // for us. If we are woken because of a timeout, then the // return status will be false, which means that we need to // queue state ourself. if (debug.val) LOG.debug( String.format( "Partition %d :: take() -> " + "Blocking for %d ms", this.partitionId, waitTime)); needsUpdateQueue = (this.isReady.await(waitTime, TimeUnit.MILLISECONDS) == false); } // Our txn is ready to run now, so we don't need to block else { if (debug.val) LOG.debug( String.format( "Partition %d :: take() -> " + "Ready to retrieve next txn immediately [waitTime=%d, isEmpty=%s]", this.partitionId, waitTime, isEmpty)); needsUpdateQueue = true; } } catch (InterruptedException ex) { this.isReady.signal(); throw ex; } if (needsUpdateQueue) this.checkQueueState(false); } // WHILE // The next txn is ready to run now! assert (this.state == QueueState.UNBLOCKED); retval = super.poll(); // 2012-01-06 // This could be null because there is a race condition if all of the // txns are removed by another thread right before we try to // poll our queue. if (retval != null) { this.lastTxnPopped = retval.getTransactionId(); this.txnsPopped++; // Call this again to prime the next txn this.checkQueueState(true); } if (trace.val) LOG.trace( String.format("Partition %d :: take() -> Leaving blocking section", this.partitionId)); } finally { if (trace.val) LOG.trace(String.format("Partition %d :: Releasing lock", this.partitionId)); this.lock.unlock(); } if (debug.val) LOG.debug(String.format("Partition %d :: take() -> %s", this.partitionId, retval)); return (retval); }