| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
There was a problem hiding this comment.
This PR fixes shutdown/restart correctness in ExecutorServiceManager.stop() to prevent executor/thread leaks and ensure the manager can be restarted cleanly after a stop (including interrupted stop paths).
Changes:
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java | Ensures shutdown logic runs reliably (including scheduled executor) and resets state in finally. |
| operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManagerTest.java | Adds regression tests covering scheduled executor shutdown and restartability after stop. |
operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManagerTest.java:55
manager.stop(SHUTDOWN_TIMEOUT);
manager.start(configurationService);
// start() is a no-op unless stop() reset the started flag, which would leave the manager
// handing out already terminated executors
assertThat(manager.reconcileExecutorService().isShutdown()).isFalse();
assertThat(manager.cachingExecutorService().isShutdown()).isFalse();
assertThat(manager.scheduledExecutorService().isShutdown()).isFalse();
manager.stop(SHUTDOWN_TIMEOUT);
}
Sorry, something went wrong.
…tor manager `ExecutorServiceManager.stop` had three problems, all on the interrupted path or affecting the scheduled executor. `scheduledExecutorService` was never shut down. It is created on every `start()` and exposed through a public accessor, but `stop()` only shut down the reconcile, workflow and caching executors, so the pool (and any non-daemon threads a caller created through the accessor) outlived the operator and leaked again on every restart. The helper pool leaked when interrupted. `Executors.newFixedThreadPool(3)` was created inside the `try` and only shut down on the success path, so an `InterruptedException` from `invokeAll` left three non-daemon threads behind - in a shutdown path, where they then keep the JVM alive. `started` was not reset when interrupted. It was only set to false on the success path, so after an interrupted `stop()` the executors were already shut down but `start()` would see `started == true` and do nothing. The operator then looked started while every `execute` on the terminated reconcile executor failed with `RejectedExecutionException`. Moves the cleanup into a `finally`, includes the scheduled executor in the graceful shutdown, and clears both nullable references there. Adds regression tests for the scheduled executor shutdown and for restartability; the former fails without this change.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
LGTM
Sorry, something went wrong.
There was a problem hiding this comment.
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManagerTest.java:58
void canBeRestartedAfterStop() {
ConfigurationService configurationService = new BaseConfigurationService();
var manager = configurationService.getExecutorServiceManager();
manager.stop(SHUTDOWN_TIMEOUT);
manager.start(configurationService);
// start() is a no-op unless stop() reset the started flag, which would leave the manager
// handing out already terminated executors
assertThat(manager.reconcileExecutorService().isShutdown()).isFalse();
assertThat(manager.cachingExecutorService().isShutdown()).isFalse();
assertThat(manager.scheduledExecutorService().isShutdown()).isFalse();
manager.stop(SHUTDOWN_TIMEOUT);
}
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java:157
} catch (InterruptedException e) {
log.debug("Exception closing executor: {}", e.getLocalizedMessage());
Thread.currentThread().interrupt();
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
ExecutorServiceManager.stop had three problems, all on the interrupted
path or affecting the scheduled executor.
scheduledExecutorService was never shut down. It is created on every
start() and exposed through a public accessor, but stop() only shut
down the reconcile, workflow and caching executors, so the pool (and any
non-daemon threads a caller created through the accessor) outlived the
operator and leaked again on every restart.
The helper pool leaked when interrupted. Executors.newFixedThreadPool(3)
was created inside the try and only shut down on the success path, so an
InterruptedException from invokeAll left three non-daemon threads
behind - in a shutdown path, where they then keep the JVM alive.
started was not reset when interrupted. It was only set to false on the
success path, so after an interrupted stop() the executors were already
shut down but start() would see started == true and do nothing. The
operator then looked started while every execute on the terminated
reconcile executor failed with RejectedExecutionException.
Moves the cleanup into a finally, includes the scheduled executor in the
graceful shutdown, and clears both nullable references there.
Adds regression tests for the scheduled executor shutdown and for
restartability; the former fails without this change.
Part of #3517