Webcomponents sometimes spend multiple seconds or minutes in native code

XMLWordPrintable

    • web
    • windows

      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

        1. Demo3.java
          7 kB
          Praveen Narayanaswamy
        2. Console_log.txt
          5 kB
          Praveen Narayanaswamy

            Assignee:
            Jay Bhaskar
            Reporter:
            Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: