This is an automated email from the ASF dual-hosted git repository.

dlmarion pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo-classloaders.git


The following commit(s) were added to refs/heads/main by this push:
     new b4c3f7d  Add tools for ContextDefinition create and cleanup (#33)
b4c3f7d is described below

commit b4c3f7d7cda49759af71c4f655b244a35ee59de3
Author: Dave Marion <[email protected]>
AuthorDate: Fri Jan 16 10:14:53 2026 -0500

    Add tools for ContextDefinition create and cleanup (#33)
    
    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 JMX MXBean that will return the list of files in the local cache
    directory that are referenced by classloaders created by the factory
    in this module and are still in use in the JVM. Users can connect to
    the JMX MXBean to retrieve the list to aid in the cleanup of the local
    cache directory.
    
    
    Co-authored-by: Christopher Tubbs <[email protected]>
    Co-authored-by: Keith Turner <[email protected]>
---
 .github/workflows/maven-on-demand.yaml             |  2 +-
 .github/workflows/maven.yaml                       |  2 +-
 modules/local-caching-classloader/README.md        | 31 ++++++--
 modules/local-caching-classloader/pom.xml          | 17 ++++
 .../lcc/LocalCachingContextClassLoaderFactory.java | 35 +++++++-
 .../lcc/definition/ContextDefinition.java          | 52 +++++++++++-
 .../classloader/lcc/jmx/ContextClassLoaders.java   | 33 ++++++++
 .../lcc/jmx/ContextClassLoadersMXBean.java         | 35 ++++++++
 .../classloader/lcc/util/DeduplicationCache.java   | 10 +++
 .../MiniAccumuloClusterClassLoaderFactoryTest.java | 92 +++++++++++++++++++++-
 pom.xml                                            | 15 +++-
 11 files changed, 307 insertions(+), 17 deletions(-)

diff --git a/.github/workflows/maven-on-demand.yaml 
b/.github/workflows/maven-on-demand.yaml
index 5bdd47c..ca60d24 100644
--- a/.github/workflows/maven-on-demand.yaml
+++ b/.github/workflows/maven-on-demand.yaml
@@ -50,7 +50,7 @@ jobs:
       timeout-minutes: 345
       run: mvn -B -V -e -ntp "-Dstyle.color=always" ${{ 
github.event.inputs.goals }}
       env:
-        MAVEN_OPTS: -Djansi.force=true
+        MAVEN_OPTS: -Djansi.force=true -Dapache.snapshots=true
     - name: Upload unit test results
       if: ${{ failure() }}
       uses: actions/upload-artifact@v4
diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml
index 099e04f..434a20e 100644
--- a/.github/workflows/maven.yaml
+++ b/.github/workflows/maven.yaml
@@ -45,7 +45,7 @@ jobs:
     - name: Build with Maven (verify javadoc:jar)
       run: mvn -B -V -e -ntp "-Dstyle.color=always" verify javadoc:jar 
-DskipFormat -DverifyFormat
       env:
-        MAVEN_OPTS: -Djansi.force=true
+        MAVEN_OPTS: -Djansi.force=true -Dapache.snapshots=true
     - name: Upload unit test results
       if: ${{ failure() }}
       uses: actions/upload-artifact@v4
diff --git a/modules/local-caching-classloader/README.md 
b/modules/local-caching-classloader/README.md
index 0e66de6..cb5589c 100644
--- a/modules/local-caching-classloader/README.md
+++ b/modules/local-caching-classloader/README.md
@@ -122,9 +122,18 @@ download, and any modification will likely cause 
unexpected behavior.
 ## Creating a ContextDefinition file
 
 Users may take advantage of the `ContextDefinition.create(int,URL[])` method to
-construct a `ContextDefinition` object. This will calculate the checksums of
-the classpath elements. `ContextDefinition.toJson()` can be used to serialize
-the `ContextDefinition` to a `String` to store in a file.
+construct a `ContextDefinition` object, programmatically. This will calculate
+the checksums of the classpath elements. `ContextDefinition.toJson()` can be
+used to serialize the `ContextDefinition` to a `String` to store in a file.
+
+Alternatively, if this library's jar is built and placed onto Accumulo's
+`CLASSPATH`, then one can run `bin/accumulo create-context-definition` to
+create the ContextDefinition json file using the command-line. The resulting
+json is printed to stdout and can be redirected to a file. The command takes
+two arguments:
+
+1. the monitor interval, in seconds (e.g. `-i 300`), and
+2. a list of file URLs (e.g. `hdfs://host:port/path/to/one.jar 
file://host/path/to/two.jar`)
 
 ## Updating a ContextDefinition file
 
@@ -177,12 +186,22 @@ unused files from the cache. While the context definition 
JSON files are always
 safe to delete, it is not recommended to do so for any that are still in use,
 because they can be useful for troubleshooting.
 
+To aid in this task, a JMX MXBean has been created to expose the files that are
+still referenced by the classloaders that have been created by this factory and
+currently still exist in the system. For an example of how to use this MXBean,
+please 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 local cache
+directory (set by property `general.custom.classloader.lcc.cache.dir`) that are
+NOT in the set of referenced files, so long as no new classloaders have been
+created that reference the files being deleted.
+
 **IMPORTANT**: as mentioned earlier, it is not safe to delete resource files
 that are still referenced by any `ClassLoader` instances. Each `ClassLoader`
 instance assumes that the locally cached resources exist and can be read. They
-will not attempt to download any files.  Downloading and verifying files only
-occurs when `ClassLoader` instances are initially created for a context
-definition.
+will not attempt to download any files.  Downloading files only occurs when
+`ClassLoader` instances are initially created for a context definition.
 
 ## Accumulo Configuration
 
diff --git a/modules/local-caching-classloader/pom.xml 
b/modules/local-caching-classloader/pom.xml
index 8b39c34..0428499 100644
--- a/modules/local-caching-classloader/pom.xml
+++ b/modules/local-caching-classloader/pom.xml
@@ -51,6 +51,17 @@
       <artifactId>spotbugs-annotations</artifactId>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <!-- needed for annotation processor during compile, but not after -->
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>com.beust</groupId>
+      <artifactId>jcommander</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>com.github.ben-manes.caffeine</groupId>
       <artifactId>caffeine</artifactId>
@@ -82,6 +93,12 @@
       <artifactId>accumulo-core</artifactId>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <!-- provided by accumulo -->
+      <groupId>org.apache.accumulo</groupId>
+      <artifactId>accumulo-start</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.hadoop</groupId>
       <artifactId>hadoop-client-api</artifactId>
diff --git 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/LocalCachingContextClassLoaderFactory.java
 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/LocalCachingContextClassLoaderFactory.java
index 6e5ff22..94fed63 100644
--- 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/LocalCachingContextClassLoaderFactory.java
+++ 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/LocalCachingContextClassLoaderFactory.java
@@ -22,12 +22,15 @@ import static java.util.Objects.requireNonNull;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.lang.management.ManagementFactory;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.nio.file.Path;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
@@ -36,8 +39,16 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+
 import org.apache.accumulo.classloader.lcc.definition.ContextDefinition;
 import org.apache.accumulo.classloader.lcc.definition.Resource;
+import org.apache.accumulo.classloader.lcc.jmx.ContextClassLoaders;
+import org.apache.accumulo.classloader.lcc.jmx.ContextClassLoadersMXBean;
 import org.apache.accumulo.classloader.lcc.util.DeduplicationCache;
 import org.apache.accumulo.classloader.lcc.util.LccUtils;
 import org.apache.accumulo.classloader.lcc.util.LocalStore;
@@ -97,7 +108,7 @@ public class LocalCachingContextClassLoaderFactory 
implements ContextClassLoader
 
   // to keep this coherent with the contextDefs, updates to this should be 
done in the compute
   // method of contextDefs
-  private final DeduplicationCache<String,URL[],URLClassLoader> classloaders =
+  private static final DeduplicationCache<String,URL[],URLClassLoader> 
classloaders =
       new DeduplicationCache<>(LccUtils::createClassLoader, 
Duration.ofHours(24), null);
 
   private final AtomicReference<LocalStore> localStore = new 
AtomicReference<>();
@@ -144,6 +155,16 @@ public class LocalCachingContextClassLoaderFactory 
implements ContextClassLoader
       throw new UncheckedIOException("Unable to create the local storage area 
at " + baseCacheDir,
           e);
     }
+    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
+    }
   }
 
   @Override
@@ -279,4 +300,16 @@ public class LocalCachingContextClassLoaderFactory 
implements ContextClassLoader
 
   }
 
+  public static Map<String,List<String>> getReferencedFiles() {
+    final Map<String,List<String>> referencedContexts = new HashMap<>();
+    classloaders.forEach((cacheKey, cl) -> {
+      List<String> files = new ArrayList<>();
+      for (URL u : cl.getURLs()) {
+        files.add(u.toString());
+      }
+      referencedContexts.put(cacheKey, files);
+    });
+    return referencedContexts;
+  }
+
 }
diff --git 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/definition/ContextDefinition.java
 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/definition/ContextDefinition.java
index 356bde8..8c13fee 100644
--- 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/definition/ContextDefinition.java
+++ 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/definition/ContextDefinition.java
@@ -28,21 +28,41 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 
 import org.apache.accumulo.classloader.lcc.resolvers.FileResolver;
+import org.apache.accumulo.core.cli.Help;
+import org.apache.accumulo.start.spi.KeywordExecutable;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
 
+import com.beust.jcommander.Parameter;
+import com.google.auto.service.AutoService;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Suppliers;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
-public class ContextDefinition {
+@AutoService(KeywordExecutable.class)
+public class ContextDefinition implements KeywordExecutable {
 
+  static class Opts extends Help {
+    @Parameter(names = {"-i", "--interval"}, required = true,
+        description = "monitor interval (in seconds)", arity = 1, order = 1)
+    int monitorInterval;
+
+    @Parameter(required = true, description = "classpath element URL (<url>[ 
<url>...])",
+        arity = -1, order = 2)
+    public List<String> files = new ArrayList<>();
+  }
+
+  // pretty-print uses Unix newline
   private static final Gson GSON =
       new GsonBuilder().disableJdkUnsafe().setPrettyPrinting().create();
 
@@ -122,6 +142,34 @@ public class ContextDefinition {
   }
 
   public String toJson() {
-    return GSON.toJson(this);
+    // GSON pretty print uses Unix line-endings, and may or may not have a 
trailing one, so
+    // ensure a trailing one exists, so it's included in checksum computations 
and in
+    // any files written from this (for better readability)
+    return GSON.toJson(this).stripTrailing() + "\n";
+  }
+
+  @Override
+  public String keyword() {
+    return "create-context-definition";
+  }
+
+  @Override
+  public String description() {
+    return "Creates and prints a Context Definition";
+  }
+
+  @Override
+  public void execute(String[] args) throws Exception {
+    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory(new 
Configuration()));
+
+    Opts opts = new Opts();
+    opts.parseArgs(ContextDefinition.class.getName(), args);
+    URL[] urls = new URL[opts.files.size()];
+    int count = 0;
+    for (String f : opts.files) {
+      urls[count++] = new URL(f);
+    }
+    ContextDefinition def = create(opts.monitorInterval, urls);
+    System.out.print(def.toJson());
   }
 }
diff --git 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoaders.java
 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoaders.java
new file mode 100644
index 0000000..4a2a0a9
--- /dev/null
+++ 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoaders.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ *
+ *   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 org.apache.accumulo.classloader.lcc.jmx;
+
+import java.util.List;
+import java.util.Map;
+
+import 
org.apache.accumulo.classloader.lcc.LocalCachingContextClassLoaderFactory;
+
+public class ContextClassLoaders implements ContextClassLoadersMXBean {
+
+  @Override
+  public Map<String,List<String>> getReferencedFiles() {
+    return LocalCachingContextClassLoaderFactory.getReferencedFiles();
+  }
+
+}
diff --git 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoadersMXBean.java
 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoadersMXBean.java
new file mode 100644
index 0000000..753f3b9
--- /dev/null
+++ 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/jmx/ContextClassLoadersMXBean.java
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ *   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 org.apache.accumulo.classloader.lcc.jmx;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+public interface ContextClassLoadersMXBean {
+
+  static ObjectName getObjectName() throws MalformedObjectNameException {
+    return new 
ObjectName("org.apache.accumulo.classloader:type=ContextClassLoaders");
+  }
+
+  Map<String,List<String>> getReferencedFiles();
+
+}
diff --git 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/util/DeduplicationCache.java
 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/util/DeduplicationCache.java
index 54b3a1f..4edaa46 100644
--- 
a/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/util/DeduplicationCache.java
+++ 
b/modules/local-caching-classloader/src/main/java/org/apache/accumulo/classloader/lcc/util/DeduplicationCache.java
@@ -21,6 +21,7 @@ package org.apache.accumulo.classloader.lcc.util;
 import static java.util.Objects.requireNonNull;
 
 import java.time.Duration;
+import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -75,4 +76,13 @@ public class DeduplicationCache<KEY,PARAMS,VALUE> {
     return 
canonicalWeakValuesCache.asMap().keySet().stream().anyMatch(keyPredicate);
   }
 
+  public void forEach(final BiConsumer<KEY,VALUE> consumer) {
+    canonicalWeakValuesCache.cleanUp();
+    canonicalWeakValuesCache.asMap().forEach((k, v) -> {
+      if (v != null) {
+        consumer.accept(k, v);
+      }
+    });
+  }
+
 }
diff --git 
a/modules/local-caching-classloader/src/test/java/org/apache/accumulo/classloader/lcc/MiniAccumuloClusterClassLoaderFactoryTest.java
 
b/modules/local-caching-classloader/src/test/java/org/apache/accumulo/classloader/lcc/MiniAccumuloClusterClassLoaderFactoryTest.java
index deb259e..07909eb 100644
--- 
a/modules/local-caching-classloader/src/test/java/org/apache/accumulo/classloader/lcc/MiniAccumuloClusterClassLoaderFactoryTest.java
+++ 
b/modules/local-caching-classloader/src/test/java/org/apache/accumulo/classloader/lcc/MiniAccumuloClusterClassLoaderFactoryTest.java
@@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -42,13 +43,25 @@ import java.nio.file.attribute.PosixFilePermissions;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 
+import javax.management.JMX;
+import javax.management.MalformedObjectNameException;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
 import org.apache.accumulo.classloader.lcc.definition.ContextDefinition;
+import org.apache.accumulo.classloader.lcc.definition.Resource;
+import org.apache.accumulo.classloader.lcc.jmx.ContextClassLoadersMXBean;
+import org.apache.accumulo.classloader.lcc.resolvers.FileResolver;
+import org.apache.accumulo.classloader.lcc.util.LocalStore;
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.AccumuloException;
@@ -77,14 +90,24 @@ import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
 
 public class MiniAccumuloClusterClassLoaderFactoryTest extends 
SharedMiniClusterBase {
 
+  private static final Logger LOG =
+      LoggerFactory.getLogger(MiniAccumuloClusterClassLoaderFactoryTest.class);
+
   private static class TestMACConfiguration implements 
MiniClusterConfigurationCallback {
 
     @Override
     public void configureMiniCluster(MiniAccumuloConfigImpl cfg,
         org.apache.hadoop.conf.Configuration coreSite) {
+      cfg.getJvmOptions().add("-XX:-PerfDisableSharedMem");
       cfg.setNumTservers(3);
       cfg.setProperty(Property.TSERV_NATIVEMAP_ENABLED.getKey(), "false");
       cfg.setProperty(Property.GENERAL_CONTEXT_CLASSLOADER_FACTORY.getKey(),
@@ -130,9 +153,9 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
 
   @Test
   public void testClassLoader() throws Exception {
-
-    var baseDirPath = tempDir.resolve("base");
-    var jsonDirPath = baseDirPath.resolve("contextFiles");
+    final var baseDirPath = tempDir.resolve("base");
+    final var resourcesDirPath = baseDirPath.resolve("resources");
+    final var jsonDirPath = tempDir.resolve("simulatedRemoteContextFiles");
     Files.createDirectory(jsonDirPath, PERMISSIONS);
 
     // Create a context definition that only references jar A
@@ -143,6 +166,10 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
     Files.writeString(testContextDefFile.toPath(), testContextDefJson, 
StandardOpenOption.CREATE);
     assertTrue(Files.exists(testContextDefFile.toPath()));
 
+    Resource jarAResource = testContextDef.getResources().iterator().next();
+    String jarALocalFileName = LocalStore.localResourceName(
+        FileResolver.resolve(jarAResource.getLocation()).getFileName(), 
jarAResource.getChecksum());
+
     final String[] names = this.getUniqueNames(1);
     try (AccumuloClient client =
         Accumulo.newClient().from(getCluster().getClientProperties()).build()) 
{
@@ -198,6 +225,10 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
       // before applying the iterator
       final byte[] jarAValueBytes = "foo".getBytes(UTF_8);
       assertEquals(0, countExpectedValues(client, tableName, jarAValueBytes));
+      Set<String> refFiles = getReferencedFiles();
+      assertEquals(1, refFiles.size());
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarALocalFileName).toUri().toURL().toString()));
 
       // Attach a scan iterator to the table
       IteratorSetting is = new IteratorSetting(101, "example", 
ITER_CLASS_NAME);
@@ -209,6 +240,10 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
       while (count != 1000) {
         count = countExpectedValues(client, tableName, jarAValueBytes);
       }
+      refFiles = getReferencedFiles();
+      assertEquals(1, refFiles.size());
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarALocalFileName).toUri().toURL().toString()));
 
       // Update the context definition to point to jar B
       final ContextDefinition testContextDefUpdate =
@@ -218,6 +253,11 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
           StandardOpenOption.TRUNCATE_EXISTING);
       assertTrue(Files.exists(testContextDefFile.toPath()));
 
+      Resource jarBResource = 
testContextDefUpdate.getResources().iterator().next();
+      String jarBLocalFileName = LocalStore.localResourceName(
+          FileResolver.resolve(jarBResource.getLocation()).getFileName(),
+          jarBResource.getChecksum());
+
       // Wait 2x the monitor interval
       Thread.sleep(2 * MONITOR_INTERVAL_SECS * 1000);
 
@@ -226,6 +266,12 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
       // by the iterator
       final byte[] jarBValueBytes = "bar".getBytes(UTF_8);
       assertEquals(1000, countExpectedValues(client, tableName, 
jarBValueBytes));
+      refFiles = getReferencedFiles();
+      assertEquals(2, refFiles.size());
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarALocalFileName).toUri().toURL().toString()));
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarBLocalFileName).toUri().toURL().toString()));
 
       // Copy jar A, create a context definition using the copy, then
       // remove the copy so that it's not found when the context classloader
@@ -252,9 +298,15 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
       Thread.sleep(2 * MONITOR_INTERVAL_SECS * 1000);
 
       // Rescan and confirm that all values get transformed to "bar"
-      // by the iterator. The previous class is still being used after
+      // by the iterator. The previous classloader is still being used after
       // the monitor interval because the jar referenced does not exist.
       assertEquals(1000, countExpectedValues(client, tableName, 
jarBValueBytes));
+      refFiles = getReferencedFiles();
+      assertEquals(2, refFiles.size());
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarALocalFileName).toUri().toURL().toString()));
+      assertTrue(refFiles
+          
.contains(resourcesDirPath.resolve(jarBLocalFileName).toUri().toURL().toString()));
 
       // Wait 2 minutes, 2 times the UPDATE_FAILURE_GRACE_PERIOD_MINS
       Thread.sleep(120_000);
@@ -268,6 +320,38 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
     }
   }
 
+  private Set<String> getReferencedFiles() {
+    final Map<String,List<String>> referencedFiles = new HashMap<>();
+    for (VirtualMachineDescriptor vmd : VirtualMachine.list()) {
+      if (vmd.displayName().contains("org.apache.accumulo.start.Main")
+          && !vmd.displayName().contains("zookeeper")) {
+        LOG.info("Attempting to connect to {}", vmd.displayName());
+        try {
+          var vm = VirtualMachine.attach(vmd);
+          String connectorAddress = vm.getAgentProperties()
+              
.getProperty("com.sun.management.jmxremote.localConnectorAddress");
+          if (connectorAddress == null) {
+            connectorAddress = vm.startLocalManagementAgent();
+          }
+          var url = new JMXServiceURL(connectorAddress);
+          try (var connector = JMXConnectorFactory.connect(url)) {
+            var mbsc = connector.getMBeanServerConnection();
+            var proxy = JMX.newMXBeanProxy(mbsc, 
ContextClassLoadersMXBean.getObjectName(),
+                ContextClassLoadersMXBean.class);
+            referencedFiles.putAll(proxy.getReferencedFiles());
+          }
+        } catch (MalformedObjectNameException | AttachNotSupportedException | 
IOException e) {
+          LOG.error("Error getting referenced files from {}", 
vmd.displayName(), e);
+        }
+      }
+    }
+    Set<String> justTheFiles = new HashSet<>();
+    referencedFiles.values().forEach(justTheFiles::addAll);
+    LOG.info("Referenced files with contexts: {}", referencedFiles);
+    LOG.info("Referenced files: {}", justTheFiles);
+    return justTheFiles;
+  }
+
   private int countExpectedValues(AccumuloClient client, String table, byte[] 
expectedValue)
       throws TableNotFoundException, AccumuloSecurityException, 
AccumuloException {
     Scanner scanner = client.createScanner(table);
diff --git a/pom.xml b/pom.xml
index 1829a88..0335e9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -135,7 +135,7 @@ under the License.
         <!-- most dependencies will be provided by the accumulo installation 
-->
         <groupId>org.apache.accumulo</groupId>
         <artifactId>accumulo-project</artifactId>
-        <version>2.1.4</version>
+        <version>2.1.5-SNAPSHOT</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -367,7 +367,13 @@ under the License.
             </goals>
             <configuration>
               <failOnWarning>true</failOnWarning>
+              <ignoredUsedUndeclaredDependencies>
+                <!-- auto-service-annotations is transitive via auto-service 
-->
+                
<undeclared>com.google.auto.service:auto-service-annotations:jar:*</undeclared>
+              </ignoredUsedUndeclaredDependencies>
               <ignoredUnusedDeclaredDependencies>
+                <!-- auto-service used by the compiler for annotation 
processing, not by code -->
+                <unused>com.google.auto.service:auto-service:jar:*</unused>
                 <!-- ignore false positive runtime dependencies -->
                 <unused>org.apache.commons:commons-vfs2-hdfs:*</unused>
                 
<unused>org.apache.httpcomponents.client5:httpclient5:*</unused>
@@ -419,7 +425,12 @@ under the License.
                 <reactorModuleConvergence />
                 <banDuplicatePomDependencyVersions />
                 <dependencyConvergence />
-                <banDynamicVersions />
+                <banDynamicVersions>
+                  <ignores>
+                    <!-- allow Accumulo snapshots -->
+                    <ignore>org.apache.accumulo:*:*-SNAPSHOT</ignore>
+                  </ignores>
+                </banDynamicVersions>
                 <bannedDependencies>
                   <excludes>
                     <!-- we redirect logging to log4j2, so we should have 
those bridges instead -->

Reply via email to