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; }
/** * 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; }