All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.softwaremill.id.IdGenerator.scala Maven / Gradle / Ivy

package com.softwaremill.id

import com.typesafe.scalalogging.StrictLogging

/** Generates *unique* identifiers. There are *no* other requirements (e.g. randomness) as to the returned values.
  */
trait IdGenerator {
  def nextId(): Long

  /** An id base at the given timestamp. Should be smaller than all ids generated at that time, and bigger than all ids
    * generated before that time.
    */
  def idBaseAt(timestamp: Long): Long
}

/** Generates time-based unique ids. Each node should have a different `workerId`.
  *
  * *Synchronizes* to assure thread-safety!
  */
class DefaultIdGenerator(workerId: Long = 1, datacenterId: Long = 1, epoch: Long = 1288834974657L) extends IdGenerator {
  private val idWorker = new IdWorker(workerId = workerId, datacenterId = datacenterId, epoch = epoch)

  def nextId(): Long = {
    synchronized {
      idWorker.nextId()
    }
  }

  def idBaseAt(timestamp: Long): Long = {
    idWorker.idForTimestamp(timestamp)
  }
}

/** An object that generates IDs. This is broken into a separate class in case we ever want to support multiple worker
  * threads per process
  *
  * Copied from: https://github.com/twitter/snowflake/tree/master/src/main/scala/com/twitter/service/snowflake Modified
  * to fit our logging, removed stats.
  *
  * Single threaded!
  */
private[id] class IdWorker(
    workerId: Long,
    datacenterId: Long,
    var sequence: Long = 0L,
    val epoch: Long = 1288834974657L
) extends StrictLogging {

  private val workerIdBits = 5L
  private val datacenterIdBits = 5L
  private val maxWorkerId = -1L ^ (-1L << workerIdBits)
  private val maxDatacenterId = -1L ^ (-1L << datacenterIdBits)
  private val sequenceBits = 12L

  private val workerIdShift = sequenceBits
  private val datacenterIdShift = sequenceBits + workerIdBits
  private val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits
  private val sequenceMask = -1L ^ (-1L << sequenceBits)

  private var lastTimestamp = -1L

  // sanity check for workerId
  if (workerId > maxWorkerId || workerId < 0) {
    throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId))
  }

  if (datacenterId > maxDatacenterId || datacenterId < 0) {
    throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId))
  }

  logger.info(
    "Id worker starting. Timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d"
      .format(
        timestampLeftShift,
        datacenterIdBits,
        workerIdBits,
        sequenceBits,
        workerId
      )
  )

  def get_worker_id(): Long = workerId
  def get_datacenter_id(): Long = datacenterId
  def get_timestamp() = System.currentTimeMillis

  def nextId(): Long = {
    var timestamp = timeGen()
    if (lastTimestamp == timestamp) {
      sequence = (sequence + 1) & sequenceMask
      if (sequence == 0) {
        timestamp = tilNextMillis(lastTimestamp)
      }
    } else {
      sequence = 0
    }

    if (timestamp < lastTimestamp) {
      logger.error("Clock is moving backwards. Rejecting requests until %d.".format(lastTimestamp))
      throw new RuntimeException(
        "Invalid system clock: Clock moved backwards. Refusing to generate id for %d milliseconds".format(
          lastTimestamp - timestamp
        )
      )
    }

    lastTimestamp = timestamp
    ((timestamp - epoch) << timestampLeftShift) |
      (datacenterId << datacenterIdShift) |
      (workerId << workerIdShift) |
      sequence
  }

  def idForTimestamp(timestamp: Long): Long = (timestamp - epoch) << timestampLeftShift

  protected def tilNextMillis(lastTimestamp: Long): Long = {
    var timestamp = timeGen()
    while (timestamp <= lastTimestamp) {
      timestamp = timeGen()
    }
    timestamp
  }

  protected def timeGen(): Long = System.currentTimeMillis()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy