Skip to content

Commit

Permalink
Switching to caching based on reflection (Azure#5576)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuchandy authored Sep 27, 2019
1 parent c14c32e commit a72d864
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 278 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
Expand All @@ -29,16 +26,15 @@
*/
final class ResponseConstructorsCache {
private final ClientLogger logger = new ClientLogger(ResponseConstructorsCache.class);
private final Map<Class<?>, ResponseConstructor> cache = new ConcurrentHashMap<>();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private final Map<Class<?>, Constructor<? extends Response<?>>> cache = new ConcurrentHashMap<>();

/**
* Identify the suitable constructor for the given response class.
*
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
ResponseConstructor get(Class<? extends Response<?>> responseClass) {
Constructor<? extends Response<?>> get(Class<? extends Response<?>> responseClass) {
return this.cache.computeIfAbsent(responseClass, this::locateResponseConstructor);
}

Expand All @@ -58,41 +54,16 @@ ResponseConstructor get(Class<? extends Response<?>> responseClass) {
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
private ResponseConstructor locateResponseConstructor(Class<?> responseClass) {
@SuppressWarnings("unchecked")
private Constructor<? extends Response<?>> locateResponseConstructor(Class<?> responseClass) {
Constructor<?>[] constructors = responseClass.getDeclaredConstructors();
// Sort constructors in the "descending order" of parameter count.
Arrays.sort(constructors, Comparator.comparing(Constructor::getParameterCount, (a, b) -> b - a));
for (Constructor<?> constructor : constructors) {
final int paramCount = constructor.getParameterCount();
if (paramCount >= 3 && paramCount <= 5) {
try {
if (paramCount == 3) {
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(3, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc3.METHOD_TYPE,
ResponseFunc3.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type()).getTarget().invoke());
} else if (paramCount == 4) {
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(4, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc4.METHOD_TYPE,
ResponseFunc4.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type()).getTarget().invoke());
} else {
// paramCount == 5
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(5, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc5.METHOD_TYPE,
ResponseFunc5.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type())
.getTarget().invoke());
}
return (Constructor<? extends Response<?>>) constructor;
} catch (Throwable t) {
throw logger.logExceptionAsError(new RuntimeException(t));
}
Expand All @@ -102,131 +73,67 @@ private ResponseConstructor locateResponseConstructor(Class<?> responseClass) {
}

/**
* Type that represent a {@link Response} constructor and can be used to invoke
* the same constructor.
* Invoke the constructor this type represents.
*
* @param constructor the constructor type
* @param decodedResponse the decoded http response
* @param bodyAsObject the http response content
* @return an instance of a {@link Response} implementation
*/
static final class ResponseConstructor {
private final int parameterCount;
private final Object responseFunc;

/**
* Creates ResponseConstructor.
*
* @param parameterCount the constructor parameter count
* @param responseFunc the functional interface which delegate its abstract method
* invocation to the invocation of a {@link Response} constructor
*/
private ResponseConstructor(int parameterCount, Object responseFunc) {
this.parameterCount = parameterCount;
this.responseFunc = responseFunc;
}
Mono<Response<?>> invoke(final Constructor<? extends Response<?>> constructor,
final HttpResponseDecoder.HttpDecodedResponse decodedResponse,
final Object bodyAsObject) {
final HttpResponse httpResponse = decodedResponse.getSourceResponse();
final HttpRequest httpRequest = httpResponse.getRequest();
final int responseStatusCode = httpResponse.getStatusCode();
final HttpHeaders responseHeaders = httpResponse.getHeaders();

/**
* Invoke the {@link Response} constructor this type represents.
*
* @param decodedResponse the decoded http response
* @param bodyAsObject the http response content
* @return an instance of a {@link Response} implementation
*/
@SuppressWarnings("unchecked")
Mono<Response<?>> invoke(final HttpResponseDecoder.HttpDecodedResponse decodedResponse,
final Object bodyAsObject) {
final HttpResponse httpResponse = decodedResponse.getSourceResponse();
final HttpRequest httpRequest = httpResponse.getRequest();
final int responseStatusCode = httpResponse.getStatusCode();
final HttpHeaders responseHeaders = httpResponse.getHeaders();
switch (this.parameterCount) {
case 3:
try {
return Mono.just((Response<?>) ((ResponseFunc3) this.responseFunc).apply(httpRequest,
final int paramCount = constructor.getParameterCount();
switch (paramCount) {
case 3:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
case 4:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
case 5:
return decodedResponse.getDecodedHeaders()
.map((Function<Object, Response<?>>) decodedHeaders -> {
try {
return constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
case 4:
try {
return Mono.just((Response<?>) ((ResponseFunc4) this.responseFunc).apply(httpRequest,
responseHeaders,
bodyAsObject,
decodedHeaders);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
})
.switchIfEmpty(Mono.defer((Supplier<Mono<Response<?>>>) () -> {
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
case 5:
return decodedResponse.getDecodedHeaders()
.map((Function<Object, Response<?>>) decodedHeaders -> {
try {
return (Response<?>) ((ResponseFunc5) this.responseFunc).apply(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
decodedHeaders);
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
})
.switchIfEmpty(Mono.defer((Supplier<Mono<Response<?>>>) () -> {
try {
return Mono.just((Response<?>) ((ResponseFunc5) this.responseFunc)
.apply(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
null));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
}));
default:
return Mono.error(new IllegalStateException(
"Response constructor with expected parameters not found."));
}
bodyAsObject,
null));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
}));
default:
throw logger.logExceptionAsError(
new IllegalStateException("Response constructor with expected parameters not found."));
}
}

@FunctionalInterface
private interface ResponseFunc3 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc3.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders);
}

@FunctionalInterface
private interface ResponseFunc4 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class,
Object.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc4.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders,
Object body);
}

@FunctionalInterface
private interface ResponseFunc5 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class,
Object.class,
Object.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc5.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders,
Object body,
Object decodedHeaders);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -488,9 +488,9 @@ private Mono<Response<?>> createResponse(HttpDecodedResponse response, Type enti
"Unable to create PagedResponse<T>. Body must be of a type that implements: " + Page.class));
}
}
ResponseConstructorsCache.ResponseConstructor ctr = this.responseConstructorsCache.get(cls);
Constructor<? extends Response<?>> ctr = this.responseConstructorsCache.get(cls);
if (ctr != null) {
return ctr.invoke(response, bodyAsObject);
return this.responseConstructorsCache.invoke(ctr, response, bodyAsObject);
} else {
return Mono.error(new RuntimeException("Cannot find suitable constructor for class " + cls));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,32 @@ public class ResponseConstructorsCacheBenchMark {
private ResponseConstructorsCacheBenchMarkTestData testData;
// Cache Types
private ResponseConstructorsCache defaultCache;
private ResponseConstructorsCacheReflection reflectionCache;
private ResponseConstructorsCacheLambdaMetaFactory lambdaMetaCache;
private ResponseConstructorsNoCacheReflection reflectionNoCache;

@Setup
public void setup() {
testData = new ResponseConstructorsCacheBenchMarkTestData();
defaultCache = new ResponseConstructorsCache();
reflectionCache = new ResponseConstructorsCacheReflection();
lambdaMetaCache = new ResponseConstructorsCacheLambdaMetaFactory();
reflectionNoCache = new ResponseConstructorsNoCacheReflection();
}

@Benchmark
@SuppressWarnings("unchecked")
public void lambdaMetaFactoryCache(Blackhole blackhole) {
public void reflectionCache(Blackhole blackhole) {
ResponseConstructorsCacheBenchMarkTestData.Input[] inputs = testData.inputs();

for (int i = 0; i < inputs.length; i++) {
Class<? extends Response<?>> responseClass =
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using LambdaMetaFactory.
ResponseConstructorsCache.ResponseConstructor constructor = defaultCache.get(responseClass);
// Step1: Locate Constructor using Reflection.
Constructor<? extends Response<?>> constructor = defaultCache.get(responseClass);
if (constructor == null) {
throw new IllegalStateException("Response constructor with expected parameters not found.");
}
// Step2: Invoke Constructor using LambdaMetaFactory functional interface.
Mono<Response<?>> response = constructor.invoke(inputs[i].decodedResponse(),
// Step2: Invoke Constructor using Reflection.
Mono<Response<?>> response = defaultCache.invoke(constructor, inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// avoid JVM dead code detection
blackhole.consume(response.block());
Expand All @@ -68,20 +68,21 @@ public void lambdaMetaFactoryCache(Blackhole blackhole) {

@Benchmark
@SuppressWarnings("unchecked")
public void reflectionCache(Blackhole blackhole) {
public void lambdaMetaFactoryCache(Blackhole blackhole) {
ResponseConstructorsCacheBenchMarkTestData.Input[] inputs = testData.inputs();

for (int i = 0; i < inputs.length; i++) {
Class<? extends Response<?>> responseClass =
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using Reflection.
Constructor<? extends Response<?>> constructor = reflectionCache.get(responseClass);
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using LambdaMetaFactory.
ResponseConstructorsCacheLambdaMetaFactory.ResponseConstructor constructor =
lambdaMetaCache.get(responseClass);
if (constructor == null) {
throw new IllegalStateException("Response constructor with expected parameters not found.");
}
// Step2: Invoke Constructor using Reflection.
Mono<Response<?>> response = reflectionCache.invoke(constructor, inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// Step2: Invoke Constructor using LambdaMetaFactory functional interface.
Mono<Response<?>> response = constructor.invoke(inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// avoid JVM dead code detection
blackhole.consume(response.block());
}
Expand Down
Loading

0 comments on commit a72d864

Please sign in to comment.