Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive routes - improve @Route method invocation performance #8536

Merged
merged 1 commit into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.quarkus.vertx.web.deployment;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -15,6 +18,10 @@
import java.util.Set;
import java.util.function.Function;

import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.inject.Singleton;

import org.jboss.jandex.AnnotationInstance;
Expand All @@ -29,14 +36,17 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.InjectableReferenceProvider;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassAnnotationExclusion;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
import io.quarkus.arc.impl.CreationalContextImpl;
import io.quarkus.arc.processor.AnnotationStore;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BeanInfo;
Expand All @@ -52,8 +62,11 @@
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.util.HashUtil;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.FieldCreator;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
Expand Down Expand Up @@ -98,6 +111,26 @@ class VertxWebProcessor {
private static final String VALUE_ORDER = "order";
private static final String SLASH = "/";

private static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class);
private static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class,
"getActiveContext", InjectableContext.class, Class.class);
private static final MethodDescriptor ARC_CONTAINER_BEAN = MethodDescriptor.ofMethod(ArcContainer.class, "bean",
InjectableBean.class, String.class);
private static final MethodDescriptor BEAN_GET_SCOPE = MethodDescriptor.ofMethod(InjectableBean.class, "getScope",
Class.class);
private static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class,
Contextual.class,
CreationalContext.class);
private static final MethodDescriptor CONTEXT_GET_IF_PRESENT = MethodDescriptor.ofMethod(Context.class, "get", Object.class,
Contextual.class);
private static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod(
InjectableReferenceProvider.class,
"get", Object.class,
CreationalContext.class);
private static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy",
void.class, Object.class,
CreationalContext.class);

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FeatureBuildItem.VERTX_WEB);
Expand Down Expand Up @@ -295,6 +328,12 @@ void addAdditionalRoutes(
detectConflictingRoutes(matchers);
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void initRouteHandlers(VertxWebRecorder recorder, BeanContainerBuildItem container) {
recorder.initHandlers();
}

@BuildStep
AnnotationsTransformerBuildItem annotationTransformer(CustomScopeAnnotationsBuildItem scopes) {
return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() {
Expand Down Expand Up @@ -365,22 +404,93 @@ private String generateHandler(BeanInfo bean, MethodInfo method, ClassOutput cla
ClassCreator invokerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName)
.interfaces(RouteHandler.class).build();

// The descriptor is: void invokeBean(Object context)
MethodCreator invoke = invokerCreator.getMethodCreator("invokeBean", void.class, Object.class);
// ArcContainer container = Arc.container();
// InjectableBean<Foo: bean = container.bean("1");
// InstanceHandle<Foo> handle = container().instance(bean);
// handle.get().foo(ctx);
ResultHandle containerHandle = invoke
.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class));
ResultHandle beanHandle = invoke.invokeInterfaceMethod(
MethodDescriptor.ofMethod(ArcContainer.class, "bean", InjectableBean.class, String.class),
containerHandle, invoke.load(bean.getIdentifier()));
ResultHandle instanceHandle = invoke.invokeInterfaceMethod(
MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, InjectableBean.class),
containerHandle, beanHandle);
ResultHandle beanInstanceHandle = invoke
.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle);
// Initialized state
FieldCreator beanField = invokerCreator.getFieldCreator("bean", InjectableBean.class)
.setModifiers(ACC_PRIVATE | ACC_FINAL);
FieldCreator contextField = null;
FieldCreator containerField = null;
if (BuiltinScope.APPLICATION.is(bean.getScope()) || BuiltinScope.SINGLETON.is(bean.getScope())) {
// Singleton and application contexts are always active and unambiguous
contextField = invokerCreator.getFieldCreator("context", InjectableContext.class)
.setModifiers(ACC_PRIVATE | ACC_FINAL);
} else {
containerField = invokerCreator.getFieldCreator("container", ArcContainer.class)
.setModifiers(ACC_PRIVATE | ACC_FINAL);
}

implementInitialize(bean, invokerCreator, beanField, contextField, containerField);
implementInvoke(bean, method, invokerCreator, beanField, contextField, containerField);

invokerCreator.close();
return generatedName.replace('/', '.');
}

void implementInitialize(BeanInfo bean, ClassCreator invokerCreator, FieldCreator beanField, FieldCreator contextField,
FieldCreator containerField) {
MethodCreator initialize = invokerCreator.getMethodCreator("initialize", void.class);
ResultHandle containerHandle = initialize
.invokeStaticMethod(ARC_CONTAINER);
ResultHandle beanHandle = initialize.invokeInterfaceMethod(
ARC_CONTAINER_BEAN,
containerHandle, initialize.load(bean.getIdentifier()));
initialize.writeInstanceField(beanField.getFieldDescriptor(), initialize.getThis(), beanHandle);
if (contextField != null) {
initialize.writeInstanceField(contextField.getFieldDescriptor(), initialize.getThis(),
initialize.invokeInterfaceMethod(
ARC_CONTAINER_GET_ACTIVE_CONTEXT,
containerHandle, initialize
.invokeInterfaceMethod(
BEAN_GET_SCOPE,
beanHandle)));
} else {
initialize.writeInstanceField(containerField.getFieldDescriptor(), initialize.getThis(), containerHandle);
}
initialize.returnValue(null);
}

void implementInvoke(BeanInfo bean, MethodInfo method, ClassCreator invokerCreator, FieldCreator beanField,
FieldCreator contextField, FieldCreator containerField) {
// The descriptor is: void invoke(RoutingContext context)
MethodCreator invoke = invokerCreator.getMethodCreator("invoke", void.class, RoutingContext.class);
ResultHandle beanHandle = invoke.readInstanceField(beanField.getFieldDescriptor(), invoke.getThis());
AssignableResultHandle beanInstanceHandle = invoke.createVariable(Object.class);
AssignableResultHandle creationlContextHandle = invoke.createVariable(CreationalContextImpl.class);

if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
// Always create a new dependent instance
invoke.assign(creationlContextHandle,
invoke.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class),
beanHandle));
invoke.assign(beanInstanceHandle, invoke.invokeInterfaceMethod(
INJECTABLE_REF_PROVIDER_GET, beanHandle,
creationlContextHandle));
} else {
ResultHandle contextInvokeHandle;
if (contextField != null) {
contextInvokeHandle = invoke.readInstanceField(contextField.getFieldDescriptor(), invoke.getThis());
} else {
ResultHandle containerInvokeHandle = invoke.readInstanceField(containerField.getFieldDescriptor(),
invoke.getThis());
contextInvokeHandle = invoke.invokeInterfaceMethod(
ARC_CONTAINER_GET_ACTIVE_CONTEXT,
containerInvokeHandle, invoke
.invokeInterfaceMethod(
BEAN_GET_SCOPE,
beanHandle));
invoke.ifNull(contextInvokeHandle).trueBranch().throwException(ContextNotActiveException.class,
"Context not active: " + bean.getScope().getDotName());
}
// First try to obtain the bean via Context.get(bean)
invoke.assign(beanInstanceHandle, invoke.invokeInterfaceMethod(CONTEXT_GET_IF_PRESENT, contextInvokeHandle,
beanHandle));
// If not present, try Context.get(bean,creationalContext)
BytecodeCreator doesNotExist = invoke.ifNull(beanInstanceHandle).trueBranch();
doesNotExist.assign(beanInstanceHandle,
doesNotExist.invokeInterfaceMethod(CONTEXT_GET, contextInvokeHandle, beanHandle,
doesNotExist.newInstance(
MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class),
beanHandle)));
}

ResultHandle paramHandle;
MethodDescriptor methodDescriptor;
Expand All @@ -404,15 +514,12 @@ private String generateHandler(BeanInfo bean, MethodInfo method, ClassOutput cla
// Invoke the business method handler
invoke.invokeVirtualMethod(methodDescriptor, beanInstanceHandle, paramHandle);

// handle.destroy() - destroy dependent instance afterwards
// Destroy dependent instance afterwards
if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
invoke.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "destroy", void.class),
instanceHandle);
invoke.invokeInterfaceMethod(INJECTABLE_BEAN_DESTROY, beanHandle,
beanInstanceHandle, creationlContextHandle);
}
invoke.returnValue(null);

invokerCreator.close();
return generatedName.replace('/', '.');
}

private static String dashify(String value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.quarkus.vertx.web;

import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.PreDestroy;
import javax.enterprise.context.Dependent;

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.vertx.ext.web.RoutingContext;

public class DependentRouteTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(SimpleBean.class));

@Test
public void testSimpleRoute() {
assertFalse(SimpleBean.DESTROYED.get());
when().get("/hello").then().statusCode(200).body(is("Hello!"));
assertTrue(SimpleBean.DESTROYED.get());
}

@Dependent
static class SimpleBean {

static final AtomicBoolean DESTROYED = new AtomicBoolean(false);

@Route(path = "/hello")
void hello(RoutingContext context) {
context.response().setStatusCode(200).end("Hello!");
}

@PreDestroy
void destroy() {
DESTROYED.set(true);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.quarkus.arc.Arc;
import io.quarkus.arc.ManagedContext;
import io.quarkus.arc.runtime.BeanInvoker;
import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser;
import io.quarkus.vertx.web.Route;
import io.vertx.core.Handler;
Expand All @@ -13,7 +12,19 @@
*
* @see Route
*/
public interface RouteHandler extends Handler<RoutingContext>, BeanInvoker<RoutingContext> {
public interface RouteHandler extends Handler<RoutingContext> {

/**
* Initialize the handler instance before it is put into service.
*/
void initialize();

/**
* Invokes the route method.
*
* @param context
*/
void invoke(RoutingContext context);

@Override
default void handle(RoutingContext context) {
Expand All @@ -23,14 +34,14 @@ default void handle(RoutingContext context) {
if (user != null) {
Arc.container().beanManager().fireEvent(user.getSecurityIdentity());
}
invokeBean(context);
invoke(context);
} else {
try {
requestContext.activate();
if (user != null) {
Arc.container().beanManager().fireEvent(user.getSecurityIdentity());
}
invokeBean(context);
invoke(context);
} finally {
requestContext.terminate();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.vertx.web.runtime;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

import io.quarkus.runtime.annotations.Recorder;
Expand All @@ -13,6 +15,8 @@
@Recorder
public class VertxWebRecorder {

static volatile List<RouteHandler> handlers = new ArrayList<>();

@SuppressWarnings("unchecked")
public Handler<RoutingContext> createHandler(String handlerClassName) {
try {
Expand All @@ -22,13 +26,22 @@ public Handler<RoutingContext> createHandler(String handlerClassName) {
}
Class<? extends Handler<RoutingContext>> handlerClazz = (Class<? extends Handler<RoutingContext>>) cl
.loadClass(handlerClassName);
return handlerClazz.getDeclaredConstructor().newInstance();
RouteHandler handler = (RouteHandler) handlerClazz.getDeclaredConstructor().newInstance();
handlers.add(handler);
return handler;
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException
| InvocationTargetException e) {
throw new IllegalStateException("Unable to create route handler: " + handlerClassName, e);
}
}

public void initHandlers() {
for (RouteHandler routeHandler : handlers) {
routeHandler.initialize();
}
handlers.clear();
}

public Function<Router, io.vertx.ext.web.Route> createRouteFunction(RouteMatcher matcher,
Handler<RoutingContext> bodyHandler) {
return new Function<Router, io.vertx.ext.web.Route>() {
Expand Down