/**
   * 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;
  }
Beispiel #10
0
  /**
   * 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);
  }