clojure.core.async/timeout
uses System/currentTimeMillis
to calculate its deadline (see here and here). While the docstring for System/currentTimeMillis
doesn't mention it explicitly, it's quite clearly unsuitable for this purpose because it returns the current time as reported by the OS. Since that time can change arbitrarily (e.g. by correction via NTP), the deadline calculations may end up inconsistent. This behavior is very much intended, see e.g. this ticket where it was suggested to switch System/currentTimeMillis
over to a monotonic clock where possible. It was refused with the following comment:
System.currentTimeMillis() is required to return milliseconds since the epoch and should reflect external changes to the system/wall-clock time. CLOCK_MONOTONIC_COARSE does not provide that and so can not be used for this purpose.
Also, the docstring for java.util.concurrent.ScheduledExecutorService
states this quite plainly:
All schedule methods accept relative delays and periods as arguments, not absolute times or dates. It is a simple matter to transform an absolute time represented as a Date to the required form. For example, to schedule at a certain future date, you can use: schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS). Beware however that expiration of a relative delay need not coincide with the current Date at which the task is enabled due to network time synchronization protocols, clock drift, or other factors.
Since all we care about here is deltas, it seems advisable to switch to System/nanoTime
instead. Unfortunately, its docstring also doesn't make its properties very obvious. It doesn't mention that it's a monotonic clock but only hints at it by stating:
Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds. This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time. [...] The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
A case in point for using it is OpenJDK's implementation of java.util.concurrent.ScheduledThreadPoolExecutor
whose deadline calculation method does so, as well as its getDelay
implementation.
Furthermore--if it's anything to go by--various StackOverflow posts also discuss this and arrive at the same conclusion, e.g. this one:
Nonetheless, nanoTime() should still be preferred for implementing timed blocking, interval waiting, timeouts, etc. to currentTimeMillis() because the latter is a subject to the "time going backward" phenomenon (e. g. due to server time correction), i. e. currentTimeMillis() is not suitable for measuring time intervals at all. See this answer for more information.
See also a similar question about core.cache
.