Repository: eclipse-vertx/vert.x

Problem statement:
Vertx.deployVerticle(…); calls completion handler on different context
I'm using `io.vertx:vertx-core:3.9.3` and I have some code in a *Verticle* that looks similar to this:

```java
assert Vertx.currentContext() == context;
vertx.deployVerticle(someVerticle, new DeploymentOptions().setWorker(true), result -> {
  assert Vertx.currentContext() == context; // <-- AssertionError is thrown here
  ...
});
```

The second assertion sometimes fails. I expect `deployVerticle(…)` to always call the completion handler in the context from where it was called. Was this assumption wrong or is this a bug in *Vert.x*? The *Javadoc* does not say anything about it but here is a quote from [the manual](https://vertx.io/docs/vertx-core/groovy/#_standard_verticles):

> When you call any other methods that takes a handler on a core API from an event loop then Vert.x will guarantee that those handlers, when called, will be executed on the same event loop.

I also noticed that the assertion only seems to fail when the deployment fails with the following reason.

> Verticle deployment failed.Could not be added as child of parent verticle

In my experience, this means that the Verticle where I call the method was stopped. When the assertion fails, the context returned by `Vertx.currentContext()` outside and inside the completion handler are named like `EventLoopContext@35d0ced2` and `io.vertx.core.impl.WorkerContext@1bf4c13e` respectively. (Maybe Vert.x calls the handler from the context of the worker which was about to be started?)


Hints:
Thanks for the report, this looks like a bug.

When invoked from a verticle, the context of the deploy callback should be
the same as of the verticle, regardless of the result

Le lun. 12 oct. 2020 à 12:12, Johannes Spangenberg <notifications@github.com>
a écrit :

> I'm using io.vertx:vertx-core:3.9.3 and I have some code in a *Verticle*
> that looks similar to this:
>
> assert Vertx.currentContext() == context;
>
> vertx.deployVerticle(someVerticle, new DeploymentOptions().setWorker(true), result -> {
>
>   assert Vertx.currentContext() == context; // <-- AssertionError is thrown here
>
>   ...
>
> });
>
> The second assertion sometimes fails. I expect deployVerticle(…) to
> always call the completion handler in the context from where it was called.
> Was this assumption wrong or is this a bug in *Vert.x*? The *Javadoc*
> does not say anything about it but here is a quote from the manual
> <https://vertx.io/docs/vertx-core/groovy/#_standard_verticles>:
>
> When you call any other methods that takes a handler on a core API from an
> event loop then Vert.x will guarantee that those handlers, when called,
> will be executed on the same event loop.
>
> I also noticed that the assertion only seems to fail when the deployment
> fails with the following reason.
>
> Verticle deployment failed.Could not be added as child of parent verticle
>
> In my experience, this means that the Verticle where I call the method was
> stopped. When the assertion fails, the context returned by
> Vertx.currentContext() outside and inside the completion handlers are
> named like EventLoopContext@35d0ced2 and
> io.vertx.core.impl.WorkerContext@1bf4c13e respectively. (Maybe Vert.x
> calls the handler from the context of the worker which was about to be
> started?)
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/eclipse-vertx/vert.x/issues/3600>, or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AALOLNWPITCMO7CMVB3S3IDSKLJCNANCNFSM4SMTF6HQ>
> .
>

I was able to reproduce the issue outside of the project were I found it. At least on my machine. The reproduction depends on a race condition. Not sure if it works for you.
https://github.com/JojOatXGME/vertx-issue-3600/blob/main/src/main/java/Issue3600.java

Patch:
diff --git a/src/main/java/io/vertx/core/impl/DeploymentManager.java b/src/main/java/io/vertx/core/impl/DeploymentManager.java
--- a/src/main/java/io/vertx/core/impl/DeploymentManager.java
+++ b/src/main/java/io/vertx/core/impl/DeploymentManager.java
@@ -561,7 +561,7 @@ private void doDeploy(String identifier,
                   deployment.child = true;
                 } else {
                   // Orphan
-                  deployment.undeploy(event -> reportFailure(new NoStackTraceThrowable("Verticle deployment failed.Could not be added as child of parent verticle"), context, completionHandler));
+                  deployment.undeploy(event -> reportFailure(new NoStackTraceThrowable("Verticle deployment failed.Could not be added as child of parent verticle"), callingContext, completionHandler));
                   return;
                 }
               }


Test patch:
diff --git a/src/test/java/io/vertx/core/DeploymentTest.java b/src/test/java/io/vertx/core/DeploymentTest.java
--- a/src/test/java/io/vertx/core/DeploymentTest.java
+++ b/src/test/java/io/vertx/core/DeploymentTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
+ * Copyright (c) 2011-2020 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License 2.0 which is available at
@@ -12,13 +12,16 @@
 package io.vertx.core;
 
 import io.vertx.core.eventbus.Message;
-import io.vertx.core.impl.*;
+import io.vertx.core.impl.ContextInternal;
+import io.vertx.core.impl.Deployment;
+import io.vertx.core.impl.VertxInternal;
 import io.vertx.core.impl.verticle.CompilingClassLoader;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
-import io.vertx.test.core.*;
-import io.vertx.test.verticles.sourceverticle.SourceVerticle;
+import io.vertx.test.core.TestUtils;
+import io.vertx.test.core.VertxTestBase;
 import io.vertx.test.verticles.*;
+import io.vertx.test.verticles.sourceverticle.SourceVerticle;
 import org.junit.Test;
 
 import java.io.File;
@@ -1598,6 +1601,44 @@ public void start(final Promise<Void> startFuture) throws Exception {
     await();
   }
 
+  @Test
+  public void testUndeployParentDuringChildDeployment() throws Exception {
+    CountDownLatch deployLatch = new CountDownLatch(2);
+    CountDownLatch undeployLatch = new CountDownLatch(1);
+
+    MyAsyncVerticle childVerticle = new MyAsyncVerticle(startPromise -> {
+      deployLatch.countDown();
+      Vertx.currentContext().<Void>executeBlocking(prom -> {
+        try {
+          undeployLatch.await();
+          prom.complete();
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+          prom.fail(e.getMessage());
+        }
+      }, startPromise);
+    }, Promise::complete);
+
+    MyAsyncVerticle verticle = new MyAsyncVerticle(startPromise -> {
+      Context parentVerticleContext = Vertx.currentContext();
+      parentVerticleContext.owner().deployVerticle(childVerticle, onFailure(t -> {
+        assertSame(parentVerticleContext, Vertx.currentContext());
+        testComplete();
+      }));
+      startPromise.complete();
+    }, stopPromise -> {
+      undeployLatch.countDown();
+    });
+    AtomicReference<String> deploymentID = new AtomicReference<>();
+    vertx.deployVerticle(verticle, onSuccess(id -> {
+      deploymentID.set(id);
+      deployLatch.countDown();
+    }));
+    awaitLatch(deployLatch);
+    vertx.undeploy(deploymentID.get());
+    await();
+  }
+
   // TODO
 
   // Multi-threaded workers