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

com.wavesplatform.utils.Time.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.utils

import java.net.{InetAddress, SocketTimeoutException}
import monix.eval.Task
import monix.execution.ExecutionModel
import monix.execution.schedulers.SchedulerService
import org.apache.commons.net.ntp.NTPUDPClient

import java.time.Duration
import scala.concurrent.duration.DurationInt

trait Time {
  def correctedTime(): Long
  def getTimestamp(): Long
}

class NTP(ntpServer: String) extends Time with ScorexLogging with AutoCloseable {
  private[this] val ExpirationTimeout = 60.seconds
  private[this] val RetryDelay        = 10.seconds
  private[this] val ResponseTimeout   = 10.seconds

  private[this] implicit val scheduler: SchedulerService =
    Schedulers.singleThread(name = "time-impl", reporter = log.error("Error in NTP", _), ExecutionModel.AlwaysAsyncExecution)

  private[this] val client = new NTPUDPClient()
  client.setDefaultTimeout(Duration.ofMillis(ResponseTimeout.toMillis))

  @volatile private[this] var ntpTimestamp = System.currentTimeMillis()
  @volatile private[this] var nanoTime     = System.nanoTime()

  def correctedTime(): Long = {
    val timestamp = ntpTimestamp
    val offset    = (System.nanoTime() - nanoTime) / 1000000
    timestamp + offset
  }

  @volatile private[this] var txTime: Long = 0

  def getTimestamp(): Long = {
    txTime = Math.max(correctedTime(), txTime + 1)
    txTime
  }

  private[this] val updateTask: Task[Unit] = {
    def newOffsetTask: Task[Option[(InetAddress, Long, Long)]] = Task {
      try {
        client.open()
        val beforeRequest   = System.nanoTime()
        val info            = client.getTime(InetAddress.getByName(ntpServer))
        val message         = info.getMessage
        val ntpTime         = message.getTransmitTimeStamp.getTime
        val serverSpentTime = message.getTransmitTimeStamp.getTime - message.getReceiveTimeStamp.getTime
        val roundripTime    = (System.nanoTime() - beforeRequest) / 1000000 - serverSpentTime
        val corrected       = ntpTime + roundripTime / 2
        Some((info.getAddress, corrected, System.nanoTime()))
      } catch {
        case _: SocketTimeoutException =>
          None
        case t: Throwable =>
          log.warn("Problems with NTP: ", t)
          None
      } finally {
        client.close()
      }
    }

    newOffsetTask.flatMap {
      case None if !scheduler.isShutdown => updateTask.delayExecution(RetryDelay)
      case Some((server, ntpTimestamp, nanoTime)) if !scheduler.isShutdown =>
        log.trace(s"Adjusting time with ${ntpTimestamp - System.currentTimeMillis()} milliseconds, source: ${server.getHostName}.")
        this.ntpTimestamp = ntpTimestamp
        this.nanoTime = nanoTime
        updateTask.delayExecution(ExpirationTimeout)
      case _ => Task.unit
    }
  }

  private[this] val taskHandle = updateTask.runAsyncLogErr

  override def close(): Unit = {
    log.trace("Shutting down Time")
    taskHandle.cancel()
    scheduler.shutdown()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy