Skip to content

Conversation

@dlmarion
Copy link
Contributor

@dlmarion dlmarion commented Dec 3, 2025

Modified ContextDefinition to implement KeywordExecutable so that the user can use accumulo create-context-definition to create the json required for the ContextDefinition file.

Added a Cleaner to call URLClassLoader.close on instances that are about to be garbage collected. The LocalCachingContextCleaner is used to register the URLClassLoader with the Cleaner and this class also keeps references to the URLClassLoaders. The list is accessed from a MXBean so that a user can use JMX to retrieve a list of files that are referenced in the local cache directory from the classloaders that have been created but not yet gc'd. This should aid users in determining which files can be removed from the local cache directory.

Modified ContextDefinition to implement KeywordExecutable so that
the user can use `accumulo create-context-definition` to create the
json required for the ContextDefinition file.

Added a Cleaner to call `URLClassLoader.close` on instances that are
about to be garbage collected. The LocalCachingContextCleaner is used
to register the URLClassLoader with the Cleaner and this class also
keeps references to the URLClassLoaders. The list is accessed from
a MXBean so that a user can use JMX to retrieve a list of files that
are referenced in the local cache directory from the classloaders
that have been created but not yet gc'd. This should aid users in
determining which files can be removed from the local cache directory.
final URL invalidDefUrl = new URL(fs.getUri().toString() + invalid.toUri().toString());

ContextClassLoaderException ex = assertThrows(ContextClassLoaderException.class,
assertThrows(ContextClassLoaderException.class,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error changed when I set GSON to use pretty printing. Not sure the exact error message is a concern here.

@Override
public void configureMiniCluster(MiniAccumuloConfigImpl cfg,
org.apache.hadoop.conf.Configuration coreSite) {
cfg.removeJvmOption("-XX:+PerfDisableSharedMem");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires apache/accumulo#5999 to be merged. This JVM option was not allowing the test to attach to the locally running Accumulo JVM processes. Additionally, I think this is what causes jps not to report the processes.

<banDuplicatePomDependencyVersions />
<dependencyConvergence />
<banDynamicVersions />
<!-- <banDynamicVersions />-->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can revert this when 2.1.5 is released and the version changed above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What in 2.1.5 does this PR depend on that is not in 2.1.4?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could create a reminder issue for this.

@dlmarion
Copy link
Contributor Author

dlmarion commented Dec 3, 2025

This won't build successfully until apache/accumulo#5999 is merged. Tests pass locally for me.

public static void registerClassLoader(final URLClassLoader cl) {
LOADERS.add(new SoftReference<>(cl));
CLEANER.register(cl, () -> {
LOADERS.removeIf((sr) -> sr.get() == cl);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SoftReference may be cleared when this is called. May just want to remove if SoftReference.get() returns null.

public class LocalCachingContextCleaner {

private static final Logger LOG = LoggerFactory.getLogger(LocalCachingContextCleaner.class);
private static final List<SoftReference<URLClassLoader>> LOADERS = new ArrayList<>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to WeakReference?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A WeakRef for this list seems good. As long as the classloader is refereced by the caffeine cache the weak ref will be alive. The javadocs for weak ref mention the following also.

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with replacing with WeakRef. From what I understand, if no strong refs exist, weak refs will be removed on next GC, but soft refs will only be removed on GC when/if memory is scarce. So things like getReferencedClassLoaders() will be more accurate with weak ref

Copy link
Member

@ctubbsii ctubbsii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't quite understand why this needs to depend on 2.1.5-SNAPSHOT that isn't available in 2.1.4.

# under the License.
#

org.apache.accumulo.classloader.lcc.definition.ContextDefinition No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manually adding this file is fine, but you could also have used the auto-service plugin with the annotation to generate this during the build, so we have a little more flexibility and can be assured that the file will be updated if/when we change the class name, add more, etc.

Comment on lines +38 to +39
LOADERS.add(new SoftReference<>(cl));
CLEANER.register(cl, () -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work. SoftReferences prevent the monitor object from becoming phantom-reachable, a prerequisite to run the cleaner. Instead, look at what I did in the CleanerUtil class in the main Accumulo code. In order to clean up the Closeable, it has to be contained within the monitor object that becomes phantom-reachable. And then, you can close the objects that were contained within it. The main catch is that you can't have anything registered with the cleaner referencing the object being monitored. So, you can't close the object being monitored this way, but you can close what's inside it. So, you can close a URLClassLoader held inside a LocalCachingContext, if the LocalCachingContext becomes phantom-reachable, but you can't watch for the URLClassLoader to be phantom reachable and then close it.

Comment on lines +208 to +217
try {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(new ContextClassLoaders(), ContextClassLoadersMXBean.getObjectName());
} catch (MalformedObjectNameException | MBeanRegistrationException
| NotCompliantMBeanException e) {
throw new IllegalStateException("Error registering MBean", e);
} catch (InstanceAlreadyExistsException e) {
// instance was re-init'd. This is likely to happen during tests
// can ignore as no issue here
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this stuff for?

see the test method `MiniAccumuloClusterClassLoaderFactoryTest.getReferencedFiles`. This method attaches to the
local Accumulo JVM processes to get the set of referenced files. It should be safe to delete files that are located
in the base cache directory (set by property `general.custom.classloader.lcc.cache.dir`) that are NOT in the set
of referenced files.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
of referenced files.
of referenced files and existed before references were gathered.

and unused old files within a context cache directory.
and unused old files within a context cache directory. To aid in this task a JMX MXBean has been created to expose the
files that are still referenced by the classloaders that are created. For an example of how to use this MXBean, please
see the test method `MiniAccumuloClusterClassLoaderFactoryTest.getReferencedFiles`. This method attaches to the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a link to the test class.

Suggested change
see the test method `MiniAccumuloClusterClassLoaderFactoryTest.getReferencedFiles`. This method attaches to the
see the test method [MiniAccumuloClusterClassLoaderFactoryTest.getReferencedFiles](src/main/test/.../MiniAccumuloClusterClassLoaderFactoryTest.java). This method attaches to the

<banDuplicatePomDependencyVersions />
<dependencyConvergence />
<banDynamicVersions />
<!-- <banDynamicVersions />-->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could create a reminder issue for this.

public class LocalCachingContextCleaner {

private static final Logger LOG = LoggerFactory.getLogger(LocalCachingContextCleaner.class);
private static final List<SoftReference<URLClassLoader>> LOADERS = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A WeakRef for this list seems good. As long as the classloader is refereced by the caffeine cache the weak ref will be alive. The javadocs for weak ref mention the following also.

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. 


public static List<URLClassLoader> getReferencedClassLoaders() {
List<URLClassLoader> cll = new ArrayList<>();
LOADERS.forEach(sr -> cll.add(sr.get()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not add things where ref.get() returns null.


public static void registerClassLoader(final URLClassLoader cl) {
LOADERS.add(new SoftReference<>(cl));
CLEANER.register(cl, () -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if cleaner is needed, but also not too familiar w/ cleaners. Could do the following to just remove any garbage whenever something is added. Seems like that would work well w/ weak refs.

Suggested change
CLEANER.register(cl, () -> {
LOADERS.removeIf(ref->ref.get() == null);

public class LocalCachingContextCleaner {

private static final Logger LOG = LoggerFactory.getLogger(LocalCachingContextCleaner.class);
private static final List<SoftReference<URLClassLoader>> LOADERS = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static final List<SoftReference<URLClassLoader>> LOADERS = new ArrayList<>();
private static final List<SoftReference<URLClassLoader>> LOADERS = Collections.synchronizedList(new ArrayList<>());

Comment on lines +227 to 228
assertThrows(ContextClassLoaderException.class,
() -> FACTORY.getClassLoader(invalidDefUrl.toString()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current change is fine, but it would probably be good to still ensure the cause is a JSON problem. Could do something like the following

Suggested change
assertThrows(ContextClassLoaderException.class,
() -> FACTORY.getClassLoader(invalidDefUrl.toString()));
Throwable exception = assertThrows(ContextClassLoaderException.class,
() -> FACTORY.getClassLoader(invalidDefUrl.toString()));
// ensure json is somewhere in the exception
outer: while (exception != null) {
for (var trace : exception.getStackTrace()) {
if (trace.toString().contains("com.google.gson")) {
break outer;
}
}
exception = exception.getCause();
}
assertNotNull(exception);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants