-
Notifications
You must be signed in to change notification settings - Fork 16
Use a ScheduledExecutorService to handle periodic tasks #433
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,12 +28,15 @@ | |||||||||||||||||||||
| package com.gluonhq.richtextarea; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import java.util.Objects; | ||||||||||||||||||||||
| import java.util.Timer; | ||||||||||||||||||||||
| import java.util.TimerTask; | ||||||||||||||||||||||
| import java.util.concurrent.Executors; | ||||||||||||||||||||||
| import java.util.concurrent.ScheduledExecutorService; | ||||||||||||||||||||||
| import java.util.concurrent.ScheduledFuture; | ||||||||||||||||||||||
| import java.util.concurrent.TimeUnit; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class SmartTimer { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private Timer timer; | ||||||||||||||||||||||
| private ScheduledExecutorService scheduler; | ||||||||||||||||||||||
| private ScheduledFuture<?> scheduledTask; | ||||||||||||||||||||||
| private final Runnable task; | ||||||||||||||||||||||
| private final long delay; | ||||||||||||||||||||||
| private final long period; | ||||||||||||||||||||||
|
|
@@ -45,22 +48,24 @@ public SmartTimer( Runnable task, long delay, long period) { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public void pause() { | ||||||||||||||||||||||
| if ( timer != null ) { | ||||||||||||||||||||||
| timer.cancel(); | ||||||||||||||||||||||
| timer = null; | ||||||||||||||||||||||
| if (scheduledTask != null) { | ||||||||||||||||||||||
| scheduledTask.cancel(true); | ||||||||||||||||||||||
| scheduledTask = null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (scheduler != null) { | ||||||||||||||||||||||
| scheduler.shutdownNow(); | ||||||||||||||||||||||
|
Comment on lines
+52
to
+56
|
||||||||||||||||||||||
| scheduledTask.cancel(true); | |
| scheduledTask = null; | |
| } | |
| if (scheduler != null) { | |
| scheduler.shutdownNow(); | |
| scheduledTask.cancel(false); | |
| scheduledTask = null; | |
| } | |
| if (scheduler != null) { | |
| scheduler.shutdown(); |
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.
No awaitTermination after shutdownNow().
shutdownNow() returns immediately. If evictUnusedObjects is mid-execution when dispose() calls pause(), the task continues running against potentially nulled-out fields. We have to add
scheduler.shutdownNow();
scheduler.awaitTermination(100, TimeUnit.MILLISECONDS);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.
should set a name like "rta-cache-eviction" for debuggability
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.
that would limit the usage to the existing cache eviction (which TBH I'm not sure we need)
Copilot
AI
Mar 11, 2026
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.
start() schedules the provided task to run on the executor’s background thread. In this project, the only current use (paragraphListView::evictUnusedObjects) traverses JavaFX scene-graph nodes and mutates caches from RichTextAreaSkin, which must occur on the JavaFX Application Thread; running it off-thread can lead to IllegalStateException/undefined behavior. Consider wrapping the runnable so it executes via Platform.runLater(...) (or switching this utility to a JavaFX Timeline), so SmartTimer callbacks are always executed on the FX thread.
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 are not going to make assumptions on how clients of this code are working. There is no guarantee that all users of this code require running on the JavaFX appthread. Moreover, it's bad practice to use the JavaFX appthread until the very last moment where the scene graph is manipulated.
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 there be a scenario where scheduler is null but scheduledTask is not? It doesn't harm to add another if statement before re-initializing scheduledTask ?
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.
That is very unlikely, but theoretically possible if one thread invokes pause and another invokes start. In that case, it doesn't help adding an if-statement, as the system is unstable already.
In order to prevent the potential race condition, I made the start and pause synchronized. There is no scenario now where scheduler is null while scheduledTask is not.
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.
For cache eviction, scheduleWithFixedDelay is semantically more correct (wait N after completion, not wall-clock intervals), comparing to scheduleAtFixedRate
Uh oh!
There was an error while loading. Please reload this page.
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.
cancel(true)beforeshutdownNow()is redundant -- shutdownNow() already interrupts workers and drains the queue.cancel(true) + shutdownNow()interrupts a running task, The originalTimer.cancel()did not interrupt - it only prevented future executions. This is a behavior change that could leaveevictUnusedObjectshalf-done (e.g. fonts evicted but images not). Should we usecancel(false) + shutdown()?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 thought about that. I'm more worried about jobs that keep running, so I'm personally in favor of interrupting.