Let Display#sleep() react to thread interruption - #3061
Conversation
1d16fbe to
a919984
Compare
|
I did not find any OS API method that allows to wait for a message until a timeout occurs instead of the inifinitely blocking to boolean messageReceived = false;
while (!messageReceived && !thread.isInterrupted()) {
messageReceived = OS.PeekMessage (msg, 0, 0, 0, OS.PM_NOREMOVE);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
thread.interrupt();
break;
}
}At least the test locally passes with it. |
Thanks for looking into it, the problem is that it will immediately return but always with 20ms delay (what might be too much), I think we can't do any better then on windows for the moment. |
|
On win32 I think you can leave in waitmessage, but always queue a 50ms timer so that you never wait more than 50ms. I don't think it is particularly trivial to do that though as ideally you want the sleep method to handle that particular timer going off locally rather than waiting for the main event loop to handle it. The gtk side looks good to me on code inspection, but I don't know the implications of changing return value, for example in cases where there are two conditions that caused loop to exit, should it still return true. I don't see any real uses of the sleep return value so far in the code base, so not enough of an issue for me to want to spend more time on it. I guess its too bad we can't throw InterruptedException as that would be a very disruptive API change. |
That's a nice idea but you would indeed need to process the timer event inside the I shortly experimented with a simple timer and a timer callback and both of them do not seem to be processed by the event queue afterwards if the timer is killed after it elapsed (and thus made long wakeupId = OS.SetTimer(0, 0, 50, 0);
boolean result = OS.WaitMessage();
OS.KillTimer(0, wakeupId);long timer = WakeupMessage.sendAfter(50);
boolean result = OS.WaitMessage();
OS.KillTimer(0, timer);
...
private static class WakeupMessage {
private final Callback osCallback;
private WakeupMessage() {
// Has to have TIMERPROC signature, see https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-timerproc
osCallback = new Callback(this, "run", void.class, new Type[] { int.class, int.class, int.class, int.class} );
}
@SuppressWarnings("unused") // Executed as callback method referenced by signature description
public void run(int hwnd, int msg , int idEvent, int dwTime) {
OS.KillTimer(hwnd, idEvent);
System.out.println("lalala");
osCallback.dispose();
}
static long sendAfter(int millis) {
try {
WakeupMessage fakeMessage = new WakeupMessage();
return OS.SetTimer(0, 0, millis, fakeMessage.osCallback.getAddress());
} catch (SWTError error) {
}
return 0;
}
}I now found that is the OS method |
|
I just wanted to mention that I don't want to overcomplicate the matter here and if its not working on windows it is fine for me |
| */ | ||
| if (timeout [0] < 0) timeout [0] = 50; | ||
|
|
||
| wake = false; |
There was a problem hiding this comment.
Isn't the problem setting wake to true before this line with "bad" timing?
I wonder what this line is for... @akurtakov any idea? Maybe it was from a time before the timeout above was set? It seems very odd that Display.wake() would work only if timed to be within the looping 50 ms sleep...
There was a problem hiding this comment.
TBH, I have no clue . This code has been there since the famous SWT HEAD reset commit so no clue where/why/how/what it is there for.
There was a problem hiding this comment.
If I am not mistaken, the wake = false line was introduced together with the timeout in this commit: 400a419
304c0e9 to
cff48ab
Compare
cff48ab to
99966a7
Compare
There was a problem hiding this comment.
Pull request overview
This PR addresses #3059 by making Display#sleep() on GTK and Cocoa stop blocking when the UI thread is interrupted, and adds a JUnit test to validate the behavior (skipping Windows due to WaitMessage() limitations).
Changes:
- GTK: exit the
sleep()wait loop when the UI thread is interrupted and propagate interruption via the method’s return path. - Cocoa: replace the “sleep forever” run-loop wait with a timeout-based loop to allow interruption checks.
- Tests: add a new unit test ensuring
sleep()reacts to thread interruption (disabled on Windows).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Display.java | Adds regression test for interruption responsiveness in Display#sleep() |
| bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java | Updates GTK sleep() loop/return to account for thread interruption |
| bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java | Implements timeout-based run-loop waiting to allow interruption checks |
Comments suppressed due to low confidence (1)
bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java:5226
- Returning only !thread.isInterrupted() can incorrectly report "no event" when the thread is interrupted at the same time an SWT message is queued (e.g., asyncExec/wake). This can violate the documented return semantics (“true if an event requiring dispatching was placed on the queue”). Consider returning true when the synchronizer queue is non-empty, even if the interrupt flag is set.
return !thread.isInterrupted();
| do { | ||
| NSRunLoop.currentRunLoop().runMode(OS.NSDefaultRunLoopMode, NSDate.dateWithTimeIntervalSinceNow(0.05)); | ||
| } while (synchronizer.isMessagesEmpty() && !thread.isInterrupted()); |
| if (!GTK.GTK4) GDK.gdk_threads_enter (); | ||
| sendPostExternalEventDispatchEvent (); | ||
| return true; | ||
| return !thread.isInterrupted(); |
| // Success! Clean up and return | ||
| Thread.interrupted(); // clear flag | ||
| return; |
Fix #3059
I did not found a good way on Windows (@HeikoKlare ?) and would like to get feedback on the MacOS part, maybe @akurtakov / @jonahgraham can take a peek on the gtk side?