You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2019/12/18 16:39:10 UTC

[lucene-solr] 32/36: SOLR-13579: Simplify the API, add more tests.

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

ab pushed a commit to branch jira/solr-13579
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit 5860e3dc96fee08b453fc1db76edc836d1a41631
Author: Andrzej Bialecki <ab...@apache.org>
AuthorDate: Tue Nov 26 15:10:45 2019 +0100

    SOLR-13579: Simplify the API, add more tests.
---
 .../solr/handler/admin/ResourceManagerHandler.java |  16 +-
 .../{ManagedComponent.java => ChangeListener.java} |  26 ++-
 .../solr/managed/DefaultResourceManager.java       |  32 ++--
 .../solr/managed/DefaultResourceManagerPool.java   | 168 ------------------
 ...java => DefaultResourceManagerPoolFactory.java} |  53 +++---
 .../org/apache/solr/managed/ManagedComponent.java  |   6 +-
 .../apache/solr/managed/ManagedComponentId.java    |  25 +--
 .../apache/solr/managed/NoOpResourceManager.java   | 113 +++---------
 .../org/apache/solr/managed/ResourceManager.java   |  16 +-
 .../apache/solr/managed/ResourceManagerPlugin.java |  93 ----------
 .../apache/solr/managed/ResourceManagerPool.java   | 190 +++++++++++++++++++--
 ...actory.java => ResourceManagerPoolFactory.java} |  15 +-
 .../{PoolContext.java => ResourcePoolContext.java} |   2 +-
 ...anagedContext.java => SolrResourceContext.java} |   4 +-
 ...cheManagerPlugin.java => CacheManagerPool.java} | 112 ++++++------
 .../apache/solr/managed/types/package-info.java    |   2 +-
 .../java/org/apache/solr/search/CaffeineCache.java |  21 ++-
 .../src/java/org/apache/solr/search/SolrCache.java |   2 +
 .../org/apache/solr/search/SolrCacheHolder.java    |   6 +-
 ...nagerPool.java => TestResourceManagerPool.java} |  83 +++------
 ...lugin.java => TestCacheManagerPluginCloud.java} |   2 +-
 .../solr/managed/types/TestCacheManagerPool.java   | 117 +++++++++++++
 22 files changed, 507 insertions(+), 597 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ResourceManagerHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ResourceManagerHandler.java
index 854da43..dd587ca 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ResourceManagerHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ResourceManagerHandler.java
@@ -151,7 +151,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
         result.add("resources", pool.getComponents().keySet());
         try {
           Map<String, Map<String, Object>> values = pool.getCurrentValues();
-          result.add("totalValues", pool.getResourceManagerPlugin().aggregateTotalValues(values));
+          result.add("totalValues", pool.aggregateTotalValues(values));
         } catch (Exception e) {
           log.warn("Error getting current values from pool " + name, e);
           result.add("error", "Error getting current values: " + e.toString());
@@ -225,7 +225,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
     if (poolName == null || poolName.isBlank()) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Resource operation requires '" + POOL_PARAM + "' parameter.");
     }
-    ResourceManagerPool pool = resourceManager.getPool(poolName);
+    ResourceManagerPool<ManagedComponent> pool = resourceManager.getPool(poolName);
     if (pool == null) {
       throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Pool '" + poolName + "' not found.");
     }
@@ -241,7 +241,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
           result.add(n, perRes);
           perRes.add("class", component.getClass().getName());
           try {
-            perRes.add("resourceLimits", pool.getResourceManagerPlugin().getResourceLimits(component));
+            perRes.add("resourceLimits", pool.getResourceLimits(component));
           } catch (Exception e) {
             log.warn("Error getting resourceLimits of " + component.getManagedComponentId(), e);
             result.add("error", "Error getting resource limits of " + resName + ": " + e.toString());
@@ -255,13 +255,13 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
         }
         result.add("class", component.getClass().getName());
         try {
-          result.add("resourceLimits", pool.getResourceManagerPlugin().getResourceLimits(component));
+          result.add("resourceLimits", pool.getResourceLimits(component));
         } catch (Exception e) {
           log.warn("Error getting resource limits of " + resName + "/" + poolName + " : " + e.toString(), e);
           result.add("error", "Error getting resource limits of " + resName + ": " + e.toString());
         }
         try {
-          result.add("monitoredValues", pool.getResourceManagerPlugin().getMonitoredValues(component));
+          result.add("monitoredValues", pool.getMonitoredValues(component));
         } catch (Exception e) {
           log.warn("Error getting monitored values of " + resName + "/" + poolName + " : " + e.toString(), e);
           result.add("error", "Error getting monitored values of " + resName + ": " + e.toString());
@@ -273,7 +273,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
           throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Component '" + resName + " not found in pool '" + poolName + "'.");
         }
         try {
-          result.add("resourceLimits", pool.getResourceManagerPlugin().getResourceLimits(managedComponent1));
+          result.add("resourceLimits", pool.getResourceLimits(managedComponent1));
         } catch (Exception e) {
           log.warn("Error getting resource limits of " + resName + "/" + poolName + " : " + e.toString(), e);
           result.add("error", "Error getting resource limits of " + resName + ": " + e.toString());
@@ -285,7 +285,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
           throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Resource '" + resName + " not found in pool '" + poolName + "'.");
         }
         try {
-          Map<String, Object> currentLimits = new HashMap<>(pool.getResourceManagerPlugin().getResourceLimits(managedComponent2));
+          Map<String, Object> currentLimits = new HashMap<>(pool.getResourceLimits(managedComponent2));
           Map<String, Object> newLimits = getMap(params, LIMIT_PREFIX_PARAM);
           newLimits.forEach((k, v) -> {
             if (v == null) {
@@ -295,7 +295,7 @@ public class ResourceManagerHandler extends RequestHandlerBase implements Permis
             }
           });
           try {
-            pool.getResourceManagerPlugin().setResourceLimits(managedComponent2, newLimits);
+            pool.setResourceLimits(managedComponent2, newLimits);
             result.add("success", newLimits);
           } catch (Exception e) {
             log.warn("Error setting resource limits of " + resName + "/" + poolName + " : " + e.toString(), e);
diff --git a/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java b/solr/core/src/java/org/apache/solr/managed/ChangeListener.java
similarity index 58%
copy from solr/core/src/java/org/apache/solr/managed/ManagedComponent.java
copy to solr/core/src/java/org/apache/solr/managed/ChangeListener.java
index 4ea5922..49fbad6 100644
--- a/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java
+++ b/solr/core/src/java/org/apache/solr/managed/ChangeListener.java
@@ -17,24 +17,18 @@
 package org.apache.solr.managed;
 
 /**
- * A managed component.
+ *
  */
-public interface ManagedComponent extends AutoCloseable {
-  /**
-   * Unique name of this component. By convention id-s form a colon-separated hierarchy.
-   */
-  ManagedComponentId getManagedComponentId();
-
-  void initializeManagedComponent(ResourceManager resourceManager, String poolName, String... otherPools);
+public interface ChangeListener {
 
   /**
-   * Component context used for managing additional component state for the purpose of resource management.
+   * Notify about changing a limit of a resource.
+   * @param poolName pool name where resource is managed.
+   * @param component managed component
+   * @param limitName limit name
+   * @param newRequestedVal requested new value of the resource limit.
+   * @param newActualVal actual value applied to the resource configuration. Note: this may differ from the
+   *                     value requested due to internal logic of the component.
    */
-  ManagedContext getManagedContext();
-
-  default void close() throws Exception {
-    if (getManagedContext() != null) {
-      getManagedContext().close();
-    }
-  }
+  void changedLimit(String poolName, ManagedComponent component, String limitName, Object newRequestedVal, Object newActualVal);
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManager.java b/solr/core/src/java/org/apache/solr/managed/DefaultResourceManager.java
index 069628d..6042717 100644
--- a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManager.java
+++ b/solr/core/src/java/org/apache/solr/managed/DefaultResourceManager.java
@@ -33,7 +33,7 @@ import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.managed.types.CacheManagerPlugin;
+import org.apache.solr.managed.types.CacheManagerPool;
 import org.apache.solr.search.SolrCache;
 import org.apache.solr.util.DefaultSolrThreadFactory;
 import org.slf4j.Logger;
@@ -60,7 +60,7 @@ public class DefaultResourceManager extends ResourceManager {
 
   static {
     Map<String, Object> params = new HashMap<>();
-    params.put(CommonParams.TYPE, CacheManagerPlugin.TYPE);
+    params.put(CommonParams.TYPE, CacheManagerPool.TYPE);
     // unlimited RAM
     params.put(SolrCache.MAX_RAM_MB_PARAM, -1L);
     DEFAULT_NODE_POOLS.put(NODE_SEARCHER_CACHE_POOL, params);
@@ -82,7 +82,7 @@ public class DefaultResourceManager extends ResourceManager {
   protected boolean isClosed = false;
   protected boolean enabled = true;
 
-  protected ResourceManagerPluginFactory resourceManagerPluginFactory;
+  protected ResourceManagerPoolFactory resourceManagerPoolFactory;
   protected SolrResourceLoader loader;
 
 
@@ -97,7 +97,7 @@ public class DefaultResourceManager extends ResourceManager {
     scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);
     scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
     // TODO: make configurable based on plugin info
-    resourceManagerPluginFactory = new DefaultResourceManagerPluginFactory(loader,
+    resourceManagerPoolFactory = new DefaultResourceManagerPoolFactory(loader,
         pluginInfo != null ?
             (Map<String, Object>)pluginInfo.initArgs.toMap(new HashMap<>()).getOrDefault("plugins", Collections.emptyMap()) :
             Collections.emptyMap());
@@ -118,7 +118,12 @@ public class DefaultResourceManager extends ResourceManager {
   }
 
   @Override
-  public void createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception {
+  public ResourceManagerPoolFactory getResourceManagerPoolFactory() {
+    return resourceManagerPoolFactory;
+  }
+
+  @Override
+  public ResourceManagerPool createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception {
     ensureActive();
     if (resourcePools.containsKey(name)) {
       throw new IllegalArgumentException("Pool '" + name + "' already exists.");
@@ -126,16 +131,19 @@ public class DefaultResourceManager extends ResourceManager {
     if (resourcePools.size() >= maxNumPools) {
       throw new IllegalArgumentException("Maximum number of pools (" + maxNumPools + ") reached.");
     }
-    DefaultResourceManagerPool newPool = new DefaultResourceManagerPool(name, type, resourceManagerPluginFactory, poolLimits, args);
+    ResourceManagerPool newPool = resourceManagerPoolFactory.create(name, type, this, poolLimits, args);
     newPool.scheduleDelaySeconds = Integer.parseInt(String.valueOf(args.getOrDefault(SCHEDULE_DELAY_SECONDS_PARAM, DEFAULT_SCHEDULE_DELAY_SECONDS)));
     resourcePools.putIfAbsent(name, newPool);
-    newPool.scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
-          log.info("- running pool " + newPool.getName() + " / " + newPool.getType());
-          newPool.run();
-        }, 0,
-        timeSource.convertDelay(TimeUnit.SECONDS, newPool.scheduleDelaySeconds, TimeUnit.MILLISECONDS),
-        TimeUnit.MILLISECONDS);
+    if (timeSource != null) {
+      newPool.scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {
+            log.info("- running pool " + newPool.getName() + " / " + newPool.getType());
+            newPool.manage();
+          }, 0,
+          timeSource.convertDelay(TimeUnit.SECONDS, newPool.scheduleDelaySeconds, TimeUnit.MILLISECONDS),
+          TimeUnit.MILLISECONDS);
+    }
     log.info("- created pool " + newPool.getName() + " / " + newPool.getType());
+    return newPool;
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPool.java b/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPool.java
deleted file mode 100644
index f00866c..0000000
--- a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPool.java
+++ /dev/null
@@ -1,168 +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.solr.managed;
-
-import java.io.IOException;
-import java.lang.invoke.MethodHandles;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This class manages a pool of resources of the same type, which use the same
- * {@link ResourceManagerPlugin} implementation for managing their resource limits.
- */
-public class DefaultResourceManagerPool implements ResourceManagerPool {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private final Map<String, ManagedComponent> components = new ConcurrentHashMap<>();
-  private Map<String, Object> poolLimits;
-  private final String type;
-  private final Class<? extends ManagedComponent> componentClass;
-  private final String name;
-  private final ResourceManagerPlugin resourceManagerPlugin;
-  private final Map<String, Object> args;
-  private final PoolContext poolContext = new PoolContext();
-  private final ReentrantLock updateLock = new ReentrantLock();
-  int scheduleDelaySeconds;
-  ScheduledFuture<?> scheduledFuture;
-
-  /**
-   * Create a pool of resources to manage.
-   * @param name unique name of the pool
-   * @param type one of the supported pool types (see {@link ResourceManagerPluginFactory})
-   * @param factory factory of {@link ResourceManagerPlugin}-s of the specified type
-   * @param poolLimits pool limits (keys are controlled tags)
-   * @param args parameters for the {@link ResourceManagerPlugin}
-   * @throws Exception when initialization of the management plugin fails.
-   */
-  public DefaultResourceManagerPool(String name, String type, ResourceManagerPluginFactory factory, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception {
-    this.name = name;
-    this.type = type;
-    this.resourceManagerPlugin = factory.create(type, args);
-    this.componentClass = factory.getComponentClassByType(type);
-    this.poolLimits = new TreeMap<>(poolLimits);
-    this.args = new HashMap<>(args);
-  }
-
-  @Override
-  public String getName() {
-    return name;
-  }
-
-  @Override
-  public String getType() {
-    return type;
-  }
-
-  @Override
-  public Map<String, Object> getParams() {
-    return args;
-  }
-
-  @Override
-  public ResourceManagerPlugin getResourceManagerPlugin() {
-    return resourceManagerPlugin;
-  }
-
-  @Override
-  public void registerComponent(ManagedComponent managedComponent) {
-    if (!componentClass.isAssignableFrom(managedComponent.getClass())) {
-      log.debug("Pool type '" + type + "' is not supported by the component " + managedComponent.getManagedComponentId());
-      return;
-    }
-    ManagedComponent existing = components.putIfAbsent(managedComponent.getManagedComponentId().toString(), managedComponent);
-    if (existing != null) {
-      throw new IllegalArgumentException("Component '" + managedComponent.getManagedComponentId() + "' already exists in pool '" + name + "' !");
-    }
-  }
-
-  @Override
-  public boolean unregisterComponent(String name) {
-    return components.remove(name) != null;
-  }
-
-  @Override
-  public boolean isRegistered(String componentId) {
-    return components.containsKey(componentId);
-  }
-
-  @Override
-  public Map<String, ManagedComponent> getComponents() {
-    return Collections.unmodifiableMap(components);
-  }
-
-  @Override
-  public Map<String, Map<String, Object>> getCurrentValues() throws InterruptedException {
-    updateLock.lockInterruptibly();
-    try {
-      // collect the current values
-      Map<String, Map<String, Object>> currentValues = new HashMap<>();
-      for (ManagedComponent managedComponent : components.values()) {
-        try {
-          currentValues.put(managedComponent.getManagedComponentId().toString(), resourceManagerPlugin.getMonitoredValues(managedComponent));
-        } catch (Exception e) {
-          log.warn("Error getting managed values from " + managedComponent.getManagedComponentId(), e);
-        }
-      }
-      return Collections.unmodifiableMap(currentValues);
-    } finally {
-      updateLock.unlock();
-    }
-  }
-
-  @Override
-  public Map<String, Object> getPoolLimits() {
-    return poolLimits;
-  }
-
-  @Override
-  public void setPoolLimits(Map<String, Object> poolLimits) {
-    this.poolLimits = new HashMap(poolLimits);
-  }
-
-  @Override
-  public PoolContext getPoolContext() {
-    return poolContext;
-  }
-
-  @Override
-  public void run() {
-    try {
-      resourceManagerPlugin.manage(this);
-    } catch (Exception e) {
-      log.warn("Error running management plugin " + getName(), e);
-    }
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (scheduledFuture != null) {
-      scheduledFuture.cancel(true);
-      scheduledFuture = null;
-    }
-    components.clear();
-    poolContext.clear();
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPluginFactory.java b/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPoolFactory.java
similarity index 55%
rename from solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPluginFactory.java
rename to solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPoolFactory.java
index 0f5cf79..3db59c7 100644
--- a/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPluginFactory.java
+++ b/solr/core/src/java/org/apache/solr/managed/DefaultResourceManagerPoolFactory.java
@@ -22,39 +22,41 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.solr.core.SolrResourceLoader;
-import org.apache.solr.managed.types.CacheManagerPlugin;
+import org.apache.solr.managed.types.CacheManagerPool;
 import org.apache.solr.search.SolrCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Default implementation of {@link ResourceManagerPluginFactory}.
+ * Default implementation of {@link ResourceManagerPoolFactory}.
  */
-public class DefaultResourceManagerPluginFactory implements ResourceManagerPluginFactory {
+public class DefaultResourceManagerPoolFactory implements ResourceManagerPoolFactory {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private static final Map<String, Class<? extends ResourceManagerPlugin>> typeToPluginClass = new HashMap<>();
+  private static final Map<String, Class<? extends ResourceManagerPool>> typeToPoolClass = new HashMap<>();
   private static final Map<String, Class<? extends ManagedComponent>> typeToComponentClass = new HashMap<>();
 
-  public static final String TYPE_TO_PLUGIN = "typeToPlugin";
+  public static final String TYPE_TO_POOL = "typeToPool";
   public static final String TYPE_TO_COMPONENT = "typeToComponent";
 
   static {
-    typeToPluginClass.put(CacheManagerPlugin.TYPE, CacheManagerPlugin.class);
-    typeToComponentClass.put(CacheManagerPlugin.TYPE, SolrCache.class);
+    typeToPoolClass.put(CacheManagerPool.TYPE, CacheManagerPool.class);
+    typeToPoolClass.put(NoOpResourceManager.NOOP, NoOpResourceManager.NoOpResourcePool.class);
+    typeToComponentClass.put(CacheManagerPool.TYPE, SolrCache.class);
+    typeToComponentClass.put(NoOpResourceManager.NOOP, NoOpResourceManager.NoOpManagedComponent.class);
   }
 
   private final SolrResourceLoader loader;
 
-  public DefaultResourceManagerPluginFactory(SolrResourceLoader loader, Map<String, Object> config) {
+  public DefaultResourceManagerPoolFactory(SolrResourceLoader loader, Map<String, Object> config) {
     this.loader = loader;
-    Map<String, String> typeToPluginMap = (Map<String, String>)config.getOrDefault(TYPE_TO_PLUGIN, Collections.emptyMap());
+    Map<String, String> typeToPoolMap = (Map<String, String>)config.getOrDefault(TYPE_TO_POOL, Collections.emptyMap());
     Map<String, String> typeToComponentMap = (Map<String, String>)config.getOrDefault(TYPE_TO_COMPONENT, Collections.emptyMap());
-    Map<String, Class<? extends ResourceManagerPlugin>> newPlugins = new HashMap<>();
+    Map<String, Class<? extends ResourceManagerPool>> newPlugins = new HashMap<>();
     Map<String, Class<? extends ManagedComponent>> newComponents = new HashMap<>();
-    typeToPluginMap.forEach((type, className) -> {
+    typeToPoolMap.forEach((type, className) -> {
       try {
-        Class<? extends ResourceManagerPlugin> pluginClazz = loader.findClass(className, ResourceManagerPlugin.class);
+        Class<? extends ResourceManagerPool> pluginClazz = loader.findClass(className, ResourceManagerPool.class);
         newPlugins.put(type, pluginClazz);
       } catch (Exception e) {
         log.warn("Error finding plugin class", e);
@@ -63,7 +65,7 @@ public class DefaultResourceManagerPluginFactory implements ResourceManagerPlugi
     typeToComponentMap.forEach((type, className) -> {
       try {
         Class<? extends ManagedComponent> componentClazz = loader.findClass(className, ManagedComponent.class);
-        if (typeToPluginClass.containsKey(type) || newPlugins.containsKey(type)) {
+        if (typeToPoolClass.containsKey(type) || newPlugins.containsKey(type)) {
           newComponents.put(type, componentClazz);
         }
       } catch (Exception e) {
@@ -75,7 +77,7 @@ public class DefaultResourceManagerPluginFactory implements ResourceManagerPlugi
       if (!newComponents.containsKey(type) && !typeToComponentClass.containsKey(type)) {
         return;
       }
-      typeToPluginClass.put(type, pluginClass);
+      typeToPoolClass.put(type, pluginClass);
       if (newComponents.containsKey(type)) {
         typeToComponentClass.put(type, newComponents.get(type));
       }
@@ -83,14 +85,23 @@ public class DefaultResourceManagerPluginFactory implements ResourceManagerPlugi
   }
 
   @Override
-  public <T extends ManagedComponent> ResourceManagerPlugin<T> create(String type, Map<String, Object> params) throws Exception {
-    Class<? extends ResourceManagerPlugin> pluginClazz = typeToPluginClass.get(type);
+  public <T extends ManagedComponent> ResourceManagerPool<T> create(String name, String type, ResourceManager resourceManager,
+                                                                    Map<String, Object> poolLimits, Map<String, Object> poolParams) throws Exception {
+    Class<? extends ResourceManagerPool> pluginClazz = typeToPoolClass.get(type);
     if (pluginClazz == null) {
       throw new IllegalArgumentException("Unsupported plugin type '" + type + "'");
     }
-    ResourceManagerPlugin<T> resourceManagerPlugin = loader.newInstance(pluginClazz.getName(), ResourceManagerPlugin.class);
-    resourceManagerPlugin.init(params);
-    return resourceManagerPlugin;
+    Class<? extends ManagedComponent> componentClass = typeToComponentClass.get(type);
+    if (componentClass == null) {
+      throw new IllegalArgumentException("Unsupported component type '" + type + "'");
+    }
+    ResourceManagerPool<T> resourceManagerPool = loader.newInstance(
+        pluginClazz.getName(),
+        ResourceManagerPool.class,
+        null,
+        new Class[]{String.class, String.class, ResourceManager.class, Map.class, Map.class},
+        new Object[]{name, type, resourceManager, poolLimits, poolParams});
+    return resourceManagerPool;
   }
 
   @Override
@@ -99,7 +110,7 @@ public class DefaultResourceManagerPluginFactory implements ResourceManagerPlugi
   }
 
   @Override
-  public Class<? extends ResourceManagerPlugin> getPluginClassByType(String type) {
-    return typeToPluginClass.get(type);
+  public Class<? extends ResourceManagerPool> getPoolClassByType(String type) {
+    return typeToPoolClass.get(type);
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java b/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java
index 4ea5922..df5c6c0 100644
--- a/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java
+++ b/solr/core/src/java/org/apache/solr/managed/ManagedComponent.java
@@ -30,11 +30,11 @@ public interface ManagedComponent extends AutoCloseable {
   /**
    * Component context used for managing additional component state for the purpose of resource management.
    */
-  ManagedContext getManagedContext();
+  SolrResourceContext getSolrResourceContext();
 
   default void close() throws Exception {
-    if (getManagedContext() != null) {
-      getManagedContext().close();
+    if (getSolrResourceContext() != null) {
+      getSolrResourceContext().close();
     }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/ManagedComponentId.java b/solr/core/src/java/org/apache/solr/managed/ManagedComponentId.java
index 9b80e2d..9f47d4a 100644
--- a/solr/core/src/java/org/apache/solr/managed/ManagedComponentId.java
+++ b/solr/core/src/java/org/apache/solr/managed/ManagedComponentId.java
@@ -29,21 +29,18 @@ public class ManagedComponentId {
 
   public static final String SEPARATOR = ":";
 
-  private final String type;
   private final String name;
   private final String[] path;
   private final String id;
 
-  public ManagedComponentId(String type, Object component, String... path) {
-    this(type, SolrMetricProducer.getUniqueMetricTag(component, null), path);
+  public ManagedComponentId(Object component, String... path) {
+    this(SolrMetricProducer.getUniqueMetricTag(component, null), path);
   }
 
-  ManagedComponentId(String type, String name, String... path) {
-    this.type = type;
+  ManagedComponentId(String name, String... path) {
     this.name = name;
     this.path = path;
     StringBuilder sb = new StringBuilder();
-    sb.append(type);
     if (path != null) {
       for (String pathEl : path) {
         if (sb.length() > 0) {
@@ -59,10 +56,6 @@ public class ManagedComponentId {
     id = sb.toString();
   }
 
-  public String getType() {
-    return type;
-  }
-
   public String getName() {
     return name;
   }
@@ -80,16 +73,12 @@ public class ManagedComponentId {
       return null;
     }
     String[] parts = fullName.split(SEPARATOR);
-    if (parts.length < 2) {
-      throw new RuntimeException("at least 2 parts (type and name) must be present: " + fullName);
-    }
-    if (parts.length > 2) {
-      String type = parts[0];
+    if (parts.length > 1) {
       String name = parts[parts.length - 1];
-      String[] path = Arrays.copyOfRange(parts, 1, parts.length - 1);
-      return new ManagedComponentId(type, name, path);
+      String[] path = Arrays.copyOfRange(parts, 0, parts.length - 1);
+      return new ManagedComponentId(name, path);
     } else {
-      return new ManagedComponentId(parts[0], parts[1]);
+      return new ManagedComponentId(parts[0]);
     }
   }
 }
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/managed/NoOpResourceManager.java b/solr/core/src/java/org/apache/solr/managed/NoOpResourceManager.java
index 9dc0fc3..f6ce453 100644
--- a/solr/core/src/java/org/apache/solr/managed/NoOpResourceManager.java
+++ b/solr/core/src/java/org/apache/solr/managed/NoOpResourceManager.java
@@ -30,121 +30,49 @@ public class NoOpResourceManager extends ResourceManager {
 
   public static final NoOpResourceManager INSTANCE = new NoOpResourceManager();
 
-  private static final class NoOpResourceManagerPlugin implements ResourceManagerPlugin {
-    static final NoOpResourceManagerPlugin INSTANCE = new NoOpResourceManagerPlugin();
-
-    @Override
-    public String getType() {
-      return NOOP;
-    }
-
-    @Override
-    public Collection<String> getMonitoredParams() {
-      return Collections.emptySet();
-    }
-
-    @Override
-    public Collection<String> getControlledParams() {
-      return Collections.emptySet();
-    }
-
-    @Override
-    public Map<String, Object> getMonitoredValues(ManagedComponent component) throws Exception {
-      return Collections.emptyMap();
-    }
-
+  public static class NoOpManagedComponent implements ManagedComponent {
     @Override
-    public void setResourceLimit(ManagedComponent component, String limitName, Object value) throws Exception {
-      // no-op
+    public ManagedComponentId getManagedComponentId() {
+      return ManagedComponentId.of(NOOP);
     }
 
     @Override
-    public Map<String, Object> getResourceLimits(ManagedComponent component) throws Exception {
-      return Collections.emptyMap();
-    }
+    public void initializeManagedComponent(ResourceManager resourceManager, String poolName, String... otherPools) {
 
-    @Override
-    public void manage(ResourceManagerPool pool) throws Exception {
-      // no-op
     }
 
     @Override
-    public void init(Map params) {
-      // no-op
+    public SolrResourceContext getSolrResourceContext() {
+      return null;
     }
   }
 
-  private static final class NoOpResourcePool implements ResourceManagerPool {
-    static final NoOpResourcePool INSTANCE = new NoOpResourcePool();
-
-    @Override
-    public String getName() {
-      return NOOP;
-    }
-
-    @Override
-    public String getType() {
-      return NOOP;
-    }
+  public static final class NoOpResourcePool<NoOpManagedComponent> extends ResourceManagerPool {
+    static final NoOpResourcePool<NoOpResourceManager.NoOpManagedComponent> INSTANCE =
+        new NoOpResourcePool<>(NoOpResourceManager.INSTANCE, Collections.emptyMap(), Collections.emptyMap());
 
-    @Override
-    public ResourceManagerPlugin getResourceManagerPlugin() {
-      return NoOpResourceManagerPlugin.INSTANCE;
+    public NoOpResourcePool(ResourceManager resourceManager, Map poolLimits, Map poolParams) {
+      super(NOOP, NOOP, resourceManager, poolLimits, poolParams);
     }
 
     @Override
-    public void registerComponent(ManagedComponent managedComponent) {
-      // no-op
-    }
-
-    @Override
-    public boolean unregisterComponent(String name) {
-      return false;
-    }
-
-    @Override
-    public boolean isRegistered(String componentId) {
-      return false;
-    }
-
-    @Override
-    public Map<String, ManagedComponent> getComponents() {
+    public Map<String, Object> getMonitoredValues(ManagedComponent component) {
       return Collections.emptyMap();
     }
 
     @Override
-    public Map<String, Map<String, Object>> getCurrentValues() throws InterruptedException {
-      return Collections.emptyMap();
+    protected Object doSetResourceLimit(ManagedComponent component, String limitName, Object value) throws Exception {
+      return value;
     }
 
     @Override
-    public Map<String, Object> getPoolLimits() {
+    public Map<String, Object> getResourceLimits(ManagedComponent component) throws Exception {
       return Collections.emptyMap();
     }
 
     @Override
-    public Map<String, Object> getParams() {
-      return Collections.emptyMap();
-    }
+    protected void doManage() throws Exception {
 
-    @Override
-    public void setPoolLimits(Map<String, Object> poolLimits) {
-      // no-op
-    }
-
-    @Override
-    public PoolContext getPoolContext() {
-      return null;
-    }
-
-    @Override
-    public void close() throws IOException {
-      // no-op
-    }
-
-    @Override
-    public void run() {
-      // no-op
     }
   }
 
@@ -154,8 +82,13 @@ public class NoOpResourceManager extends ResourceManager {
   }
 
   @Override
-  public void createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception {
-    // no-op
+  public ResourceManagerPoolFactory getResourceManagerPoolFactory() {
+    return null;
+  }
+
+  @Override
+  public ResourceManagerPool createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception {
+    return NoOpResourcePool.INSTANCE;
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/managed/ResourceManager.java b/solr/core/src/java/org/apache/solr/managed/ResourceManager.java
index 364a99d..44b9c86 100644
--- a/solr/core/src/java/org/apache/solr/managed/ResourceManager.java
+++ b/solr/core/src/java/org/apache/solr/managed/ResourceManager.java
@@ -30,7 +30,6 @@ import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.schema.FieldType;
-import org.apache.solr.util.SolrPluginUtils;
 import org.apache.solr.util.plugin.PluginInfoInitialized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,7 +37,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Base class for resource management. It uses a flat model where there are named
  * resource pools of a given type, each pool with its own defined resource limits. Components can be added
- * to a pool for the management of a specific aspect of that component using {@link ResourceManagerPlugin}.
+ * to a pool for the management of a specific aspect of that component.
  */
 public abstract class ResourceManager implements SolrCloseable, PluginInfoInitialized {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -162,14 +161,17 @@ public abstract class ResourceManager implements SolrCloseable, PluginInfoInitia
     }
   }
 
+  public abstract ResourceManagerPoolFactory getResourceManagerPoolFactory();
+
   /**
    * Create a named resource management pool.
-   * @param name pool name
-   * @param type pool type (one of the supported {@link ResourceManagerPlugin} types)
-   * @param poolLimits pool limits
-   * @param args other parameters. These are also used for creating a {@link ResourceManagerPlugin}
+   * @param name pool name (must not be empty)
+   * @param type pool type (one of the supported {@link ResourceManagerPool} types)
+   * @param poolLimits pool limits (must not be null)
+   * @param args other parameters (must not be null).
+   * @return newly created and scheduled resource pool
    */
-  public abstract void createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception;
+  public abstract ResourceManagerPool createPool(String name, String type, Map<String, Object> poolLimits, Map<String, Object> args) throws Exception;
 
   /**
    * List all currently existing pool names.
diff --git a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPlugin.java b/solr/core/src/java/org/apache/solr/managed/ResourceManagerPlugin.java
deleted file mode 100644
index dc6a5b6..0000000
--- a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPlugin.java
+++ /dev/null
@@ -1,93 +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.solr.managed;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A plugin that implements an algorithm for managing a pool of resources of a given type.
- */
-public interface ResourceManagerPlugin<T extends ManagedComponent> {
-
-  /** Plugin symbolic type. */
-  String getType();
-
-  void init(Map<String, Object> params);
-
-  /**
-   * Name of monitored parameters that {@link ManagedComponent}-s managed by this plugin
-   * are expected to support.
-   */
-  Collection<String> getMonitoredParams();
-  /**
-   * Name of controlled parameters that {@link ManagedComponent}-s managed by this plugin
-   * are expected to support.
-   */
-  Collection<String> getControlledParams();
-
-  /**
-   * Return current values of monitored parameters. Note: the resulting map may contain also
-   * other implementation-specific parameter values.
-   * @param component monitored component
-   */
-  Map<String, Object> getMonitoredValues(T component) throws Exception;
-
-  default void setResourceLimits(T component, Map<String, Object> limits) throws Exception {
-    if (limits == null || limits.isEmpty()) {
-      return;
-    }
-    for (Map.Entry<String, Object> entry : limits.entrySet()) {
-      setResourceLimit(component, entry.getKey(), entry.getValue());
-    }
-  }
-
-  void setResourceLimit(T component, String limitName, Object value) throws Exception;
-
-  Map<String, Object> getResourceLimits(T component) throws Exception;
-
-  /**
-   * Manage resources in a pool. This method is called periodically by {@link ResourceManager},
-   * according to a schedule defined by the pool.
-   * @param pool pool instance.
-   */
-  void manage(ResourceManagerPool pool) throws Exception;
-
-  /**
-   * Return aggregated current monitored values.
-   * <p>Default implementation of this method simply sums up all non-negative numeric values across
-   * components and ignores any non-numeric values.</p>
-   */
-  default Map<String, Object> aggregateTotalValues(Map<String, Map<String, Object>> perComponentValues) {
-    // calculate the totals
-    Map<String, Object> newTotalValues = new HashMap<>();
-    perComponentValues.values().forEach(map -> map.forEach((k, v) -> {
-      // only calculate totals for numbers
-      if (!(v instanceof Number)) {
-        return;
-      }
-      Double val = ((Number)v).doubleValue();
-      // -1 and MAX_VALUE are our special guard values
-      if (val < 0 || val.longValue() == Long.MAX_VALUE || val.longValue() == Integer.MAX_VALUE) {
-        return;
-      }
-      newTotalValues.merge(k, val, (v1, v2) -> ((Number)v1).doubleValue() + ((Number)v2).doubleValue());
-    }));
-    return newTotalValues;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPool.java b/solr/core/src/java/org/apache/solr/managed/ResourceManagerPool.java
index 4f1a786..dfa822f 100644
--- a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPool.java
+++ b/solr/core/src/java/org/apache/solr/managed/ResourceManagerPool.java
@@ -1,56 +1,216 @@
 package org.apache.solr.managed;
 
 import java.io.Closeable;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  *
  */
-public interface ResourceManagerPool extends Runnable, Closeable {
+public abstract class ResourceManagerPool<T extends ManagedComponent> implements Closeable {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  protected final String name;
+  protected final String type;
+  protected Map<String, Object> poolLimits;
+  protected final Map<String, T> components = new ConcurrentHashMap<>();
+  protected final ResourceManager resourceManager;
+  protected final Class<? extends ManagedComponent> componentClass;
+  private final Map<String, Object> poolParams;
+  protected final ResourcePoolContext poolContext = new ResourcePoolContext();
+  protected final List<ChangeListener> listeners = new ArrayList<>();
+  protected final ReentrantLock updateLock = new ReentrantLock();
+  protected int scheduleDelaySeconds;
+  protected ScheduledFuture<?> scheduledFuture;
+
+  public ResourceManagerPool(String name, String type, ResourceManager resourceManager,
+                                Map<String, Object> poolLimits, Map<String, Object> poolParams) {
+    this.name = name;
+    this.type = type;
+    this.resourceManager = resourceManager;
+    this.componentClass = resourceManager.getResourceManagerPoolFactory().getComponentClassByType(type);
+    this.poolLimits = new HashMap<>(poolLimits);
+    this.poolParams = new HashMap<>(poolParams);
+  }
 
   /** Unique pool name. */
-  String getName();
+  public String getName() {
+    return name;
+  }
 
   /** Pool type. */
-  String getType();
+  public String getType() {
+    return type;
+  }
 
-  ResourceManagerPlugin getResourceManagerPlugin();
+  public ResourceManager getResourceManager() {
+    return resourceManager;
+  }
 
   /** Add component to this pool. */
-  void registerComponent(ManagedComponent managedComponent);
+  public void registerComponent(T managedComponent) {
+    if (!componentClass.isAssignableFrom(managedComponent.getClass())) {
+      log.debug("Pool type '" + type + "' is not supported by the component " + managedComponent.getManagedComponentId());
+      return;
+    }
+    ManagedComponent existing = components.putIfAbsent(managedComponent.getManagedComponentId().toString(), managedComponent);
+    if (existing != null) {
+      throw new IllegalArgumentException("Component '" + managedComponent.getManagedComponentId() + "' already exists in pool '" + name + "' !");
+    }
+  }
 
   /** Remove named component from this pool. */
-  boolean unregisterComponent(String componentId);
+  public boolean unregisterComponent(String componentId) {
+    return components.remove(name) != null;
+  }
 
   /**
    * Check whether a named component is registered in this pool.
    * @param componentId component id
    * @return true if the component with this name is registered, false otherwise.
    */
-  boolean isRegistered(String componentId);
+  public boolean isRegistered(String componentId) {
+    return components.containsKey(componentId);
+  }
 
   /** Get components managed by this pool. */
-  Map<String, ManagedComponent> getComponents();
+  public Map<String, T> getComponents() {
+    return Collections.unmodifiableMap(components);
+  }
+
+  public void addChangeListener(ChangeListener listener) {
+    if (!listeners.contains(listener)) {
+      listeners.add(listener);
+    }
+  }
+
+  public void removeChangeListener(ChangeListener listener) {
+    listeners.remove(listener);
+  }
+
 
   /**
    * Get the current monitored values from all resources. Result is a map with resource names as keys,
    * and param/value maps as values.
    */
-  Map<String, Map<String, Object>> getCurrentValues() throws InterruptedException;
+  public Map<String, Map<String, Object>> getCurrentValues() throws InterruptedException {
+    updateLock.lockInterruptibly();
+    try {
+      // collect the current values
+      Map<String, Map<String, Object>> currentValues = new HashMap<>();
+      for (T managedComponent : components.values()) {
+        try {
+          currentValues.put(managedComponent.getManagedComponentId().toString(), getMonitoredValues(managedComponent));
+        } catch (Exception e) {
+          log.warn("Error getting managed values from " + managedComponent.getManagedComponentId(), e);
+        }
+      }
+      return Collections.unmodifiableMap(currentValues);
+    } finally {
+      updateLock.unlock();
+    }
+  }
 
-  /** Get current pool limits. */
-  Map<String, Object> getPoolLimits();
+  public abstract Map<String, Object> getMonitoredValues(T component) throws Exception;
 
-  /** Get parameters specified during creation. */
-  Map<String, Object> getParams();
+  public void setResourceLimits(T component, Map<String, Object> limits) throws Exception {
+    if (limits == null || limits.isEmpty()) {
+      return;
+    }
+    for (Map.Entry<String, Object> entry : limits.entrySet()) {
+      setResourceLimit(component, entry.getKey(), entry.getValue());
+    }
+  }
+
+  public Object setResourceLimit(T component, String limitName, Object value) throws Exception {
+    Object newActualLimit = doSetResourceLimit(component, limitName, value);
+    for (ChangeListener listener : listeners) {
+      listener.changedLimit(getName(), component, limitName, value, newActualLimit);
+    }
+    return newActualLimit;
+  }
+
+  protected abstract Object doSetResourceLimit(T component, String limitName, Object value) throws Exception;
+
+  public abstract Map<String, Object> getResourceLimits(T component) throws Exception;
+
+  /**
+   * Calculate aggregated monitored values.
+   * <p>Default implementation of this method simply sums up all non-negative numeric values across
+   * components and ignores any non-numeric values.</p>
+   */
+  public Map<String, Object> aggregateTotalValues(Map<String, Map<String, Object>> perComponentValues) {
+    // calculate the totals
+    Map<String, Object> newTotalValues = new HashMap<>();
+    perComponentValues.values().forEach(map -> map.forEach((k, v) -> {
+      // only calculate totals for numbers
+      if (!(v instanceof Number)) {
+        return;
+      }
+      Double val = ((Number)v).doubleValue();
+      // -1 and MAX_VALUE are our special guard values
+      if (val < 0 || val.longValue() == Long.MAX_VALUE || val.longValue() == Integer.MAX_VALUE) {
+        return;
+      }
+      newTotalValues.merge(k, val, (v1, v2) -> ((Number)v1).doubleValue() + ((Number)v2).doubleValue());
+    }));
+    return newTotalValues;
+  }
+
+  /** Get current pool limits. */
+  public Map<String, Object> getPoolLimits() {
+    return Collections.unmodifiableMap(poolLimits);
+  }
 
   /**
    * Pool limits are defined using controlled tags.
    */
-  void setPoolLimits(Map<String, Object> poolLimits);
+  public void setPoolLimits(Map<String, Object> poolLimits) {
+    this.poolLimits = new HashMap(poolLimits);
+  }
+
+  /** Get parameters specified during creation. */
+  public Map<String, Object> getParams() {
+    return Collections.unmodifiableMap(poolParams);
+  }
 
   /**
    * Pool context used for managing additional pool state.
    */
-  PoolContext getPoolContext();
+  public ResourcePoolContext getResourcePoolContext() {
+    return poolContext;
+  }
+
+  public void manage() {
+    updateLock.lock();
+    try {
+      doManage();
+    } catch (Exception e) {
+      log.warn("Exception caught managing pool " + getName(), e);
+    } finally {
+      updateLock.unlock();
+    }
+  }
+
+  protected abstract void doManage() throws Exception;
+
+  public void close() throws IOException {
+    if (scheduledFuture != null) {
+      scheduledFuture.cancel(true);
+      scheduledFuture = null;
+    }
+    components.clear();
+    poolContext.clear();
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPluginFactory.java b/solr/core/src/java/org/apache/solr/managed/ResourceManagerPoolFactory.java
similarity index 67%
rename from solr/core/src/java/org/apache/solr/managed/ResourceManagerPluginFactory.java
rename to solr/core/src/java/org/apache/solr/managed/ResourceManagerPoolFactory.java
index 508b5e9..8110481 100644
--- a/solr/core/src/java/org/apache/solr/managed/ResourceManagerPluginFactory.java
+++ b/solr/core/src/java/org/apache/solr/managed/ResourceManagerPoolFactory.java
@@ -19,16 +19,17 @@ package org.apache.solr.managed;
 import java.util.Map;
 
 /**
- * Factory for creating instances of {@link ResourceManagerPlugin}-s.
+ * Factory for creating instances of {@link ResourceManagerPool}-s.
  */
-public interface ResourceManagerPluginFactory {
+public interface ResourceManagerPoolFactory {
 
   /**
-   * Create a plugin of a given symbolic type.
-   * @param type plugin symbolic type
-   * @param params plugin parameters
+   * Create a pool of a given symbolic type.
+   * @param type pool symbolic type
+   * @param poolParams pool parameters
    */
-  <T extends ManagedComponent> ResourceManagerPlugin<T> create(String type, Map<String, Object> params) throws Exception;
+  <T extends ManagedComponent> ResourceManagerPool<T> create(String name, String type, ResourceManager resourceManager,
+                                                             Map<String, Object> poolLimits, Map<String, Object> poolParams) throws Exception;
 
   /**
    * Get the implementation class for a component of a given symbolic type.
@@ -40,5 +41,5 @@ public interface ResourceManagerPluginFactory {
    * Get the implementation class for a plugin of a given symbolic type.
    * @param type symbolic type
    */
-  Class<? extends ResourceManagerPlugin> getPluginClassByType(String type);
+  Class<? extends ResourceManagerPool> getPoolClassByType(String type);
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/PoolContext.java b/solr/core/src/java/org/apache/solr/managed/ResourcePoolContext.java
similarity index 55%
rename from solr/core/src/java/org/apache/solr/managed/PoolContext.java
rename to solr/core/src/java/org/apache/solr/managed/ResourcePoolContext.java
index 221e1d0..dd1889c 100644
--- a/solr/core/src/java/org/apache/solr/managed/PoolContext.java
+++ b/solr/core/src/java/org/apache/solr/managed/ResourcePoolContext.java
@@ -5,5 +5,5 @@ import java.util.concurrent.ConcurrentHashMap;
 /**
  *
  */
-public class PoolContext extends ConcurrentHashMap<String, Object> {
+public class ResourcePoolContext extends ConcurrentHashMap<String, Object> {
 }
diff --git a/solr/core/src/java/org/apache/solr/managed/ManagedContext.java b/solr/core/src/java/org/apache/solr/managed/SolrResourceContext.java
similarity index 90%
rename from solr/core/src/java/org/apache/solr/managed/ManagedContext.java
rename to solr/core/src/java/org/apache/solr/managed/SolrResourceContext.java
index f426d7c..09d86dce 100644
--- a/solr/core/src/java/org/apache/solr/managed/ManagedContext.java
+++ b/solr/core/src/java/org/apache/solr/managed/SolrResourceContext.java
@@ -24,12 +24,12 @@ import java.util.Set;
 /**
  *
  */
-public class ManagedContext implements Closeable {
+public class SolrResourceContext implements Closeable {
   private final ResourceManager resourceManager;
   private final String[] poolNames;
   private final ManagedComponent component;
 
-  public ManagedContext(ResourceManager resourceManager, ManagedComponent component, String poolName, String... otherPools) {
+  public SolrResourceContext(ResourceManager resourceManager, ManagedComponent component, String poolName, String... otherPools) {
     this.resourceManager = resourceManager;
     Set<String> pools = new LinkedHashSet<>();
     pools.add(poolName);
diff --git a/solr/core/src/java/org/apache/solr/managed/types/CacheManagerPlugin.java b/solr/core/src/java/org/apache/solr/managed/types/CacheManagerPool.java
similarity index 61%
rename from solr/core/src/java/org/apache/solr/managed/types/CacheManagerPlugin.java
rename to solr/core/src/java/org/apache/solr/managed/types/CacheManagerPool.java
index 695b046..cee4fe7 100644
--- a/solr/core/src/java/org/apache/solr/managed/types/CacheManagerPlugin.java
+++ b/solr/core/src/java/org/apache/solr/managed/types/CacheManagerPool.java
@@ -17,19 +17,19 @@
 package org.apache.solr.managed.types;
 
 import java.lang.invoke.MethodHandles;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
 
-import org.apache.solr.managed.ResourceManagerPlugin;
+import org.apache.solr.managed.ResourceManager;
 import org.apache.solr.managed.ResourceManagerPool;
+import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.search.SolrCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * An implementation of {@link org.apache.solr.managed.ResourceManagerPlugin} specific to
+ * An implementation of {@link org.apache.solr.managed.ResourceManagerPool} specific to
  * the management of {@link org.apache.solr.search.SolrCache} instances.
  * <p>This plugin calculates the total size and maxRamMB of all registered cache instances
  * and adjusts each cache's limits so that the aggregated values again fit within the pool limits.</p>
@@ -37,46 +37,39 @@ import org.slf4j.LoggerFactory;
  * which can be adjusted using configuration parameter {@link #DEAD_BAND}. If monitored values don't
  * exceed the limits +/- the dead band then no action is taken.</p>
  */
-public class CacheManagerPlugin implements ResourceManagerPlugin<SolrCache> {
+public class CacheManagerPool extends ResourceManagerPool<SolrCache> {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static String TYPE = "cache";
 
   public static final String DEAD_BAND = "deadBand";
-  public static final float DEFAULT_DEAD_BAND = 0.1f;
+  public static final double DEFAULT_DEAD_BAND = 0.1;
 
-  protected static final Map<String, String> controlledToMonitored = new HashMap<>();
+  protected static final Map<String, Function<Map<String, Object>, Double>> controlledToMonitored = new HashMap<>();
 
   static {
-    controlledToMonitored.put(SolrCache.MAX_RAM_MB_PARAM, SolrCache.RAM_BYTES_USED_PARAM);
-    controlledToMonitored.put(SolrCache.MAX_SIZE_PARAM, SolrCache.SIZE_PARAM);
+    controlledToMonitored.put(SolrCache.MAX_RAM_MB_PARAM, values -> {
+      Number ramBytes = (Number) values.get(SolrCache.RAM_BYTES_USED_PARAM);
+      return ramBytes != null ? ramBytes.doubleValue() / SolrCache.MB : 0.0;
+    });
+    controlledToMonitored.put(SolrCache.MAX_SIZE_PARAM, values ->
+        ((Number)values.getOrDefault(SolrCache.MAX_SIZE_PARAM, -1.0)).doubleValue());
   }
 
-  protected static final Collection<String> MONITORED_PARAMS = Arrays.asList(
-      SolrCache.SIZE_PARAM,
-      SolrCache.HIT_RATIO_PARAM,
-      SolrCache.RAM_BYTES_USED_PARAM
-  );
-
-  protected static final Collection<String> CONTROLLED_PARAMS = Arrays.asList(
-      SolrCache.MAX_RAM_MB_PARAM,
-      SolrCache.MAX_SIZE_PARAM
-  );
-
-  protected float deadBand = DEFAULT_DEAD_BAND;
-
-  @Override
-  public Collection<String> getMonitoredParams() {
-    return MONITORED_PARAMS;
-  }
+  protected double deadBand = DEFAULT_DEAD_BAND;
 
-  @Override
-  public Collection<String> getControlledParams() {
-    return CONTROLLED_PARAMS;
+  public CacheManagerPool(String name, String type, ResourceManager resourceManager, Map<String, Object> poolLimits, Map<String, Object> poolParams) {
+    super(name, type, resourceManager, poolLimits, poolParams);
+    String deadBandStr = String.valueOf(poolParams.getOrDefault(DEAD_BAND, DEFAULT_DEAD_BAND));
+    try {
+      deadBand = Double.parseDouble(deadBandStr);
+    } catch (Exception e) {
+      log.warn("Invalid deadBand parameter value '" + deadBandStr + "', using default " + DEFAULT_DEAD_BAND);
+    }
   }
 
   @Override
-  public void setResourceLimit(SolrCache component, String limitName, Object val) {
+  public Object doSetResourceLimit(SolrCache component, String limitName, Object val) {
     if (!(val instanceof Number)) {
       try {
         val = Long.parseLong(String.valueOf(val));
@@ -98,6 +91,7 @@ public class CacheManagerPlugin implements ResourceManagerPlugin<SolrCache> {
       default:
         throw new IllegalArgumentException("Unsupported limit name '" + limitName + "'");
     }
+    return value.intValue();
   }
 
   @Override
@@ -110,60 +104,50 @@ public class CacheManagerPlugin implements ResourceManagerPlugin<SolrCache> {
 
   @Override
   public Map<String, Object> getMonitoredValues(SolrCache component) throws Exception {
-    return component.getSolrMetricsContext().getMetricsSnapshot();
-  }
-
-  @Override
-  public String getType() {
-    return TYPE;
-  }
-
-  @Override
-  public void init(Map<String, Object> params) {
-    String deadBandStr = String.valueOf(params.getOrDefault(DEAD_BAND, DEFAULT_DEAD_BAND));
-    try {
-      deadBand = Float.parseFloat(deadBandStr);
-    } catch (Exception e) {
-      log.warn("Invalid deadBand parameter value '" + deadBandStr + "', using default " + DEFAULT_DEAD_BAND);
+    Map<String, Object> values = new HashMap<>();
+    values.put(SolrCache.SIZE_PARAM, component.size());
+    values.put(SolrCache.RAM_BYTES_USED_PARAM, component.ramBytesUsed());
+    SolrMetricsContext metricsContext = component.getSolrMetricsContext();
+    if (metricsContext != null) {
+      Map<String, Object> metrics = metricsContext.getMetricsSnapshot();
+      String hitRatioKey = component.getCategory().toString() + "." + metricsContext.getScope() + "." + SolrCache.HIT_RATIO_PARAM;
+      values.put(SolrCache.HIT_RATIO_PARAM, metrics.get(hitRatioKey));
     }
+    return values;
   }
 
   @Override
-  public void manage(ResourceManagerPool pool) throws Exception {
-    Map<String, Map<String, Object>> currentValues = pool.getCurrentValues();
-    Map<String, Object> totalValues = pool.getResourceManagerPlugin().aggregateTotalValues(currentValues);
+  protected void doManage() throws Exception {
+    Map<String, Map<String, Object>> currentValues = getCurrentValues();
+    Map<String, Object> totalValues = aggregateTotalValues(currentValues);
     // pool limits are defined using controlled tags
-    pool.getPoolLimits().forEach((poolLimitName, value) -> {
+    poolLimits.forEach((poolLimitName, value) -> {
       // only numeric limits are supported
       if (value == null || !(value instanceof Number)) {
         return;
       }
-      float poolLimitValue = ((Number)value).floatValue();
+      double poolLimitValue = ((Number)value).doubleValue();
       if (poolLimitValue <= 0) {
         return;
       }
-      String monitoredTag = controlledToMonitored.get(poolLimitName);
-      if (monitoredTag == null) {
-        return;
-      }
-      Object tv = totalValues.get(monitoredTag);
-      if (tv == null || !(tv instanceof Number)) {
+      Function<Map<String, Object>, Double> func = controlledToMonitored.get(poolLimitName);
+      if (func == null) {
         return;
       }
-      Number totalValue = (Number) tv;
-      if (totalValue.floatValue() <= 0.0f) {
+      Double totalValue = func.apply(totalValues);
+      if (totalValue.doubleValue() <= 0.0) {
         return;
       }
-      float totalDelta = poolLimitValue - totalValue.floatValue();
+      double totalDelta = poolLimitValue - totalValue.doubleValue();
 
       // dead band to avoid thrashing
       if (Math.abs(totalDelta / poolLimitValue) < deadBand) {
         return;
       }
 
-      float changeRatio = poolLimitValue / totalValue.floatValue();
-      // modify current limits by the changeRatio
-      pool.getComponents().forEach((name, component) -> {
+      double changeRatio = poolLimitValue / totalValue.doubleValue();
+      // modify evenly every component's current limits by the changeRatio
+      components.forEach((name, component) -> {
         Map<String, Object> resourceLimits = getResourceLimits((SolrCache) component);
         Object limit = resourceLimits.get(poolLimitName);
         // XXX we could attempt here to control eg. ramBytesUsed by adjusting maxSize limit
@@ -171,11 +155,11 @@ public class CacheManagerPlugin implements ResourceManagerPlugin<SolrCache> {
         if (limit == null || !(limit instanceof Number)) {
           return;
         }
-        float currentResourceLimit = ((Number)limit).floatValue();
+        double currentResourceLimit = ((Number)limit).doubleValue();
         if (currentResourceLimit <= 0) { // undefined or unsupported
           return;
         }
-        float newLimit = currentResourceLimit * changeRatio;
+        double newLimit = currentResourceLimit * changeRatio;
         try {
           setResourceLimit((SolrCache) component, poolLimitName, newLimit);
         } catch (Exception e) {
diff --git a/solr/core/src/java/org/apache/solr/managed/types/package-info.java b/solr/core/src/java/org/apache/solr/managed/types/package-info.java
index a7dde4d..5b8e137 100644
--- a/solr/core/src/java/org/apache/solr/managed/types/package-info.java
+++ b/solr/core/src/java/org/apache/solr/managed/types/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * Implementations of {@link org.apache.solr.managed.ResourceManagerPlugin} specialized for
+ * Implementations of {@link org.apache.solr.managed.ResourceManagerPool} specialized for
  * particular types of objects.
  */
 package org.apache.solr.managed.types;
diff --git a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
index 2002d4b..d40b319 100644
--- a/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
+++ b/solr/core/src/java/org/apache/solr/search/CaffeineCache.java
@@ -36,9 +36,8 @@ import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.managed.ManagedComponentId;
-import org.apache.solr.managed.ManagedContext;
+import org.apache.solr.managed.SolrResourceContext;
 import org.apache.solr.managed.ResourceManager;
-import org.apache.solr.managed.types.CacheManagerPlugin;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.slf4j.Logger;
@@ -91,7 +90,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase implements SolrCache<K, V
   private MetricsMap cacheMap;
   private SolrMetricsContext solrMetricsContext;
 
-  private ManagedContext managedContext;
+  private SolrResourceContext solrResourceContext;
   private ManagedComponentId managedComponentId;
 
   private long initialRamBytes = 0;
@@ -117,7 +116,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase implements SolrCache<K, V
     }
     str = (String) args.get(MAX_RAM_MB_PARAM);
     int maxRamMB = str == null ? -1 : Double.valueOf(str).intValue();
-    maxRamBytes = maxRamMB < 0 ? Long.MAX_VALUE : maxRamMB * 1024L * 1024L;
+    maxRamBytes = maxRamMB < 0 ? Long.MAX_VALUE : maxRamMB * MB;
     str = (String) args.get(CLEANUP_THREAD_PARAM);
     cleanupThread = str != null && Boolean.parseBoolean(str);
     if (cleanupThread) {
@@ -265,12 +264,12 @@ public class CaffeineCache<K, V> extends SolrCacheBase implements SolrCache<K, V
 
   @Override
   public int getMaxRamMB() {
-    return maxRamBytes != Long.MAX_VALUE ? (int) (maxRamBytes / 1024L / 1024L) : -1;
+    return maxRamBytes != Long.MAX_VALUE ? (int) (maxRamBytes / MB) : -1;
   }
 
   @Override
   public void setMaxRamMB(int maxRamMB) {
-    long newMaxRamBytes = maxRamMB < 0 ? Long.MAX_VALUE : maxRamMB * 1024L * 1024L;
+    long newMaxRamBytes = maxRamMB < 0 ? Long.MAX_VALUE : maxRamMB * MB;
     if (newMaxRamBytes != maxRamBytes) {
       maxRamBytes = newMaxRamBytes;
       Optional<Eviction<K, V>> evictionOpt = cache.policy().eviction();
@@ -388,13 +387,13 @@ public class CaffeineCache<K, V> extends SolrCacheBase implements SolrCache<K, V
         map.put("cumulative_evictions", cumulativeStats.evictionCount());
       }
     });
-    solrMetricsContext.gauge(cacheMap, true, scope, getCategory().toString());
+    solrMetricsContext.gauge(cacheMap, true, null, getCategory().toString());
   }
 
   @Override
   public void initializeManagedComponent(ResourceManager resourceManager, String poolName, String... otherPools) {
-    managedComponentId = new ManagedComponentId(CacheManagerPlugin.TYPE, this, solrMetricsContext.getRegistryName(), getCategory().toString(), solrMetricsContext.getScope());
-    managedContext = new ManagedContext(resourceManager, this, poolName, otherPools);
+    managedComponentId = new ManagedComponentId(this, solrMetricsContext.getRegistryName(), getCategory().toString(), solrMetricsContext.getScope());
+    solrResourceContext = new SolrResourceContext(resourceManager, this, poolName, otherPools);
   }
 
   @Override
@@ -403,7 +402,7 @@ public class CaffeineCache<K, V> extends SolrCacheBase implements SolrCache<K, V
   }
 
   @Override
-  public ManagedContext getManagedContext() {
-    return managedContext;
+  public SolrResourceContext getSolrResourceContext() {
+    return solrResourceContext;
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCache.java b/solr/core/src/java/org/apache/solr/search/SolrCache.java
index a8ead8c..ecc0308 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrCache.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCache.java
@@ -55,6 +55,8 @@ public interface SolrCache<K,V> extends SolrInfoBean, ManagedComponent, Accounta
   /** Use a background thread for cache evictions and cleanup. */
   String CLEANUP_THREAD_PARAM = "cleanupThread";
 
+  long MB = 1024L * 1024L;
+
   /**
    * The initialization routine. Instance specific arguments are passed in
    * the <code>args</code> map.
diff --git a/solr/core/src/java/org/apache/solr/search/SolrCacheHolder.java b/solr/core/src/java/org/apache/solr/search/SolrCacheHolder.java
index 5f1dbf8..8f4b3d6 100644
--- a/solr/core/src/java/org/apache/solr/search/SolrCacheHolder.java
+++ b/solr/core/src/java/org/apache/solr/search/SolrCacheHolder.java
@@ -22,7 +22,7 @@ import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.solr.managed.ManagedComponentId;
-import org.apache.solr.managed.ManagedContext;
+import org.apache.solr.managed.SolrResourceContext;
 import org.apache.solr.managed.ResourceManager;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.slf4j.Logger;
@@ -166,7 +166,7 @@ public class SolrCacheHolder<K, V> implements SolrCache<K,V> {
   }
 
   @Override
-  public ManagedContext getManagedContext() {
-    return delegate.getManagedContext();
+  public SolrResourceContext getSolrResourceContext() {
+    return delegate.getSolrResourceContext();
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/managed/TestDefaultResourceManagerPool.java b/solr/core/src/test/org/apache/solr/managed/TestResourceManagerPool.java
similarity index 69%
rename from solr/core/src/test/org/apache/solr/managed/TestDefaultResourceManagerPool.java
rename to solr/core/src/test/org/apache/solr/managed/TestResourceManagerPool.java
index ebb9129..9294d23 100644
--- a/solr/core/src/test/org/apache/solr/managed/TestDefaultResourceManagerPool.java
+++ b/solr/core/src/test/org/apache/solr/managed/TestResourceManagerPool.java
@@ -1,8 +1,6 @@
 package org.apache.solr.managed;
 
 import java.lang.invoke.MethodHandles;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -23,7 +21,7 @@ import org.slf4j.LoggerFactory;
 /**
  *
  */
-public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
+public class TestResourceManagerPool extends SolrTestCaseJ4 {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private static final int SPEED = 50;
@@ -33,15 +31,8 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
   private ResourceManager resourceManager;
   private SolrResourceLoader loader;
 
-  public interface MockManagedComponent extends ManagedComponent {
-    int getFoo();
-    int getBar();
-    int getBaz();
-    void setFoo(int foo);
-  }
-
-  public static class TestComponent implements MockManagedComponent {
-    ManagedContext context;
+  public static class TestComponent implements ManagedComponent {
+    SolrResourceContext context;
     ManagedComponentId id;
     int foo, bar, baz;
 
@@ -49,22 +40,18 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
       this.id = ManagedComponentId.of(id);
     }
 
-    @Override
     public int getFoo() {
       return foo;
     }
 
-    @Override
     public int getBar() {
       return bar;
     }
 
-    @Override
     public int getBaz() {
       return baz;
     }
 
-    @Override
     public void setFoo(int foo) {
       this.foo = foo;
       this.bar = foo + 1;
@@ -78,19 +65,18 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
 
     @Override
     public void initializeManagedComponent(ResourceManager resourceManager, String poolName, String... otherPools) {
-      context = new ManagedContext(resourceManager, this, poolName, otherPools);
+      context = new SolrResourceContext(resourceManager, this, poolName, otherPools);
     }
 
     @Override
-    public ManagedContext getManagedContext() {
+    public SolrResourceContext getSolrResourceContext() {
       return context;
     }
   }
 
-  public static class MockManagerPlugin implements ResourceManagerPlugin<MockManagedComponent> {
-
-    public MockManagerPlugin() {
-
+  public static class MockManagerPool extends ResourceManagerPool<TestComponent> {
+    public MockManagerPool(String name, String type, ResourceManager resourceManager, Map<String, Object> poolLimits, Map<String, Object> poolParams) {
+      super(name, type, resourceManager, poolLimits, poolParams);
     }
 
     @Override
@@ -99,22 +85,7 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
     }
 
     @Override
-    public void init(Map<String, Object> params) {
-
-    }
-
-    @Override
-    public Collection<String> getMonitoredParams() {
-      return Arrays.asList("foo", "bar", "baz");
-    }
-
-    @Override
-    public Collection<String> getControlledParams() {
-      return Collections.singleton("foo");
-    }
-
-    @Override
-    public Map<String, Object> getMonitoredValues(MockManagedComponent component) throws Exception {
+    public Map<String, Object> getMonitoredValues(TestComponent component) throws Exception {
       Map<String, Object> result = new HashMap<>();
       result.put("bar", component.getBar());
       result.put("baz", component.getBaz());
@@ -123,36 +94,36 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
     }
 
     @Override
-    public void setResourceLimit(MockManagedComponent component, String limitName, Object value) throws Exception {
+    public Object doSetResourceLimit(TestComponent component, String limitName, Object value) throws Exception {
       if (limitName.equals("foo") && value instanceof Number) {
         component.setFoo(((Number)value).intValue());
+        return ((Number)value).intValue();
       } else {
         throw new Exception("invalid limit name or value");
       }
     }
 
     @Override
-    public Map<String, Object> getResourceLimits(MockManagedComponent component) throws Exception {
+    public Map<String, Object> getResourceLimits(TestComponent component) throws Exception {
       return Collections.singletonMap("foo", component.getFoo());
     }
 
     @Override
-    public void manage(ResourceManagerPool pool) throws Exception {
+    public void doManage() throws Exception {
       if (manageStartLatch.getCount() == 0) { // already fired
         return;
       }
       manageStartLatch.countDown();
       log.info("-- managing");
-      Map<String, Map<String, Object>> currentValues = pool.getCurrentValues();
-      Map<String, Object> totalValues = pool.getResourceManagerPlugin().aggregateTotalValues(currentValues);
-      Map<String, Object> poolLimits = pool.getPoolLimits();
+      Map<String, Map<String, Object>> currentValues = getCurrentValues();
+      Map<String, Object> totalValues = aggregateTotalValues(currentValues);
       if (poolLimits.containsKey("foo")) {
         // manage
         if (totalValues.containsKey("bar")) {
           int totalValue = ((Number)totalValues.get("bar")).intValue();
           int poolLimit = ((Number)poolLimits.get("foo")).intValue();
           if (totalValue > poolLimit) {
-            for (ManagedComponent cmp : pool.getComponents().values()) {
+            for (Object cmp : getComponents().values()) {
               TestComponent component = (TestComponent)cmp;
               int foo = component.getFoo();
               if (foo > 0) {
@@ -175,10 +146,10 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
     initArgs.put("plugins", config);
     Map<String, String> plugins = new HashMap<>();
     Map<String, String> components = new HashMap<>();
-    config.put(DefaultResourceManagerPluginFactory.TYPE_TO_PLUGIN, plugins);
-    config.put(DefaultResourceManagerPluginFactory.TYPE_TO_COMPONENT, components);
-    plugins.put("mock", MockManagerPlugin.class.getName());
-    components.put("mock", MockManagedComponent.class.getName());
+    config.put(DefaultResourceManagerPoolFactory.TYPE_TO_POOL, plugins);
+    config.put(DefaultResourceManagerPoolFactory.TYPE_TO_COMPONENT, components);
+    plugins.put("mock", MockManagerPool.class.getName());
+    components.put("mock", TestComponent.class.getName());
     resourceManager.init(new PluginInfo("resourceManager", initArgs));
   }
 
@@ -198,21 +169,21 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
     resourceManager.createPool("test", "mock", Collections.singletonMap("foo", 10), Collections.emptyMap());
     assertNotNull(resourceManager.getPool("test"));
     for (int i = 0; i < 10; i++) {
-      TestComponent component = new TestComponent("test:component:" + i);
+      TestComponent component = new TestComponent("component:" + i);
       component.setFoo(i);
       resourceManager.registerComponent("test", component);
     }
     ResourceManagerPool pool = resourceManager.getPool("test");
     assertEquals(10, pool.getComponents().size());
     Map<String, Map<String, Object>> currentValues = pool.getCurrentValues();
-    Map<String, Object> totalValues = pool.getResourceManagerPlugin().aggregateTotalValues(currentValues);
+    Map<String, Object> totalValues = pool.aggregateTotalValues(currentValues);
     assertNotNull(totalValues.get("bar"));
     assertEquals(55, ((Number)totalValues.get("bar")).intValue());
     assertNotNull(totalValues.get("baz"));
     assertEquals(65, ((Number)totalValues.get("baz")).intValue());
-    for (ManagedComponent cmp : pool.getComponents().values()) {
+    for (Object cmp : pool.getComponents().values()) {
       TestComponent component = (TestComponent)cmp;
-      Map<String, Object> limits = pool.getResourceManagerPlugin().getResourceLimits(component);
+      Map<String, Object> limits = pool.getResourceLimits(component);
       assertEquals(1, limits.size());
       assertNotNull(limits.get("foo"));
       String name = component.getManagedComponentId().getName();
@@ -228,15 +199,15 @@ public class TestDefaultResourceManagerPool extends SolrTestCaseJ4 {
     boolean await = manageFinishLatch.await(30000 / SPEED, TimeUnit.MILLISECONDS);
     assertTrue("did not finish in time", await);
     currentValues = pool.getCurrentValues();
-    totalValues = pool.getResourceManagerPlugin().aggregateTotalValues(currentValues);
+    totalValues = pool.aggregateTotalValues(currentValues);
     assertNotNull(totalValues.get("bar"));
     assertEquals(46, ((Number)totalValues.get("bar")).intValue());
     assertNotNull(totalValues.get("baz"));
     assertEquals(56, ((Number)totalValues.get("baz")).intValue());
     int changed = 0;
-    for (ManagedComponent cmp : pool.getComponents().values()) {
+    for (Object cmp : pool.getComponents().values()) {
       TestComponent component = (TestComponent)cmp;
-      Map<String, Object> limits = pool.getResourceManagerPlugin().getResourceLimits(component);
+      Map<String, Object> limits = pool.getResourceLimits(component);
       assertEquals(1, limits.size());
       assertNotNull(limits.get("foo"));
       String name = component.getManagedComponentId().getName();
diff --git a/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPlugin.java b/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPluginCloud.java
similarity index 59%
rename from solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPlugin.java
rename to solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPluginCloud.java
index dab094f..2abcca6 100644
--- a/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPlugin.java
+++ b/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPluginCloud.java
@@ -5,5 +5,5 @@ import org.apache.solr.cloud.SolrCloudTestCase;
 /**
  *
  */
-public class TestCacheManagerPlugin extends SolrCloudTestCase {
+public class TestCacheManagerPluginCloud extends SolrCloudTestCase {
 }
diff --git a/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPool.java b/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPool.java
new file mode 100644
index 0000000..9e22158
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/managed/types/TestCacheManagerPool.java
@@ -0,0 +1,117 @@
+package org.apache.solr.managed.types;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.lucene.util.Accountable;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.managed.DefaultResourceManager;
+import org.apache.solr.managed.ManagedComponent;
+import org.apache.solr.managed.ResourceManager;
+import org.apache.solr.managed.ResourceManagerPool;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricsContext;
+import org.apache.solr.search.CaffeineCache;
+import org.apache.solr.search.NoOpRegenerator;
+import org.apache.solr.search.SolrCache;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class TestCacheManagerPool extends SolrTestCaseJ4 {
+
+  ResourceManager resourceManager;
+
+  @Before
+  public void setupTest() throws Exception {
+    initCore("solrconfig.xml", "schema.xml");
+    // disable automatic scheduling of pool runs
+    resourceManager = new DefaultResourceManager(h.getCore().getResourceLoader(), null);
+    resourceManager.init(null);
+  }
+
+  private static final long KB = 1024;
+  private static final long MB = 1024 * KB;
+
+  private static class ChangeListener implements org.apache.solr.managed.ChangeListener {
+    Map<String, Map<String, Object>> changedValues = new ConcurrentHashMap<>();
+
+    @Override
+    public void changedLimit(String poolName, ManagedComponent component, String limitName, Object newRequestedVal, Object newActualVal) {
+      Map<String, Object> perComponent = changedValues.computeIfAbsent(component.getManagedComponentId().toString(), id -> new ConcurrentHashMap<>());
+      perComponent.put(limitName, newActualVal);
+    }
+
+    public void clear() {
+      changedValues.clear();
+    }
+  }
+
+  @Test
+  public void testPoolLimits() throws Exception {
+    ResourceManagerPool pool = resourceManager.createPool("test", CacheManagerPool.TYPE, Collections.singletonMap("maxRamMB", 200), Collections.emptyMap());
+    SolrMetricManager metricManager = new SolrMetricManager();
+    SolrMetricsContext solrMetricsContext = new SolrMetricsContext(metricManager, "fooRegistry", "barScope", "bazTag");
+    List<SolrCache> caches = new ArrayList<>();
+    for (int i = 0; i < 10; i++) {
+      SolrCache<String, Accountable> cache = new CaffeineCache<>();
+      Map<String, String> params = new HashMap<>();
+      params.put("maxRamMB", "50");
+      cache.init(params, null, new NoOpRegenerator());
+      cache.initializeMetrics(solrMetricsContext, "child-" + i);
+      cache.initializeManagedComponent(resourceManager, "test");
+      caches.add(cache);
+    }
+    ChangeListener listener = new ChangeListener();
+    pool.addChangeListener(listener);
+    // fill up all caches just below the global limit, evenly with small values
+    for (int i = 0; i < 202; i++) {
+      for (SolrCache<String, Accountable> cache : caches) {
+        cache.put("id-" + i, new Accountable() {
+          @Override
+          public long ramBytesUsed() {
+            return 100 * KB;
+          }
+        });
+      }
+    }
+    pool.manage();
+    Map<String, Object> totalValues = pool.aggregateTotalValues(pool.getCurrentValues());
+
+    assertEquals("should not adjust (within deadband): " + listener.changedValues.toString(), 0, listener.changedValues.size());
+    // add a few large values to exceed the total limit
+    // but without exceeding local (cache) limit
+    for (int i = 0; i < 10; i++) {
+      caches.get(0).put("large-" + i, new Accountable() {
+        @Override
+        public long ramBytesUsed() {
+          return 2560 * KB;
+        }
+      });
+    }
+    pool.manage();
+    totalValues = pool.aggregateTotalValues(pool.getCurrentValues());
+
+    assertEquals("should adjust all: " + listener.changedValues.toString(), 10, listener.changedValues.size());
+    listener.clear();
+    pool.manage();
+    totalValues = pool.aggregateTotalValues(pool.getCurrentValues());
+    assertEquals("should adjust all again: " + listener.changedValues.toString(), 10, listener.changedValues.size());
+    listener.clear();
+    pool.manage();
+    totalValues = pool.aggregateTotalValues(pool.getCurrentValues());
+    assertEquals("should not adjust (within deadband): " + listener.changedValues.toString(), 0, listener.changedValues.size());
+  }
+
+  @After
+  public void teardownTest() throws Exception {
+    resourceManager.close();
+  }
+}