SLING-13170 Replace Timer with injectable ScheduledExecutorService#55
SLING-13170 Replace Timer with injectable ScheduledExecutorService#55daniancu wants to merge 5 commits into
Conversation
Replace the fire-and-forget Timer in startProcessing() with a ScheduledExecutorService that is properly shut down on deactivate and can be replaced in tests via setScheduler(). This eliminates the flaky Thread.sleep(4000) in testTopologyChange by using a ManualScheduler that captures tasks for on-demand execution, making the test deterministic and ~8x faster. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Ai-Assisted-By: claude
- Use AtomicReference for thread-safe scheduler access - Guard startProcessing() against null scheduler and RejectedExecutionException when shutdown races with task submission - Model realistic shutdown semantics in ManualScheduler test fake - Add testDeactivatePreventsDelayedNotification covering deactivation clearing pending tasks and schedule/shutdown race safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Ai-Assisted-By: claude
…ivate - testStartProcessingWithNullScheduler: covers the scheduler.get()==null branch when deactivate() has already cleared the AtomicReference - testDeactivateWithNullScheduler: covers deactivate() when scheduler was never initialized or already cleared (including double-deactivate) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Ai-Assisted-By: claude
| * and clears pending tasks; {@link #schedule} after shutdown throws {@link RejectedExecutionException}; | ||
| * {@link #runAll()} skips execution after shutdown. | ||
| */ | ||
| private static class ManualScheduler implements ScheduledExecutorService { |
There was a problem hiding this comment.
I would externalize this internal class into a dedicated class.
|
|
||
| /** | ||
| * Replace the scheduler used for delayed topology change processing. | ||
| * Package-private for testability. |
There was a problem hiding this comment.
This is an OSGi component, why would this setScheduler be needed, if simply another could be injected (if it was made so that it would be an OSGi service as well)?
There was a problem hiding this comment.
it's just a backdoor for testing since tests don't use OSGi. in production we do not need to inject, it runs with the default one
Move ManualScheduler from a private inner class in JobManagerConfigurationTest to a standalone package-private class for better readability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the package-private setScheduler() setter with reflection-based injection in tests, consistent with how startupDelayListener is already injected. The scheduler AtomicReference is now only accessed directly by production code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Ai-Assisted-By: claude
Changes since initial PRCommit 2 — Harden scheduler lifecycle
Commit 3 — Null scheduler tests
Commit 4 — Extract ManualScheduler
Commit 5 — Remove setScheduler from production code
|
|
@stefanseifert IIRC you have a Windows system, can you please check this PR? I get constant failures in the CI for Windows machines, and I would like to understand if this is a Windows problem or rather a CI issue. |
|



Summary
java.util.TimerinstartProcessing()with aScheduledExecutorServicethat is properly shut down ondeactivate(), preventing thread leakssetScheduler()so tests can inject a controllable schedulertestTopologyChangeto use aManualSchedulerthat captures tasks for on-demand execution, eliminating the flakyThread.sleep(4000)and making the test deterministic and ~8x fasterTest plan
mvn test -Dtest=JobManagerConfigurationTest#testTopologyChange— passes in ~1s (was ~9s)mvn test— all 109 tests pass🤖 Generated with Claude Code