-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Clock interface, remove TimeProvider #2998
Update Clock interface, remove TimeProvider #2998
Conversation
…llers to more accurately describe what they are trying to do. Fixes deephaven#2992
This is somewhat orthogonal to #2999, but there will be some conflicts in either case. This PR is more about the clock interface and plumbing the callers intentions as deep as possible; while the other PR is more about making an allocation free system clock. |
/** | ||
* Real-time local clock impl with a delta adjustment for all time values. | ||
*/ | ||
public class DeltaLocalClockImpl implements Clock, Serializable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use this in scripts sometimes when setting up a replayer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll bring it back.
* | ||
* @return the nano time | ||
*/ | ||
long nanoTime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure this belongs here...seems like we should just use System.nanoTime()
and exclude this from the interface. If you want possibly-fake nanos we have currentTimeNanos()
— what are the situations where it really would be right to use this nanoTime()
instead of System.nanoTime()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that you might want to replay the behavior that you would otherwise get w/ System.nanoTime()
(either for unit test, or replaying data), but you don't want to pay the price of currentTimeNanos()
in production.
For example, to use AutoTuningIncrementalReleaseFilter, there are three options:
Clock.nanoTime()
- step replayable; fast nanoTime in productionSystem.nanoTime()
- not replayable; fast nanoTime in productionClock.currentTimeNanos()
- step replayable; slower currentTimeNanos in production
If you don't care about replayability / unit-testing wrt your timings, then you should use System.nanoTime()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'm still a bit skeptical of the legitimacy of case 1, but it does make the interface more complete
|
||
@Override | ||
public final DateTime currentTime() { | ||
return new DateTime(currentTimeNanos()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think get rid of this class and just make this a default method in TimeProvider
. You have to use nanos as the source for any TimeProvider anyway, since a TimeProvider's only job is providing a DateTime
and you're limited to a single long
when making a DateTime
anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm strongly anti-default methods - I think it muddies the water, and potentially provides places where calls are virtualized where they otherwise wouldn't need to be.
For example, io.deephaven.engine.table.impl.replay.DataDrivenReplayer
is explicitly based off of DateTime
; if we accidentally forget to override currentTime()
, then we'd still get correct, but inefficient, behavior.
I know we use it in a lot of other places, but if I had my way I would remove most of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it's completely acceptable to create a TimeProvider
wholly from System.currentTimeMillis()
if you wanted.
@@ -2336,4 +2376,38 @@ public static String getPartitionFromTimestampSeconds(@NotNull final DateTimeFor | |||
} | |||
return dateTimeFormatter.format(Instant.ofEpochMilli(timestampSeconds * 1_000)); | |||
} | |||
|
|||
private enum TimeProviderImpl implements TimeProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why enum?
I think it'd be more straightforward to just add this INSTANCE as a field in TimeProvider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, when stateless singleton structures can be represented as enum, you gain niceties - equality, hashcode, etc for free.
* | ||
* @return the nano time | ||
*/ | ||
long nanoTime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'm still a bit skeptical of the legitimacy of case 1, but it does make the interface more complete
'--add-opens=java.management/sun.management=ALL-UNNAMED', | ||
# Allow our clock-impl project to access internals | ||
'--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could just use --add-exports
instead of --add-opens
— maybe vaguely better not to enable reflection where we don't need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--add-opens
is necessary from the runtime perspective. --add-exports
is a compile-time flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe there is some nuance I'm missing, will test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great find - --add-opens
is more aggressive than --add-exports
in allowing reflective access; something we don't need for hotspot-impl
nor clock-impl
. Updated to reflect this.
buildSrc/src/main/groovy/io/deephaven/project/util/CombinedJavadoc.groovy
Show resolved
Hide resolved
clock-impl/src/main/java/io/deephaven/base/clock/SystemClockJdkInternalMiscVm.java
Outdated
Show resolved
Hide resolved
* | ||
* @see SystemClock | ||
*/ | ||
public interface Clock { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might want to ask @chipkent to look at your interface.
|
||
@Override | ||
public final long nanoTime() { | ||
return currentTimeNanos(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a kind of questionable implementation, since nanoTime()
has different semantics than the other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a larger question of "Should we have unit-testability for nanoTime logic; and should that interface be on Clock?"
Confirmed w/ Charles, we might want fine replayability of nanoTime for unit tests, even though we don't have them right now.
The next part is "should this be part of clock?"
It may be reasonable to have a separate interface since it is more bespoke than Clock; but what I don't want to encourage is users just deciding to use Clock#currentTimeNanos
because it's more convenient than plumbing through an additional nanoTime interface. That said, there aren't many users of Clock#nanoTime
(only AutoTuningIncrementalReleaseFilter and BaseIncrementalReleaseFilter), so it would be a small change against this PR.
Options:
- Leave
Clock#nanoTime
as-is - Create new interface
MeasureNanos
. - Remove
Clock#nanoTime
, and explicitly useSystem.nanoTime
in BaseIncrementalReleaseFilter/AutoTuningIncrementalReleaseFilter
I'm actually leaning 3 > 2 > 1; at least until we get to the point where we need nanoTime replayability for unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proceeded w/ 3
|
||
@Override | ||
public Instant instantNanos() { | ||
return delegate.instantNanos().plusNanos(deltaNanos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd be better off implementing this and instantMillis()
in terms of nanos, right? This allocates 2 Instant
s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I'm not as concerned w/ the efficiency of non-standard clock implementations - regardless, I think the current implementation is the "correct" implementation, given that we should always delegate the same method to delegate
given it knows more about its implementation than DeltaClock does.
If we really needed to optimize for applying deltas to clocks, that would suggest we should instead have an interface:
interface Clock {
...
public Instant instantNanos(long deltaNanos);
...
}
But I think we can agree that would be overkill.
clock/src/main/java/io/deephaven/base/clock/SystemClockMillisImpl.java
Outdated
Show resolved
Hide resolved
@@ -20,8 +22,33 @@ public DataDrivenReplayer(DateTime startTime, DateTime endTime) { | |||
} | |||
|
|||
@Override | |||
public DateTime currentTime() { | |||
return currentTime; | |||
public long currentTimeMillis() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can move this up a level to avoid repetition, delegating to an abstract accessor for currentTime
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the hierarchy right now is a bit confusing, with DataDrivenReplayer and FixedStepReplayer (extends io.deephaven.engine.table.impl.replay.Replayer) having completely separate overrides than the parent class.
I think it could be cleaned up, but it would be a pretty major gutting that would be better done in isolation IMO.
/** | ||
* Replay historical data as simulated real-time data. | ||
*/ | ||
public class Replayer implements ReplayerInterface, Runnable { | ||
private static final Logger log = LoggerFactory.getLogger(ShiftObliviousInstrumentedListener.class); | ||
private static final int MILLIS_TO_NANOS_FACTOR = 1_000_000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we want to prefer using TimeUnit
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll delegate back to DateTimeUtils.millisToNanos etc. And remove ENABLE_MICROTIME_HACK in the process.
engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableAggregationTest.java
Outdated
Show resolved
Hide resolved
@@ -43,7 +44,12 @@ protected void setUp() throws Exception { | |||
super.setUp(); | |||
|
|||
now = new MutableLong(0); | |||
timeProvider = () -> new DateTime(now.longValue()); | |||
clock = new ClockNanoBase() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do this a lot. Maybe we want one based on a MutableLong
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elevated TestClock.
/** | ||
* The {@link SystemClock}. Provides singleton semantics around {@link SystemClock#of()}. | ||
* | ||
* @return the system clock | ||
*/ | ||
static SystemClock systemUTC() { | ||
return SystemClockInstance.INSTANCE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this method belongs. It is specific to a single clock, and that clock can be obtained via other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the method may actually be better named system()
...
That said, it does serve an important service not fulfilled by other methods - it's essentially providing a singleton cache around SystemClock.of()
. (SystemClockInstance
itself is not public.)
|
||
import java.io.IOException; | ||
import java.util.TimerTask; | ||
|
||
/** | ||
* An interface for replaying historical data as simulated real-time data. | ||
*/ | ||
public interface ReplayerInterface { | ||
public interface ReplayerInterface extends Clock { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on making the replayer instance a Clock
vs returning a Clock
? I might lean to the second, since it seems funny to say that the replayer is a clock. Also, "is a Clock
" is causing a bunch of methods to be duplicated in many files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to change this to "replayer returns a clock" but it's not going to solve any of the duplication - the replayer and classes that extend replayer seem to be built fundamentally different, so I don't think there's an easy path to de-duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did find a slight way to simplify this by doing "returns a Clock" - and I agree it's a better interface.
long epochSecond = nanos / 1000000000; | ||
long nanoAdjustment = nanos % 1000000000; | ||
return Instant.ofEpochSecond(epochSecond, nanoAdjustment); | ||
return Instant.ofEpochSecond(0, nanos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looked correct before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a slight improvement, we don't have to do the math ourselves. The internals of ofEpochSecond can deal directly w/ long nanos
.
*/ | ||
public static TimeProvider timeProvider; | ||
public static Clock clock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a static clock here is funky, since we can support multiple replayers, where we probably want the clock to be set based upon the context of the replayer. We are certainly no worse than we were, but we should have a ticket to address this at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - it would be much better to inject and pass along a Clock instead of having a static. I'll create an issue and link it from DateTimeUtils.
Updates the Clock interface providing a much richer selection of methods, allowing the caller to best specify how they actually want to use the results. Removes TimeProvider interface and replaces with Clock. Allows the removal of a lot of extraneous allocation. An allocation-free Clock implementation is provided, but gracefully falls back to the Java UTC clock / Instant implementation.
Fixes #2992