package xapi.time.impl; import xapi.time.api.Moment; import xapi.time.service.TimeService; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; public abstract class AbstractTimeService extends ImmutableMoment implements TimeService { private static final long serialVersionUID = 1130197439830993337L; public AbstractTimeService() { super(System.currentTimeMillis()); delta = new AtomicReference<>(birth()); marginOfError = 1; } protected volatile double now; protected final AtomicReference<Double> delta; private double marginOfError; @Override public double birth() { return super.millis(); } @Override public Moment now() { return new ImmutableMoment(Math.max(delta.get(), millis())); } @Override public Moment clone(Moment moment) { return new ImmutableMoment(moment.millis()); } @Override public double millis() { return System.currentTimeMillis(); } @Override public double lastTick() { return delta.get(); } @Override public Moment nowPlusOne() { return new ImmutableMoment(tick()); } /** * The number of slices between a millisecond, * valid until Wed Sep 07 2039 08:47:35 GMT-7, * which is the unix timestamp 0x200_0000_0000 * (when the significand of timestamps rolls over to the 42nd bit). * * This is the smallest number you can safely increment * a double precision floating point representation of a timestamp * and not "lose" the increment by having it rounded off by floating point math. * * This will need to be bumped up in 2039 to ensure all math stays precise. * * From Math.ulp: * * An ulp, unit in the last place, of a double value is the positive * distance between this floating-point value and the double value next larger in magnitude * */ protected static final double TIME_ULP = 1./0x1000; @Override public double tick() { // As nasty as this is, it is the only way to ensure that // we produce monatomically increasing double values. double newNow = System.currentTimeMillis(); // use atomic reference to get long-lived (if necessary) CAS semantics return delta.updateAndGet(i->{ if (i < newNow) { return newNow; } double next = i + TIME_ULP; // on very fast systems that hit this method very quickly, // we will request more double values than can fit between // milliseconds, to ensure that we never return a value that is greater than the // current system time in milliseconds. if (next >= getMarginOfError() + newNow) { // check if we would return a value in the future... while (newNow == System.currentTimeMillis()) { // We'll park to give other threads a chance to pick up work. // When testing with daemon threads, this greatly reduces the // number of times we spin here. This was tested by counting // how many times this loop runs more than once, when there are // a fairly large number of daemons running; LockSupport.parkNanos(1); } next = System.currentTimeMillis(); } return next; }); } /** * @return The fractional number of milliseconds ahead of the system clock we may generate nowPlusOne() values. * * Default is 1 millisecond. Smaller values will allow for fewer spaces between milliseconds, * while larger values will prevent increments from blocking if nowPlusOne is call more than 0x1000 in a millisecond. * (0x1000 is the most bits that can fit between java double precision floating point timestamps). * * Setting the margin of error to a large value will make the code run very fast, * at the risk of using timestamps in the semi-"distant" future; * when a nowPlusOne() or tick() call occurs many times in a millisecond, * this margin of error will cause the atomic increment to block until the system time is within the margin of error. * * Using a margin of error of 0 will cause tick() and nowPlusOne() to block until the system time catches up, * and will perform very poorly when used concurrently. */ public double getMarginOfError() { return marginOfError; } /** * @param marginOfError - A decimal amount of milliseconds that we will allow .tick() or nowPlusOne() to get ahead * of the current time in millis(). * * Default is 1 millisecond. Smaller values will allow for fewer spaces between milliseconds (less unique doubles), * while larger values will prevent increments from blocking if nowPlusOne is call more than 0x1000 in a millisecond. * (0x1000 is the most bits that can fit between java double precision floating point timestamps). * * Setting the margin of error to a large value will make the code run very fast, * at the risk of using timestamps in the semi-"distant" future; * when a nowPlusOne() or tick() call occurs many times in a millisecond, * this margin of error will cause the atomic increment to block until the system time is within the margin of error. * * Using a margin of error of 0 will cause tick() and nowPlusOne() to block until the system time catches up, * and will perform very poorly when used concurrently. * */ public void setMarginOfError(double marginOfError) { this.marginOfError = Math.max(0, marginOfError); } }