From 4c876d2e725523b0ba698e55ccd0d72e22177034 Mon Sep 17 00:00:00 2001 From: Becky Auger-Williams Date: Fri, 24 Apr 2026 11:32:43 +0100 Subject: [PATCH 1/4] Turn UI update thread into a singleton so all updates across all windows come from a single thread --- .../RepresentationUpdateThrottle.java | 18 +++++++++++++++++- .../representation/ToolkitRepresentation.java | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java index a286a27c3a..c5a8be8981 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java @@ -53,6 +53,9 @@ public class RepresentationUpdateThrottle /** Pause between updates to prevent flooding the UI thread */ private static final long update_delay = Preferences.update_delay; + /** Singleton instance of this class */ + private static RepresentationUpdateThrottle INSTANCE; + /** Executor for UI thread */ private final Executor gui_executor; @@ -73,8 +76,21 @@ public class RepresentationUpdateThrottle */ private final Set> updateable = new LinkedHashSet<>(); + /** Get instance of this class to perform updates on the UI thread. This class + * is a singleton to ensure that only one thread is scheduling jobs on the UI + * thread. + * + * @param gui_executor Executor for UI thread + */ + public static RepresentationUpdateThrottle getInstance(final Executor gui_executor) { + if(INSTANCE == null) { + INSTANCE = new RepresentationUpdateThrottle(gui_executor); + } + return INSTANCE; + } + /** @param gui_executor Executor for UI thread */ - public RepresentationUpdateThrottle(final Executor gui_executor) + private RepresentationUpdateThrottle(final Executor gui_executor) { final String name = "RepresentationUpdateThrottle" + instance.incrementAndGet(); logger.log(Level.FINE, "Create " + name); diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java index 957fcbd6ee..ee26bc515b 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/ToolkitRepresentation.java @@ -74,7 +74,7 @@ abstract public class ToolkitRepresentation implements E private final boolean edit_mode; - private final RepresentationUpdateThrottle throttle = new RepresentationUpdateThrottle(this); + private final RepresentationUpdateThrottle throttle = RepresentationUpdateThrottle.getInstance(this); /** * Listener list From db7a5b0d2202f85707919745377fc532702fbfe8 Mon Sep 17 00:00:00 2001 From: Becky Auger-Williams Date: Fri, 24 Apr 2026 14:17:16 +0100 Subject: [PATCH 2/4] Fix test to use new UpdateThrottle singleton --- .../display/builder/representation/UpdateThrottleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/display/representation/src/test/java/org/csstudio/display/builder/representation/UpdateThrottleTest.java b/app/display/representation/src/test/java/org/csstudio/display/builder/representation/UpdateThrottleTest.java index d7112f7797..085513d876 100644 --- a/app/display/representation/src/test/java/org/csstudio/display/builder/representation/UpdateThrottleTest.java +++ b/app/display/representation/src/test/java/org/csstudio/display/builder/representation/UpdateThrottleTest.java @@ -28,7 +28,7 @@ @SuppressWarnings("nls") public class UpdateThrottleTest { - private final RepresentationUpdateThrottle throttle = new RepresentationUpdateThrottle(Executors.newSingleThreadExecutor()); + private final RepresentationUpdateThrottle throttle = RepresentationUpdateThrottle.getInstance(Executors.newSingleThreadExecutor()); private class TestWidgetRepresentation extends WidgetRepresentation { From 50d937e6af7fa08713d0819b81d74775940f1027 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Mon, 27 Apr 2026 10:12:53 +0100 Subject: [PATCH 3/4] Rename INSTANCE to instance and remove previous definition as we now only have a single UpdateThrottle and so do not need to name the instance based on how many there are. --- .../RepresentationUpdateThrottle.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java index c5a8be8981..d1b7ba28c1 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java @@ -38,9 +38,6 @@ @SuppressWarnings("nls") public class RepresentationUpdateThrottle { - /** Instance counter to aid in debugging the throttle start/shutdown */ - private static final AtomicInteger instance = new AtomicInteger(); - /** Period in seconds for logging update performance */ private static final int performance_log_period_secs = Preferences.performance_log_period_secs; @@ -54,7 +51,7 @@ public class RepresentationUpdateThrottle private static final long update_delay = Preferences.update_delay; /** Singleton instance of this class */ - private static RepresentationUpdateThrottle INSTANCE; + private static RepresentationUpdateThrottle instance; /** Executor for UI thread */ private final Executor gui_executor; @@ -83,16 +80,16 @@ public class RepresentationUpdateThrottle * @param gui_executor Executor for UI thread */ public static RepresentationUpdateThrottle getInstance(final Executor gui_executor) { - if(INSTANCE == null) { - INSTANCE = new RepresentationUpdateThrottle(gui_executor); + if(instance == null) { + instance = new RepresentationUpdateThrottle(gui_executor); } - return INSTANCE; + return instance; } /** @param gui_executor Executor for UI thread */ private RepresentationUpdateThrottle(final Executor gui_executor) { - final String name = "RepresentationUpdateThrottle" + instance.incrementAndGet(); + final String name = "RepresentationUpdateThrottle"; logger.log(Level.FINE, "Create " + name); this.gui_executor = gui_executor; throttle_thread = new Thread(this::doRun); From df482b8c2aab857d2c3b114516d9d8da55ba99c7 Mon Sep 17 00:00:00 2001 From: Rebecca Williams Date: Wed, 29 Apr 2026 18:04:11 +0100 Subject: [PATCH 4/4] Only shutdown UpdateThrottle when last reference has been closed --- .../representation/RepresentationUpdateThrottle.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java index d1b7ba28c1..e827336126 100644 --- a/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java +++ b/app/display/representation/src/main/java/org/csstudio/display/builder/representation/RepresentationUpdateThrottle.java @@ -38,6 +38,9 @@ @SuppressWarnings("nls") public class RepresentationUpdateThrottle { + /** Reference counter to determine when to safely shutdown */ + private static final AtomicInteger reference_count = new AtomicInteger(); + /** Period in seconds for logging update performance */ private static final int performance_log_period_secs = Preferences.performance_log_period_secs; @@ -83,6 +86,7 @@ public static RepresentationUpdateThrottle getInstance(final Executor gui_execut if(instance == null) { instance = new RepresentationUpdateThrottle(gui_executor); } + reference_count.incrementAndGet(); return instance; } @@ -231,7 +235,13 @@ private void updateInUI(final WidgetRepresentation[] representations, /** Shutdown the throttle thread and wait for it to exit */ public void shutdown() { + // Only shutdown if this is the last reference + if (reference_count.decrementAndGet() != 0){ + return; + } + run = false; + instance = null; synchronized (updateable) { updateable.notifyAll();