/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.db; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.Clock; import org.threeten.bp.Instant; import org.threeten.bp.ZoneId; import org.threeten.bp.temporal.TemporalUnit; import com.opengamma.util.OpenGammaClock; /** * A database-backed clock. * <p> * This only queries the database once per second, using simple interpolation from nanoTime() between calls. */ class DbClock extends Clock { private static final Logger s_logger = LoggerFactory.getLogger(DbClock.class); /** * The connector. */ private final DbConnector _connector; /** * The timestamp precision. */ private final TemporalUnit _precision; /** * The zone. */ private final ZoneId _zone; /** * The lock. */ private final ReadWriteLock _lock = new ReentrantReadWriteLock(true); /** * The base database instant. */ private volatile Instant _nowInstant; /** * The last "now" instant returned. Any "now"s subsequently returned must not be before this. */ private final AtomicReference<Instant> _previousNow = new AtomicReference<Instant>(); /** * The {@link System#nanoTime} of the now instant. */ private volatile long _nowNanoTime; /** * Creates the clock. * * @param connector the connector, not null */ DbClock(DbConnector connector) { this(connector, OpenGammaClock.getZone()); } /** * Creates the clock. * * @param connector the connector, not null */ DbClock(DbConnector connector, ZoneId zone) { _connector = Objects.requireNonNull(connector, "connector"); _precision = _connector.getDialect().getTimestampPrecision(); _zone = zone; long now = System.nanoTime(); long base = now - 2_000_000_000L; if (base > now) { // overflow base = Long.MIN_VALUE; } _nowNanoTime = base; } //------------------------------------------------------------------------- private Instant instantImpl() { long nowNanos = System.nanoTime(); _lock.readLock().lock(); if (nowNanos - (_nowNanoTime + 1_000_000_000L) > 0 || _nowInstant == null) { _lock.readLock().unlock(); // safely upgrade to write lock _lock.writeLock().lock(); try { // recheck, as per double checked locking if (nowNanos - (_nowNanoTime + 1_000_000_000L) > 0 || _nowInstant == null) { _nowInstant = DbDateUtils.fromSqlTimestamp(_connector.nowDb()).truncatedTo(_precision); _nowNanoTime = System.nanoTime(); return _nowInstant; } else { _lock.readLock().lock(); // safely downgrade to read lock } } finally { _lock.writeLock().unlock(); } } // calculate interpolated time Instant result; try { nowNanos = System.nanoTime(); long interpolate = Math.max(nowNanos - _nowNanoTime, 0); int precisionNano = _precision.getDuration().getNano(); interpolate = (interpolate / precisionNano) * precisionNano; result = _nowInstant.plusNanos(interpolate); } finally { _lock.readLock().unlock(); } return result; } @Override public Instant instant() { final Instant instant = instantImpl(); Instant previous = _previousNow.get(); if (previous == null) { if (_previousNow.compareAndSet(null, instant)) { // This is the first result return instant; } else { // Another thread did the first result; check against that previous = _previousNow.get(); // [PLAT-3965] This might not be necessary. I think the problem is more to do with two successive calls from the same thread getting invalid times } } do { if (previous.isAfter(instant)) { // Can't have time going backwards; have it stand still instead s_logger.debug("Returning previous time instant {} instead of {}", previous, instant); return previous; } // Time has progressed; update the reference if (_previousNow.compareAndSet(previous, instant)) { return instant; } // Another thread has returned a time; check against that previous = _previousNow.get(); // [PLAT-3965] This might not be necessary. I think the problem is more to do with two successive calls from the same thread getting invalid times } while (true); } //------------------------------------------------------------------------- @Override public ZoneId getZone() { return _zone; } @Override public Clock withZone(final ZoneId zone) { Objects.requireNonNull(zone, "zone"); return new DbClock(_connector, zone); } //------------------------------------------------------------------------- @Override public String toString() { return "DbClock"; } }