Guava-free Same Thread Executor#5413
Conversation
The `MoreExecs.sameThreadExecutor()` is deprecated as per https://github.com/google/guava/blob/v18.0/guava/src/com/google/common/util/concurrent/MoreExecutors.java#L266-L267 and the function signature moves around a lot in future guava releases. This creates a new `SameThreadExecutorService` using more modern java features.
| }).writeValueAsString(taskGroups.get(groupId).sequenceOffsets); | ||
| final Map<String, Object> context = spec.getContext() == null | ||
| ? ImmutableMap.of("checkpoints", checkpoints, IS_INCREMENTAL_HANDOFF_SUPPORTED, true) | ||
| ? ImmutableMap.of( |
There was a problem hiding this comment.
Please replace ternary with if-else
| @@ -361,11 +362,17 @@ public void testBasics() throws Exception | |||
|
|
|||
| for (DataSegment publishedSegment : publishedSegments) { | |||
| Optional<Map.Entry<SegmentDescriptor, Pair<Executor, Runnable>>> optional = handOffCallbacks.entrySet().stream() | |||
There was a problem hiding this comment.
Please break line before .entrySet() to make reduce indentation below. Same below in this class.
There was a problem hiding this comment.
or thought I did, checking
There was a problem hiding this comment.
haha, pushed to wrong repo, fixed now
| SupervisorManager.class))); | ||
| tac = new LocalTaskActionClientFactory( | ||
| taskStorage, | ||
| new TaskActionToolbox(taskLockbox, mdc, emitter, EasyMock.createMock( |
| import java.util.concurrent.TimeoutException; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
|
|
||
| public class SameThreadExecutorService extends AbstractExecutorService |
There was a problem hiding this comment.
Is this code just copied from Guava? Could you refer to a file on Github (under google/guava) that contains it, in javadoc comment?
Could you add "TODO: replace with ... when updated to Guava X", and a corresponding issue?
There was a problem hiding this comment.
It is not copied from guava
|
@leventov addressed comments |
b-slim
left a comment
There was a problem hiding this comment.
Overall LGTM, can you also ban those functions as well to avoid future invocation?
| notices.add(new RunNotice()); | ||
| } | ||
| }, MoreExecutors.sameThreadExecutor() | ||
| }, Execs.sameThreadExecutor() |
There was a problem hiding this comment.
Execs.sameThreadExecutor() should be on the next line.
| public Runnable commit() | ||
| { | ||
| return () -> {}; | ||
| return () -> { |
There was a problem hiding this comment.
It's Runnables.getNoopRunnable().
| public Runnable commit() | ||
| { | ||
| return () -> {}; | ||
| return () -> { |
| return DummyExecutorService.INSTANCE; | ||
| } | ||
|
|
||
| public static ListeningExecutorService sameThreadExecutor() |
There was a problem hiding this comment.
It was a mistake in Guava's API to call this method this way, because it gives full impression that some static constant is returned from it (I thought that until now), while in reality this method (and the removed method in Guava) creates a new Object each time. (Guava developers fixed this mistake by calling the new method newDirectExecutorService().)
However, I think that Druid actually never tries to terminate this service, so it would be better to keep the method name as is, actually cache the executor service, and simplify it's implementation, by either throwing an UnsupportedOperationException in shutdown() and shutdownNow() or making them no-ops, and always returning false from isTerminated(), and removing all state and synchronization from the class.
There was a problem hiding this comment.
That's reasonable
|
I'm waiting on #5413 (review) because doing it will collide with #5414 |
| publishedSegment.getShardSpec().getPartitionNum() | ||
| ))) | ||
| .findFirst(); | ||
| Optional<Map.Entry<SegmentDescriptor, Pair<Executor, Runnable>>> optional = handOffCallbacks |
There was a problem hiding this comment.
This construction looks like handOffCallbacks.get()...
There was a problem hiding this comment.
I'd rather not change any logic. This just retains the prior code
| publishedSegment.getShardSpec().getPartitionNum() | ||
| ))) | ||
| .findFirst(); | ||
| Optional<Map.Entry<SegmentDescriptor, Pair<Executor, Runnable>>> optional = handOffCallbacks |
There was a problem hiding this comment.
I'm not going to change logic in this PR
| publishedSegment.getShardSpec().getPartitionNum() | ||
| ))) | ||
| .findFirst(); | ||
| Optional<Map.Entry<SegmentDescriptor, Pair<Executor, Runnable>>> optional = handOffCallbacks |
There was a problem hiding this comment.
logic is retained as the prior on purpose
| /** | ||
| * A simple class that implements the ExecutorService interface, but runs the code on a call to submit | ||
| */ | ||
| public class SameThreadExecutorService extends AbstractExecutorService |
There was a problem hiding this comment.
Please make this class a singleton
There was a problem hiding this comment.
I made it singleton in Execs
There was a problem hiding this comment.
Execs doesn't prohibit instantiation of new SameThreadExecutorService objects.
There was a problem hiding this comment.
Yes that is true. Why force singleton?
There was a problem hiding this comment.
To prohibit accidential instantiation.
Referencing Execs in the javadoc comment would be useful too.
There was a problem hiding this comment.
Made the class constructor package private and added some more comments.
| final long nanos = TimeUnit.NANOSECONDS.convert(timeout, unit); | ||
| final long millis = TimeUnit.MILLISECONDS.convert(timeout, unit); | ||
| final int sleepNanos = (int) (nanos - millis * 1_000_000L); | ||
| Thread.sleep(millis, sleepNanos); |
There was a problem hiding this comment.
I don't think sleeping makes much sense here, either return immediately or throw an UnsupportedOperationException.
There was a problem hiding this comment.
The contract says it will wait, so I wait
There was a problem hiding this comment.
awaitTermination() should be called only after shutdown() or shutdownNow(), which throw UnsupportedOperationException, so it's even more reasonable to throw it from awaitTermination()
There was a problem hiding this comment.
The interface is unambiguous : https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#awaitTermination-long-java.util.concurrent.TimeUnit-
Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
Which is what this implementation does.
There was a problem hiding this comment.
Javadocs of shutdown() and shutdownNow() also don't give an option to throw an UnsupportedOperationException. Yet when awaitTermination() is called on SameThreadExecutorService, it should be a programming bug, so better to report it rather than silence.
There was a problem hiding this comment.
UnsupportedOperationException is an unchecked exception, it does not need to be declared
There was a problem hiding this comment.
Yes, and that is why there is no problem with throwing it from the method, although the interface doesn't specify. I mean, if we throw UOE from shutdown() and shutdownNow(), rather than make those methods no-ops, it makes just as much sense to throw UOE from awaitTermination() as well. And so to keep them consistent.
There was a problem hiding this comment.
@drcrallen the PR looks good to me, except this thing, but it's not blocking for merge. However I still think it should be changed.
* SameTheadExecutorService constructor now package private
| */ | ||
| public class SameThreadExecutorService extends AbstractExecutorService | ||
| { | ||
| // Use io.druid.java.util.common.concurrent.Execs#sameThreadExecutor() |
There was a problem hiding this comment.
Could you please make this a Javadoc comment
There was a problem hiding this comment.
Or just move to class-level comment
There was a problem hiding this comment.
Added class level comment
|
@leventov any other comments here? |
|
|
||
| /** | ||
| * A simple class that implements the ExecutorService interface, but runs the code on a call to submit | ||
| * Use io.druid.java.util.common.concurrent.Execs#sameThreadExecutor() to get the instance |
|
@leventov anything else here? |
|
ick, looks like |
|
had to revert "only one instance" for the same thread executor service :( |
|
Why? |
|
@leventov some of the tests close out the executor service. Announcer comes to mind. Fixing that is a bit much for this pr. |
|
In this case, why wouldn't we temporarily implement all shutdown(), shutdownNow() and isShutdown() as no-ops? And switch to throwing UnsupportedOperationException once #5527 is fixed? |
|
@leventov because that is only one use case, I'm not sure if there are others since I didn't try to get as many tests to pass as possible. I don't want to make assumptions about how the code is using executor services if it is already shown to do odd things with who-controls-this-executor. As such keeping behavior close to the executor service interface is desired. This PR keeps behavior closer to the executor service interface |
|
Having those methods implemented as no-ops (hence |
|
The constructor is package private, all instances must come through |
|
I'm concerned about potential performance impact of this solution. Now the executor that is supposed to be a cost-free abstraction (just execute the Runnable's run or Callable's call, passed in) does two operations with a Phaser. As the very minimum, it's a flush or read/write CPU core pipelines. And if some instance of It seems to me that the no-op solution doesn't have any risks and couldn't theoretically have any performance impact. |
|
@leventov |
|
@drcrallen can we take this to the finish line? |
|
@drcrallen the current
I didn't mention that before because I wanted to persuade you not to use |
|
This is too old at this point. closing |
The
MoreExecs.sameThreadExecutor()is deprecated as perhttps://github.com/google/guava/blob/v18.0/guava/src/com/google/common/util/concurrent/MoreExecutors.java#L266-L267
and the function signature moves around a lot in future guava releases.
This creates a new
SameThreadExecutorServiceusing more modern javafeatures.