com.fluxtion.agrona.concurrent.OffsetEpochNanoClock Maven / Gradle / Ivy
/*
* Copyright 2014-2024 Real Logic Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fluxtion.agrona.concurrent;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* An accurate, zero-gc, pure-java, {@link EpochNanoClock} that calculates an initial epoch nano time based on
* {@link System#currentTimeMillis()} and then uses that offset to adjust the return value of
* {@link System#nanoTime()} to the UNIX epoch.
*
* The {@link #sample()} method can be used in order to reset these initial values if your system clock gets updated.
*
* This class can be used from multiple threads safely.
*
* @see org.agrona.concurrent.SystemEpochNanoClock
*/
public class OffsetEpochNanoClock implements EpochNanoClock
{
private static final int DEFAULT_MAX_MEASUREMENT_RETRIES = 100;
private static final long DEFAULT_MEASUREMENT_THRESHOLD_NS = 250;
private static final long DEFAULT_RESAMPLE_INTERVAL_NS = HOURS.toNanos(1);
private final int maxMeasurementRetries;
private final long measurementThresholdNs;
private final long resampleIntervalNs;
private volatile TimeFields timeFields;
/**
* Constructs the clock with default configuration.
*/
public OffsetEpochNanoClock()
{
this(DEFAULT_MAX_MEASUREMENT_RETRIES, DEFAULT_MEASUREMENT_THRESHOLD_NS, DEFAULT_RESAMPLE_INTERVAL_NS);
}
/**
* Constructs the clock with custom configuration parameters.
*
* @param maxMeasurementRetries the maximum number of times that this clock will attempt to re-sample the initial
* time values.
* @param measurementThresholdNs the desired accuracy window for the initial clock samples.
* @param resampleIntervalNs the desired interval before the samples are automatically recalculated. The seed
* recalculation enables the system to minimise clock drift if the system clock is
* updated.
*/
@SuppressWarnings("this-escape")
public OffsetEpochNanoClock(
final int maxMeasurementRetries, final long measurementThresholdNs, final long resampleIntervalNs)
{
this.maxMeasurementRetries = maxMeasurementRetries;
this.measurementThresholdNs = measurementThresholdNs;
this.resampleIntervalNs = resampleIntervalNs;
sample();
}
/**
* Explicitly resample the initial seeds.
*/
public void sample()
{
// Loop attempts to find a measurement that is accurate to a given threshold
long bestInitialCurrentNanoTime = 0, bestInitialNanoTime = 0;
long bestNanoTimeWindow = Long.MAX_VALUE;
final int maxMeasurementRetries = this.maxMeasurementRetries;
final long measurementThresholdNs = this.measurementThresholdNs;
for (int i = 0; i < maxMeasurementRetries; i++)
{
final long firstNanoTime = System.nanoTime();
final long initialCurrentTimeMillis = System.currentTimeMillis();
final long secondNanoTime = System.nanoTime();
final long nanoTimeWindow = secondNanoTime - firstNanoTime;
if (nanoTimeWindow < measurementThresholdNs)
{
timeFields = new TimeFields(
MILLISECONDS.toNanos(initialCurrentTimeMillis),
(firstNanoTime + secondNanoTime) >> 1,
true);
return;
}
else if (nanoTimeWindow < bestNanoTimeWindow)
{
bestInitialCurrentNanoTime = MILLISECONDS.toNanos(initialCurrentTimeMillis);
bestInitialNanoTime = (firstNanoTime + secondNanoTime) >> 1;
bestNanoTimeWindow = nanoTimeWindow;
}
}
// If we never get a time below the threshold, pick the narrowest window we've seen so far.
timeFields = new TimeFields(
bestInitialCurrentNanoTime,
bestInitialNanoTime,
false);
}
/**
* {@inheritDoc}
*/
public long nanoTime()
{
final TimeFields timeFields = this.timeFields;
final long nanoTimeAdjustment = System.nanoTime() - timeFields.initialNanoTime;
if (nanoTimeAdjustment < 0 || nanoTimeAdjustment > resampleIntervalNs)
{
sample();
return nanoTime();
}
return timeFields.initialCurrentNanoTime + nanoTimeAdjustment;
}
/**
* Gets whether the clock sampled the initial time offset accurately.
*
* @return true if the clock sampled the initial time offset accurately.
*/
public boolean isWithinThreshold()
{
return timeFields.isWithinThreshold;
}
static final class TimeFields
{
final long initialCurrentNanoTime;
final long initialNanoTime;
final boolean isWithinThreshold;
private TimeFields(
final long initialCurrentNanoTime, final long initialNanoTime, final boolean isWithinThreshold)
{
this.initialNanoTime = initialNanoTime;
this.initialCurrentNanoTime = initialCurrentNanoTime;
this.isWithinThreshold = isWithinThreshold;
}
}
}