-
Notifications
You must be signed in to change notification settings - Fork 926
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use StackWalker for TTEXT serialization format when on Java 9+ (#1686) (
#2055) Motivation: In `StructContext`, we need to acquire the class name from the stack trace using `Thread.current().getStackTrace()`: - https://github.com/line/armeria/blob/armeria-0.82.0/thrift/src/main/java/com/linecorp/armeria/common/thrift/text/StructContext.java#L104-L164 However, `getStackTrace()` is a high-overhead operation since it has to walk the call stack up to its root. We could leverage Java 9's stack-walking API so we can stop climbing the call stack when we found what we want. Modification: Use StackWalker for getting a full stack trace in Thrift TEXT serialization format for Java 9+ Result: - Better TTEXT performance on Java 9+ - Closes #1686
- Loading branch information
1 parent
1e839a8
commit df3fde1
Showing
5 changed files
with
414 additions
and
58 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
...c/main/java/com/linecorp/armeria/common/thrift/text/AbstractThriftMessageClassFinder.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,110 @@ | ||
/* | ||
* Copyright 2019 LINE Corporation | ||
* | ||
* LINE Corporation licenses this file to you under the Apache License, | ||
* version 2.0 (the "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at: | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
* License for the specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
// ================================================================================================= | ||
// Copyright 2011 Twitter, Inc. | ||
// ------------------------------------------------------------------------------------------------- | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this work except in compliance with the License. | ||
// You may obtain a copy of the License in the LICENSE file, or at: | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// ================================================================================================= | ||
package com.linecorp.armeria.common.thrift.text; | ||
|
||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.Modifier; | ||
import java.util.function.Supplier; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import org.apache.thrift.TApplicationException; | ||
import org.apache.thrift.TBase; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.linecorp.armeria.internal.thrift.TApplicationExceptions; | ||
|
||
abstract class AbstractThriftMessageClassFinder implements Supplier<Class<?>> { | ||
private static final Logger logger = LoggerFactory.getLogger(AbstractThriftMessageClassFinder.class); | ||
|
||
@Nullable | ||
static Class<?> getClassByName(String className) { | ||
try { | ||
return Class.forName(className); | ||
} catch (ClassNotFoundException ex) { | ||
logger.warn("Can't find a class: {}", className, ex); | ||
} | ||
return null; | ||
} | ||
|
||
@Nullable | ||
static Class<?> getMatchedClass(@Nullable Class<?> clazz) { | ||
if (clazz == null) { | ||
return null; | ||
} | ||
// Note, we need to check | ||
// if the class is abstract, because abstract class does not have metaDataMap | ||
// if the class has no-arg constructor, because FieldMetaData.getStructMetaDataMap | ||
// calls clazz.newInstance | ||
if (isTBase(clazz) && !isAbstract(clazz) && hasNoArgConstructor(clazz)) { | ||
return clazz; | ||
} | ||
|
||
if (isTApplicationException(clazz)) { | ||
return clazz; | ||
} | ||
|
||
if (isTApplicationExceptions(clazz)) { | ||
return TApplicationException.class; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
static boolean isTBase(Class<?> clazz) { | ||
return TBase.class.isAssignableFrom(clazz); | ||
} | ||
|
||
private static boolean isTApplicationExceptions(Class<?> clazz) { | ||
return clazz == TApplicationExceptions.class; | ||
} | ||
|
||
private static boolean isTApplicationException(Class<?> clazz) { | ||
return TApplicationException.class.isAssignableFrom(clazz); | ||
} | ||
|
||
private static boolean isAbstract(Class<?> clazz) { | ||
return Modifier.isAbstract(clazz.getModifiers()); | ||
} | ||
|
||
private static boolean hasNoArgConstructor(Class<?> clazz) { | ||
final Constructor<?>[] allConstructors = clazz.getConstructors(); | ||
for (Constructor<?> ctor : allConstructors) { | ||
final Class<?>[] pType = ctor.getParameterTypes(); | ||
if (pType.length == 0) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
...rc/main/java/com/linecorp/armeria/common/thrift/text/DefaultThriftMessageClassFinder.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,55 @@ | ||
/* | ||
* Copyright 2019 LINE Corporation | ||
* | ||
* LINE Corporation licenses this file to you under the Apache License, | ||
* version 2.0 (the "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at: | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
* License for the specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
// ================================================================================================= | ||
// Copyright 2011 Twitter, Inc. | ||
// ------------------------------------------------------------------------------------------------- | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this work except in compliance with the License. | ||
// You may obtain a copy of the License in the LICENSE file, or at: | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// ================================================================================================= | ||
package com.linecorp.armeria.common.thrift.text; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
final class DefaultThriftMessageClassFinder extends AbstractThriftMessageClassFinder { | ||
|
||
@Nullable | ||
@Override | ||
public Class<?> get() { | ||
final StackTraceElement[] frames = | ||
Thread.currentThread().getStackTrace(); | ||
|
||
for (StackTraceElement f : frames) { | ||
final String className = f.getClassName(); | ||
final Class<?> matchedClazz = getMatchedClass(getClassByName(className)); | ||
|
||
if (matchedClazz != null) { | ||
return matchedClazz; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
|
142 changes: 142 additions & 0 deletions
142
...in/java/com/linecorp/armeria/common/thrift/text/StackWalkingThriftMessageClassFinder.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,142 @@ | ||
/* | ||
* Copyright 2019 LINE Corporation | ||
* | ||
* LINE Corporation licenses this file to you under the Apache License, | ||
* version 2.0 (the "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at: | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
* License for the specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
// ================================================================================================= | ||
// Copyright 2011 Twitter, Inc. | ||
// ------------------------------------------------------------------------------------------------- | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this work except in compliance with the License. | ||
// You may obtain a copy of the License in the LICENSE file, or at: | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// ================================================================================================= | ||
package com.linecorp.armeria.common.thrift.text; | ||
|
||
import java.lang.invoke.MethodHandle; | ||
import java.lang.invoke.MethodHandles; | ||
import java.lang.invoke.MethodHandles.Lookup; | ||
import java.lang.invoke.MethodType; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
import java.util.function.Function; | ||
import java.util.stream.Stream; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
final class StackWalkingThriftMessageClassFinder extends AbstractThriftMessageClassFinder { | ||
|
||
private static final Logger logger = | ||
LoggerFactory.getLogger(StackWalkingThriftMessageClassFinder.class); | ||
|
||
private static final String INVOKING_FAIL_MSG = | ||
"Failed to invoke StackWalker.StackFrame.getDeclaringClass():"; | ||
|
||
@Nullable | ||
private final Function<Stream<Object>, Class<?>> walkHandler; | ||
private final MethodHandle walkMH; | ||
private final Object stackWalker; | ||
|
||
StackWalkingThriftMessageClassFinder() throws Throwable { | ||
final ClassLoader classLoader = ClassLoader.getSystemClassLoader(); | ||
final Lookup lookup = MethodHandles.lookup(); | ||
|
||
final Class<?> stackWalkerClass = classLoader.loadClass("java.lang.StackWalker"); | ||
walkMH = lookup.findVirtual(stackWalkerClass, | ||
"walk", | ||
MethodType.methodType(Object.class, Function.class)); | ||
|
||
final Class<?> stackFrameClass = classLoader.loadClass("java.lang.StackWalker$StackFrame"); | ||
Function<Object, Class<?>> getClassByStackFrameTemp; | ||
Object instance; | ||
|
||
try { | ||
// StackWalker instantiate with RETAIN_CLASS_REFERENCE option | ||
final Class<?> Option = classLoader.loadClass("java.lang.StackWalker$Option"); | ||
final MethodHandle getInstanceMH = | ||
lookup.findStatic(stackWalkerClass, | ||
"getInstance", | ||
MethodType.methodType(stackWalkerClass, Option)); | ||
final Enum<?> RETAIN_CLASS_REFERENCE = | ||
Arrays.stream((Enum<?>[]) Option.getEnumConstants()) | ||
.filter(op -> "RETAIN_CLASS_REFERENCE".equals(op.name())) | ||
.findFirst().orElseGet(null); | ||
|
||
if (RETAIN_CLASS_REFERENCE == null) { | ||
throw new IllegalStateException("Failed to get RETAIN_CLASS_REFERENCE option"); | ||
} | ||
instance = getInstanceMH.invoke(RETAIN_CLASS_REFERENCE); | ||
final MethodHandle getDeclaringClassMH = lookup.findVirtual(stackFrameClass, | ||
"getDeclaringClass", | ||
MethodType.methodType(Class.class)); | ||
|
||
getClassByStackFrameTemp = stackFrame -> { | ||
try { | ||
return getMatchedClass((Class<?>) getDeclaringClassMH.invoke(stackFrame)); | ||
} catch (Throwable t) { | ||
logger.warn(INVOKING_FAIL_MSG, t); | ||
} | ||
return null; | ||
}; | ||
} catch (Throwable throwable) { | ||
// StackWalker instantiate without option | ||
logger.warn("Falling back to StackWalker without option:", throwable); | ||
final MethodHandle getInstanceMH = | ||
lookup.findStatic(stackWalkerClass, | ||
"getInstance", | ||
MethodType.methodType(stackWalkerClass)); | ||
final MethodHandle getClassNameMH = | ||
lookup.findVirtual(stackFrameClass, "getClassName", MethodType.methodType(String.class)); | ||
|
||
instance = getInstanceMH.invoke(); | ||
getClassByStackFrameTemp = stackFrame -> { | ||
try { | ||
return getMatchedClass(getClassByName(getClassNameMH.invoke(stackFrame).toString())); | ||
} catch (Throwable t) { | ||
logger.warn(INVOKING_FAIL_MSG, t); | ||
} | ||
return null; | ||
}; | ||
} | ||
|
||
stackWalker = instance; | ||
|
||
final Function<Object, Class<?>> getClassByStackFrame = getClassByStackFrameTemp; | ||
walkHandler = stackFrameStream -> | ||
stackFrameStream.map(getClassByStackFrame) | ||
.filter(Objects::nonNull) | ||
.findFirst() | ||
.orElse(null); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public Class<?> get() { | ||
try { | ||
return (Class<?>) walkMH.invoke(stackWalker, walkHandler); | ||
} catch (Throwable t) { | ||
throw new IllegalStateException("Failed to invoke StackWalker.walk():", t); | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.