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

Performance: @ThreadLocal annotated bean causes ClassUtils.forName() upon every access. #11293

Open
patrickmhaller opened this issue Oct 31, 2024 · 1 comment
Labels
type: bug Something isn't working

Comments

@patrickmhaller
Copy link

patrickmhaller commented Oct 31, 2024

Expected Behavior

Access to thread-local values should be typically as fast as possible and avoid expensive operations, such as proxy bean creation or invoking a ClassLoader. The observed repetitive call to io.micronaut.core.reflect.ClassUtils.forName() on every access to a @ThreadLocal bean method seems unintentional to me.

I would have assumed accesses to @ThreadLocal annotated beans to perform comparably to classic java.lang.ThreadLocal. However, using a classic java.lang.ThreadLocal is significantly faster.

AbstractInitializableBeanDefinition.getScope() has a specific optimization for SINGLETON_SCOPE. Possibly similar is conceivable for @ThreadLocal. Or hopefully the generated $DateUtils$Definition$Intercepted.getStdDateFormat()
could avoid the whole getProxyTargetBean() chain entirely.

Actual Behaviour

Referring to below example code, every access to the injected @ThreadLocal annotated bean via
dateUtils.getStdDateFormat();
attempts to

  • DefaultApplicationContext(DefaultBeanContext).getProxyTargetBean
  • DefaultApplicationContext(DefaultBeanContext).findCustomScope
  • eventually load "io.micronaut.runtime.context.scope.ThreadLocal.class" from the ClassLoader.

Steps To Reproduce

Given a minimal bean

@io.micronaut.runtime.context.scope.ThreadLocal
public class DateUtils {

	private final StdDateFormat stdDateFormat = new StdDateFormat();

	public DateFormat getStdDateFormat() {
		return stdDateFormat;
	}
}

consumed via

	@Inject
	DateUtils dateUtils;
	
	DateFormat df = dateUtils.getStdDateFormat();

sees the sequences of logs like

2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Successfully loaded class io.micronaut.runtime.context.scope.ThreadLocal
2024-10-31 00:47:42 [default-nioEventLoopGroup-1-4] DEBUG i.micronaut.core.reflect.ClassUtils - Attempting to dynamically load class io.micronaut.runtime.context.scope.ThreadLocal

if debug logging is configured for io.micronaut.core.reflect.ClassUtils.

Note that the invocations are all on the same thread.

Stacktrace

Thread [pool-2-thread-1] (Suspended (breakpoint at line 276 in ClassUtils))	
	ClassUtils.forName(String, ClassLoader) line: 276	
	$DateUtils$Definition(AbstractInitializableBeanDefinition<T>).lambda$getScope$0(String) line: 311	
	0x00007fbf4c3aa9a0.apply(Object) line: not available	
	Optional<T>.flatMap(Function<? super T,Optional<? extends U>>) line: 289	
	$DateUtils$Definition(AbstractInitializableBeanDefinition<T>).getScope() line: 307	
	DefaultApplicationContext(DefaultBeanContext).findCustomScope(BeanResolutionContext, BeanDefinition<T>, boolean, boolean) line: 3033	
	DefaultApplicationContext(DefaultBeanContext).resolveBeanRegistration(BeanResolutionContext, BeanDefinition<T>, Argument<T>, Qualifier<T>) line: 3005	
	DefaultApplicationContext(DefaultBeanContext).getProxyTargetBean(BeanResolutionContext, BeanDefinition<T>, Argument<T>, Qualifier<T>) line: 1505	
	$DateUtils$Definition$Intercepted.interceptedTarget() line: not available	
	$DateUtils$Definition$Intercepted.getStdDateFormat() line: not available	
	GridRepository.writeLiveMeasurement(IEvent<Named,String>, JsonNode) line: 304	
...

with
name "io.micronaut.runtime.context.scope.ThreadLocal" (id=179)
classLoader ClassLoaders$AppClassLoader (id=103)

Environment Information

  • Operating System: Gentoo Linux
  • JDK: openjdk 21.0.5 2024-10-15 LTS
    OpenJDK Runtime Environment Temurin-21.0.5+11 (build 21.0.5+11-LTS)
    OpenJDK 64-Bit Server VM Temurin-21.0.5+11 (build 21.0.5+11-LTS, mixed mode, sharing)
  • Micronaut 4.6.3

Example Application

n/a

Version

4.6.3

@graemerocher graemerocher added the type: bug Something isn't working label Oct 31, 2024
@graemerocher
Copy link
Contributor

the class loading lookups shouldn't be needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug Something isn't working
Projects
Status: No status
Development

No branches or pull requests

2 participants