gt:         assert(txnId <= TIMESTAMP_MAX_VALUE);
context: /* This file is part of VoltDB.
   * Copyright (C) 2008-2018 VoltDB Inc.
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package org.voltcore;
  
  import java.util.Calendar;
  import java.util.Date;
  
  import org.voltcore.logging.VoltLogger;
  
  /**
   * <p>The TransactionIdManager creates Transaction ids that
   * get assigned to VoltDB timestamps. A transaction id contains
   * three fields, the time of creation, a counter to ensure local
   * ordering, and the siteid of the generating site.</p>
   *
   * <p>This class also contains methods to examine the embedded values of
   * transaction ids.</p>
   *
   * <p>If the clocks of two different machines are reasonably in sync,
   * txn ids created at the same time on different machines will be reasonably
   * close in value. Thus transaction ids can be used for a global ordering.</p>
   *
   */
  public class TransactionIdManager {
      // bit sizes for each of the fields in the 64-bit id
      // note, these add up to 63 bits to make dealing with
      // signed / unsigned conversions easier.
      static final long TIMESTAMP_BITS = 40;
      static final long COUNTER_BITS = 6;
      static final long INITIATORID_BITS = 17;
  
      // VOLT_EPOCH holds the time in millis since 1/1/2008 at 12am.
      // The current time - VOLT_EPOCH should fit nicely in 40 bits
      // of memory.
      static final long VOLT_EPOCH = getEpoch();
      public static long getEpoch() {
          Calendar c = Calendar.getInstance();
          c.setTimeInMillis(0);
          c.set(2008, 0, 1, 0, 0, 0);
          c.set(Calendar.MILLISECOND, 0);
          c.set(Calendar.ZONE_OFFSET, 0);
          c.set(Calendar.DST_OFFSET, 0);
          long retval = c.getTimeInMillis();
          return retval;
      }
  
      // maximum values for the fields
      // used for bit-shifts and error checking
      static final long TIMESTAMP_MAX_VALUE = (1L << TIMESTAMP_BITS) - 1L;
      static final long COUNTER_MAX_VALUE = (1L << COUNTER_BITS) - 1L;
      static final long INITIATORID_MAX_VALUE = (1L << INITIATORID_BITS) - 1L;
  
      // the local siteid
      long initiatorId;
      // the time of the previous txn id generation
      long lastUsedTime = -1;
      // the number of txns generated during the same value
      // for System.currentTimeMillis()
      long counterValue = 0;
  
      // remembers the last txn generated
      long lastTxnId = 0;
  
  
      // salt used for testing to simulate clock skew
      // when non-zero, getting the time will be adjusted
      // by this value
      long m_timestampTestingSalt = 0;
  
      long BACKWARD_TIME_FORGIVENESS_WINDOW_MS;
  
      public interface Clock {
          long get();
          void sleep(long millis) throws InterruptedException;
      }
  
      private final Clock m_clock;
  
      /**
       * Initialize the TransactionIdManager for this site
       * @param initiatorId The siteId of the current site.
       * @param timestampTestingSalt Value of the salt used to skew a clock in testing.
       */
      public TransactionIdManager(long initiatorId, long timestampTestingSalt, long backwardsTimeForgivenessWindow) {
          this(initiatorId, timestampTestingSalt, backwardsTimeForgivenessWindow, new Clock() {
              @Override
              public long get() {
                  return System.currentTimeMillis();
              }
  
              @Override
              public void sleep(long millis) throws InterruptedException {
                  Thread.sleep(millis);
              }
          });
      }
  
      /**
       * Initialize the TransactionIdManager for this site
       * @param initiatorId The siteId of the current site.
       * @param timestampTestingSalt Value of the salt used to skew a clock in testing.
       */
      TransactionIdManager(long initiatorId, long timestampTestingSalt, long backwardsTimeForgivenessWindow, Clock clock) {
          this.initiatorId = initiatorId;
  
          m_timestampTestingSalt = timestampTestingSalt;
          m_clock = clock;
  
          // warn if running with a simulated clock skew
          // this should only be used for testing
          if (m_timestampTestingSalt != 0) {
              VoltLogger log = new VoltLogger("HOST");
              log.warn(String.format("Initiator (id=%d) running in test mode with non-zero timestamp testing value: %d",
                       initiatorId, timestampTestingSalt));
          }
  
  
          BACKWARD_TIME_FORGIVENESS_WINDOW_MS = backwardsTimeForgivenessWindow;
      }
  
      /**
       * Generate a unique id that contains a timestamp, a counter
       * and a siteid packed into a 64-bit long value. Subsequent calls
       * to this method will return strictly larger long values.
       * @return The newly generated transaction id.
       */
      public long getNextUniqueTransactionId() {
          // get the current time, usually the salt value is zero
          // in testing it is used to simulate clock skew
          long currentTime = m_clock.get() + m_timestampTestingSalt;
          if (currentTime == lastUsedTime) {
              // increment the counter for this millisecond
              counterValue++;
  
              // handle the case where we've run out of counter values
              // for this particular millisecond (feels unlikely)
              if (counterValue > COUNTER_MAX_VALUE) {
                  // spin until the next millisecond
                  while (currentTime == lastUsedTime) {
                      currentTime = m_clock.get();
                  }
                  // reset the counter and lastUsedTime for the new millisecond
                  lastUsedTime = currentTime;
                  counterValue = 0;
              }
          }
          else {
              // reset the counter and lastUsedTime for the new millisecond
              if (currentTime < lastUsedTime) {
                  VoltLogger log = new VoltLogger("HOST");
                  double diffSeconds = (lastUsedTime - currentTime) / 1000.0;
                  String msg = String.format("Initiator time moved backwards from: %d to %d, a difference of %.2f seconds.",
                          lastUsedTime, currentTime, diffSeconds);
                  log.error(msg);
                  System.err.println(msg);
                  // if the diff is less than some specified amount of time, wait a bit
                  if ((lastUsedTime - currentTime) < BACKWARD_TIME_FORGIVENESS_WINDOW_MS) {
                      log.info("This node will delay any stored procedures sent to it.");
                      log.info(String.format("This node will resume full operation in  %.2f seconds.", diffSeconds));
  
                      long count = BACKWARD_TIME_FORGIVENESS_WINDOW_MS;
                      // note, the loop should stop once lastUsedTime is PASSED, not current
                      while ((currentTime <= lastUsedTime) && (count-- > 0)) {
                          try {
                              m_clock.sleep(1);
                          } catch (InterruptedException e) {}
                          currentTime = m_clock.get();
                      }
                      // if the loop above ended because it ran too much
                      if (count < 0) {
                          org.voltdb.VoltDB.crashLocalVoltDB(
                                "VoltDB was unable to recover after the system time was externally negatively adusted. " +
                                 "It is possible that there is a serious system time or NTP error. ", false, null);
                      }
                  }
                  // crash immediately if time has gone backwards by too much
                  else {
                      org.voltdb.VoltDB.crashLocalVoltDB(
                              String.format("%.2f is larger than the max allowable number of seconds that " +
                                      "the clock can be negatively adjusted (%d)",
                                  diffSeconds, BACKWARD_TIME_FORGIVENESS_WINDOW_MS / 1000), false, null);
                  }
              }
              lastUsedTime = currentTime;
              counterValue = 0;
          }
  
          lastTxnId = makeIdFromComponents(currentTime, counterValue, initiatorId);
  
          return lastTxnId;
      }
  
      public static long makeIdFromComponents(long ts, long seqNo, long initiatorId) {
          // compute the time in millis since VOLT_EPOCH
          long txnId = ts - VOLT_EPOCH;
          // verify all fields are the right size
  