vert-x3 / vertx-web

HTTP web applications for Vert.x
Apache License 2.0
1.11k stars 535 forks source link

Weird behaviour using Virtual Threads #2665

Closed MYDIH closed 4 weeks ago

MYDIH commented 1 month ago

Version

4.5.10

Context

Hi !

I have some intances in a production product where calling a specific endpoint will block a LivenessProbe enpoint and in the end kill my kubernetes pod. I just thought of the virtual threads implementation as an event loop spawning new virtual threads on each request (since it's cheap), now I'm not really sure anymore ...

Slightly off topic and potentially wrong

Some following issue though is that we can't use Future.await() in an executeBlocking handler ... Since the handler **is** run in a virtual thread, I would expect Future.await() to run properly, providing async/await like semantics. Drilling down a bit I saw that the context in the executeBlocking handler is not reported as an eventLoop context nor a blocking context (I think that isVertxThread is just false)

Reproducer

import static io.vertx.core.Future.await;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.ThreadingModel;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;

public class TestVirtualThreads extends AbstractVerticle {
  @Override
  public void start() {
    try {
      Router router = Router.router(vertx);
      router
          .route("/health1")
          .handler(
              ctx -> {
                System.out.println("Health1: " + Thread.currentThread().getName());
                ctx.response().end();
              });
      router
          .route("/health2")
          .handler(
              ctx -> {
                System.out.println("Health2: " + Thread.currentThread().getName());
                await(
                    vertx.executeBlocking(
                        () -> {
                          System.out.println("Blocking: " + Thread.currentThread().getName());
                          Thread.sleep(10000);
                          return null;
                        }));
                ctx.response().end();
              });

      vertx.createHttpServer().requestHandler(router).listen(8080);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Vertx.vertx()
        .deployVerticle(
            new TestVirtualThreads(),
            new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD));

    //noinspection InfiniteLoopStatement
    while (true) { // spurious wakeups
      synchronized (TestVirtualThreads.class) {
        TestVirtualThreads.class.wait();
      }
    }
  }
}

Steps to reproduce

  1. Hit the /health1 road and confirm it's responsive
  2. Hit the /health2 road and confirm it's blocking
  3. Hit the /health1 road while blocked on /health2 and see it's blocking too

Extra

Looks like disabling order in the executeBlocking call resolve this, but I don't understand why it impacts non-blocking calls though ...

Thanks for your time !

vietj commented 4 weeks ago

yes I see pretty much what the issue is and most likely we would need to use a different task queue for execute blocking

vietj commented 4 weeks ago

ordered false does not use this queue actually that is why it works correctly

vietj commented 4 weeks ago

this should fix the issue https://github.com/eclipse-vertx/vert.x/pull/5365

vietj commented 4 weeks ago

https://github.com/eclipse-vertx/vert.x/pull/5367