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

psalagnac pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 28a2ee239a3 SOLR-17777: Fix leaked metric registry after core unload 
(#3382)
28a2ee239a3 is described below

commit 28a2ee239a32a58dad36aa14fa5f6a7187740131
Author: Pierre Salagnac <[email protected]>
AuthorDate: Mon Jul 7 11:11:27 2025 +0200

    SOLR-17777: Fix leaked metric registry after core unload (#3382)
    
    This fixes a minor memory leak when deleting a core. I don't think it can 
have a significant impact on heap, but the fix is trivial and not risky.
---
 solr/CHANGES.txt                                   |  2 ++
 .../org/apache/solr/metrics/SolrMetricManager.java | 22 +++++++++++--
 .../org/apache/solr/cloud/MigrateReplicasTest.java |  3 +-
 .../org/apache/solr/cloud/ReplaceNodeTest.java     |  3 +-
 .../solr/metrics/SolrCoreMetricManagerTest.java    | 37 ++++++++++++++++++++++
 5 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 7f67abc2108..f2268dd6934 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -339,6 +339,8 @@ Bug Fixes
 
 * SOLR-17711: Remove total request timeout during recovery that was 
inadvertently added. (Luke Kot-Zaniewski)
 
+* SOLR-17777: Fix a very minor memory leak in metrics reporting code when a 
core is deleted. (Pierre Salagnac)
+
 Dependency Upgrades
 ---------------------
 * SOLR-17471: Upgrade Lucene to 9.12.1. (Pierre Salagnac, Christine Poerschke)
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java 
b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 52c468445cb..fa05c2797dc 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -448,19 +448,35 @@ public class SolrMetricManager {
   }
 
   /**
-   * Get (or create if not present) a named registry
+   * Get (or create if not present) a named registry. This method always 
creates and persists a new
+   * registry in case it does not already exist.
    *
    * @param registry name of the registry
    * @return existing or newly created registry
    */
   public MetricRegistry registry(String registry) {
+    return registry(registry, true);
+  }
+
+  /**
+   * Get (or create if not present) a named registry.
+   *
+   * @param registry name of the registry.
+   * @param create When false and the registry does not exist, we return null 
instead of creating a
+   *     new one.
+   */
+  public MetricRegistry registry(String registry, boolean create) {
     registry = enforcePrefix(registry);
     if (isSharedRegistry(registry)) {
       return SharedMetricRegistries.getOrCreate(registry);
     } else {
       swapLock.lock();
       try {
-        return getOrCreateRegistry(registries, registry);
+        if (create) {
+          return getOrCreateRegistry(registries, registry);
+        } else {
+          return registries.get(registry);
+        }
       } finally {
         swapLock.unlock();
       }
@@ -830,7 +846,7 @@ public class SolrMetricManager {
     if (tagSegment == null) {
       return 0;
     }
-    MetricRegistry registry = registry(registryName);
+    MetricRegistry registry = registry(registryName, false);
     if (registry == null) return 0;
     AtomicInteger removed = new AtomicInteger();
     registry.removeMatching(
diff --git a/solr/core/src/test/org/apache/solr/cloud/MigrateReplicasTest.java 
b/solr/core/src/test/org/apache/solr/cloud/MigrateReplicasTest.java
index 9f8993f2ce6..296dcf001a5 100644
--- a/solr/core/src/test/org/apache/solr/cloud/MigrateReplicasTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/MigrateReplicasTest.java
@@ -199,7 +199,8 @@ public class MigrateReplicasTest extends SolrCloudTestCase {
 
     // check replication metrics on this jetty - see SOLR-14924
     for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
-      if (jetty.getCoreContainer() == null) {
+      if (emptyNode.equals(jetty.getNodeName())) {
+        // No cores on this node, ignore it
         continue;
       }
       SolrMetricManager metricManager = 
jetty.getCoreContainer().getMetricManager();
diff --git a/solr/core/src/test/org/apache/solr/cloud/ReplaceNodeTest.java 
b/solr/core/src/test/org/apache/solr/cloud/ReplaceNodeTest.java
index 59427ff26df..cb3305d92f7 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ReplaceNodeTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ReplaceNodeTest.java
@@ -179,7 +179,8 @@ public class ReplaceNodeTest extends SolrCloudTestCase {
 
     // check replication metrics on this jetty - see SOLR-14924
     for (JettySolrRunner jetty : cluster.getJettySolrRunners()) {
-      if (jetty.getCoreContainer() == null) {
+      if (emptyNode.equals(jetty.getNodeName())) {
+        // No cores on this node, ignore it
         continue;
       }
       SolrMetricManager metricManager = 
jetty.getCoreContainer().getMetricManager();
diff --git 
a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java 
b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
index 3559a665f91..365a997339f 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
@@ -23,14 +23,22 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.UpdateParams;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.apache.solr.request.SolrQueryRequestBase;
 import org.apache.solr.schema.FieldType;
+import org.apache.solr.update.CommitUpdateCommand;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -177,4 +185,33 @@ public class SolrCoreMetricManagerTest extends 
SolrTestCaseJ4 {
     assertEquals("solr.core.collection1", registryName);
     assertNull(leaderRegistryName);
   }
+
+  /** Check the metric registry specific to a core is removed once the core is 
unloaded. */
+  @Test
+  public void testNoRegistryAfterUnload() throws Exception {
+
+    String coreRegistryName;
+
+    CoreContainer cc = h.getCoreContainer();
+    SolrMetricManager metricManager = cc.getMetricManager();
+
+    SolrCore core = cc.create("to-unload", Map.of("configSet", "minimal"));
+    coreRegistryName = core.getSolrMetricsContext().getRegistryName();
+
+    Set<String> names = metricManager.registryNames();
+    assertTrue("missing registry", names.contains(coreRegistryName));
+
+    // Commit and wait for searcher to ensure the searcher is created. This is 
required to make sure
+    // the core inner thread does not create it *after* we asked the container 
to unload the core.
+    SolrParams params = new MapSolrParams(Map.of(UpdateParams.WAIT_SEARCHER, 
"true"));
+    core.getUpdateHandler()
+        .commit(new CommitUpdateCommand(new SolrQueryRequestBase(h.getCore(), 
params) {}, false));
+
+    cc.unload("to-unload");
+
+    // Make sure the core metric registry does not exist anymore once we fully 
removed the core
+    // from the container
+    names = metricManager.registryNames();
+    assertFalse(names.contains(coreRegistryName));
+  }
 }

Reply via email to