-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Introduce ClosingBinder #20960
Introduce ClosingBinder #20960
Conversation
kokosing
commented
Mar 6, 2024
•
edited
Loading
edited
I haven't migrated all use cases or haven't fixed all wrong places yet. I wanted to first discuss the proposed solution. |
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 if this is the 'discussion' you were expecting, but here it is.
public ExecutorServiceCleanupBinder(Binder binder) | ||
{ | ||
multibinder = newSetBinder(binder, ExecutorService.class, ForCleanup.class); | ||
binder.bind(Cleanup.class).asEagerSingleton(); |
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 not a guice expert, but I'd check whether binding as eager singleton means that it will instantiate with the multibinder in the state that it is at this point in time, or whether it will instantiate once it iterated all the modules for any additional multibinding.
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.
Since the cleanup class constructor depends on the set of executors, it will be created after this set (this is dependency in the graph)
@Retention(RUNTIME) | ||
@Target({FIELD, PARAMETER, METHOD}) | ||
@BindingAnnotation | ||
private @interface ForCleanup {} |
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.
NIT: maybe 'forCleanup' is to broad? 'forExecutorCleanup'?
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.
It is fine to me, because it is private annotation. Also now I also used this for closeables too.
@@ -363,7 +363,11 @@ List<OutputStatsEstimatorFactory> getCompositeOutputDataSizeEstimatorDelegateFac | |||
install(new QueryExecutionFactoryModule()); | |||
|
|||
// cleanup | |||
binder.bind(ExecutorCleanup.class).asEagerSingleton(); | |||
executorServiceCleanupBinder(binder) |
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.
are we now going to centralize this registration? That is, if we have an executor that we want to cleanly shutdown in say troubleshooting module, we'd need to move the binding here?
if I misunderstood, then calling 'executorServiceCleanupBinder' would still work when called in many places? (in terms of eager singleton and such).
Will this be added in worker module as well?
The biggest problem we see is in test code. I'm missing a code sample or a text explanation that would show how does it improve things for tests?
Does this technique help with code outside of trino OSS (also plugins)?
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.
troubleshooting module
What is "troubleshooting module"? I cannot find it in Trino.
are we now going to centralize this registration?
No.
then calling 'executorServiceCleanupBinder' would still work when called in many places?
Yes.
Will this be added in worker module as well?
If needed in can be added there too.
Does this technique help with code outside of trino OSS (also plugins)?
I think so.
The biggest problem we see is in test code. I'm missing a code sample or a text explanation that would show how does it improve things for tests?
Added test. Thanks.
@@ -460,30 +464,4 @@ private void bindLowMemoryTaskKiller(LowMemoryTaskKillerPolicy policy, Class<? e | |||
.to(clazz) | |||
.in(Scopes.SINGLETON))); | |||
} | |||
|
|||
public static class ExecutorCleanup |
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.
can you explain how is your modification improving over what we had before here?
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.
It was just a code extraction. No improvement here. But see the last commit now.
import static java.lang.annotation.ElementType.PARAMETER; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
||
public class ExecutorServiceCleanupBinder |
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.
how can we make sure this is used for all executors?
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 cannot :(
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.
Potentially, we can introduce a static rule that @PreDestroy
annotation cannot be used directly in classes that ends with *Module.java
. I am not sure if it is easy to be done with the modernizer.
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 take a look into modernizer and I didn't find anything that I can easily use :(
core/trino-main/src/main/java/io/trino/server/CoordinatorModule.java
Outdated
Show resolved
Hide resolved
adb4dc8
to
292c1d0
Compare
Comments addressed. Please take a look again. Thank you! :) |
292c1d0
to
449e796
Compare
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 like the abstraction you introduced. +1 for testing.
Left a limited number of 'consider for the future' comments, but generally can go without addressing them. Thank you for handling this nicely.
public void shutdown() | ||
throws IOException | ||
{ | ||
executors.forEach(ExecutorService::shutdownNow); |
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 not sure if the shutdownNow
is the preferred behaviour. I can imagine that some folks might want to implement graceful shutdown with a timeout?
Do we try to call the 'shutdown' or 'close' methods on all the objects even if the first one in the loop throws an exception?
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 it is desired behavior. At this point when we stop the lifecycle all the processing should be done or abandoned. Also this code fallows how it was already implemented in places where there were no resource leaks. I prefer to change one thing at a time.
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 could make this more generic and explicit by having a single registration type
public ClosingBinder register(Class<?> type, Callable callable) ...
public ClosingBinder register(Class<?> type, Class<? extends Annotation> annotation, Callable callable) ...
Then the callers would provide the action
.register(ExecutorService.class, ForAsyncHttp.class, ExecutorService::shutdownNow)
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 have tried to make it generic and I am unable to implement in a simple way. This generic methods requires a possibility to store a different closure method for a resource instance. To implement this I need to store some kind of mapping of a resource instance to its closure method.
The current implementation is simple as executor service will be closed only in a single way and closeable is also closed in single way.
lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/TestClosingBinder.java
Show resolved
Hide resolved
lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/TestClosingBinder.java
Show resolved
Hide resolved
449e796
to
f86153c
Compare
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ClosingBinder.java
Outdated
Show resolved
Hide resolved
{ | ||
executors = newSetBinder(binder, ExecutorService.class, ForCleanup.class); | ||
closeables = newSetBinder(binder, Closeable.class, ForCleanup.class); | ||
binder.bind(Cleanup.class).in(Scopes.SINGLETON); |
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 should be asEagerSingleton()
since it should always be created
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.
ping @kokosing . LGTM otherwise.
BTW I had a Guice question - according to table at https://github.com/google/guice/wiki/Scopes#eager-singletons seems all are equivalent - what do I miss? We seem to inherit PRODUCTION from https://github.com/airlift/airlift/blob/3f2f819f9bc08d848c45c3508e906f3fabdf8d3d/bootstrap/src/main/java/io/airlift/bootstrap/Bootstrap.java#L264
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.
You are correct that in practice it won't make a difference, but bindings shouldn't make assumptions about the stage Guice is running in. It also makes the intent more clear.
lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ClosingBinder.java
Outdated
Show resolved
Hide resolved
f86153c
to
752c139
Compare
Comments addressed, please take a look. |
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.
LGTM.
As of today there's no cases where something other than shutdownNow
is called anyway.
It is easy to make a mistake when closing a resource on a method in the guice module annotated with @PreDestroy. Such method won't be called and resource may leak. This does not affect production, but it may leak resources during the test. ClosingBinder is created in order to help mitigate this problem by introducing simple utility to close resources in guice modules.
There is no logic difference here. This commit replaces existing code with usage of closing binder without changing any logic.
752c139
to
c2b5224
Compare
There is schema registry client that was not closed. |
Yes, good catch. I meant that for executors other than |
nice, thanks @kokosing |