A DESCRIPTION OF THE PROBLEM :
Our Customers use JavaFx/WebComponents. They report that after a few hours their clients become sporadicly unresponsive for several minutes, and when they are inclined to wait long enough it suddenly works again as if nothing happend. Our investigation has shown that when these freezes happen JavaFx will be 'stuck' in native Code, for example in tkDoJsCGarbageCollection.
This behaviour appears to be linked to an update of javafx.
I have created a micro, that does not 'exactly' reprdoduce our customers behaviour but is able to demonstrate a severe performace regression.
I tested a few javafx Versions and found 17.0.9 and older demonstrating 'good' performance/ responsiveness and all currently supported or early access versions to be affected by this bug.
REGRESSION - Customer Java Version: 17.0.17
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached micro
It somewhat speedruns the realworld customer experience of doing many edits in html pages for multiple hours.
CUSTOMER SUBMITTED WORKAROUND :
downgrade to javafx 17.0.2/17.0.9
---------- BEGIN SOURCE ----------
package org.example;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;
import java.util.logging.Logger;
public class Demo3 {
private static String HTML = """
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body style="padding:10px;">
<span style="font-weight:bold;">Die Meldung</span>
<span style="font-weight:bold;color:Blue;">${umsEventText}</span>
<br>
<label>Ja<input type="checkbox" name="boxyes" id="boxyes" data-ums-is-defeasible="false"></label>
<script>
document.getElementById("boxyes").addEventListener("change", function () {
host.journalize("yep");
});
</script>
</body>
</html>
""";
private static final Logger log = Logger.getLogger(Demo3.class.getName());
private static MyHost hardReferenceForHost;
private static CompletableFuture<Void> loadSucceed;
private static CompletableFuture<Void> loadSchedule;
public static void main(String[] args) throws IOException {
log.info("starting");
Platform.startup(new Runnable() {
@Override
public void run() {
}
});
final var timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
final var now = Instant.now();
Platform.runLater(new Runnable() {
@Override
public void run() {
final var between = Duration.between(now, Instant.now());
log.info("between = " + between);
if (between.toSeconds() > 10) {
System.exit(1);
}
}
});
}
});
timer.setRepeats(true);
timer.start();
var executor = Executors.newSingleThreadScheduledExecutor();
//we cant wait for the document to be loaded because thats broken as well, see the other bug report
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
loadSucceed = new CompletableFuture<>();
loadSchedule = new CompletableFuture<>();
load();
try {
loadSchedule.get();
loadSucceed.get(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
//we ran into the other bug, loads sometimes hang for no reason
log.warning("timed out waiting for load to succeed");
}
}
}, 10, 10, TimeUnit.MILLISECONDS);
}
private static void load() {
Platform.runLater(new Runnable() {
@Override
public void run() {
aLoad();
}
});
}
static void addEventListener(final Document document, final EventListener listener) {
if (document == null) {
return;
}
final EventTarget eventTarget = (EventTarget) document;
eventTarget.addEventListener("input", listener, true);
eventTarget.addEventListener("change", listener, true);
eventTarget.addEventListener("customevent", listener, true);
eventTarget.addEventListener("click", listener, true);
}
public static void aLoad() {
final var webView = new WebView();
final WebEngine engine = webView.getEngine();
webView.getEngine().getHistory().setMaxSize(0);
webView.setContextMenuEnabled(false);
webView.getEngine().documentProperty().addListener((obs, oldDoc, newDoc) -> {
log.fine("load suceeded hurraaa = ");
loadSucceed.complete(null);
stuff(engine, webView);
});
engine.loadContent(HTML);
log.fine(" expect load ");
loadSchedule.complete(null);
}
public static class MyHost {
public void processQ2() {
log.fine("Callback through javascript");
}
public void journalize(String message) {
log.fine("message = " + message);
}
}
static void stuff(WebEngine engine, WebView webView) {
final Document document = engine.getDocument();
final JSObject jsObject = (JSObject) engine.executeScript("window");
final NodeList bodyList = engine.getDocument().getElementsByTagName("body");
final Element element = (Element) bodyList.item(0);
hardReferenceForHost = new MyHost();
//because setMember uses a weak reference
jsObject.setMember("host", hardReferenceForHost);
jsObject.setMember("justToConfuseYou", new Object() {
});
engine.executeScript("window.host.processQ2();");
addEventListener(document, new EventListener() {
@Override
public void handleEvent(final Event evt) {
Platform.runLater(() -> {
// System.out.println("evt = " + evt);
});
}
});
engine.setOnError(new EventHandler<WebErrorEvent>() {
@Override
public void handle(final WebErrorEvent webErrorEvent) {
log.warning("Error: " + webErrorEvent.getMessage());
}
});
log.fine(" expect callback through javascript ");
webView.getEngine().executeScript("var evt = document.createEvent(\"MouseEvents\");\n" +
" evt.initMouseEvent(\"click\", true, true, window, 1, 0, 0, 0, 0,\n" +
" false, false, false, false, 0, null);\n" +
"\n" +
" var cb = document.getElementById(\"boxyes\");\n" +
" cb.dispatchEvent(evt);");
}
}
---------- END SOURCE ----------
FREQUENCY :
OFTEN
Our Customers use JavaFx/WebComponents. They report that after a few hours their clients become sporadicly unresponsive for several minutes, and when they are inclined to wait long enough it suddenly works again as if nothing happend. Our investigation has shown that when these freezes happen JavaFx will be 'stuck' in native Code, for example in tkDoJsCGarbageCollection.
This behaviour appears to be linked to an update of javafx.
I have created a micro, that does not 'exactly' reprdoduce our customers behaviour but is able to demonstrate a severe performace regression.
I tested a few javafx Versions and found 17.0.9 and older demonstrating 'good' performance/ responsiveness and all currently supported or early access versions to be affected by this bug.
REGRESSION - Customer Java Version: 17.0.17
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached micro
It somewhat speedruns the realworld customer experience of doing many edits in html pages for multiple hours.
CUSTOMER SUBMITTED WORKAROUND :
downgrade to javafx 17.0.2/17.0.9
---------- BEGIN SOURCE ----------
package org.example;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;
import java.util.logging.Logger;
public class Demo3 {
private static String HTML = """
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body style="padding:10px;">
<span style="font-weight:bold;">Die Meldung</span>
<span style="font-weight:bold;color:Blue;">${umsEventText}</span>
<br>
<label>Ja<input type="checkbox" name="boxyes" id="boxyes" data-ums-is-defeasible="false"></label>
<script>
document.getElementById("boxyes").addEventListener("change", function () {
host.journalize("yep");
});
</script>
</body>
</html>
""";
private static final Logger log = Logger.getLogger(Demo3.class.getName());
private static MyHost hardReferenceForHost;
private static CompletableFuture<Void> loadSucceed;
private static CompletableFuture<Void> loadSchedule;
public static void main(String[] args) throws IOException {
log.info("starting");
Platform.startup(new Runnable() {
@Override
public void run() {
}
});
final var timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
final var now = Instant.now();
Platform.runLater(new Runnable() {
@Override
public void run() {
final var between = Duration.between(now, Instant.now());
log.info("between = " + between);
if (between.toSeconds() > 10) {
System.exit(1);
}
}
});
}
});
timer.setRepeats(true);
timer.start();
var executor = Executors.newSingleThreadScheduledExecutor();
//we cant wait for the document to be loaded because thats broken as well, see the other bug report
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
loadSucceed = new CompletableFuture<>();
loadSchedule = new CompletableFuture<>();
load();
try {
loadSchedule.get();
loadSucceed.get(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
//we ran into the other bug, loads sometimes hang for no reason
log.warning("timed out waiting for load to succeed");
}
}
}, 10, 10, TimeUnit.MILLISECONDS);
}
private static void load() {
Platform.runLater(new Runnable() {
@Override
public void run() {
aLoad();
}
});
}
static void addEventListener(final Document document, final EventListener listener) {
if (document == null) {
return;
}
final EventTarget eventTarget = (EventTarget) document;
eventTarget.addEventListener("input", listener, true);
eventTarget.addEventListener("change", listener, true);
eventTarget.addEventListener("customevent", listener, true);
eventTarget.addEventListener("click", listener, true);
}
public static void aLoad() {
final var webView = new WebView();
final WebEngine engine = webView.getEngine();
webView.getEngine().getHistory().setMaxSize(0);
webView.setContextMenuEnabled(false);
webView.getEngine().documentProperty().addListener((obs, oldDoc, newDoc) -> {
log.fine("load suceeded hurraaa = ");
loadSucceed.complete(null);
stuff(engine, webView);
});
engine.loadContent(HTML);
log.fine(" expect load ");
loadSchedule.complete(null);
}
public static class MyHost {
public void processQ2() {
log.fine("Callback through javascript");
}
public void journalize(String message) {
log.fine("message = " + message);
}
}
static void stuff(WebEngine engine, WebView webView) {
final Document document = engine.getDocument();
final JSObject jsObject = (JSObject) engine.executeScript("window");
final NodeList bodyList = engine.getDocument().getElementsByTagName("body");
final Element element = (Element) bodyList.item(0);
hardReferenceForHost = new MyHost();
//because setMember uses a weak reference
jsObject.setMember("host", hardReferenceForHost);
jsObject.setMember("justToConfuseYou", new Object() {
});
engine.executeScript("window.host.processQ2();");
addEventListener(document, new EventListener() {
@Override
public void handleEvent(final Event evt) {
Platform.runLater(() -> {
// System.out.println("evt = " + evt);
});
}
});
engine.setOnError(new EventHandler<WebErrorEvent>() {
@Override
public void handle(final WebErrorEvent webErrorEvent) {
log.warning("Error: " + webErrorEvent.getMessage());
}
});
log.fine(" expect callback through javascript ");
webView.getEngine().executeScript("var evt = document.createEvent(\"MouseEvents\");\n" +
" evt.initMouseEvent(\"click\", true, true, window, 1, 0, 0, 0, 0,\n" +
" false, false, false, false, 0, null);\n" +
"\n" +
" var cb = document.getElementById(\"boxyes\");\n" +
" cb.dispatchEvent(evt);");
}
}
---------- END SOURCE ----------
FREQUENCY :
OFTEN