-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34636 from cescoffier/run-in-safe-duplicated-context
Add a RunInSafeDuplicatedContext annotation
- Loading branch information
Showing
6 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
extensions/vertx/deployment/src/test/java/io/quarkus/vertx/locals/SafeVertxContextTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package io.quarkus.vertx.locals; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
import static org.junit.jupiter.api.Assertions.fail; | ||
|
||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.inject.Inject; | ||
|
||
import org.assertj.core.api.Assertions; | ||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.spec.JavaArchive; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkus.test.QuarkusUnitTest; | ||
import io.quarkus.vertx.core.runtime.context.SafeVertxContext; | ||
import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle; | ||
import io.smallrye.common.vertx.VertxContext; | ||
import io.vertx.core.Context; | ||
import io.vertx.mutiny.core.Vertx; | ||
|
||
/** | ||
* Verify the behavior of the interceptor handling {@link SafeVertxContext} | ||
*/ | ||
public class SafeVertxContextTest { | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.setArchiveProducer(() -> ShrinkWrap | ||
.create(JavaArchive.class).addClasses(MyBean.class)); | ||
|
||
@Inject | ||
MyBean bean; | ||
|
||
@Inject | ||
Vertx vertx; | ||
|
||
@Test | ||
void testWhenRunningFromUnmarkedDuplicatedContext() throws InterruptedException { | ||
Context dc = VertxContext.getOrCreateDuplicatedContext(vertx.getDelegate()); | ||
CountDownLatch latch = new CountDownLatch(1); | ||
dc.runOnContext(ignored -> { | ||
bean.run(); | ||
bean.runWithForce(); | ||
latch.countDown(); | ||
}); | ||
|
||
Assertions.assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); | ||
} | ||
|
||
@Test | ||
void testWhenRunningFromSafeDuplicatedContext() throws InterruptedException { | ||
Context dc = VertxContext.getOrCreateDuplicatedContext(vertx.getDelegate()); | ||
VertxContextSafetyToggle.setContextSafe(dc, true); | ||
CountDownLatch latch = new CountDownLatch(1); | ||
dc.runOnContext(ignored -> { | ||
bean.run(); | ||
bean.runWithForce(); | ||
latch.countDown(); | ||
}); | ||
|
||
Assertions.assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); | ||
} | ||
|
||
@Test | ||
void testWhenRunningFromUnsafeDuplicatedContext() throws InterruptedException { | ||
Context dc = VertxContext.getOrCreateDuplicatedContext(vertx.getDelegate()); | ||
VertxContextSafetyToggle.setContextSafe(dc, false); | ||
CountDownLatch latch = new CountDownLatch(1); | ||
dc.runOnContext(ignored -> { | ||
try { | ||
bean.run(); | ||
fail("The interceptor should have failed."); | ||
} catch (IllegalStateException e) { | ||
// Expected | ||
} | ||
bean.runWithForce(); | ||
latch.countDown(); | ||
}); | ||
|
||
Assertions.assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); | ||
} | ||
|
||
@Test | ||
void testWhenRunningOnARootContext() throws InterruptedException { | ||
Context dc = vertx.getDelegate().getOrCreateContext(); | ||
CountDownLatch latch = new CountDownLatch(1); | ||
dc.runOnContext(ignored -> { | ||
try { | ||
bean.run(); | ||
fail("The interceptor should have failed."); | ||
} catch (IllegalStateException e) { | ||
// Expected | ||
} | ||
try { | ||
bean.run(); | ||
fail("The interceptor should have failed."); | ||
} catch (IllegalStateException e) { | ||
// Expected | ||
} | ||
latch.countDown(); | ||
}); | ||
|
||
Assertions.assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); | ||
} | ||
|
||
@Test | ||
void testWhenRunningWithoutAContext() { | ||
try { | ||
bean.run(); | ||
fail("The interceptor should have failed."); | ||
} catch (IllegalStateException e) { | ||
// Expected | ||
} | ||
try { | ||
bean.run(); | ||
fail("The interceptor should have failed."); | ||
} catch (IllegalStateException e) { | ||
// Expected | ||
} | ||
} | ||
|
||
@ApplicationScoped | ||
public static class MyBean { | ||
|
||
@SafeVertxContext | ||
public void run() { | ||
assertTrue(VertxContext.isOnDuplicatedContext()); | ||
VertxContextSafetyToggle.validateContextIfExists("ErrorVeto", "ErrorDoubt"); | ||
} | ||
|
||
@SafeVertxContext(force = true) | ||
public void runWithForce() { | ||
assertTrue(VertxContext.isOnDuplicatedContext()); | ||
VertxContextSafetyToggle.validateContextIfExists("ErrorVeto", "ErrorDoubt"); | ||
} | ||
|
||
} | ||
|
||
} |
36 changes: 36 additions & 0 deletions
36
...s/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/SafeVertxContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package io.quarkus.vertx.core.runtime.context; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Inherited; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import jakarta.enterprise.util.Nonbinding; | ||
import jakarta.interceptor.InterceptorBinding; | ||
|
||
/** | ||
* Indicates that the annotated method should be invoked on a safe duplicated context. | ||
* This interceptor binding is a declarative alternative to the {@link VertxContextSafetyToggle}. | ||
* <p> | ||
* <strong>Important:</strong> You must only use this annotation if the annotated method does not access the context | ||
* concurrently. | ||
* <p> | ||
* If the method is not run on a duplicated context, the interceptor fails. | ||
* If the method is invoked on an unmarked duplicated context, the context is marked as `safe` and the method is called. | ||
* If the method is invoked on a `safe` duplicated context, the method is called. | ||
* If the method is invoked on an `unsafe` duplicated context, the interceptor fails, except if {@link #force()} is | ||
* set to {@code true}. In this case, the duplicated context is marked as `safe` and the method is called. | ||
*/ | ||
@InterceptorBinding | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR }) | ||
@Inherited | ||
public @interface SafeVertxContext { | ||
|
||
/** | ||
* @return whether the current safety flag of the current context must be overridden. | ||
*/ | ||
@Nonbinding | ||
boolean force() default false; | ||
} |
40 changes: 40 additions & 0 deletions
40
...time/src/main/java/io/quarkus/vertx/core/runtime/context/SafeVertxContextInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package io.quarkus.vertx.core.runtime.context; | ||
|
||
import jakarta.inject.Inject; | ||
import jakarta.interceptor.AroundInvoke; | ||
import jakarta.interceptor.Interceptor; | ||
|
||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.arc.ArcInvocationContext; | ||
import io.vertx.core.Vertx; | ||
|
||
@SafeVertxContext | ||
@Interceptor | ||
public class SafeVertxContextInterceptor { | ||
|
||
@Inject | ||
Vertx vertx; | ||
|
||
private final static Logger LOGGER = Logger.getLogger(SafeVertxContextInterceptor.class); | ||
|
||
@AroundInvoke | ||
public Object markTheContextSafe(ArcInvocationContext ic) throws Exception { | ||
final io.vertx.core.Context current = vertx.getOrCreateContext(); | ||
if (VertxContextSafetyToggle.isExplicitlyMarkedAsSafe(current)) { | ||
return ic.proceed(); | ||
} | ||
|
||
var annotation = ic.findIterceptorBinding(SafeVertxContext.class); | ||
boolean unsafe = VertxContextSafetyToggle.isExplicitlyMarkedAsUnsafe(current); | ||
if (unsafe && annotation.force()) { | ||
LOGGER.debugf("Force the duplicated context as `safe` while is was explicitly marked as `unsafe` in %s.%s", | ||
ic.getMethod().getDeclaringClass().getName(), ic.getMethod().getName()); | ||
} else if (unsafe) { | ||
throw new IllegalStateException( | ||
"Unable to mark the context as safe, as the current context is explicitly marked as unsafe"); | ||
} | ||
VertxContextSafetyToggle.setContextSafe(current, true); | ||
return ic.proceed(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters