Skip to content

Commit b3c34a1

Browse files
committed
[Win32] Avoid blocking operations on Edge due to OS message processing
The Edge browser implementation currently spins the event queue without any possibility to escape in case no OS message or not the expected OS message to proceed occurs and is processed. In order to avoid that operations called on Edge deadlock, this change introduces timeouts for OS event processing inside Edge: - The existing processNextOSMessage() method is replaced with processOSMessagesUntil(condition) that spins the event queue until the condition is met or a timeout occurred - CompletableFutures used for the queuing events on the WebView instance are extended to wake the display in order to avoid that the OS message processing is unnecessary sleeping until a timeout wakes it up
1 parent 5d535ce commit b3c34a1

File tree

1 file changed

+58
-55
lines changed
  • bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser

1 file changed

+58
-55
lines changed

bundles/org.eclipse.swt/Eclipse SWT Browser/win32/org/eclipse/swt/browser/Edge.java

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.time.*;
2222
import java.util.*;
2323
import java.util.concurrent.*;
24+
import java.util.concurrent.atomic.*;
2425
import java.util.function.*;
2526

2627
import org.eclipse.swt.*;
@@ -58,6 +59,7 @@ class Edge extends WebBrowser {
5859
private static final String ABOUT_BLANK = "about:blank";
5960

6061
private static final int MAXIMUM_CREATION_RETRIES = 5;
62+
private static final Duration MAXIMUM_OPERATION_TIME = Duration.ofSeconds(5);
6163

6264
private record WebViewEnvironment(ICoreWebView2Environment environment, ArrayList<Edge> instances) {
6365
public WebViewEnvironment(ICoreWebView2Environment environment) {
@@ -260,9 +262,7 @@ static int callAndWait(long[] ppv, ToIntFunction<IUnknown> callable) {
260262
phr[0] = callable.applyAsInt(completion);
261263
// "completion" callback may be called asynchronously,
262264
// so keep processing next OS message that may call it
263-
while (phr[0] == COM.S_OK && ppv[0] == 0) {
264-
processNextOSMessage();
265-
}
265+
processOSMessagesUntil(() -> phr[0] != COM.S_OK || ppv[0] != 0);
266266
completion.Release();
267267
return phr[0];
268268
}
@@ -280,9 +280,7 @@ static int callAndWait(String[] pstr, ToIntFunction<IUnknown> callable) {
280280
phr[0] = callable.applyAsInt(completion);
281281
// "completion" callback may be called asynchronously,
282282
// so keep processing next OS message that may call it
283-
while (phr[0] == COM.S_OK && pstr[0] == null) {
284-
processNextOSMessage();
285-
}
283+
processOSMessagesUntil(() -> phr[0] != COM.S_OK || pstr[0] != null);
286284
completion.Release();
287285
return phr[0];
288286
}
@@ -298,7 +296,7 @@ class WebViewWrapper {
298296

299297
class WebViewProvider {
300298

301-
private CompletableFuture<WebViewWrapper> webViewWrapperFuture = new CompletableFuture<>();
299+
private CompletableFuture<WebViewWrapper> webViewWrapperFuture = wakeDisplayAfterFuture(new CompletableFuture<>());
302300
private CompletableFuture<Void> lastWebViewTask = webViewWrapperFuture.thenRun(() -> {});;
303301

304302
ICoreWebView2 initializeWebView(ICoreWebView2Controller controller) {
@@ -365,95 +363,91 @@ private ICoreWebView2_13 initializeWebView_13(ICoreWebView2 webView) {
365363
return null;
366364
}
367365

368-
ICoreWebView2 getWebView(boolean waitForPendingWebviewTasksToFinish) {
366+
private WebViewWrapper getWebViewWrapper(boolean waitForPendingWebviewTasksToFinish) {
369367
if(waitForPendingWebviewTasksToFinish) {
370-
waitForFutureToFinish(lastWebViewTask);
368+
processOSMessagesUntil(lastWebViewTask::isDone);
371369
}
372-
return webViewWrapperFuture.join().webView;
370+
return webViewWrapperFuture.join();
371+
}
372+
373+
private WebViewWrapper getWebViewWrapper() {
374+
processOSMessagesUntil(webViewWrapperFuture::isDone);
375+
return webViewWrapperFuture.join();
376+
}
377+
378+
ICoreWebView2 getWebView(boolean waitForPendingWebviewTasksToFinish) {
379+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView;
373380
}
374381

375382
ICoreWebView2_2 getWebView_2(boolean waitForPendingWebviewTasksToFinish) {
376-
if(waitForPendingWebviewTasksToFinish) {
377-
waitForFutureToFinish(lastWebViewTask);
378-
}
379-
return webViewWrapperFuture.join().webView_2;
383+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView_2;
380384
}
381385

382386
boolean isWebView_2Available() {
383-
waitForFutureToFinish(webViewWrapperFuture);
384-
return webViewWrapperFuture.join().webView_2 != null;
387+
return getWebViewWrapper().webView_2 != null;
385388
}
386389

387390
ICoreWebView2_10 getWebView_10(boolean waitForPendingWebviewTasksToFinish) {
388-
if(waitForPendingWebviewTasksToFinish) {
389-
waitForFutureToFinish(lastWebViewTask);
390-
}
391-
return webViewWrapperFuture.join().webView_10;
391+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView_10;
392392
}
393393

394394
boolean isWebView_10Available() {
395-
waitForFutureToFinish(webViewWrapperFuture);
396-
return webViewWrapperFuture.join().webView_10 != null;
395+
return getWebViewWrapper().webView_10 != null;
397396
}
398397

399398
ICoreWebView2_11 getWebView_11(boolean waitForPendingWebviewTasksToFinish) {
400-
if(waitForPendingWebviewTasksToFinish) {
401-
waitForFutureToFinish(lastWebViewTask);
402-
}
403-
return webViewWrapperFuture.join().webView_11;
399+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView_11;
404400
}
405401

406402
boolean isWebView_11Available() {
407-
waitForFutureToFinish(webViewWrapperFuture);
408-
return webViewWrapperFuture.join().webView_11 != null;
403+
return getWebViewWrapper().webView_11 != null;
409404
}
410405

411406
ICoreWebView2_12 getWebView_12(boolean waitForPendingWebviewTasksToFinish) {
412-
if(waitForPendingWebviewTasksToFinish) {
413-
waitForFutureToFinish(lastWebViewTask);
414-
}
415-
return webViewWrapperFuture.join().webView_12;
407+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView_12;
416408
}
417409

418410
boolean isWebView_12Available() {
419-
waitForFutureToFinish(webViewWrapperFuture);
420-
return webViewWrapperFuture.join().webView_12 != null;
411+
return getWebViewWrapper().webView_12 != null;
421412
}
422413

423414
ICoreWebView2_13 getWebView_13(boolean waitForPendingWebviewTasksToFinish) {
424-
if(waitForPendingWebviewTasksToFinish) {
425-
waitForFutureToFinish(lastWebViewTask);
426-
}
427-
return webViewWrapperFuture.join().webView_13;
415+
return getWebViewWrapper(waitForPendingWebviewTasksToFinish).webView_13;
428416
}
429417

430418
boolean isWebView_13Available() {
431-
waitForFutureToFinish(webViewWrapperFuture);
432-
return webViewWrapperFuture.join().webView_13 != null;
419+
return getWebViewWrapper().webView_13 != null;
433420
}
434421

435422
/*
436423
* Schedule a given runnable in a queue to execute when the webView is free and
437424
* has finished all the pending tasks queued before it.
438425
*/
439426
void scheduleWebViewTask(Runnable action) {
440-
lastWebViewTask = lastWebViewTask.thenRun(() -> {
441-
action.run();
427+
lastWebViewTask = wakeDisplayAfterFuture(lastWebViewTask.thenRun(action::run));
428+
}
429+
430+
<T> CompletableFuture<T> wakeDisplayAfterFuture(CompletableFuture<T> future) {
431+
return future.handle((nil1, nil2) -> {
432+
Display display = Display.getDefault();
433+
if (!display.isDisposed()) {
434+
try {
435+
display.wake();
436+
} catch (SWTException e) {
437+
// ignore then, this can happen due to the async nature between our check for
438+
// disposed and the actual call to wake the display can be disposed
439+
}
440+
}
441+
return null;
442442
});
443443
}
444-
445-
private <T> void waitForFutureToFinish(CompletableFuture<T> future) {
446-
while(!future.isDone()) {
447-
processNextOSMessage();
448-
}
449-
}
450-
451444
}
452445

453446
/**
454-
* Processes a single OS message using {@link Display#readAndDispatch()}. This
447+
* Processes single OS messages using {@link Display#readAndDispatch()}. This
455448
* is required for processing the OS events during browser initialization, since
456-
* Edge browser initialization happens asynchronously.
449+
* Edge browser initialization happens asynchronously. Messages are processed
450+
* until the given condition is fulfilled or a timeout occurs.
457451
* <p>
458452
* {@link Display#readAndDispatch()} also processes events scheduled for
459453
* asynchronous execution via {@link Display#asyncExec(Runnable)}. This may
@@ -462,13 +456,22 @@ private <T> void waitForFutureToFinish(CompletableFuture<T> future) {
462456
* events for initialization. Thus, this method does not implement an ordinary
463457
* readAndDispatch loop, but waits for an OS event to be processed.
464458
*/
465-
private static void processNextOSMessage() {
459+
private static void processOSMessagesUntil(Supplier<Boolean> condition) {
466460
Display display = Display.getCurrent();
467461
MSG msg = new MSG();
468-
while (!OS.PeekMessage (msg, 0, 0, 0, OS.PM_NOREMOVE)) {
469-
display.sleep();
462+
AtomicBoolean timeoutOccurred = new AtomicBoolean();
463+
// The timer call also wakes up the display to avoid being stuck in display.sleep()
464+
display.timerExec((int) MAXIMUM_OPERATION_TIME.toMillis(), () -> timeoutOccurred.set(true));
465+
while (!condition.get() && !timeoutOccurred.get()) {
466+
if (OS.PeekMessage(msg, 0, 0, 0, OS.PM_NOREMOVE)) {
467+
display.readAndDispatch();
468+
} else {
469+
display.sleep();
470+
}
471+
}
472+
if (!condition.get()) {
473+
SWT.error(SWT.ERROR_UNSPECIFIED, null, " Waiting for Edge operation to terminate timed out");
470474
}
471-
display.readAndDispatch();
472475
}
473476

474477
static ICoreWebView2CookieManager getCookieManager() {

0 commit comments

Comments
 (0)