This is an automated email from the ASF dual-hosted git repository.
exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new f90656f980 NIFI-15388 Fixed Reference Leak in ReflectionUtils
annotation cache (#10691)
f90656f980 is described below
commit f90656f9809cb0dbfe323aa5cab7369933931765
Author: Bob Paulin <[email protected]>
AuthorDate: Thu Jan 8 15:04:21 2026 -0600
NIFI-15388 Fixed Reference Leak in ReflectionUtils annotation cache (#10691)
- WeakHashMap does not work properly to remove keys and values due to
strong reference to the Key found in the Cache Value
- Keys should be programatically invalidated when the ClassLoader is closed
to allow classes to be Garbage Collected
- Moved ReflectionUtils to nifi-nar-utils so it can be shared by
nifi-framework-nar-utils and nifi-framework-components
Signed-off-by: David Handermann <[email protected]>
---
.../apache/nifi/FlowRegistryClientInitializer.java | 2 +-
.../nifi/init/ControllerServiceInitializer.java | 1 +
.../nifi/init/FlowAnalysisRuleInitializer.java | 1 +
.../nifi/init/ParameterProviderInitializer.java | 1 +
.../org/apache/nifi/init/ProcessorInitializer.java | 1 +
.../java/org/apache/nifi/init/ReflectionUtils.java | 119 ---------------------
.../apache/nifi/init/ReportingTaskInitializer.java | 1 +
.../org/apache/nifi/nar/InstanceClassLoader.java | 2 +
.../java/org/apache/nifi/util/ReflectionUtils.java | 27 ++---
9 files changed, 22 insertions(+), 133 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/FlowRegistryClientInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/FlowRegistryClientInitializer.java
index c9152badd2..1040eb7623 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/FlowRegistryClientInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/FlowRegistryClientInitializer.java
@@ -19,7 +19,6 @@ package org.apache.nifi;
import org.apache.nifi.annotation.lifecycle.OnShutdown;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.init.ConfigurableComponentInitializer;
-import org.apache.nifi.init.ReflectionUtils;
import org.apache.nifi.mock.MockComponentLogger;
import org.apache.nifi.mock.MockConfigurationContext;
import org.apache.nifi.mock.MockFlowRegistryClientInitializationContext;
@@ -28,6 +27,7 @@ import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.registry.flow.FlowRegistryClient;
import org.apache.nifi.registry.flow.FlowRegistryClientInitializationContext;
import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a FlowRegistryClient using a
MockFlowRegistryClientInitializationContext;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ControllerServiceInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ControllerServiceInitializer.java
index ebb225bb33..8b39e0d593 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ControllerServiceInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ControllerServiceInitializer.java
@@ -27,6 +27,7 @@ import
org.apache.nifi.mock.MockControllerServiceInitializationContext;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a ControllerService using a
MockControllerServiceInitializationContext
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/FlowAnalysisRuleInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/FlowAnalysisRuleInitializer.java
index 7e17a983a0..fc2d5b8c21 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/FlowAnalysisRuleInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/FlowAnalysisRuleInitializer.java
@@ -26,6 +26,7 @@ import
org.apache.nifi.mock.MockFlowAnalysisRuleInitializationContext;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a FlowAnalysisRule using a
MockFlowAnalysisRuleInitializationContext;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ParameterProviderInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ParameterProviderInitializer.java
index dea6174df0..ce9708b9a0 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ParameterProviderInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ParameterProviderInitializer.java
@@ -26,6 +26,7 @@ import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.parameter.ParameterProvider;
import org.apache.nifi.parameter.ParameterProviderInitializationContext;
import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a ParameterProvider using a MockReportingInitializationContext;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ProcessorInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ProcessorInitializer.java
index 06b0fe4fd5..3c874aee96 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ProcessorInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ProcessorInitializer.java
@@ -26,6 +26,7 @@ import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.ProcessorInitializationContext;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a Processor using a MockProcessorInitializationContext
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReflectionUtils.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReflectionUtils.java
deleted file mode 100644
index 96366e0530..0000000000
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReflectionUtils.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF 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
- *
- * http://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 org.apache.nifi.init;
-
-import org.apache.nifi.logging.ComponentLog;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * This class is a copy of org.apache.nifi.util.ReflectionUtils. Ultimately the
- * documentation generation component should be moved to a place where it can
- * depend on this directly instead of copying it in.
- *
- *
- */
-public class ReflectionUtils {
-
- private static final Logger LOG =
LoggerFactory.getLogger(ReflectionUtils.class);
-
- /**
- * Invokes all methods on the given instance that have been annotated with
- * the given annotation. If the signature of the method that is defined in
- * <code>instance</code> uses 1 or more parameters, those parameters must
be
- * specified by the <code>args</code> parameter. However, if more arguments
- * are supplied by the <code>args</code> parameter than needed, the extra
- * arguments will be ignored.
- *
- * @param annotation annotation
- * @param instance instance
- * @param logger the ComponentLog to use for logging any errors. If null,
- * will use own logger, but that will not generate bulletins or easily tie
- * to the Processor's log messages.
- * @param args args
- * @return <code>true</code> if all appropriate methods were invoked and
- * returned without throwing an Exception, <code>false</code> if one of the
- * methods threw an Exception or could not be invoked; if
<code>false</code>
- * is returned, an error will have been logged.
- */
- public static boolean quietlyInvokeMethodsWithAnnotation(final Class<?
extends Annotation> annotation, final Object instance, final ComponentLog
logger, final Object... args) {
-
- for (final Method method : instance.getClass().getMethods()) {
- if (method.isAnnotationPresent(annotation)) {
- method.setAccessible(true);
- final Class<?>[] argumentTypes = method.getParameterTypes();
- if (argumentTypes.length > args.length) {
- if (logger == null) {
- LOG.error("Unable to invoke method {} on {} because
method expects {} parameters but only {} were given", method.getName(),
instance, argumentTypes.length, args.length);
- } else {
- logger.error("Unable to invoke method {} on {} because
method expects {} parameters but only {} were given", method.getName(),
instance, argumentTypes.length, args.length);
- }
-
- return false;
- }
-
- for (int i = 0; i < argumentTypes.length; i++) {
- final Class<?> argType = argumentTypes[i];
- if (!argType.isAssignableFrom(args[i].getClass())) {
- if (logger == null) {
- LOG.error("Unable to invoke method {} on {}
because method parameter {} is expected to be of type {} but argument passed
was of type {}",
- method.getName(), instance, i, argType,
args[i].getClass());
- } else {
- logger.error("Unable to invoke method {} on {}
because method parameter {} is expected to be of type {} but argument passed
was of type {}",
- method.getName(), instance, i, argType,
args[i].getClass());
- }
-
- return false;
- }
- }
-
- try {
- if (argumentTypes.length == args.length) {
- method.invoke(instance, args);
- } else {
- final Object[] argsToPass = new
Object[argumentTypes.length];
- System.arraycopy(args, 0, argsToPass, 0,
argsToPass.length);
-
- method.invoke(instance, argsToPass);
- }
- } catch (final InvocationTargetException ite) {
- if (logger == null) {
- LOG.error("Unable to invoke method {} on {}",
method.getName(), instance, ite.getCause());
- LOG.error("", ite.getCause());
- } else {
- logger.error("Unable to invoke method {} on {}",
method.getName(), instance, ite.getCause());
- }
- } catch (final IllegalAccessException |
IllegalArgumentException t) {
- if (logger == null) {
- LOG.error("Unable to invoke method {} on {}",
method.getName(), instance, t);
- LOG.error("", t);
- } else {
- logger.error("Unable to invoke method {} on {}",
method.getName(), instance, t);
- }
-
- return false;
- }
- }
- }
-
- return true;
- }
-}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskInitializer.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskInitializer.java
index 848529363c..aa586bf9d1 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskInitializer.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskInitializer.java
@@ -26,6 +26,7 @@ import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.reporting.ReportingInitializationContext;
import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.util.ReflectionUtils;
/**
* Initializes a ReportingTask using a MockReportingInitializationContext;
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
index 9d5430f7a3..f938724eb4 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.nar;
+import org.apache.nifi.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,6 +80,7 @@ public class InstanceClassLoader extends
AbstractNativeLibHandlingClassLoader {
if (parent instanceof SharedInstanceClassLoader) {
((SharedInstanceClassLoader) parent).close();
}
+ ReflectionUtils.invalidateCacheForClassLoader(this);
}
/**
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
b/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/util/ReflectionUtils.java
similarity index 95%
rename from
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
rename to
nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/util/ReflectionUtils.java
index d2f54af296..93ce739f27 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/util/ReflectionUtils.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/util/ReflectionUtils.java
@@ -25,18 +25,19 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
-import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class ReflectionUtils {
private static final Logger LOG =
LoggerFactory.getLogger(ReflectionUtils.class);
- private static Map<Class<?>, Map<Annotations, List<Method>>>
annotationCache = new WeakHashMap<>();
+ private static Map<Class<?>, Map<Annotations, List<Method>>>
annotationCache = new ConcurrentHashMap<>();
/**
* Invokes all methods on the given instance that have been annotated with
the given Annotation. If the signature of the method that is defined in
<code>instance</code> uses 1 or more parameters,
@@ -166,13 +167,11 @@ public class ReflectionUtils {
// This can add up to several seconds very easily.
final Annotations annotations = new Annotations(annotationClasses);
- synchronized (annotationCache) {
- final Map<Annotations, List<Method>> innerMap =
annotationCache.get(clazz);
- if (innerMap != null) {
- final List<Method> methods = innerMap.get(annotations);
- if (methods != null) {
- return methods;
- }
+ final Map<Annotations, List<Method>> innerMap =
annotationCache.get(clazz);
+ if (innerMap != null) {
+ final List<Method> methods = innerMap.get(annotations);
+ if (methods != null) {
+ return methods;
}
}
@@ -180,14 +179,16 @@ public class ReflectionUtils {
final List<Method> methods = discoverMethodsWithAnnotations(clazz,
annotationClasses);
// Store the discovered methods in our cache so that they are
available next time.
- synchronized (annotationCache) {
- final Map<Annotations, List<Method>> innerMap =
annotationCache.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>());
- innerMap.putIfAbsent(annotations, methods);
- }
+ final Map<Annotations, List<Method>> discoveredInnerMap =
annotationCache.computeIfAbsent(clazz, key -> new
ConcurrentHashMap<>(Collections.singletonMap(annotations, methods)));
+ discoveredInnerMap.putIfAbsent(annotations, methods);
return methods;
}
+ public static void invalidateCacheForClassLoader(final ClassLoader
classLoader) {
+ annotationCache.keySet().removeIf((clazz ->
Objects.equals(clazz.getClassLoader(), classLoader)));
+ }
+
private static List<Method> discoverMethodsWithAnnotations(final Class<?>
clazz, final Class<? extends Annotation>[] annotations) {
// Consider two methods equal if they have the same name and same
parameter types.
final Comparator<Method> comparator =
Comparator.comparing(Method::getName).thenComparing(new Comparator<>() {