From 8b06d56b456a31938a5b0bc0997a8c87fd579e92 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Thu, 6 Aug 2020 11:46:52 +0300 Subject: [PATCH 1/8] Truncate time to milliseconds. --- base/src/main/java/io/spine/base/Time.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index 94a5edbd33..e86388dd5d 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -27,6 +27,7 @@ import javax.annotation.concurrent.ThreadSafe; import java.time.Instant; import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -149,12 +150,14 @@ private SystemTimeProvider() { @Override public Timestamp currentTime() { - Instant now = Instant.now(); + Instant now = Instant.now() + .truncatedTo(ChronoUnit.MILLIS); int nanosOnly = IncrementalNanos.valueForTime(now); - Timestamp result = Timestamp.newBuilder() - .setSeconds(now.getEpochSecond()) - .setNanos(now.getNano() + nanosOnly) - .build(); + Timestamp result = Timestamp + .newBuilder() + .setSeconds(now.getEpochSecond()) + .setNanos(now.getNano() + nanosOnly) + .build(); return result; } } From db0c38b10ff08567f3d49d1e04ad62b00963782c Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Thu, 6 Aug 2020 16:08:26 +0300 Subject: [PATCH 2/8] Bump the version. --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 325bb7dc1d..485c83b5b1 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -25,7 +25,7 @@ * as we want to manage the versions in a single source. */ -val SPINE_VERSION = "1.5.22" +val SPINE_VERSION = "1.5.23" project.extra.apply { this["spineVersion"] = SPINE_VERSION From a5c73805e83fe6fdff4bf21c4628436753a0e645 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Fri, 7 Aug 2020 12:21:51 +0300 Subject: [PATCH 3/8] Improve performance using `currentTimeMillis()` directly. --- base/src/main/java/io/spine/base/Time.java | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index e86388dd5d..f45624c017 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -27,7 +27,6 @@ import javax.annotation.concurrent.ThreadSafe; import java.time.Instant; import java.time.ZoneId; -import java.time.temporal.ChronoUnit; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -141,6 +140,8 @@ default ZoneId currentZone() { @VisibleForTesting static class SystemTimeProvider implements Provider { + public static final int NANOSECONDS_IN_MILLISECOND = 1_000_000; + @VisibleForTesting static final Provider INSTANCE = new SystemTimeProvider(); @@ -150,13 +151,14 @@ private SystemTimeProvider() { @Override public Timestamp currentTime() { - Instant now = Instant.now() - .truncatedTo(ChronoUnit.MILLIS); - int nanosOnly = IncrementalNanos.valueForTime(now); + long millis = System.currentTimeMillis(); + long seconds = (millis / 1000); + int nanos = (int) (millis % 1000) * NANOSECONDS_IN_MILLISECOND; + int nanosOnly = IncrementalNanos.valueForTime(seconds, nanos); Timestamp result = Timestamp .newBuilder() - .setSeconds(now.getEpochSecond()) - .setNanos(now.getNano() + nanosOnly) + .setSeconds(seconds) + .setNanos(nanos + nanosOnly) .build(); return result; } @@ -199,25 +201,27 @@ static final class IncrementalNanos { private static final IncrementalNanos instance = new IncrementalNanos(); private int counter; - private Instant previousValue; - private synchronized int getNextValue(Instant forTime) { - if (forTime.equals(previousValue)) { - previousValue = forTime; + private long previousSeconds; + private int previousNanos; + + private synchronized int getNextValue(long seconds, int nanos) { + if (previousSeconds == seconds && previousNanos == nanos) { counter += NANOS_PER_MICROSECOND; counter = counter % MAX_VALUE; } else { - previousValue = forTime; counter = 0; } + previousSeconds = seconds; + previousNanos = nanos; return counter; } /** * Obtains the next nanosecond value. */ - static int valueForTime(Instant forTime) { - return instance.getNextValue(forTime); + static int valueForTime(long seconds, int nanos) { + return instance.getNextValue(seconds, nanos); } } } From 964f8b6d2eca6cf324b1da44cf9cf36cc3aa0991 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Fri, 7 Aug 2020 12:22:47 +0300 Subject: [PATCH 4/8] Adapt tests to code changes. --- base/src/test/java/io/spine/base/TimeTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/base/src/test/java/io/spine/base/TimeTest.java b/base/src/test/java/io/spine/base/TimeTest.java index ca11036880..cf663b93ec 100644 --- a/base/src/test/java/io/spine/base/TimeTest.java +++ b/base/src/test/java/io/spine/base/TimeTest.java @@ -106,8 +106,10 @@ class IncrementalNanosEmulator { " within a single point of wall-clock time") void differentValuesForTheSameTime() { Instant now = Instant.now(); - assertThat(IncrementalNanos.valueForTime(now)) - .isLessThan(IncrementalNanos.valueForTime(now)); + long seconds = now.getEpochSecond(); + int nanos = now.getNano(); + assertThat(IncrementalNanos.valueForTime(seconds, nanos)) + .isLessThan(IncrementalNanos.valueForTime(seconds, nanos)); } @SuppressWarnings("ResultOfMethodCallIgnored") @@ -116,8 +118,11 @@ void differentValuesForTheSameTime() { void resetsNanosForNewInstant() { Instant now = Instant.now(); Instant oneMsLater = now.plus(1, ChronoUnit.MILLIS); - IncrementalNanos.valueForTime(now); // Ignore this value. - int value = IncrementalNanos.valueForTime(oneMsLater); + IncrementalNanos + .valueForTime(now.getEpochSecond(), now.getNano()); // Ignore this value. + long oneMsLaterSeconds = oneMsLater.getEpochSecond(); + int oneMsLaterNanos = oneMsLater.getNano(); + int value = IncrementalNanos.valueForTime(oneMsLaterSeconds, oneMsLaterNanos); assertThat(value).isEqualTo(0); } } From 23bf759bc3e07f346d3d1bae38c9418a3e6d5385 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Fri, 7 Aug 2020 15:28:58 +0300 Subject: [PATCH 5/8] Add javadoc with the link to JDK bug report system. --- base/src/main/java/io/spine/base/Time.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index f45624c017..a52a1c933e 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -149,6 +149,17 @@ static class SystemTimeProvider implements Provider { private SystemTimeProvider() { } + /** + * {@inheritDoc} + * + *

Starting from Java 9 the precision of time may differ from platform to platform and + * depends on the underlying clock used by the JVM, the + * issue on this in the + * JDK bug system. To be consistent on different platforms and use {@link IncrementalNanos} + * without risk of overflowing the {@code nano} values this method intentionally calculates + * time in millisecond precision even if the underlying clock may offer more precise + * values. + */ @Override public Timestamp currentTime() { long millis = System.currentTimeMillis(); @@ -183,7 +194,8 @@ public Timestamp currentTime() { *

The returned nanosecond value starts at {@code 0} and never exceeds {@code 999 999}. * It is designed to keep the millisecond value provided by a typical-JVM system clock intact. * - *

The nanosecond value is reset for each new passed {@link Instant} value. + *

The nanosecond value is reset for each new passed {@code seconds} and {@code nanos} + * values. * That allows to receive {@code 1 000} distinct time values per millisecond. * *

In case the upper bound of the nanos is reached, meaning that there were more than From f8c458c97cd15db09e91beba5577c3b7080ccbce Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Tue, 11 Aug 2020 18:42:43 +0300 Subject: [PATCH 6/8] Improve javadoc. --- base/src/main/java/io/spine/base/Time.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index a52a1c933e..beb183b752 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -153,12 +153,13 @@ private SystemTimeProvider() { * {@inheritDoc} * *

Starting from Java 9 the precision of time may differ from platform to platform and - * depends on the underlying clock used by the JVM, the - * issue on this in the - * JDK bug system. To be consistent on different platforms and use {@link IncrementalNanos} - * without risk of overflowing the {@code nano} values this method intentionally calculates - * time in millisecond precision even if the underlying clock may offer more precise - * values. + * depends on the underlying clock used by the JVM. See the corresponding + * issue on this matter. + * + *

In order to provide consistent behavior on different platforms and avoid the overflow + * of the emulated nanosecond value, this method intentionally uses the system time + * in millisecond precision. Therefore even if the system clock may offer more precise + * values, the {@code System.currentTimeMillis()} is used as a base for the returned values. */ @Override public Timestamp currentTime() { @@ -194,8 +195,7 @@ public Timestamp currentTime() { *

The returned nanosecond value starts at {@code 0} and never exceeds {@code 999 999}. * It is designed to keep the millisecond value provided by a typical-JVM system clock intact. * - *

The nanosecond value is reset for each new passed {@code seconds} and {@code nanos} - * values. + *

The nanosecond value is reset for each new passed {@code seconds} and {@code nanos} values. * That allows to receive {@code 1 000} distinct time values per millisecond. * *

In case the upper bound of the nanos is reached, meaning that there were more than From e613542a9f3676ef7aa54ed251831f26371474e9 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Tue, 11 Aug 2020 18:43:37 +0300 Subject: [PATCH 7/8] Use predefined value instead of defining the constant. --- base/src/main/java/io/spine/base/Time.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index beb183b752..c113d19134 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -30,6 +30,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * Utilities for working with time information. @@ -140,8 +141,6 @@ default ZoneId currentZone() { @VisibleForTesting static class SystemTimeProvider implements Provider { - public static final int NANOSECONDS_IN_MILLISECOND = 1_000_000; - @VisibleForTesting static final Provider INSTANCE = new SystemTimeProvider(); @@ -165,7 +164,8 @@ private SystemTimeProvider() { public Timestamp currentTime() { long millis = System.currentTimeMillis(); long seconds = (millis / 1000); - int nanos = (int) (millis % 1000) * NANOSECONDS_IN_MILLISECOND; + @SuppressWarnings("NumericCastThatLosesPrecision") + int nanos = (int) (millis % 1000) * (int) MILLISECONDS.toNanos(1); int nanosOnly = IncrementalNanos.valueForTime(seconds, nanos); Timestamp result = Timestamp .newBuilder() From f922309f0e5f98039ae61e4d8fe24dfbbff12592 Mon Sep 17 00:00:00 2001 From: Nick Dolhii Date: Tue, 11 Aug 2020 18:44:53 +0300 Subject: [PATCH 8/8] Fix javadoc style. --- base/src/main/java/io/spine/base/Time.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/io/spine/base/Time.java b/base/src/main/java/io/spine/base/Time.java index c113d19134..7b2e65c539 100644 --- a/base/src/main/java/io/spine/base/Time.java +++ b/base/src/main/java/io/spine/base/Time.java @@ -195,8 +195,8 @@ public Timestamp currentTime() { *

The returned nanosecond value starts at {@code 0} and never exceeds {@code 999 999}. * It is designed to keep the millisecond value provided by a typical-JVM system clock intact. * - *

The nanosecond value is reset for each new passed {@code seconds} and {@code nanos} values. - * That allows to receive {@code 1 000} distinct time values per millisecond. + *

The nanosecond value is reset for each new passed {@code seconds} and {@code nanos} + * values. That allows to receive {@code 1 000} distinct time values per millisecond. * *

In case the upper bound of the nanos is reached, meaning that there were more than * {@code 1 000} calls to this class within a millisecond, the nanosecond value is reset