You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2023/01/22 17:13:09 UTC

[solr] branch branch_9x updated: SOLR-16591: Deprecate transient Cores. Simplify SolrCores. (#1243)

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

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


The following commit(s) were added to refs/heads/branch_9x by this push:
     new be27d4573a9 SOLR-16591: Deprecate transient Cores. Simplify SolrCores. (#1243)
be27d4573a9 is described below

commit be27d4573a9a134231dc90568afa9201e041f73a
Author: David Smiley <ds...@salesforce.com>
AuthorDate: Sat Jan 21 18:52:51 2023 -0500

    SOLR-16591: Deprecate transient Cores. Simplify SolrCores. (#1243)
    
    The "Transient Cores" feature is now deprecated.
    In solr.xml "<transientCoreCacheFactory>" no longer works; it wasn't documented either.
    Some internals were changed as well to simplify the standard case of having no transient cores.
---
 solr/CHANGES.txt                                   |   4 +
 .../java/org/apache/solr/core/CoreContainer.java   |  31 +--
 .../src/java/org/apache/solr/core/NodeConfig.java  |  29 +--
 .../src/java/org/apache/solr/core/SolrCores.java   | 257 +++++++--------------
 .../java/org/apache/solr/core/SolrXmlConfig.java   |   3 +-
 .../apache/solr/core/TransientSolrCoreCache.java   |  18 +-
 .../solr/core/TransientSolrCoreCacheDefault.java   |  48 +---
 .../solr/core/TransientSolrCoreCacheFactory.java   | 101 --------
 .../core/TransientSolrCoreCacheFactoryDefault.java |  31 ---
 .../org/apache/solr/core/TransientSolrCores.java   | 173 ++++++++++++++
 .../solr/handler/admin/CoreAdminOperation.java     |   2 +-
 .../src/test-files/solr/solr-transientCores.xml    |  20 ++
 solr/core/src/test-files/solr/solr.xml             |   5 -
 .../test/org/apache/solr/core/TestLazyCores.java   |   6 +-
 .../src/test/org/apache/solr/core/TestSolrXml.java |  19 +-
 .../solr/metrics/SolrMetricsIntegrationTest.java   |   4 +-
 .../pages/configuring-solr-xml.adoc                |   3 +-
 .../pages/major-changes-in-solr-9.adoc             |   1 +
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |   2 +-
 19 files changed, 329 insertions(+), 428 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 1c1e56f64b5..d5e7bcdbf1b 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -209,6 +209,10 @@ Other Changes
 
 * SOLR-16544: Improve documentation on how to contribute to Solr (Justin Sweeney)
 
+* SOLR-16591: The "Transient Cores" feature is now deprecated.  In solr.xml "<transientCoreCacheFactory>"
+  no longer works; it wasn't documented either.  Some internals were changed as well to simplify
+  the standard case of having no transient cores.  (David Smiley)
+
 * SOLR-16626: Upgrade to Netty 4.1.87.Final (Kevin Risden)
 
 * SOLR-16627: Upgrade google-cloud-bom to 0.184.0, re2j to 1.6, and grpc to 1.51.0 (Kevin Risden)
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index a32b552fe99..666536abc72 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -175,7 +175,7 @@ public class CoreContainer {
     ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider());
   }
 
-  final SolrCores solrCores = new SolrCores(this);
+  final SolrCores solrCores;
 
   public static class CoreLoadFailure {
 
@@ -391,6 +391,7 @@ public class CoreContainer {
     this.cfg = requireNonNull(config);
     this.loader = config.getSolrResourceLoader();
     this.solrHome = config.getSolrHome();
+    this.solrCores = SolrCores.newSolrCores(this);
     this.nodeKeyPair = new SolrNodeKeyPair(cfg.getCloudConfig());
     containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler(nodeKeyPair));
     if (null != this.cfg.getBooleanQueryMaxClauseCount()) {
@@ -637,6 +638,7 @@ public class CoreContainer {
    */
   protected CoreContainer(Object testConstructor) {
     solrHome = null;
+    solrCores = null;
     nodeKeyPair = null;
     loader = null;
     coresLocator = null;
@@ -767,8 +769,6 @@ public class CoreContainer {
 
     solrClientCache = new SolrClientCache(updateShardHandler.getDefaultHttpClient());
 
-    solrCores.load(loader);
-
     StartupLoggingUtils.checkRequestLogging();
 
     hostName = cfg.getNodeName();
@@ -1784,11 +1784,16 @@ public class CoreContainer {
   }
 
   /**
-   * Gets the permanent (non-transient) cores that are currently loaded.
+   * Gets all loaded cores, consistent with {@link #getLoadedCoreNames()}. Caller doesn't need to
+   * close.
+   *
+   * <p>NOTE: rather dangerous API because each core is not reserved (could in theory be closed).
+   * Prefer {@link #getLoadedCoreNames()} and then call {@link #getCore(String)} then close it.
    *
    * @return An unsorted list. This list is a new copy, it can be modified by the caller (e.g. it
-   *     can be sorted).
+   *     can be sorted). Don't need to close them.
    */
+  @Deprecated
   public List<SolrCore> getCores() {
     return solrCores.getCores();
   }
@@ -2296,17 +2301,6 @@ public class CoreContainer {
     return solrCores.isLoaded(name);
   }
 
-  /**
-   * Gets a solr core descriptor for a core that is not loaded. Note that if the caller calls this
-   * on a loaded core, the unloaded descriptor will be returned.
-   *
-   * @param cname - name of the unloaded core descriptor to load. NOTE:
-   * @return a coreDescriptor. May return null
-   */
-  public CoreDescriptor getUnloadedCoreDescriptor(String cname) {
-    return solrCores.getUnloadedCoreDescriptor(cname);
-  }
-
   /** The primary path of a Solr server's config, cores, and misc things. Absolute. */
   // TODO return Path
   public String getSolrHome() {
@@ -2391,11 +2385,6 @@ public class CoreContainer {
     }
   }
 
-  // Occasionally we need to access the transient cache handler in places other than coreContainer.
-  public TransientSolrCoreCache getTransientCache() {
-    return solrCores.getTransientCacheHandler();
-  }
-
   /**
    * @param solrCore the core against which we check if there has been a tragic exception
    * @return whether this Solr core has tragic exception
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index ad9f9844ee1..c6763cf2205 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -92,9 +92,7 @@ public class NodeConfig {
 
   private final int replayUpdatesThreads;
 
-  @Deprecated
-  // This should be part of the transientCacheConfig, remove in 7.0
-  private final int transientCacheSize;
+  @Deprecated private final int transientCacheSize;
 
   private final boolean useSchemaCache;
 
@@ -104,8 +102,6 @@ public class NodeConfig {
 
   private final MetricsConfig metricsConfig;
 
-  private final PluginInfo transientCacheConfig;
-
   private final PluginInfo tracerConfig;
 
   // Track if this config was loaded from zookeeper so that we can skip validating the zookeeper
@@ -141,7 +137,6 @@ public class NodeConfig {
       Properties solrProperties,
       PluginInfo[] backupRepositoryPlugins,
       MetricsConfig metricsConfig,
-      PluginInfo transientCacheConfig,
       PluginInfo tracerConfig,
       boolean fromZookeeper,
       String defaultZkHost,
@@ -176,7 +171,6 @@ public class NodeConfig {
     this.solrProperties = solrProperties;
     this.backupRepositoryPlugins = backupRepositoryPlugins;
     this.metricsConfig = metricsConfig;
-    this.transientCacheConfig = transientCacheConfig;
     this.tracerConfig = tracerConfig;
     this.fromZookeeper = fromZookeeper;
     this.defaultZkHost = defaultZkHost;
@@ -378,10 +372,6 @@ public class NodeConfig {
     return metricsConfig;
   }
 
-  public PluginInfo getTransientCachePluginInfo() {
-    return transientCacheConfig;
-  }
-
   public PluginInfo getTracerConfiguratorPluginInfo() {
     return tracerConfig;
   }
@@ -556,17 +546,12 @@ public class NodeConfig {
     private CloudConfig cloudConfig;
     private int coreLoadThreads = DEFAULT_CORE_LOAD_THREADS;
     private int replayUpdatesThreads = Runtime.getRuntime().availableProcessors();
-
-    @Deprecated
-    // Remove in 7.0 and put it all in the transientCache element in solrconfig.xml
-    private int transientCacheSize = DEFAULT_TRANSIENT_CACHE_SIZE;
-
+    @Deprecated private int transientCacheSize = -1;
     private boolean useSchemaCache = false;
     private String managementPath;
     private Properties solrProperties = new Properties();
     private PluginInfo[] backupRepositoryPlugins;
     private MetricsConfig metricsConfig;
-    private PluginInfo transientCacheConfig;
     private PluginInfo tracerConfig;
     private boolean fromZookeeper = false;
     private String defaultZkHost;
@@ -580,8 +565,6 @@ public class NodeConfig {
     // No:of core load threads in cloud mode is set to a default of 8
     public static final int DEFAULT_CORE_LOAD_THREADS_IN_CLOUD = 8;
 
-    public static final int DEFAULT_TRANSIENT_CACHE_SIZE = Integer.MAX_VALUE;
-
     private static final String DEFAULT_ADMINHANDLERCLASS =
         "org.apache.solr.handler.admin.CoreAdminHandler";
     private static final String DEFAULT_INFOHANDLERCLASS =
@@ -705,7 +688,7 @@ public class NodeConfig {
       return this;
     }
 
-    // Remove in Solr 7.0
+    // Remove in Solr 10.0
     @Deprecated
     public NodeConfigBuilder setTransientCacheSize(int transientCacheSize) {
       this.transientCacheSize = transientCacheSize;
@@ -737,11 +720,6 @@ public class NodeConfig {
       return this;
     }
 
-    public NodeConfigBuilder setSolrCoreCacheFactoryConfig(PluginInfo transientCacheConfig) {
-      this.transientCacheConfig = transientCacheConfig;
-      return this;
-    }
-
     public NodeConfigBuilder setTracerConfig(PluginInfo tracerConfig) {
       this.tracerConfig = tracerConfig;
       return this;
@@ -816,7 +794,6 @@ public class NodeConfig {
           solrProperties,
           backupRepositoryPlugins,
           metricsConfig,
-          transientCacheConfig,
           tracerConfig,
           fromZookeeper,
           defaultZkHost,
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCores.java b/solr/core/src/java/org/apache/solr/core/SolrCores.java
index bc868568889..0f6616a4586 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCores.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCores.java
@@ -36,10 +36,14 @@ import org.apache.solr.logging.MDCLoggingContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class SolrCores {
+/** AKA CoreManager: Holds/manages {@link SolrCore}s within {@link CoreContainer}. */
+public class SolrCores {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  // for locking around manipulating any of the core maps.
+  protected final Object modifyLock = new Object();
 
-  private static final Object modifyLock =
-      new Object(); // for locking around manipulating any of the core maps.
   private final Map<String, SolrCore> cores = new LinkedHashMap<>(); // For "permanent" cores
 
   // These descriptors, once loaded, will _not_ be unloaded, i.e. they are not "transient".
@@ -47,49 +51,40 @@ class SolrCores {
 
   private final CoreContainer container;
 
-  private Set<String> currentlyLoadingCores =
+  private final Set<String> currentlyLoadingCores =
       Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
 
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
   // This map will hold objects that are being currently operated on. The core (value) may be null
   // in the case of initial load. The rule is, never to any operation on a core that is currently
   // being operated upon.
-  private static final Set<String> pendingCoreOps = new HashSet<>();
+  private final Set<String> pendingCoreOps = new HashSet<>();
 
   // Due to the fact that closes happen potentially whenever anything is _added_ to the transient
   // core list, we need to essentially queue them up to be handled via pendingCoreOps.
-  private static final List<SolrCore> pendingCloses = new ArrayList<>();
-
-  private TransientSolrCoreCacheFactory transientSolrCoreCacheFactory;
+  private final List<SolrCore> pendingCloses = new ArrayList<>();
+
+  public static SolrCores newSolrCores(CoreContainer coreContainer) {
+    final int transientCacheSize = coreContainer.getConfig().getTransientCacheSize();
+    if (transientCacheSize > 0) {
+      return new TransientSolrCores(coreContainer, transientCacheSize);
+    } else {
+      return new SolrCores(coreContainer);
+    }
+  }
 
   SolrCores(CoreContainer container) {
     this.container = container;
   }
 
-  protected void addCoreDescriptor(CoreDescriptor p) {
+  public void addCoreDescriptor(CoreDescriptor p) {
     synchronized (modifyLock) {
-      if (p.isTransient()) {
-        getTransientCacheHandler().addTransientDescriptor(p.getName(), p);
-      } else {
-        residentDescriptors.put(p.getName(), p);
-      }
-    }
-  }
-
-  protected void removeCoreDescriptor(CoreDescriptor p) {
-    synchronized (modifyLock) {
-      if (p.isTransient()) {
-        getTransientCacheHandler().removeTransientDescriptor(p.getName());
-      } else {
-        residentDescriptors.remove(p.getName());
-      }
+      residentDescriptors.put(p.getName(), p);
     }
   }
 
-  public void load(SolrResourceLoader loader) {
+  public void removeCoreDescriptor(CoreDescriptor p) {
     synchronized (modifyLock) {
-      transientSolrCoreCacheFactory = TransientSolrCoreCacheFactory.newInstance(loader, container);
+      residentDescriptors.remove(p.getName());
     }
   }
 
@@ -97,39 +92,36 @@ class SolrCores {
   // down, so we need to make a temporary copy of the names and shut them down outside the lock.
   protected void close() {
     waitForLoadingCoresToFinish(30 * 1000);
-    Collection<SolrCore> coreList = new ArrayList<>();
-
-    // Release transient core cache.
-    synchronized (modifyLock) {
-      if (transientSolrCoreCacheFactory != null) {
-        getTransientCacheHandler().close();
-      }
-    }
 
     // It might be possible for one of the cores to move from one list to another while we're
     // closing them. So loop through the lists until they're all empty. In particular, the core
     // could have moved from the transient list to the pendingCloses list.
-    do {
-      coreList.clear();
+    while (true) {
+      Collection<SolrCore> coreList = new ArrayList<>();
+
       synchronized (modifyLock) {
-        // make a copy of the cores then clear the map so the core isn't handed out to a request
-        // again
-        coreList.addAll(cores.values());
-        cores.clear();
-        if (transientSolrCoreCacheFactory != null) {
-          coreList.addAll(getTransientCacheHandler().prepareForShutdown());
+        // remove all loaded cores; add to our working list.
+        for (String name : getLoadedCoreNames()) {
+          final var core = remove(name);
+          if (core != null) { // maybe in pendingCloses due to transient core eviction
+            coreList.add(core);
+          }
         }
 
         coreList.addAll(pendingCloses);
         pendingCloses.clear();
       }
 
+      if (coreList.isEmpty()) {
+        break;
+      }
+
       ExecutorService coreCloseExecutor =
           ExecutorUtil.newMDCAwareFixedThreadPool(
               Integer.MAX_VALUE, new SolrNamedThreadFactory("coreCloseExecutor"));
       try {
         for (SolrCore core : coreList) {
-          coreCloseExecutor.submit(
+          coreCloseExecutor.execute(
               () -> {
                 MDCLoggingContext.setCore(core);
                 try {
@@ -142,27 +134,20 @@ class SolrCores {
                 } finally {
                   MDCLoggingContext.clear();
                 }
-                return core;
               });
         }
       } finally {
         ExecutorUtil.shutdownAndAwaitTermination(coreCloseExecutor);
       }
-
-    } while (coreList.size() > 0);
+    }
   }
 
   // Returns the old core if there was a core of the same name.
   // WARNING! This should be the _only_ place you put anything into the list of transient cores!
-  protected SolrCore putCore(CoreDescriptor cd, SolrCore core) {
+  public SolrCore putCore(CoreDescriptor cd, SolrCore core) {
     synchronized (modifyLock) {
       addCoreDescriptor(cd); // cd must always be registered if we register a core
-
-      if (cd.isTransient()) {
-        return getTransientCacheHandler().addCore(cd.getName(), core);
-      } else {
-        return cores.put(cd.getName(), core);
-      }
+      return cores.put(cd.getName(), core);
     }
   }
 
@@ -175,16 +160,16 @@ class SolrCores {
    *     <p>Note: This is one of the places where SolrCloud is incompatible with Transient Cores.
    *     This call is used in cancelRecoveries, transient cores don't participate.
    */
-  List<SolrCore> getCores() {
-
+  @Deprecated
+  public List<SolrCore> getCores() {
     synchronized (modifyLock) {
       return new ArrayList<>(cores.values());
     }
   }
 
   /**
-   * Gets the cores that are currently loaded, i.e. cores that have 1> loadOnStartup=true and are
-   * either not-transient or, if transient, have been loaded and have not been aged out 2>
+   * Gets the cores that are currently loaded, i.e. cores that have 1: loadOnStartup=true and are
+   * either not-transient or, if transient, have been loaded and have not been aged out 2:
    * loadOnStartup=false and have been loaded but either non-transient or have not been aged out.
    *
    * <p>Put another way, this will not return any names of cores that are lazily loaded but have not
@@ -193,9 +178,9 @@ class SolrCores {
    * @return An unsorted list. This list is a new copy, it can be modified by the caller (e.g. it
    *     can be sorted).
    */
-  List<String> getLoadedCoreNames() {
+  public List<String> getLoadedCoreNames() {
     synchronized (modifyLock) {
-      return distinctSetsUnion(cores.keySet(), getTransientCacheHandler().getLoadedCoreNames());
+      return new ArrayList<>(cores.keySet());
     }
   }
 
@@ -209,56 +194,30 @@ class SolrCores {
    */
   public List<String> getAllCoreNames() {
     synchronized (modifyLock) {
-      return distinctSetsUnion(
-          residentDescriptors.keySet(), getTransientCacheHandler().getAllCoreNames());
+      return new ArrayList<>(residentDescriptors.keySet());
     }
   }
 
-  /**
-   * Makes the union of two distinct sets.
-   *
-   * @return An unsorted list. This list is a new copy, it can be modified by the caller (e.g. it
-   *     can be sorted).
-   */
-  private static <T> List<T> distinctSetsUnion(Set<T> set1, Set<T> set2) {
-    assert areSetsDistinct(set1, set2);
-    List<T> union = new ArrayList<>(set1.size() + set2.size());
-    union.addAll(set1);
-    union.addAll(set2);
-    return union;
-  }
-
-  /** Indicates whether two sets are distinct (intersection is empty). */
-  private static <T> boolean areSetsDistinct(Set<T> set1, Set<T> set2) {
-    return set1.stream().noneMatch(set2::contains);
-  }
-
   /**
    * Gets the number of currently loaded permanent (non transient) cores. Faster equivalent for
    * {@link #getCores()}.size().
    */
-  int getNumLoadedPermanentCores() {
+  public int getNumLoadedPermanentCores() {
     synchronized (modifyLock) {
       return cores.size();
     }
   }
 
   /** Gets the number of currently loaded transient cores. */
-  int getNumLoadedTransientCores() {
-    synchronized (modifyLock) {
-      return getTransientCacheHandler().getLoadedCoreNames().size();
-    }
+  public int getNumLoadedTransientCores() {
+    // TODO; this metric ought to simply not exist here
+    return 0;
   }
 
   /** Gets the number of unloaded cores, including permanent and transient cores. */
-  int getNumUnloadedCores() {
+  public int getNumUnloadedCores() {
     synchronized (modifyLock) {
-      assert areSetsDistinct(
-          residentDescriptors.keySet(), getTransientCacheHandler().getAllCoreNames());
-      return getTransientCacheHandler().getAllCoreNames().size()
-          - getTransientCacheHandler().getLoadedCoreNames().size()
-          + residentDescriptors.size()
-          - cores.size();
+      return residentDescriptors.size() - cores.size();
     }
   }
 
@@ -268,17 +227,15 @@ class SolrCores {
    */
   public int getNumAllCores() {
     synchronized (modifyLock) {
-      assert areSetsDistinct(
-          residentDescriptors.keySet(), getTransientCacheHandler().getAllCoreNames());
-      return residentDescriptors.size() + getTransientCacheHandler().getAllCoreNames().size();
+      return residentDescriptors.size();
     }
   }
 
-  protected void swap(String n0, String n1) {
-
+  public void swap(String n0, String n1) {
     synchronized (modifyLock) {
       SolrCore c0 = cores.get(n0);
       SolrCore c1 = cores.get(n1);
+      // TODO DWS: honestly this doesn't appear to work properly unless the core is loaded
       if (c0 == null) { // Might be an unloaded transient core
         c0 = container.getCore(n0);
         if (c0 == null) {
@@ -309,31 +266,21 @@ class SolrCores {
     }
   }
 
-  protected SolrCore remove(String name) {
-
+  public SolrCore remove(String name) {
     synchronized (modifyLock) {
-      SolrCore ret = cores.remove(name);
-      // It could have been a newly-created core. It could have been a transient core. The
-      // newly-created cores in particular should be checked. It could have been a dynamic core.
-      if (ret == null) {
-        ret = getTransientCacheHandler().removeCore(name);
-      }
-      return ret;
+      return cores.remove(name);
     }
   }
 
-  SolrCore getCoreFromAnyList(String name, boolean incRefCount) {
+  public SolrCore getCoreFromAnyList(String name, boolean incRefCount) {
     return getCoreFromAnyList(name, incRefCount, null);
   }
 
   /* If you don't increment the reference count, someone could close the core before you use it. */
-  SolrCore getCoreFromAnyList(String name, boolean incRefCount, UUID coreId) {
+  public SolrCore getCoreFromAnyList(String name, boolean incRefCount, UUID coreId) {
     synchronized (modifyLock) {
-      SolrCore core = cores.get(name);
+      SolrCore core = getLoadedCoreWithoutIncrement(name);
 
-      if (core == null) {
-        core = getTransientCacheHandler().getCore(name);
-      }
       if (core != null && coreId != null && !coreId.equals(core.uniqueId)) return null;
 
       if (core != null && incRefCount) {
@@ -344,57 +291,47 @@ class SolrCores {
     }
   }
 
+  /** (internal) Return a core that is already loaded, if it is. NOT incremented! */
+  protected SolrCore getLoadedCoreWithoutIncrement(String name) {
+    synchronized (modifyLock) {
+      return cores.get(name);
+    }
+  }
+
   // See SOLR-5366 for why the UNLOAD command needs to know whether a core is actually loaded or
   // not, it might have to close the core. However, there's a race condition. If the core happens to
   // be in the pending "to close" queue, we should NOT close it in unload core.
-  protected boolean isLoadedNotPendingClose(String name) {
-    // Just all be synchronized
+  public boolean isLoadedNotPendingClose(String name) {
     synchronized (modifyLock) {
-      if (cores.containsKey(name)) {
-        return true;
+      if (!isLoaded(name)) {
+        return false;
       }
-      if (getTransientCacheHandler().containsCore(name)) {
-        // Check pending
-        for (SolrCore core : pendingCloses) {
-          if (core.getName().equals(name)) {
-            return false;
-          }
+      // Check pending
+      for (SolrCore core : pendingCloses) {
+        if (core.getName().equals(name)) {
+          return false;
         }
-
-        return true;
       }
-    }
-    return false;
-  }
 
-  protected boolean isLoaded(String name) {
-    synchronized (modifyLock) {
-      return cores.containsKey(name) || getTransientCacheHandler().containsCore(name);
+      return true;
     }
   }
 
-  protected CoreDescriptor getUnloadedCoreDescriptor(String cname) {
+  public boolean isLoaded(String name) {
     synchronized (modifyLock) {
-      CoreDescriptor desc = residentDescriptors.get(cname);
-      if (desc == null) {
-        desc = getTransientCacheHandler().getTransientDescriptor(cname);
-        if (desc == null) {
-          return null;
-        }
-      }
-      return new CoreDescriptor(cname, desc);
+      return cores.containsKey(name);
     }
   }
 
   /** The core is currently loading, unloading, or reloading. */
-  boolean hasPendingCoreOps(String name) {
+  protected boolean hasPendingCoreOps(String name) {
     synchronized (modifyLock) {
       return pendingCoreOps.contains(name);
     }
   }
 
   // Wait here until any pending operations (load, unload or reload) are completed on this core.
-  protected SolrCore waitAddPendingCoreOps(String name) {
+  public SolrCore waitAddPendingCoreOps(String name) {
 
     // Keep multiple threads from operating on a core at one time.
     synchronized (modifyLock) {
@@ -433,7 +370,7 @@ class SolrCores {
 
   // We should always be removing the first thing in the list with our name! The idea here is to NOT
   // do anything on any core while some other operation is working on that core.
-  protected void removeFromPendingOps(String name) {
+  public void removeFromPendingOps(String name) {
     synchronized (modifyLock) {
       if (!pendingCoreOps.remove(name)) {
         log.warn("Tried to remove core {} from pendingCoreOps and it wasn't there. ", name);
@@ -442,7 +379,7 @@ class SolrCores {
     }
   }
 
-  protected Object getModifyLock() {
+  public Object getModifyLock() {
     return modifyLock;
   }
 
@@ -450,7 +387,7 @@ class SolrCores {
   // opened or closed by another thread. So within this lock we'll walk along the list of pending
   // closes until we find something NOT in the list of threads currently being loaded or reloaded.
   // The "usual" case will probably return the very first one anyway.
-  protected SolrCore getCoreToClose() {
+  public SolrCore getCoreToClose() {
     synchronized (modifyLock) {
       for (SolrCore core : pendingCloses) {
         if (!pendingCoreOps.contains(core.getName())) {
@@ -472,11 +409,7 @@ class SolrCores {
    */
   public CoreDescriptor getCoreDescriptor(String coreName) {
     synchronized (modifyLock) {
-      CoreDescriptor coreDescriptor = residentDescriptors.get(coreName);
-      if (coreDescriptor != null) {
-        return coreDescriptor;
-      }
-      return getTransientCacheHandler().getTransientDescriptor(coreName);
+      return residentDescriptors.get(coreName);
     }
   }
 
@@ -488,13 +421,7 @@ class SolrCores {
    */
   public List<CoreDescriptor> getCoreDescriptors() {
     synchronized (modifyLock) {
-      Collection<CoreDescriptor> transientCoreDescriptors =
-          getTransientCacheHandler().getTransientDescriptors();
-      List<CoreDescriptor> coreDescriptors =
-          new ArrayList<>(residentDescriptors.size() + transientCoreDescriptors.size());
-      coreDescriptors.addAll(residentDescriptors.values());
-      coreDescriptors.addAll(transientCoreDescriptors);
-      return coreDescriptors;
+      return new ArrayList<>(residentDescriptors.values());
     }
   }
 
@@ -560,18 +487,4 @@ class SolrCores {
       modifyLock.notifyAll(); // Wakes up closer thread too
     }
   }
-
-  /**
-   * @return the cache holding the transient cores; never null.
-   */
-  public TransientSolrCoreCache getTransientCacheHandler() {
-    synchronized (modifyLock) {
-      if (transientSolrCoreCacheFactory == null) {
-        throw new SolrException(
-            SolrException.ErrorCode.SERVER_ERROR,
-            getClass().getName() + " not loaded; call load() before using it");
-      }
-      return transientSolrCoreCacheFactory.getTransientSolrCoreCache();
-    }
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 647c9c71019..77c7adb67e0 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -168,8 +168,6 @@ public class SolrXmlConfig {
     configBuilder.setSolrResourceLoader(loader);
     configBuilder.setUpdateShardHandlerConfig(updateConfig);
     configBuilder.setShardHandlerFactoryConfig(getPluginInfo(root.get("shardHandlerFactory")));
-    configBuilder.setSolrCoreCacheFactoryConfig(
-        getPluginInfo(root.get("transientCoreCacheFactory")));
     configBuilder.setTracerConfig(getPluginInfo(root.get("tracerConfig")));
     configBuilder.setLogWatcherConfig(loadLogWatcherConfig(root.get("logging")));
     configBuilder.setSolrProperties(loadProperties(root, substituteProperties));
@@ -380,6 +378,7 @@ public class SolrXmlConfig {
                 builder.setReplayUpdatesThreads(it.intVal(-1));
                 break;
               case "transientCacheSize":
+                log.warn("solr.xml transientCacheSize -- transient cores is deprecated");
                 builder.setTransientCacheSize(it.intVal(-1));
                 break;
               case "allowUrls":
diff --git a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCache.java b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCache.java
index 85d941c44af..e0119635eda 100644
--- a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCache.java
+++ b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCache.java
@@ -25,19 +25,13 @@ import java.util.Set;
  * control of transient caches (i.e. any core defined with transient=true) should override this
  * class.
  *
- * <p>Register your plugin in solr.xml similarly to:
- *
- * <p>&lt;transientCoreCacheFactory name="transientCoreCacheFactory"
- * class="TransientSolrCoreCacheFactoryDefault"&gt; &lt;int
- * name="transientCacheSize"&gt;4&lt;/int&gt; &lt;/transientCoreCacheFactory&gt;
- *
  * <p>WARNING: There is quite a bit of higher-level locking done by the CoreContainer to avoid
  * various race conditions etc. You should _only_ manipulate them within the method calls designed
  * to change them. E.g. only add to the transient core descriptors in addTransientDescriptor etc.
  *
  * <p>Trust the higher-level code (mainly SolrCores and CoreContainer) to call the appropriate
  * operations when necessary and to coordinate shutting down cores, manipulating the internal
- * structures and the like..
+ * structures and the like.
  *
  * <p>The only real action you should _initiate_ is to close a core for whatever reason, and do that
  * by calling notifyCoreCloseListener(coreToClose); The observer will call back to removeCore(name)
@@ -60,11 +54,9 @@ import java.util.Set;
  * <p>In particular, DO NOT reach into the transientCores structure from a method called to
  * manipulate core descriptors or vice-versa.
  */
+@Deprecated(since = "9.2")
 public abstract class TransientSolrCoreCache {
 
-  /** Gets the core container that encloses this cache. */
-  public abstract CoreContainer getContainer();
-
   /** Adds the newly-opened core to the list of open cores. */
   public abstract SolrCore addCore(String name, SolrCore core);
 
@@ -86,12 +78,6 @@ public abstract class TransientSolrCoreCache {
   /** Returns whether the cache contains the named core. */
   public abstract boolean containsCore(String name);
 
-  /**
-   * This method will be called when the container is to be shut down. It returns all transient solr
-   * cores and clear any internal structures that hold them.
-   */
-  public abstract Collection<SolrCore> prepareForShutdown();
-
   // These methods allow the implementation to maintain control over the core descriptors.
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheDefault.java b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheDefault.java
index 9754074bb79..2c5ee34e9e7 100644
--- a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheDefault.java
+++ b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheDefault.java
@@ -20,14 +20,11 @@ package org.apache.solr.core;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import org.apache.solr.common.util.NamedList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,11 +32,15 @@ import org.slf4j.LoggerFactory;
  * Cache of the most frequently accessed transient cores. Keeps track of all the registered
  * transient cores descriptors, including the cores in the cache as well as all the others.
  */
+@Deprecated(since = "9.2")
 public class TransientSolrCoreCacheDefault extends TransientSolrCoreCache {
+  // TODO move into TransientSolrCores; remove TransientSolrCoreCache base/abstraction.
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  public static final int DEFAULT_TRANSIENT_CACHE_SIZE = Integer.MAX_VALUE;
+  public static final String TRANSIENT_CACHE_SIZE = "transientCacheSize";
 
-  protected final CoreContainer coreContainer;
+  private final TransientSolrCores solrCores;
 
   /**
    * "Lazily loaded" cores cache with limited size. When the max size is reached, the least accessed
@@ -53,12 +54,8 @@ public class TransientSolrCoreCacheDefault extends TransientSolrCoreCache {
    */
   protected final Map<String, CoreDescriptor> transientDescriptors;
 
-  /**
-   * @param coreContainer The enclosing {@link CoreContainer}.
-   */
-  public TransientSolrCoreCacheDefault(CoreContainer coreContainer) {
-    this.coreContainer = coreContainer;
-    int cacheMaxSize = getConfiguredCacheMaxSize(coreContainer);
+  public TransientSolrCoreCacheDefault(TransientSolrCores solrCores, int cacheMaxSize) {
+    this.solrCores = solrCores;
 
     // Now don't allow ridiculous allocations here, if the size is > 1,000, we'll just deal with
     // adding cores as they're opened. This blows up with the marker value of -1.
@@ -89,7 +86,6 @@ public class TransientSolrCoreCacheDefault extends TransientSolrCoreCache {
   }
 
   private void onEvict(SolrCore core) {
-    final SolrCores solrCores = coreContainer.solrCores;
     assert Thread.holdsLock(solrCores.getModifyLock());
     // note: the cache's maximum size isn't strictly enforced; it can grow some if we un-evict
     if (solrCores.hasPendingCoreOps(core.getName())) {
@@ -119,38 +115,10 @@ public class TransientSolrCoreCacheDefault extends TransientSolrCoreCache {
     }
   }
 
-  private int getConfiguredCacheMaxSize(CoreContainer container) {
-    int configuredCacheMaxSize = NodeConfig.NodeConfigBuilder.DEFAULT_TRANSIENT_CACHE_SIZE;
-    NodeConfig cfg = container.getNodeConfig();
-    if (cfg.getTransientCachePluginInfo() == null) {
-      // Still handle just having transientCacheSize defined in the body of solr.xml
-      // not in a transient handler clause.
-      configuredCacheMaxSize = cfg.getTransientCacheSize();
-    } else {
-      NamedList<?> args = cfg.getTransientCachePluginInfo().initArgs;
-      Object obj = args.get("transientCacheSize");
-      if (obj != null) {
-        configuredCacheMaxSize = (int) obj;
-      }
-    }
-    if (configuredCacheMaxSize < 0) { // Trap old flag
-      configuredCacheMaxSize = Integer.MAX_VALUE;
-    }
-    return configuredCacheMaxSize;
-  }
-
   @Override
-  public Collection<SolrCore> prepareForShutdown() {
-    // Return a copy of the values.
-    List<SolrCore> ret = new ArrayList<>(transientCores.asMap().values());
+  public void close() {
     transientCores.invalidateAll();
     transientCores.cleanUp();
-    return ret;
-  }
-
-  @Override
-  public CoreContainer getContainer() {
-    return coreContainer;
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactory.java b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactory.java
deleted file mode 100644
index e3ce7ee7411..00000000000
--- a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactory.java
+++ /dev/null
@@ -1,101 +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.core;
-
-import com.google.common.collect.ImmutableMap;
-import java.lang.invoke.MethodHandles;
-import java.util.Collections;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.util.plugin.PluginInfoInitialized;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An interface that allows custom transient caches to be maintained with different implementations
- */
-public abstract class TransientSolrCoreCacheFactory {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private volatile CoreContainer coreContainer = null;
-
-  /**
-   * @return the cache holding the transient cores; never null.
-   */
-  public abstract TransientSolrCoreCache getTransientSolrCoreCache();
-  /**
-   * Create a new TransientSolrCoreCacheFactory instance
-   *
-   * @param loader a SolrResourceLoader used to find the TransientSolrCacheFactory classes
-   * @param coreContainer CoreContainer that encloses all the Solr cores.
-   * @return a new, initialized TransientSolrCoreCache instance
-   */
-  public static TransientSolrCoreCacheFactory newInstance(
-      SolrResourceLoader loader, CoreContainer coreContainer) {
-    PluginInfo info = coreContainer.getConfig().getTransientCachePluginInfo();
-    if (info == null) { // definition not in our solr.xml file, use default
-      info = DEFAULT_TRANSIENT_SOLR_CACHE_INFO;
-    }
-
-    try {
-      // According to the docs, this returns a TransientSolrCoreCacheFactory with the default c'tor
-      TransientSolrCoreCacheFactory tccf =
-          loader
-              .findClass(info.className, TransientSolrCoreCacheFactory.class)
-              .getConstructor()
-              .newInstance();
-
-      // OK, now we call its init method.
-      if (PluginInfoInitialized.class.isAssignableFrom(tccf.getClass()))
-        PluginInfoInitialized.class.cast(tccf).init(info);
-      tccf.setCoreContainer(coreContainer);
-      return tccf;
-    } catch (Exception e) {
-      // Many things could cause this, bad solrconfig, mis-typed class name, whatever.
-      // Throw an exception to stop loading here; never return null.
-      throw new SolrException(
-          SolrException.ErrorCode.SERVER_ERROR,
-          "Error instantiating "
-              + TransientSolrCoreCacheFactory.class.getName()
-              + " class ["
-              + info.className
-              + "]",
-          e);
-    }
-  }
-
-  public static final PluginInfo DEFAULT_TRANSIENT_SOLR_CACHE_INFO =
-      new PluginInfo(
-          "transientSolrCoreCacheFactory",
-          ImmutableMap.of(
-              "class",
-              TransientSolrCoreCacheFactoryDefault.class.getName(),
-              "name",
-              TransientSolrCoreCacheFactory.class.getName()),
-          null,
-          Collections.<PluginInfo>emptyList());
-
-  // Need this because the plugin framework doesn't require a PluginINfo in the init method, don't
-  // see a way to pass additional parameters and we need this when we create the transient core
-  // cache, it's _really_ important.
-  public void setCoreContainer(CoreContainer coreContainer) {
-    this.coreContainer = coreContainer;
-  }
-
-  public CoreContainer getCoreContainer() {
-    return coreContainer;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactoryDefault.java b/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactoryDefault.java
deleted file mode 100644
index 0d564836fef..00000000000
--- a/solr/core/src/java/org/apache/solr/core/TransientSolrCoreCacheFactoryDefault.java
+++ /dev/null
@@ -1,31 +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.core;
-
-public class TransientSolrCoreCacheFactoryDefault extends TransientSolrCoreCacheFactory {
-
-  volatile TransientSolrCoreCache transientSolrCoreCache = null;
-
-  @Override
-  public TransientSolrCoreCache getTransientSolrCoreCache() {
-    if (transientSolrCoreCache == null) {
-      transientSolrCoreCache = new TransientSolrCoreCacheDefault(getCoreContainer());
-    }
-
-    return transientSolrCoreCache;
-  }
-}
diff --git a/solr/core/src/java/org/apache/solr/core/TransientSolrCores.java b/solr/core/src/java/org/apache/solr/core/TransientSolrCores.java
new file mode 100644
index 00000000000..2b50b2d81e3
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/TransientSolrCores.java
@@ -0,0 +1,173 @@
+/*
+ * 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.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/** A {@link SolrCores} that supports {@link CoreDescriptor#isTransient()}. */
+@Deprecated(since = "9.2")
+public class TransientSolrCores extends SolrCores {
+
+  protected final TransientSolrCoreCache transientSolrCoreCache;
+
+  public TransientSolrCores(CoreContainer container, int cacheSize) {
+    super(container);
+    transientSolrCoreCache = new TransientSolrCoreCacheDefault(this, cacheSize);
+  }
+
+  @Override
+  protected void close() {
+    super.close();
+    transientSolrCoreCache.close();
+  }
+
+  @Override
+  public void addCoreDescriptor(CoreDescriptor p) {
+    if (p.isTransient()) {
+      synchronized (modifyLock) {
+        transientSolrCoreCache.addTransientDescriptor(p.getName(), p);
+      }
+    } else {
+      super.addCoreDescriptor(p);
+    }
+  }
+
+  @Override
+  public void removeCoreDescriptor(CoreDescriptor p) {
+    if (p.isTransient()) {
+      synchronized (modifyLock) {
+        transientSolrCoreCache.removeTransientDescriptor(p.getName());
+      }
+    } else {
+      super.removeCoreDescriptor(p);
+    }
+  }
+
+  @Override
+  // Returns the old core if there was a core of the same name.
+  // WARNING! This should be the _only_ place you put anything into the list of transient cores!
+  public SolrCore putCore(CoreDescriptor cd, SolrCore core) {
+    if (cd.isTransient()) {
+      synchronized (modifyLock) {
+        addCoreDescriptor(cd); // cd must always be registered if we register a core
+        return transientSolrCoreCache.addCore(cd.getName(), core);
+      }
+    } else {
+      return super.putCore(cd, core);
+    }
+  }
+
+  @Override
+  public List<String> getLoadedCoreNames() {
+    synchronized (modifyLock) {
+      List<String> coreNames = super.getLoadedCoreNames(); // mutable
+      coreNames.addAll(transientSolrCoreCache.getLoadedCoreNames());
+      assert isSet(coreNames);
+      return coreNames;
+    }
+  }
+
+  @Override
+  public List<String> getAllCoreNames() {
+    synchronized (modifyLock) {
+      List<String> coreNames = super.getAllCoreNames(); // mutable
+      coreNames.addAll(transientSolrCoreCache.getAllCoreNames());
+      assert isSet(coreNames);
+      return coreNames;
+    }
+  }
+
+  private static boolean isSet(Collection<?> collection) {
+    return collection.size() == new HashSet<>(collection).size();
+  }
+
+  @Override
+  public int getNumLoadedTransientCores() {
+    synchronized (modifyLock) {
+      return transientSolrCoreCache.getLoadedCoreNames().size();
+    }
+  }
+
+  @Override
+  public int getNumUnloadedCores() {
+    synchronized (modifyLock) {
+      return super.getNumUnloadedCores()
+          + transientSolrCoreCache.getAllCoreNames().size()
+          - transientSolrCoreCache.getLoadedCoreNames().size();
+    }
+  }
+
+  @Override
+  public int getNumAllCores() {
+    synchronized (modifyLock) {
+      return super.getNumAllCores() + transientSolrCoreCache.getAllCoreNames().size();
+    }
+  }
+
+  @Override
+  public SolrCore remove(String name) {
+    synchronized (modifyLock) {
+      SolrCore ret = super.remove(name);
+      // It could have been a newly-created core. It could have been a transient core. The
+      // newly-created cores in particular should be checked. It could have been a dynamic core.
+      if (ret == null) {
+        ret = transientSolrCoreCache.removeCore(name);
+      }
+      return ret;
+    }
+  }
+
+  @Override
+  protected SolrCore getLoadedCoreWithoutIncrement(String name) {
+    synchronized (modifyLock) {
+      final var core = super.getLoadedCoreWithoutIncrement(name);
+      return core != null ? core : transientSolrCoreCache.getCore(name);
+    }
+  }
+
+  @Override
+  public boolean isLoaded(String name) {
+    synchronized (modifyLock) {
+      return super.isLoaded(name) || transientSolrCoreCache.containsCore(name);
+    }
+  }
+
+  @Override
+  public CoreDescriptor getCoreDescriptor(String coreName) {
+    synchronized (modifyLock) {
+      CoreDescriptor coreDescriptor = super.getCoreDescriptor(coreName);
+      if (coreDescriptor != null) {
+        return coreDescriptor;
+      }
+      return transientSolrCoreCache.getTransientDescriptor(coreName);
+    }
+  }
+
+  @Override
+  public List<CoreDescriptor> getCoreDescriptors() {
+    synchronized (modifyLock) {
+      List<CoreDescriptor> coreDescriptors = new ArrayList<>(getNumAllCores());
+      coreDescriptors.addAll(super.getCoreDescriptors());
+      coreDescriptors.addAll(transientSolrCoreCache.getTransientDescriptors());
+      return coreDescriptors;
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
index e2a4643d77e..096476e2efa 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminOperation.java
@@ -340,7 +340,7 @@ public enum CoreAdminOperation implements CoreAdminOp {
     } else {
       if (!cores.isLoaded(cname)) { // Lazily-loaded core, fill in what we can.
         // It would be a real mistake to load the cores just to get the status
-        CoreDescriptor desc = cores.getUnloadedCoreDescriptor(cname);
+        CoreDescriptor desc = cores.getCoreDescriptor(cname);
         if (desc != null) {
           info.add(NAME, desc.getName());
           info.add("instanceDir", desc.getInstanceDir());
diff --git a/solr/core/src/test-files/solr/solr-transientCores.xml b/solr/core/src/test-files/solr/solr-transientCores.xml
new file mode 100644
index 00000000000..4ab0f389938
--- /dev/null
+++ b/solr/core/src/test-files/solr/solr-transientCores.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+
+<solr>
+  <int name="transientCacheSize">4</int>
+</solr>
\ No newline at end of file
diff --git a/solr/core/src/test-files/solr/solr.xml b/solr/core/src/test-files/solr/solr.xml
index 735388f638c..439c8defdba 100644
--- a/solr/core/src/test-files/solr/solr.xml
+++ b/solr/core/src/test-files/solr/solr.xml
@@ -35,11 +35,6 @@
     <int name="connTimeout">${connTimeout:15000}</int>
   </shardHandlerFactory>
 
-  <transientCoreCacheFactory name="transientCoreCacheFactory" class="TransientSolrCoreCacheFactoryDefault">
-    <int name="transientCacheSize">4</int>
-  </transientCoreCacheFactory>
-
-
   <solrcloud>
     <str name="host">127.0.0.1</str>
     <int name="hostPort">${hostPort:8983}</int>
diff --git a/solr/core/src/test/org/apache/solr/core/TestLazyCores.java b/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
index b9bce3df267..a94d806279f 100644
--- a/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
+++ b/solr/core/src/test/org/apache/solr/core/TestLazyCores.java
@@ -53,7 +53,7 @@ import org.junit.Test;
 
 public class TestLazyCores extends SolrTestCaseJ4 {
 
-  /** Transient core cache max size defined in the test solr.xml */
+  /** Transient core cache max size defined in the test solr-transientCores.xml */
   private static final int TRANSIENT_CORE_CACHE_MAX_SIZE = 4;
 
   private File solrHomeDirectory;
@@ -97,7 +97,7 @@ public class TestLazyCores extends SolrTestCaseJ4 {
   private CoreContainer init() throws Exception {
     solrHomeDirectory = createTempDir().toFile();
 
-    copyXmlToHome(solrHomeDirectory.getAbsoluteFile(), "solr.xml");
+    copyXmlToHome(solrHomeDirectory.getAbsoluteFile(), "solr-transientCores.xml");
     for (int idx = 1; idx < 10; ++idx) {
       copyMinConf(new File(solrHomeDirectory, "collection" + idx));
     }
@@ -108,7 +108,7 @@ public class TestLazyCores extends SolrTestCaseJ4 {
 
   private CoreContainer initEmpty() throws IOException {
     solrHomeDirectory = createTempDir().toFile();
-    copyXmlToHome(solrHomeDirectory.getAbsoluteFile(), "solr.xml");
+    copyXmlToHome(solrHomeDirectory.getAbsoluteFile(), "solr-transientCores.xml");
     NodeConfig cfg = NodeConfig.loadNodeConfig(solrHomeDirectory.toPath(), null);
     return createCoreContainer(
         cfg,
diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
index 7379cc906bb..9cdc5b1a253 100644
--- a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
+++ b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java
@@ -172,20 +172,29 @@ public class TestSolrXml extends SolrTestCaseJ4 {
 
   public void testIntAsLongBad() {
     String bad = "" + TestUtil.nextLong(random(), Integer.MAX_VALUE, Long.MAX_VALUE);
-    String solrXml = "<solr><long name=\"transientCacheSize\">" + bad + "</long></solr>";
-
+    String solrXml =
+        "<solr><updateshardhandler>"
+            + "<long name=\"maxUpdateConnections\">"
+            + bad
+            + "</long>"
+            + "</updateshardhandler></solr>";
     SolrException thrown =
         assertThrows(SolrException.class, () -> SolrXmlConfig.fromString(solrHome, solrXml));
     assertEquals(
-        "Error parsing 'transientCacheSize', value '" + bad + "' cannot be parsed",
+        "Error parsing 'maxUpdateConnections', value '" + bad + "' cannot be parsed as int",
         thrown.getMessage());
   }
 
   public void testIntAsLongOk() {
     int ok = random().nextInt();
-    String solrXml = "<solr><long name=\"transientCacheSize\">" + ok + "</long></solr>";
+    String solrXml =
+        "<solr><updateshardhandler>"
+            + "<long name=\"maxUpdateConnections\">"
+            + ok
+            + "</long>"
+            + "</updateshardhandler></solr>";
     NodeConfig cfg = SolrXmlConfig.fromString(solrHome, solrXml);
-    assertEquals(ok, cfg.getTransientCacheSize());
+    assertEquals(ok, cfg.getUpdateShardHandlerConfig().getMaxUpdateConnections());
   }
 
   public void testMultiCloudSectionError() {
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index 1010697676c..25cc82c0bb2 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -146,11 +146,9 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
 
     Gauge<?> gauge = (Gauge<?>) coreMetricManager.getRegistry().getMetrics().get("CORE.indexDir");
     assertNotNull(gauge.getValue());
-    h.getCore().close();
+    deleteCore(); // closes TestHarness which closes CoreContainer which closes SolrCore
     assertEquals(metricManager.nullString(), gauge.getValue());
 
-    deleteCore();
-
     for (String reporterName : RENAMED_REPORTERS) {
       SolrMetricReporter reporter = reporters.get(reporterName + "@" + tag);
       MockMetricReporter mockReporter = (MockMetricReporter) reporter;
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
index d75e89322b7..1a6fd121f11 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/configuring-solr-xml.adoc
@@ -267,7 +267,8 @@ If you use this feature, make sure that no core-specific property is used in you
 |Optional |Default: none
 |===
 +
-Defines how many cores with `transient=true` that can be loaded before swapping the least recently used core for a new core.
+*Deprecated as of 9.2.*
+Defines how many Solr cores with `transient=true` that can be loaded before unloading an unused core for one that is needed.
 
 `configSetBaseDir`::
 +
diff --git a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
index bc2bbc7326a..faaf0991443 100644
--- a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
+++ b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
@@ -69,6 +69,7 @@ Due to changes in Lucene 9, that isn't possible any more.
 * Solr no longer duplicates certain Jetty "server" library dependencies between `server/lib` and `WEB-INF/lib` (jetty-util, jetty-io, etc.).
 This is an improvement to the binary release artifact, but Jetty does not allow web-apps (Solr) to share these libraries by default.
 The `server/contexts/solr-jetty-context.xml` now explicitly removes these restrictions, allowing Solr to share these "server" jars which now live in `server/lib/ext`.
+* The "Transient Cores" feature is now deprecated.
 
 === Tracing
 * A new `opentelemetry` module is added, with support for OTEL tracing in `OTLP` format using gRPC.
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 6ae2eb76a2e..fe81cb7e5af 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -931,7 +931,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       // If the test case set up Zk, it should still have it as available,
       // otherwise the core close will just be unnecessarily delayed.
       CoreContainer cc = h.getCoreContainer();
-      if (!cc.getCores().isEmpty() && cc.isZooKeeperAware()) {
+      if (cc.getNumAllCores() > 0 && cc.isZooKeeperAware()) {
         try {
           cc.getZkController().getZkClient().exists("/", false);
         } catch (KeeperException e) {