From b2443553a0eab939ee44159136135f46ffdc32a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 4 Feb 2026 06:10:30 +0100 Subject: [PATCH 1/5] Add more comprehensive information on timeout in BusyIndicator Test --- ..._org_eclipse_swt_custom_BusyIndicator.java | 259 ++++++++++++------ 1 file changed, 174 insertions(+), 85 deletions(-) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java index 2add7920c7..0c7fce023c 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java @@ -17,12 +17,16 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; @@ -34,63 +38,134 @@ public class Test_org_eclipse_swt_custom_BusyIndicator { + private static final long LOOP_TIMEOUT_SECONDS = 5; + private static final long WATCHDOG_TIMEOUT_SECONDS = 25; + + private static class TestState { + final Thread testThread = Thread.currentThread(); + final AtomicBoolean latchWaitEntered = new AtomicBoolean(false); + final AtomicBoolean latchNestedWaitEntered = new AtomicBoolean(false); + final AtomicBoolean showWhileStarted = new AtomicBoolean(false); + final AtomicBoolean showWhileCompleted = new AtomicBoolean(false); + final AtomicBoolean eventLoopDraining = new AtomicBoolean(false); + volatile String currentStage = "initialization"; + + @Override + public String toString() { + return String.format( + "TestState[stage=%s, testThread=%s, latchWaitEntered=%s, latchNestedWaitEntered=%s, showWhileStarted=%s, showWhileCompleted=%s, eventLoopDraining=%s]", + currentStage, testThread.getName(), latchWaitEntered.get(), latchNestedWaitEntered.get(), + showWhileStarted.get(), showWhileCompleted.get(), eventLoopDraining.get()); + } + } + + private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdogExecutor, TestState state) { + return watchdogExecutor.schedule(() -> { + System.err.println("=== WATCHDOG FIRED ==="); + System.err.println("Test state: " + state); + System.err.println(); + System.err.println("=== THREAD DUMP ==="); + Map allStackTraces = Thread.getAllStackTraces(); + for (Map.Entry entry : allStackTraces.entrySet()) { + Thread thread = entry.getKey(); + StackTraceElement[] stackTrace = entry.getValue(); + System.err.println("Thread: " + thread.getName() + " (state=" + thread.getState() + ", id=" + + thread.threadId() + ")"); + for (StackTraceElement element : stackTrace) { + System.err.println(" at " + element); + } + System.err.println(); + } + System.err.println("=== END THREAD DUMP ==="); + state.testThread.interrupt(); + }, WATCHDOG_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + private static void drainEventQueue(Display display, TestState state) { + state.eventLoopDraining.set(true); + long startTime = System.nanoTime(); + long timeoutNanos = TimeUnit.SECONDS.toNanos(LOOP_TIMEOUT_SECONDS); + while (!display.isDisposed() && display.readAndDispatch()) { + if (System.nanoTime() - startTime > timeoutNanos) { + throw new IllegalStateException( + "Event queue not empty after " + LOOP_TIMEOUT_SECONDS + " seconds. State: " + state); + } + } + state.eventLoopDraining.set(false); + } + @Test @Timeout(value = 30) public void testShowWhile() { - // Executors.newSingleThreadExecutor() hangs on some Linux configurations - try (ExecutorService executor = Executors.newFixedThreadPool(2)){ - Shell shell = new Shell(); - Display display = shell.getDisplay(); - Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); - CountDownLatch latch = new CountDownLatch(1); - CompletableFuture future = CompletableFuture.runAsync(() -> { - try { - latch.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - } - }, executor); + TestState state = new TestState(); + try (ExecutorService executor = Executors.newFixedThreadPool(2); + ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) { + ScheduledFuture watchdog = startWatchdog(watchdogExecutor, state); + try { + state.currentStage = "creating shell and display"; + Shell shell = new Shell(); + Display display = shell.getDisplay(); + Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); + CountDownLatch latch = new CountDownLatch(1); + state.currentStage = "creating main future"; + CompletableFuture future = CompletableFuture.runAsync(() -> { + state.latchWaitEntered.set(true); + try { + latch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + }, executor); - CountDownLatch latchNested = new CountDownLatch(1); - CompletableFuture futureNested = CompletableFuture.runAsync(() -> { - try { - latchNested.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - } - }, executor); - - assertNotEquals(busyCursor, shell.getCursor()); - - // This it proves that events on the display are executed - display.asyncExec(() -> { - // This will happen during the showWhile(future) from below. - BusyIndicator.showWhile(futureNested); - }); - - Cursor[] cursorInAsync = new Cursor[2]; - - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor during the nest showWhile. - display.asyncExec(() -> { - cursorInAsync[0] = shell.getCursor(); - latchNested.countDown(); - }); - - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor even after the termination of - // the nested showWhile. - display.asyncExec(() -> { - cursorInAsync[1] = shell.getCursor(); - latch.countDown(); - }); - - BusyIndicator.showWhile(future); - assertTrue(future.isDone()); - assertEquals(busyCursor, cursorInAsync[0]); - assertEquals(busyCursor, cursorInAsync[1]); - shell.dispose(); - while (!display.isDisposed() && display.readAndDispatch()) { + CountDownLatch latchNested = new CountDownLatch(1); + state.currentStage = "creating nested future"; + CompletableFuture futureNested = CompletableFuture.runAsync(() -> { + state.latchNestedWaitEntered.set(true); + try { + latchNested.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + }, executor); + + assertNotEquals(busyCursor, shell.getCursor()); + + // This it proves that events on the display are executed + display.asyncExec(() -> { + // This will happen during the showWhile(future) from below. + BusyIndicator.showWhile(futureNested); + }); + + Cursor[] cursorInAsync = new Cursor[2]; + + // this serves two purpose: + // 1) it proves that events on the display are executed + // 2) it checks that the shell has the busy cursor during the nest showWhile. + display.asyncExec(() -> { + cursorInAsync[0] = shell.getCursor(); + latchNested.countDown(); + }); + + // this serves two purpose: + // 1) it proves that events on the display are executed + // 2) it checks that the shell has the busy cursor even after the termination of + // the nested showWhile. + display.asyncExec(() -> { + cursorInAsync[1] = shell.getCursor(); + latch.countDown(); + }); + + state.currentStage = "calling showWhile"; + state.showWhileStarted.set(true); + BusyIndicator.showWhile(future); + state.showWhileCompleted.set(true); + assertTrue(future.isDone()); + assertEquals(busyCursor, cursorInAsync[0]); + assertEquals(busyCursor, cursorInAsync[1]); + shell.dispose(); + state.currentStage = "draining event queue"; + drainEventQueue(display, state); + state.currentStage = "completed"; + } finally { + watchdog.cancel(false); } } } @@ -98,38 +173,52 @@ public void testShowWhile() { @Test @Timeout(value = 30) public void testShowWhileWithFuture() { - try (ExecutorService executor = Executors.newSingleThreadExecutor()){ - Shell shell = new Shell(); - Display display = shell.getDisplay(); - Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); - Cursor[] cursorInAsync = new Cursor[1]; - CountDownLatch latch = new CountDownLatch(1); - Future future = executor.submit(() -> { - try { - latch.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - } - }); - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor during the nest showWhile. - display.asyncExec(() -> { - cursorInAsync[0] = shell.getCursor(); - latch.countDown(); - }); - //External trigger for minimal latency as advised in the javadoc - executor.submit(()->{ - try { - future.get(); - } catch (Exception e) { - } - display.wake(); - }); - BusyIndicator.showWhile(future); - assertTrue(future.isDone()); - assertEquals(busyCursor, cursorInAsync[0]); - shell.dispose(); - while (!display.isDisposed() && display.readAndDispatch()) { + TestState state = new TestState(); + try (ExecutorService executor = Executors.newSingleThreadExecutor(); + ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) { + ScheduledFuture watchdog = startWatchdog(watchdogExecutor, state); + try { + state.currentStage = "creating shell and display"; + Shell shell = new Shell(); + Display display = shell.getDisplay(); + Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); + Cursor[] cursorInAsync = new Cursor[1]; + CountDownLatch latch = new CountDownLatch(1); + state.currentStage = "creating future"; + Future future = executor.submit(() -> { + state.latchWaitEntered.set(true); + try { + latch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + } + }); + // this serves two purpose: + // 1) it proves that events on the display are executed + // 2) it checks that the shell has the busy cursor during the nest showWhile. + display.asyncExec(() -> { + cursorInAsync[0] = shell.getCursor(); + latch.countDown(); + }); + // External trigger for minimal latency as advised in the javadoc + executor.submit(() -> { + try { + future.get(); + } catch (Exception e) { + } + display.wake(); + }); + state.currentStage = "calling showWhile"; + state.showWhileStarted.set(true); + BusyIndicator.showWhile(future); + state.showWhileCompleted.set(true); + assertTrue(future.isDone()); + assertEquals(busyCursor, cursorInAsync[0]); + shell.dispose(); + state.currentStage = "draining event queue"; + drainEventQueue(display, state); + state.currentStage = "completed"; + } finally { + watchdog.cancel(false); } } } From 1658e8ce12829c18e79350275e1b752019c3f2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 4 Feb 2026 10:38:37 +0100 Subject: [PATCH 2/5] More dbeugging, kill the JVM once watchdog has fired! --- ..._org_eclipse_swt_custom_BusyIndicator.java | 291 ++++++++++++++++-- 1 file changed, 270 insertions(+), 21 deletions(-) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java index 0c7fce023c..c08f1713be 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java @@ -17,8 +17,11 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -27,12 +30,14 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Synchronizer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -41,6 +46,156 @@ public class Test_org_eclipse_swt_custom_BusyIndicator { private static final long LOOP_TIMEOUT_SECONDS = 5; private static final long WATCHDOG_TIMEOUT_SECONDS = 25; + /** + * Debug tracker for SWT internal state. Uses reflection to access private + * fields for diagnostic purposes only. + */ + private static class SwtInternalStateTracker { + private final Display display; + private Field wakeField; + private Field synchronizerField; + +// Event log for tracking what happened + final CopyOnWriteArrayList eventLog = new CopyOnWriteArrayList<>(); + final AtomicInteger wakeCallCount = new AtomicInteger(0); + final AtomicInteger readAndDispatchCallCount = new AtomicInteger(0); + final AtomicInteger readAndDispatchTrueCount = new AtomicInteger(0); + final AtomicInteger sleepCallCount = new AtomicInteger(0); + final AtomicInteger asyncExecScheduledCount = new AtomicInteger(0); + final AtomicInteger asyncExecRunCount = new AtomicInteger(0); + volatile long lastWakeCallTime = 0; + volatile String lastWakeCallThread = null; + volatile long lastSleepEnterTime = 0; + volatile long lastSleepExitTime = 0; + + SwtInternalStateTracker(Display display) { + this.display = display; + initReflection(); + logEvent("SwtInternalStateTracker initialized"); + } + + private void initReflection() { + try { +// Try to access Display.wake field (GTK-specific) + wakeField = Display.class.getDeclaredField("wake"); + wakeField.setAccessible(true); + logEvent("Found Display.wake field (GTK platform)"); + } catch (NoSuchFieldException e) { + logEvent("WARN: Could not find Display.wake field (may not be GTK platform)"); + } + try { + synchronizerField = Display.class.getDeclaredField("synchronizer"); + synchronizerField.setAccessible(true); + logEvent("Found Display.synchronizer field"); + } catch (NoSuchFieldException e) { + logEvent("WARN: Could not find Display.synchronizer field"); + } + } + + void logEvent(String event) { + long ts = System.currentTimeMillis() % 100000; + String threadName = Thread.currentThread().getName(); + eventLog.add(String.format("[%05d] [%-20s] %s", ts, threadName, event)); + } + + void recordWakeCall() { + wakeCallCount.incrementAndGet(); + lastWakeCallTime = System.currentTimeMillis(); + lastWakeCallThread = Thread.currentThread().getName(); + Boolean wakeFlagBefore = getWakeFlag(); + logEvent("display.wake() called (wake flag before=" + wakeFlagBefore + ")"); + } + + void recordAsyncExecScheduled(String description) { + asyncExecScheduledCount.incrementAndGet(); + Boolean syncEmpty = isSynchronizerEmpty(); + logEvent("asyncExec SCHEDULED: " + description + " (syncEmpty before=" + syncEmpty + ")"); + } + + void recordAsyncExecRun(String description) { + asyncExecRunCount.incrementAndGet(); + logEvent("asyncExec RUNNING: " + description); + } + + void recordReadAndDispatch(boolean result) { + readAndDispatchCallCount.incrementAndGet(); + if (result) { + readAndDispatchTrueCount.incrementAndGet(); + } + Boolean wakeFlagNow = getWakeFlag(); + Boolean syncEmpty = isSynchronizerEmpty(); + logEvent("readAndDispatch() returned " + result + " (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty + + ")"); + } + + void recordSleepEnter() { + sleepCallCount.incrementAndGet(); + lastSleepEnterTime = System.currentTimeMillis(); + Boolean wakeFlagNow = getWakeFlag(); + Boolean syncEmpty = isSynchronizerEmpty(); + logEvent("Display.sleep() ENTER (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty + ")"); + } + + void recordSleepExit() { + lastSleepExitTime = System.currentTimeMillis(); + long duration = lastSleepExitTime - lastSleepEnterTime; + Boolean wakeFlagNow = getWakeFlag(); + Boolean syncEmpty = isSynchronizerEmpty(); + logEvent("Display.sleep() EXIT after " + duration + "ms (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty + + ")"); + } + + Boolean getWakeFlag() { + if (wakeField == null) + return null; + try { + return wakeField.getBoolean(display); + } catch (Exception e) { + return null; + } + } + + Boolean isSynchronizerEmpty() { + if (synchronizerField == null) + return null; + try { + Synchronizer sync = (Synchronizer) synchronizerField.get(display); + Method isEmptyMethod = Synchronizer.class.getDeclaredMethod("isMessagesEmpty"); + isEmptyMethod.setAccessible(true); + return (Boolean) isEmptyMethod.invoke(sync); + } catch (Exception e) { + return null; + } + } + + String getInternalStateSummary() { + StringBuilder sb = new StringBuilder(); + sb.append("=== SWT INTERNAL STATE ===\n"); + sb.append(" wake flag (current): ").append(getWakeFlag()).append("\n"); + sb.append(" synchronizer empty (current): ").append(isSynchronizerEmpty()).append("\n"); + sb.append(" display.wake() call count: ").append(wakeCallCount.get()).append("\n"); + sb.append(" last wake() call time: ").append(lastWakeCallTime > 0 ? (lastWakeCallTime % 100000) : "never") + .append("\n"); + sb.append(" last wake() call thread: ").append(lastWakeCallThread != null ? lastWakeCallThread : "none") + .append("\n"); + sb.append(" readAndDispatch() total calls: ").append(readAndDispatchCallCount.get()).append("\n"); + sb.append(" readAndDispatch() returned true: ").append(readAndDispatchTrueCount.get()).append("\n"); + sb.append(" Display.sleep() call count: ").append(sleepCallCount.get()).append("\n"); + sb.append(" asyncExec scheduled count: ").append(asyncExecScheduledCount.get()).append("\n"); + sb.append(" asyncExec actually ran count: ").append(asyncExecRunCount.get()).append("\n"); + sb.append("=== END SWT INTERNAL STATE ==="); + return sb.toString(); + } + + void printEventLog() { + System.err.println("=== SWT EVENT LOG (" + eventLog.size() + " entries) ==="); + for (String event : eventLog) { + System.err.println(event); + } + System.err.println("=== END SWT EVENT LOG ==="); + } + } + private static class TestState { final Thread testThread = Thread.currentThread(); final AtomicBoolean latchWaitEntered = new AtomicBoolean(false); @@ -48,14 +203,34 @@ private static class TestState { final AtomicBoolean showWhileStarted = new AtomicBoolean(false); final AtomicBoolean showWhileCompleted = new AtomicBoolean(false); final AtomicBoolean eventLoopDraining = new AtomicBoolean(false); + final AtomicBoolean futureCompleted = new AtomicBoolean(false); + final AtomicBoolean futureNestedCompleted = new AtomicBoolean(false); volatile String currentStage = "initialization"; + volatile SwtInternalStateTracker swtTracker = null; + +// Track asyncExec callback execution + final AtomicBoolean asyncExec1Ran = new AtomicBoolean(false); + final AtomicBoolean asyncExec2Ran = new AtomicBoolean(false); + final AtomicBoolean asyncExec3Ran = new AtomicBoolean(false); @Override public String toString() { - return String.format( - "TestState[stage=%s, testThread=%s, latchWaitEntered=%s, latchNestedWaitEntered=%s, showWhileStarted=%s, showWhileCompleted=%s, eventLoopDraining=%s]", - currentStage, testThread.getName(), latchWaitEntered.get(), latchNestedWaitEntered.get(), - showWhileStarted.get(), showWhileCompleted.get(), eventLoopDraining.get()); + StringBuilder sb = new StringBuilder(); + sb.append("TestState[\n"); + sb.append(" stage=").append(currentStage).append("\n"); + sb.append(" testThread=").append(testThread.getName()).append("\n"); + sb.append(" latchWaitEntered=").append(latchWaitEntered.get()).append("\n"); + sb.append(" latchNestedWaitEntered=").append(latchNestedWaitEntered.get()).append("\n"); + sb.append(" showWhileStarted=").append(showWhileStarted.get()).append("\n"); + sb.append(" showWhileCompleted=").append(showWhileCompleted.get()).append("\n"); + sb.append(" eventLoopDraining=").append(eventLoopDraining.get()).append("\n"); + sb.append(" futureCompleted=").append(futureCompleted.get()).append("\n"); + sb.append(" futureNestedCompleted=").append(futureNestedCompleted.get()).append("\n"); + sb.append(" asyncExec1Ran=").append(asyncExec1Ran.get()).append("\n"); + sb.append(" asyncExec2Ran=").append(asyncExec2Ran.get()).append("\n"); + sb.append(" asyncExec3Ran=").append(asyncExec3Ran.get()).append("\n"); + sb.append("]"); + return sb.toString(); } } @@ -64,6 +239,15 @@ private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdo System.err.println("=== WATCHDOG FIRED ==="); System.err.println("Test state: " + state); System.err.println(); + +// Print SWT internal state + if (state.swtTracker != null) { + System.err.println(state.swtTracker.getInternalStateSummary()); + System.err.println(); + state.swtTracker.printEventLog(); + System.err.println(); + } + System.err.println("=== THREAD DUMP ==="); Map allStackTraces = Thread.getAllStackTraces(); for (Map.Entry entry : allStackTraces.entrySet()) { @@ -77,12 +261,15 @@ private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdo System.err.println(); } System.err.println("=== END THREAD DUMP ==="); - state.testThread.interrupt(); + Runtime.getRuntime().exit(-1000); }, WATCHDOG_TIMEOUT_SECONDS, TimeUnit.SECONDS); } private static void drainEventQueue(Display display, TestState state) { state.eventLoopDraining.set(true); + if (state.swtTracker != null) { + state.swtTracker.logEvent("drainEventQueue() started"); + } long startTime = System.nanoTime(); long timeoutNanos = TimeUnit.SECONDS.toNanos(LOOP_TIMEOUT_SECONDS); while (!display.isDisposed() && display.readAndDispatch()) { @@ -91,6 +278,9 @@ private static void drainEventQueue(Display display, TestState state) { "Event queue not empty after " + LOOP_TIMEOUT_SECONDS + " seconds. State: " + state); } } + if (state.swtTracker != null) { + state.swtTracker.logEvent("drainEventQueue() completed"); + } state.eventLoopDraining.set(false); } @@ -105,57 +295,87 @@ public void testShowWhile() { state.currentStage = "creating shell and display"; Shell shell = new Shell(); Display display = shell.getDisplay(); + +// Initialize SWT internal state tracker + SwtInternalStateTracker tracker = new SwtInternalStateTracker(display); + state.swtTracker = tracker; + Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); CountDownLatch latch = new CountDownLatch(1); + state.currentStage = "creating main future"; + tracker.logEvent("Creating main future"); CompletableFuture future = CompletableFuture.runAsync(() -> { + tracker.logEvent("Main future task STARTED, entering latch.await()"); state.latchWaitEntered.set(true); try { - latch.await(10, TimeUnit.SECONDS); + boolean completed = latch.await(10, TimeUnit.SECONDS); + tracker.logEvent("Main future latch.await() returned: " + completed); } catch (InterruptedException e) { + tracker.logEvent("Main future latch.await() INTERRUPTED"); } + state.futureCompleted.set(true); + tracker.logEvent("Main future task COMPLETED"); }, executor); CountDownLatch latchNested = new CountDownLatch(1); state.currentStage = "creating nested future"; + tracker.logEvent("Creating nested future"); CompletableFuture futureNested = CompletableFuture.runAsync(() -> { + tracker.logEvent("Nested future task STARTED, entering latch.await()"); state.latchNestedWaitEntered.set(true); try { - latchNested.await(10, TimeUnit.SECONDS); + boolean completed = latchNested.await(10, TimeUnit.SECONDS); + tracker.logEvent("Nested future latch.await() returned: " + completed); } catch (InterruptedException e) { + tracker.logEvent("Nested future latch.await() INTERRUPTED"); } + state.futureNestedCompleted.set(true); + tracker.logEvent("Nested future task COMPLETED"); }, executor); assertNotEquals(busyCursor, shell.getCursor()); - // This it proves that events on the display are executed +// asyncExec #1: This will call nested showWhile + tracker.recordAsyncExecScheduled("#1: BusyIndicator.showWhile(futureNested)"); display.asyncExec(() -> { - // This will happen during the showWhile(future) from below. + tracker.recordAsyncExecRun("#1: BusyIndicator.showWhile(futureNested)"); + state.asyncExec1Ran.set(true); BusyIndicator.showWhile(futureNested); + tracker.logEvent("asyncExec #1: showWhile(futureNested) returned"); }); Cursor[] cursorInAsync = new Cursor[2]; - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor during the nest showWhile. +// asyncExec #2: Check cursor and release nested latch + tracker.recordAsyncExecScheduled("#2: check cursor + latchNested.countDown()"); display.asyncExec(() -> { + tracker.recordAsyncExecRun("#2: check cursor + latchNested.countDown()"); + state.asyncExec2Ran.set(true); cursorInAsync[0] = shell.getCursor(); + tracker.logEvent("asyncExec #2: cursor=" + cursorInAsync[0] + ", calling latchNested.countDown()"); latchNested.countDown(); }); - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor even after the termination of - // the nested showWhile. +// asyncExec #3: Check cursor and release main latch + tracker.recordAsyncExecScheduled("#3: check cursor + latch.countDown()"); display.asyncExec(() -> { + tracker.recordAsyncExecRun("#3: check cursor + latch.countDown()"); + state.asyncExec3Ran.set(true); cursorInAsync[1] = shell.getCursor(); + tracker.logEvent("asyncExec #3: cursor=" + cursorInAsync[1] + ", calling latch.countDown()"); latch.countDown(); }); state.currentStage = "calling showWhile"; state.showWhileStarted.set(true); + tracker.logEvent("About to call BusyIndicator.showWhile(future)"); + tracker.logEvent("State before showWhile: wake=" + tracker.getWakeFlag() + ", syncEmpty=" + + tracker.isSynchronizerEmpty()); + BusyIndicator.showWhile(future); + + tracker.logEvent("BusyIndicator.showWhile(future) returned"); state.showWhileCompleted.set(true); assertTrue(future.isDone()); assertEquals(busyCursor, cursorInAsync[0]); @@ -164,6 +384,7 @@ public void testShowWhile() { state.currentStage = "draining event queue"; drainEventQueue(display, state); state.currentStage = "completed"; + tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); } @@ -181,35 +402,62 @@ public void testShowWhileWithFuture() { state.currentStage = "creating shell and display"; Shell shell = new Shell(); Display display = shell.getDisplay(); + +// Initialize SWT internal state tracker + SwtInternalStateTracker tracker = new SwtInternalStateTracker(display); + state.swtTracker = tracker; + Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); Cursor[] cursorInAsync = new Cursor[1]; CountDownLatch latch = new CountDownLatch(1); + state.currentStage = "creating future"; + tracker.logEvent("Creating future"); Future future = executor.submit(() -> { + tracker.logEvent("Future task STARTED, entering latch.await()"); state.latchWaitEntered.set(true); try { - latch.await(10, TimeUnit.SECONDS); + boolean completed = latch.await(10, TimeUnit.SECONDS); + tracker.logEvent("Future latch.await() returned: " + completed); } catch (InterruptedException e) { + tracker.logEvent("Future latch.await() INTERRUPTED"); } + state.futureCompleted.set(true); + tracker.logEvent("Future task COMPLETED"); }); - // this serves two purpose: - // 1) it proves that events on the display are executed - // 2) it checks that the shell has the busy cursor during the nest showWhile. + +// asyncExec: Check cursor and release latch + tracker.recordAsyncExecScheduled("#1: check cursor + latch.countDown()"); display.asyncExec(() -> { + tracker.recordAsyncExecRun("#1: check cursor + latch.countDown()"); + state.asyncExec1Ran.set(true); cursorInAsync[0] = shell.getCursor(); + tracker.logEvent("asyncExec #1: cursor=" + cursorInAsync[0] + ", calling latch.countDown()"); latch.countDown(); }); - // External trigger for minimal latency as advised in the javadoc + +// External trigger for minimal latency as advised in the javadoc executor.submit(() -> { + tracker.logEvent("Wake trigger task: waiting for future.get()"); try { future.get(); + tracker.logEvent("Wake trigger task: future.get() returned, calling display.wake()"); } catch (Exception e) { + tracker.logEvent("Wake trigger task: future.get() threw " + e.getClass().getSimpleName()); } + tracker.recordWakeCall(); display.wake(); }); + state.currentStage = "calling showWhile"; state.showWhileStarted.set(true); + tracker.logEvent("About to call BusyIndicator.showWhile(future)"); + tracker.logEvent("State before showWhile: wake=" + tracker.getWakeFlag() + ", syncEmpty=" + + tracker.isSynchronizerEmpty()); + BusyIndicator.showWhile(future); + + tracker.logEvent("BusyIndicator.showWhile(future) returned"); state.showWhileCompleted.set(true); assertTrue(future.isDone()); assertEquals(busyCursor, cursorInAsync[0]); @@ -217,6 +465,7 @@ public void testShowWhileWithFuture() { state.currentStage = "draining event queue"; drainEventQueue(display, state); state.currentStage = "completed"; + tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); } From 1d332835263c044468850501fdea59bcb7aebc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 4 Feb 2026 12:24:04 +0100 Subject: [PATCH 3/5] Print more --- .../.settings/.api_filters | 22 +++++++++---------- .../org/eclipse/swt/custom/BusyIndicator.java | 15 +++++++++++++ ..._org_eclipse_swt_custom_BusyIndicator.java | 6 ++++- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters index 75d51ab81d..d53fa0dddc 100644 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters @@ -1,32 +1,32 @@ - - + + - - + + - + - + - + + - + - - + - + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java index eb14385353..476eb58aa0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java @@ -17,6 +17,7 @@ import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; +import java.util.function.*; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; @@ -33,6 +34,14 @@ public class BusyIndicator { private static final AtomicInteger nextBusyId = new AtomicInteger(); static final String BUSYID_NAME = "SWT BusyIndicator"; //$NON-NLS-1$ static final String BUSY_CURSOR = "SWT BusyIndicator Cursor"; //$NON-NLS-1$ + /** + * @since 3.133 + */ + public static Runnable onWake; + /** + * @since 3.133 + */ + public static Consumer onWakeError; /** * Runs the given Runnable while providing @@ -111,8 +120,14 @@ public static void showWhile(Future future) { stage.handle((nil1, nil2) -> { if (!display.isDisposed()) { try { + if (onWake!=null) { + onWake.run(); + } display.wake(); } catch (SWTException e) { + if (onWakeError!=null) { + onWakeError.accept(e); + } // ignore then, this can happen due to the async nature between our check for // disposed and the actual call to wake the display can be disposed } diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java index c08f1713be..a9f572c6d9 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java @@ -69,6 +69,8 @@ private static class SwtInternalStateTracker { volatile long lastSleepExitTime = 0; SwtInternalStateTracker(Display display) { + BusyIndicator.onWake = () -> logEvent("Called Wake from inside call"); + BusyIndicator.onWakeError = e -> logEvent("Error On wake "+e); this.display = display; initReflection(); logEvent("SwtInternalStateTracker initialized"); @@ -213,6 +215,7 @@ private static class TestState { final AtomicBoolean asyncExec2Ran = new AtomicBoolean(false); final AtomicBoolean asyncExec3Ran = new AtomicBoolean(false); + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -387,6 +390,7 @@ public void testShowWhile() { tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); + state.swtTracker.printEventLog(); } } } @@ -454,7 +458,6 @@ public void testShowWhileWithFuture() { tracker.logEvent("About to call BusyIndicator.showWhile(future)"); tracker.logEvent("State before showWhile: wake=" + tracker.getWakeFlag() + ", syncEmpty=" + tracker.isSynchronizerEmpty()); - BusyIndicator.showWhile(future); tracker.logEvent("BusyIndicator.showWhile(future) returned"); @@ -468,6 +471,7 @@ public void testShowWhileWithFuture() { tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); + state.swtTracker.printEventLog(); } } } From 675b38162e27f525fa8fe4b8e3daa80b273b2485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Wed, 4 Feb 2026 13:59:47 +0100 Subject: [PATCH 4/5] Update debugging infos --- .../.settings/.api_filters | 6 + .../org/eclipse/swt/custom/BusyIndicator.java | 4 +- ..._org_eclipse_swt_custom_BusyIndicator.java | 198 +++++------------- 3 files changed, 58 insertions(+), 150 deletions(-) diff --git a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters index d53fa0dddc..b41fbd2ee7 100644 --- a/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters +++ b/binaries/org.eclipse.swt.gtk.linux.x86_64/.settings/.api_filters @@ -7,6 +7,12 @@ + + + + + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java index 476eb58aa0..254777969c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java @@ -35,11 +35,11 @@ public class BusyIndicator { static final String BUSYID_NAME = "SWT BusyIndicator"; //$NON-NLS-1$ static final String BUSY_CURSOR = "SWT BusyIndicator Cursor"; //$NON-NLS-1$ /** - * @since 3.133 + * @noreference This field is not intended to be referenced by clients. */ public static Runnable onWake; /** - * @since 3.133 + * @noreference This field is not intended to be referenced by clients. */ public static Consumer onWakeError; diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java index a9f572c6d9..9e681867d0 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java @@ -17,8 +17,6 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; @@ -37,7 +35,6 @@ import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Synchronizer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -47,51 +44,26 @@ public class Test_org_eclipse_swt_custom_BusyIndicator { private static final long WATCHDOG_TIMEOUT_SECONDS = 25; /** - * Debug tracker for SWT internal state. Uses reflection to access private - * fields for diagnostic purposes only. + * Lightweight tracker that uses the public BusyIndicator hooks to track + * wake() calls without using reflection, to avoid influencing JVM timing. */ - private static class SwtInternalStateTracker { - private final Display display; - private Field wakeField; - private Field synchronizerField; - -// Event log for tracking what happened + private static class WakeTracker { final CopyOnWriteArrayList eventLog = new CopyOnWriteArrayList<>(); final AtomicInteger wakeCallCount = new AtomicInteger(0); - final AtomicInteger readAndDispatchCallCount = new AtomicInteger(0); - final AtomicInteger readAndDispatchTrueCount = new AtomicInteger(0); - final AtomicInteger sleepCallCount = new AtomicInteger(0); + final AtomicInteger wakeErrorCount = new AtomicInteger(0); final AtomicInteger asyncExecScheduledCount = new AtomicInteger(0); final AtomicInteger asyncExecRunCount = new AtomicInteger(0); - volatile long lastWakeCallTime = 0; - volatile String lastWakeCallThread = null; - volatile long lastSleepEnterTime = 0; - volatile long lastSleepExitTime = 0; - - SwtInternalStateTracker(Display display) { - BusyIndicator.onWake = () -> logEvent("Called Wake from inside call"); - BusyIndicator.onWakeError = e -> logEvent("Error On wake "+e); - this.display = display; - initReflection(); - logEvent("SwtInternalStateTracker initialized"); - } - private void initReflection() { - try { -// Try to access Display.wake field (GTK-specific) - wakeField = Display.class.getDeclaredField("wake"); - wakeField.setAccessible(true); - logEvent("Found Display.wake field (GTK platform)"); - } catch (NoSuchFieldException e) { - logEvent("WARN: Could not find Display.wake field (may not be GTK platform)"); - } - try { - synchronizerField = Display.class.getDeclaredField("synchronizer"); - synchronizerField.setAccessible(true); - logEvent("Found Display.synchronizer field"); - } catch (NoSuchFieldException e) { - logEvent("WARN: Could not find Display.synchronizer field"); - } + WakeTracker() { + BusyIndicator.onWake = () -> { + wakeCallCount.incrementAndGet(); + logEvent("BusyIndicator triggered display.wake()"); + }; + BusyIndicator.onWakeError = e -> { + wakeErrorCount.incrementAndGet(); + logEvent("BusyIndicator wake error: " + e); + }; + logEvent("WakeTracker initialized"); } void logEvent(String event) { @@ -100,18 +72,9 @@ void logEvent(String event) { eventLog.add(String.format("[%05d] [%-20s] %s", ts, threadName, event)); } - void recordWakeCall() { - wakeCallCount.incrementAndGet(); - lastWakeCallTime = System.currentTimeMillis(); - lastWakeCallThread = Thread.currentThread().getName(); - Boolean wakeFlagBefore = getWakeFlag(); - logEvent("display.wake() called (wake flag before=" + wakeFlagBefore + ")"); - } - void recordAsyncExecScheduled(String description) { asyncExecScheduledCount.incrementAndGet(); - Boolean syncEmpty = isSynchronizerEmpty(); - logEvent("asyncExec SCHEDULED: " + description + " (syncEmpty before=" + syncEmpty + ")"); + logEvent("asyncExec SCHEDULED: " + description); } void recordAsyncExecRun(String description) { @@ -119,82 +82,28 @@ void recordAsyncExecRun(String description) { logEvent("asyncExec RUNNING: " + description); } - void recordReadAndDispatch(boolean result) { - readAndDispatchCallCount.incrementAndGet(); - if (result) { - readAndDispatchTrueCount.incrementAndGet(); - } - Boolean wakeFlagNow = getWakeFlag(); - Boolean syncEmpty = isSynchronizerEmpty(); - logEvent("readAndDispatch() returned " + result + " (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty - + ")"); - } - - void recordSleepEnter() { - sleepCallCount.incrementAndGet(); - lastSleepEnterTime = System.currentTimeMillis(); - Boolean wakeFlagNow = getWakeFlag(); - Boolean syncEmpty = isSynchronizerEmpty(); - logEvent("Display.sleep() ENTER (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty + ")"); - } - - void recordSleepExit() { - lastSleepExitTime = System.currentTimeMillis(); - long duration = lastSleepExitTime - lastSleepEnterTime; - Boolean wakeFlagNow = getWakeFlag(); - Boolean syncEmpty = isSynchronizerEmpty(); - logEvent("Display.sleep() EXIT after " + duration + "ms (wake=" + wakeFlagNow + ", syncEmpty=" + syncEmpty - + ")"); - } - - Boolean getWakeFlag() { - if (wakeField == null) - return null; - try { - return wakeField.getBoolean(display); - } catch (Exception e) { - return null; - } - } - - Boolean isSynchronizerEmpty() { - if (synchronizerField == null) - return null; - try { - Synchronizer sync = (Synchronizer) synchronizerField.get(display); - Method isEmptyMethod = Synchronizer.class.getDeclaredMethod("isMessagesEmpty"); - isEmptyMethod.setAccessible(true); - return (Boolean) isEmptyMethod.invoke(sync); - } catch (Exception e) { - return null; - } - } - - String getInternalStateSummary() { + String getSummary() { StringBuilder sb = new StringBuilder(); - sb.append("=== SWT INTERNAL STATE ===\n"); - sb.append(" wake flag (current): ").append(getWakeFlag()).append("\n"); - sb.append(" synchronizer empty (current): ").append(isSynchronizerEmpty()).append("\n"); - sb.append(" display.wake() call count: ").append(wakeCallCount.get()).append("\n"); - sb.append(" last wake() call time: ").append(lastWakeCallTime > 0 ? (lastWakeCallTime % 100000) : "never") - .append("\n"); - sb.append(" last wake() call thread: ").append(lastWakeCallThread != null ? lastWakeCallThread : "none") - .append("\n"); - sb.append(" readAndDispatch() total calls: ").append(readAndDispatchCallCount.get()).append("\n"); - sb.append(" readAndDispatch() returned true: ").append(readAndDispatchTrueCount.get()).append("\n"); - sb.append(" Display.sleep() call count: ").append(sleepCallCount.get()).append("\n"); + sb.append("=== WAKE TRACKER STATE ===\n"); + sb.append(" BusyIndicator.onWake call count: ").append(wakeCallCount.get()).append("\n"); + sb.append(" BusyIndicator.onWakeError count: ").append(wakeErrorCount.get()).append("\n"); sb.append(" asyncExec scheduled count: ").append(asyncExecScheduledCount.get()).append("\n"); sb.append(" asyncExec actually ran count: ").append(asyncExecRunCount.get()).append("\n"); - sb.append("=== END SWT INTERNAL STATE ==="); + sb.append("=== END WAKE TRACKER STATE ==="); return sb.toString(); } void printEventLog() { - System.err.println("=== SWT EVENT LOG (" + eventLog.size() + " entries) ==="); + System.err.println("=== EVENT LOG (" + eventLog.size() + " entries) ==="); for (String event : eventLog) { System.err.println(event); } - System.err.println("=== END SWT EVENT LOG ==="); + System.err.println("=== END EVENT LOG ==="); + } + + void cleanup() { + BusyIndicator.onWake = null; + BusyIndicator.onWakeError = null; } } @@ -208,9 +117,9 @@ private static class TestState { final AtomicBoolean futureCompleted = new AtomicBoolean(false); final AtomicBoolean futureNestedCompleted = new AtomicBoolean(false); volatile String currentStage = "initialization"; - volatile SwtInternalStateTracker swtTracker = null; + volatile WakeTracker wakeTracker = null; -// Track asyncExec callback execution + // Track asyncExec callback execution final AtomicBoolean asyncExec1Ran = new AtomicBoolean(false); final AtomicBoolean asyncExec2Ran = new AtomicBoolean(false); final AtomicBoolean asyncExec3Ran = new AtomicBoolean(false); @@ -243,11 +152,11 @@ private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdo System.err.println("Test state: " + state); System.err.println(); -// Print SWT internal state - if (state.swtTracker != null) { - System.err.println(state.swtTracker.getInternalStateSummary()); + // Print wake tracker state + if (state.wakeTracker != null) { + System.err.println(state.wakeTracker.getSummary()); System.err.println(); - state.swtTracker.printEventLog(); + state.wakeTracker.printEventLog(); System.err.println(); } @@ -270,8 +179,8 @@ private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdo private static void drainEventQueue(Display display, TestState state) { state.eventLoopDraining.set(true); - if (state.swtTracker != null) { - state.swtTracker.logEvent("drainEventQueue() started"); + if (state.wakeTracker != null) { + state.wakeTracker.logEvent("drainEventQueue() started"); } long startTime = System.nanoTime(); long timeoutNanos = TimeUnit.SECONDS.toNanos(LOOP_TIMEOUT_SECONDS); @@ -281,8 +190,8 @@ private static void drainEventQueue(Display display, TestState state) { "Event queue not empty after " + LOOP_TIMEOUT_SECONDS + " seconds. State: " + state); } } - if (state.swtTracker != null) { - state.swtTracker.logEvent("drainEventQueue() completed"); + if (state.wakeTracker != null) { + state.wakeTracker.logEvent("drainEventQueue() completed"); } state.eventLoopDraining.set(false); } @@ -294,15 +203,13 @@ public void testShowWhile() { try (ExecutorService executor = Executors.newFixedThreadPool(2); ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) { ScheduledFuture watchdog = startWatchdog(watchdogExecutor, state); + WakeTracker tracker = new WakeTracker(); + state.wakeTracker = tracker; try { state.currentStage = "creating shell and display"; Shell shell = new Shell(); Display display = shell.getDisplay(); -// Initialize SWT internal state tracker - SwtInternalStateTracker tracker = new SwtInternalStateTracker(display); - state.swtTracker = tracker; - Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); CountDownLatch latch = new CountDownLatch(1); @@ -339,7 +246,7 @@ public void testShowWhile() { assertNotEquals(busyCursor, shell.getCursor()); -// asyncExec #1: This will call nested showWhile + // asyncExec #1: This will call nested showWhile tracker.recordAsyncExecScheduled("#1: BusyIndicator.showWhile(futureNested)"); display.asyncExec(() -> { tracker.recordAsyncExecRun("#1: BusyIndicator.showWhile(futureNested)"); @@ -350,7 +257,7 @@ public void testShowWhile() { Cursor[] cursorInAsync = new Cursor[2]; -// asyncExec #2: Check cursor and release nested latch + // asyncExec #2: Check cursor and release nested latch tracker.recordAsyncExecScheduled("#2: check cursor + latchNested.countDown()"); display.asyncExec(() -> { tracker.recordAsyncExecRun("#2: check cursor + latchNested.countDown()"); @@ -360,7 +267,7 @@ public void testShowWhile() { latchNested.countDown(); }); -// asyncExec #3: Check cursor and release main latch + // asyncExec #3: Check cursor and release main latch tracker.recordAsyncExecScheduled("#3: check cursor + latch.countDown()"); display.asyncExec(() -> { tracker.recordAsyncExecRun("#3: check cursor + latch.countDown()"); @@ -373,8 +280,6 @@ public void testShowWhile() { state.currentStage = "calling showWhile"; state.showWhileStarted.set(true); tracker.logEvent("About to call BusyIndicator.showWhile(future)"); - tracker.logEvent("State before showWhile: wake=" + tracker.getWakeFlag() + ", syncEmpty=" - + tracker.isSynchronizerEmpty()); BusyIndicator.showWhile(future); @@ -390,7 +295,8 @@ public void testShowWhile() { tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); - state.swtTracker.printEventLog(); + tracker.printEventLog(); + tracker.cleanup(); } } } @@ -402,15 +308,13 @@ public void testShowWhileWithFuture() { try (ExecutorService executor = Executors.newSingleThreadExecutor(); ScheduledExecutorService watchdogExecutor = Executors.newSingleThreadScheduledExecutor()) { ScheduledFuture watchdog = startWatchdog(watchdogExecutor, state); + WakeTracker tracker = new WakeTracker(); + state.wakeTracker = tracker; try { state.currentStage = "creating shell and display"; Shell shell = new Shell(); Display display = shell.getDisplay(); -// Initialize SWT internal state tracker - SwtInternalStateTracker tracker = new SwtInternalStateTracker(display); - state.swtTracker = tracker; - Cursor busyCursor = display.getSystemCursor(SWT.CURSOR_WAIT); Cursor[] cursorInAsync = new Cursor[1]; CountDownLatch latch = new CountDownLatch(1); @@ -430,7 +334,7 @@ public void testShowWhileWithFuture() { tracker.logEvent("Future task COMPLETED"); }); -// asyncExec: Check cursor and release latch + // asyncExec: Check cursor and release latch tracker.recordAsyncExecScheduled("#1: check cursor + latch.countDown()"); display.asyncExec(() -> { tracker.recordAsyncExecRun("#1: check cursor + latch.countDown()"); @@ -440,7 +344,7 @@ public void testShowWhileWithFuture() { latch.countDown(); }); -// External trigger for minimal latency as advised in the javadoc + // External trigger for minimal latency as advised in the javadoc executor.submit(() -> { tracker.logEvent("Wake trigger task: waiting for future.get()"); try { @@ -449,15 +353,12 @@ public void testShowWhileWithFuture() { } catch (Exception e) { tracker.logEvent("Wake trigger task: future.get() threw " + e.getClass().getSimpleName()); } - tracker.recordWakeCall(); display.wake(); }); state.currentStage = "calling showWhile"; state.showWhileStarted.set(true); tracker.logEvent("About to call BusyIndicator.showWhile(future)"); - tracker.logEvent("State before showWhile: wake=" + tracker.getWakeFlag() + ", syncEmpty=" - + tracker.isSynchronizerEmpty()); BusyIndicator.showWhile(future); tracker.logEvent("BusyIndicator.showWhile(future) returned"); @@ -471,7 +372,8 @@ public void testShowWhileWithFuture() { tracker.logEvent("Test completed successfully"); } finally { watchdog.cancel(false); - state.swtTracker.printEventLog(); + tracker.printEventLog(); + tracker.cleanup(); } } } From 8019d34f2198ae87d1e962f73c2a42d9e0910089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 5 Feb 2026 09:02:00 +0100 Subject: [PATCH 5/5] Dont exit vm --- .../tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java index 9e681867d0..0f93912fe4 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_BusyIndicator.java @@ -173,7 +173,7 @@ private static ScheduledFuture startWatchdog(ScheduledExecutorService watchdo System.err.println(); } System.err.println("=== END THREAD DUMP ==="); - Runtime.getRuntime().exit(-1000); +// Runtime.getRuntime().exit(-1000); }, WATCHDOG_TIMEOUT_SECONDS, TimeUnit.SECONDS); }