Durations in existing JDK APIs

Kurt Alfred Kluever kak at google.com
Wed May 30 14:01:54 UTC 2018


*Hi core-libs-dev and concurrency-interest,Recently, we've been closely
studying date/time code inside of Google, and found a surprising number of
places with time unit mismatches due to overuse of primitives to represent
date/time concepts. And unlike "off-by-one" errors, these are often
"off-by-1000x" or worse...yikes! For internal APIs, we're strongly
encouraging folks to use the appropriate java.time types (i.e., Duration or
Instant) because this greatly reduces the occurrence of these
problems.There are several improvements in the JDK we'd like to propose: 1.
Rename ALL existing unitless primitive method parameters to include their
time unit. At Google, we have static analysis tools to detect unit
mismatches that work based on method names, variable names, etc. However,
when methods and method parameters are unitless, the only way to know what
correct units are is to read and understand the javadocs (which our tools
obviously can't do). Many IDEs also show method signatures with parameter
names only. There are a handful of examples in the JDK that we'd like to
update. For example, Object.wait(long timeout) would become
Object.wait(long timeoutMillis) (or similar). An incomplete list of these
APIs is included below [1].2. Add a java.time overload to some APIs that
currently represent date/time concepts using primitives. While static
analysis helps find errors, ideally people wouldn't have to decompose their
Duration instances to call these APIs. Adding a Duration overload of each
of these APIs would remove the need to decompose durations, and would
encourage developers to plumb durations through more layers of their
application. The old primitive-accepting APIs would be softly discouraged.
Note that new default implementations will have to delegate to the existing
overloads, and will have to choose between losing precision or capping
large values at e.g. 292 years (for long nanos), but it is hard to imagine
this being a serious problem in practice for any of them.1. Note: it's
probably not worth adding Duration overloads to legacy APIs (e.g.,
java.util.Timer) or low-level APIs (e.g., java.lang.Object.wait()).
Determining which APIs make the cut is certainly open to discussion.3. Add
a java.time overload to most APIs that currently accept a <long, TimeUnit>
pair. Prior to Java8, the recommended advice for accepting a logical
duration was to use a <long, TimeUnit> pair, as most
<https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/locks/LockSupport.html#parkUntil-long->
of java.util.concurrent currently does
<https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/class-use/TimeUnit.html>.
Similarly, we've seen unit mismatch bugs with these APIs as well (e.g.,
future.get(timeout.toNanos(), MILLISECONDS)). Adding a Duration overload
for each of these APIs would remove the need to decompose durations, and
would encourage broader Duration adoption. The old APIs would be softly
discouraged.1. Note: a few of the <long, TimeUnit> APIs are already
overloaded, which would make this a 2x2 explosion of methods. Whether that
sort of API explosion is acceptable is certainly open for discussion.2.
Note: We've already started adding these overloads
<https://github.com/google/guava/issues/2999> in Guava
<https://github.com/google/guava/> and have plans to do so across the
board. Caffeine <https://github.com/ben-manes/caffeine/> has also added
<https://github.com/ben-manes/caffeine/issues/221> Duration overloads.4.
Add APIs to convert between TimeUnit and Duration. As users transition to
the new Duration-centric world, these APIs will come in handy. They will
also be necessary for overload-implementers. We're proposing
Duration.of(long, TimeUnit) and TimeUnit.convert(Duration).These
recommendations, of course, should also apply to new APIs added in the
future. Note that we are not expressing an opinion at this time on whether
new APIs should or shouldn't also have a <long, TimeUnit> overload.Of
course, even comprehensive adoption of Duration in a codebase will not
eliminate every possible source of unit mismatch bugs. But it would confine
them to only the places where Durations are created. This would reduce the
risk in itself, but also makes it much easier for static analyses to detect
any remaining bugs. It also lowers the cognitive burden faced by every
developer interacting with logical durations.We realize that these are
significant changes, so we'd love to hear your thoughts. We'd also be happy
to work with Martin Buchholz to make these changes.Thanks,-Kurt Alfred
Kluever(on behalf of the Java Core Libraries Team @ Google)Appendix[1]
Unitless primitive parameters - Probably worth adding a Duration overload-
java.lang.Thread.sleep(long millis)- java.lang.Thread.sleep(long millis,
long nanos)- Note: this API is particularly weird since it accepts millis
and nanos!- java.lang.Thread.join(long millis)- java.lang.Thread.join(long
millis, long nanos)- Note: this API is particularly weird since it accepts
millis and nanos!- java.nio.channel.Selector.select(long timeout)- Probably
too low level to worry about adding a java.time overloads-
java.lang.Object.wait(long timeout)- java.lang.Object.wait(long timeout,
long nanos)- Note: this API is particularly weird since it accepts millis
and nanos!- java.lang.ReferenceQueue.remove(long timeout)-
java.util.concurrent.locks.LockSupport.parkUntil(long deadline)- Note: this
parameter represents milliseconds since epoch (an Instant)-
java.util.concurrent.locks.LockSupport.parkUntil(Object blocker, long
deadline)- Note: this parameter represents milliseconds since epoch (an
Instant)- java.util.concurrent.locks.LockSupport.parkNanos(long nanos)-
java.util.concurrent.locks.LockSupport.parkNanos(Object blocker, long
nanos)-
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.awaitUntil(Date)-
Note: this would be overloaded with an Instant-
java.util.logging.LogRecord.setMillis(long millis)- "Legacy" APIs that are
probably not worth adding a java.time overloads-
java.util.Timer.schedule(TimerTask task, Date firstTime, long period)-
java.util.Timer.schedule(TimerTask task, long delay)-
java.util.Timer.schedule(TimerTask task, Date time)-
java.util.Timer.schedule(TimerTask task, long delay, long period)-
java.util.Timer.scheduleAtFixedRate(TimerTask task, Date firstTime, long
period)- java.util.Timer.scheduleAtFixedRate(TimerTask task, long delay,
long period)- java.sql.Connection.isValid(int timeout)- Note: this
parameter is in seconds- java.sql.DriverManager.setLoginTimeout(int
seconds)- Note: this parameter is in seconds- Networking APIs (perhaps not
worth adding a Duration overload?)- java.net.InetAddress.isReachable(int
timeout)- java.net.InetAddress.isReachable(NetworkInterface netif, int ttl,
int timeout)- java.net.URLConnection.setConnectTimeout(int timeout)-
java.net.URLConnection.setReadTimeout(int timeout)-
java.net.Socket.setSoTimeout(int timeout)-
java.net.Socket.connect(SocketAddress endpoint, int timeout)-
java.net.ServerSocket.setSoTimeout(int timeout)-
java.net.DatagramSocket.setSoTimeout(int timeout)*


More information about the core-libs-dev mailing list