mvysny / vaadin-loom

Toying with Project Loom and Vaadin
2 stars 1 forks source link

Remaining virtual threads in "vaadin-loom" project #1

Closed andrejkrajnc closed 1 week ago

andrejkrajnc commented 2 weeks ago

We are testing your “vaadin-loom” project.

If the blocking dialog is opened and we close the web browser then virtual thread remains in stack trace. The virtual thread is never closed/terminated.

It this case also the jcmd Thread.dump_to_file command does not work properly. On jcmd console there is an error “com.vaadin.flow.component.UIDetachedException” and in result there is unfinished stack trace for virtual thread.

Probably after UI detach the virtual threads should be removed.

mvysny commented 1 week ago

Excellent catch, thank you. Let me take a look. I assume that the virtual thread is blocked at MainView:80 in responseFuture.get() and remains blocked even after the UI is closed. However, upon closing of the UI, the MainView.onDetach() should have been called, which should call executor.close() which in turn shuts down the internal ExecutorService, which is running all virtual threads, which should kill and clean up the virtual thread. It could be that this is not done, and I need to figure out some other way. Let me investigate.

mvysny commented 1 week ago

Yup, the virtual thread stacktrace, when blocked in the dialog, looks as follows:

#75 "Vaadin-VirtualThreadExecutor-com.vaadin.flow.component.UI@51d974b0" virtual
      java.base/java.lang.VirtualThread.park(VirtualThread.java:596)
      java.base/java.lang.System$2.parkVirtualThread(System.java:2643)
      java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:54)
      java.base/java.util.concurrent.locks.LockSupport.park(LockSupport.java:219)
      java.base/java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1864)
      java.base/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)
      java.base/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)
      java.base/java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1898)
      java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072)
      com.vaadin.starter.skeleton.MainView.confirmDialog(MainView.java:80)
      com.vaadin.starter.skeleton.MainView.lambda$new$0(MainView.java:40)
      com.vaadin.starter.skeleton.loom.VaadinSuspendingExecutor.lambda$run$0(VaadinSuspendingExecutor.java:54)
      java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
      java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:317)
      java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
      java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
mvysny commented 1 week ago

Reproduced. Steps to reproduce:

  1. Run Main.java and navigate to http://localhost:8080
  2. Press the "Blocking dialog" button
  3. Reload the page via F5
  4. Run jcmd Thread.dump_to_file and observe it throwing com.vaadin.flow.component.UIDetachedException

Unfortunately jcmd doesn't show the stacktrace of UIDetachedException, but it's possible to place a breakpoint into UIDetachedException's constructor when running in debug mode and learn the following stacktrace:

<init>:30, UIDetachedException (com.vaadin.flow.component)
handleAccessDetach:464, UI (com.vaadin.flow.component)
access:564, UI (com.vaadin.flow.component)
access:551, UI (com.vaadin.flow.component)
execute:99, VaadinSuspendingExecutor$UIExecutor (com.vaadin.starter.skeleton.loom)
submitRunContinuation:264, VirtualThread (java.lang)
tryGetStackTrace:1012, VirtualThread (java.lang)
asyncGetStackTrace:953, VirtualThread (java.lang)
getStackTrace:2447, Thread (java.lang)
...
mvysny commented 1 week ago

Fixed. The problem is as follows: when MainView.onDetach() closes the VaadinSuspendingExecutor, it in turn needs to shut down the virtual thread executor. The Executor needs to interrupt() all active virtual threads, in order to terminate them cleanly. VirtualThread.interrupt() calls VirtualThread.unpark(), which in turn calls VirtualThread.submitRunContinuation() which in turn call this.