RFR (M): 8235741: Inappropriate uses of os::javaTimeMillis()

David Holmes david.holmes at oracle.com
Fri Jan 17 05:48:14 UTC 2020


I tried but failed to generate an incremental webrev at:

http://cr.openjdk.java.net/~dholmes/8235741/webrev.v3-incr/

I hadn't qfresh'd and so it contains v2 and v3 changes :(

Full webrev at:

http://cr.openjdk.java.net/~dholmes/8235741/webrev.v3/

Changes:
- conversion macros are now typed functions
- os_posix.cpp had a name clash so fixed that and updated to use the new 
conversion functions

Thanks,
David

On 17/01/2020 12:48 pm, David Holmes wrote:
> Hi Kim,
> 
> On 17/01/2020 11:39 am, Kim Barrett wrote:
>>> On Jan 15, 2020, at 2:12 AM, David Holmes <david.holmes at oracle.com> 
>>> wrote:
>>>> src/hotspot/share/utilities/globalDefinitions.hpp
>>>>   262 // time unit conversion macros
>>>>   263
>>>>   264 #define NANOS_TO_MILLIS(ns) ((ns) / NANOSECS_PER_MILLISEC)
>>>>   265 #define MILLIS_TO_NANOS(ms) ((ms) * NANOSECS_PER_MILLISEC)
>>>> Why are these macros, rather than (template) functions?
>>>
>>> Just because I just wanted a simple textual replacement to make it 
>>> clearer that I'm converting from millis to nanos or vice versa. I 
>>> reach for macros for such simple cases.
>>>
>>>> Also, depending on the type and value of ms, MILLIS_TO_NANOS could
>>>> easily overflow, e.g. if ms type is a 32 bit type with a value of more
>>>> than ~4 seconds.  (I checked the two uses, and they happen to be okay.)
>>>
>>> These are not trying to be mathematically sound. The conversion from 
>>> millis to nanos is used in two cases:
>>>
>>> 1. Converting a current timestamp in ms to ns. Unless the current 
>>> time is set far in the future I don't think we have any issue with 
>>> overflow of  such a value.
>>>
>>> 2. converting an elapsed time in ms to ns. These will be small values 
>>> so no overflow is possible.
>>
>> In this case, "not trying to be mathematically sound" is equivalent to
>> actively but subtly dangerous to use, because of possible overflow
>> leading to either UB or catastrophically wrong results.
>>
>> Consider "int millis = 10 * 1000;", e.g. 10 seconds worth of
>> milliseconds.  I hope you will agree that this falls under your case
>> 2, e.g. that 10 seconds is not an especially big interval.  I hope you
>> will also agree that declaring a variable that holds a millisecond
>> interval as an int is reasonable in some contexts. Applying
>> MILLIS_TO_NANOS with the jint-typed NANOSECS_PER_MILLISEC, and the
>> result about 1.4 seconds worth of nanoseconds, assuming the compiler
>> doesn't do something strange because of the UB on integer overflow.
> 
> So your concern is that someone will use the macros outside of existing 
> usage contexts (where time values always using 64-bit variables) and 
> thus introduce potential overflow bugs. Okay I will use the typed 
> functions.
> 
>> (This is all in addition to the Style Guide's admonishment to use
>> inline functions rather than macros.  Macros should only be used for
>> syntactic extensions.)
> 
> 
>>>> inline int64_t nanos_to_millis(int64_t ns) {
>>>>    return ns / NANOSECS_PER_MILLISECOND;
>>>> }
>>>> inline int64_t millis_to_nanos(int64_t ms) {
>>>>    return ms * NANOSECONDS_PER_MILLISEC;
>>>> }
>>>> Also, the names don't suggest time conversions, but potentially
>>>> arbitrary unit conversions, e.g. between something in NANOUNITS and
>>>> something in MILLIUNITS.
>>>
>>> They don't have to be time conversions - the calculation is unit-less 
>>> in practice. The fact we have NANOSEC_PER_MILLISECOND et al is just 
>>> an artifact of introducing those values for timeout 
>>> calculations/conversions - it could just be NANOS_PER_MILLI etc
>>
>> If they aren't time conversions then I don't think they should be
>> using the time factors; just use NANOUNITS/MILLIUNITS.
> 
> Okay.
> 
> 
>>>> Regarding this from the audit:
>>>> --- begin ---
>>>> ./share/gc/parallel/psParallelCompact.cpp: // os::javaTimeMillis() 
>>>> does not guarantee monotonicity.
>>>> ...
>>>> ./share/gc/shared/referenceProcessor.cpp: // os::javaTimeMillis() 
>>>> does not guarantee monotonicity.
>>>> These are all describing why the subsequent code uses javaTimeNanos 
>>>> not javaTimeMillis.
>>>> --- end ---
>>>> Do we really still support platforms that don't have a monotonic
>>>> clock?  I guess we appear to at least try.  But it's really wrong that
>>>> callers of os::javaTimeNanos should even think they need to cope with
>>>> that function being non-monotonic.
>>>> Hm, I always thought System.nanoTime() was a monotonic clock, but I
>>>> don't see any such guarantee. So I guess Java just doesn't have such a
>>>> thing. Wow!
>>>> So I guess none of this is really relevant to the change at hand 
>>>> after all.
>>>
>>> I think you read the comments the wrong way round. The code uses 
>>> javaTimeNanos not javaTimeMillis because javaTimeMillis is not 
>>> monotonic and the code wants a monotonic clock. These comments were 
>>> mostly inserted when the incorrect use of javaTimeMillis was replaced 
>>> with javaTimeNanos.
>>
>> No, I did not read the comments the wrong way around.
>>
>> I was pointing out that
>>
>> (1) Some uses of os::javaTimeNanos attempt to cope with a possible
>> lack of monotonicity, while some do not and may be assuming or even
>> requiring monotonicity.  I'm wondering whether the coping attempts are
>> dead code, or instead, the latter are bugs.  That is, do we require
>> all platforms to provide a monotonic os::javaTimeNanos or not?
>> Personally, I think in today's world we should make such a requirement.
> 
> I assume you are looking at code where we explicitly ensure that time 
> never jumps backwards? This is historical. The sad fact is that over the 
> past 20+ years we have seen bug after bug after bug in the OS and 
> virtualization layers that caused the system monotonic clock source to 
> actually not behave monotonically. These bugs were prevalent enough in 
> some situations that we had to implement the workarounds in the JVM. 
> These workarounds have had costs and have varied over time. Ensuring 
> that javaTimeNanos() itself is guaranteed monotonic imposes too high a 
> cost - see related discussions in:
> 
> https://bugs.openjdk.java.net/browse/JDK-6864866
> https://bugs.openjdk.java.net/browse/JDK-6784100
> 
> regarding the situation on Solaris. In general we try to trust the 
> system monotonic clock and deal with issues if they arise.
> 
> So in a bugfree world:
> - os::javaTimeNanos is "guaranteed" monotonic (assuming the underlying 
> monotonic clock exists on that platform)
> - os::currentTimeMillis() is not monotonic as it is (required to be) 
> subject to time-of-day adjustments (e.g. ntp)
> 
>> (2) There is no documented monotonicity guarantee for System.nanoTime,
>> nor any hint of a possible issue there, which surprised me.
> 
> Again historical. Back in 2003 when we (JSR166 EG) specified this we 
> also had to consider the implications for Java ME and the constrained 
> environments in which it would execute, as well as the various 
> environments supported by Java SE**. As a practical consideration it was 
> necessary that a valid/conforming implementation of nanoTime() could be 
> currentTimeMillis()*1000000 and without making heroic, and costly, 
> efforts to ensure monotonicity - which was basically left as a quality 
> of implementation issue.
> 
> ** Windows versions without QueryPerformanceCounter; Linux 2.x kernels 
> without CLOCK_MONOTONIC
> 
> Thanks,
> David
> ----
> 
>>


More information about the hotspot-dev mailing list