| /* |
| * 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.io.Closeable; |
| import java.lang.invoke.MethodHandles; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.TimeUnit; |
| |
| import com.google.common.collect.Lists; |
| import org.apache.http.annotation.Experimental; |
| import org.apache.solr.common.AlreadyClosedException; |
| import org.apache.solr.common.ParWork; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.logging.MDCLoggingContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| class SolrCores implements Closeable { |
| private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private volatile boolean closed; |
| |
| private final Map<String, SolrCore> cores = new ConcurrentHashMap<>(32, 0.75f, 32); |
| |
| // These descriptors, once loaded, will _not_ be unloaded, i.e. they are not "transient". |
| private final Map<String, CoreDescriptor> residentDesciptors = new ConcurrentHashMap<>(32, 0.75f, 32); |
| |
| private final CoreContainer container; |
| |
| private final Object loadingSignal = new Object(); |
| |
| private final Set<String> currentlyLoadingCores = ConcurrentHashMap.newKeySet(32); |
| |
| private volatile TransientSolrCoreCacheFactory transientCoreCache; |
| |
| private volatile TransientSolrCoreCache transientSolrCoreCache = null; |
| |
| SolrCores(CoreContainer container) { |
| this.container = container; |
| } |
| |
| protected void addCoreDescriptor(CoreDescriptor p) { |
| if (p.isTransient()) { |
| if (getTransientCacheHandler() != null) { |
| getTransientCacheHandler().addTransientDescriptor(p.getName(), p); |
| } else { |
| log.warn("We encountered a core marked as transient, but there is no transient handler defined. This core will be inaccessible"); |
| } |
| } else { |
| residentDesciptors.put(p.getName(), p); |
| } |
| } |
| |
| protected void removeCoreDescriptor(CoreDescriptor p) { |
| currentlyLoadingCores.remove(p.getName()); |
| if (p.isTransient()) { |
| if (getTransientCacheHandler() != null) { |
| getTransientCacheHandler().removeTransientDescriptor(p.getName()); |
| } |
| } else { |
| residentDesciptors.remove(p.getName()); |
| } |
| } |
| |
| public void load(SolrResourceLoader loader) { |
| // TODO |
| // transientCoreCache = TransientSolrCoreCacheFactory.newInstance(loader, container); |
| } |
| |
| // We are shutting down. You can't hold the lock on the various lists of cores while they shut down, so we need to |
| // make a temporary copy of the names and shut them down outside the lock. |
| public void close() { |
| if (log.isDebugEnabled()) log.debug("Closing SolrCores"); |
| this.closed = true; |
| |
| Collection<SolrCore> coreList = new ArrayList<>(); |
| |
| TransientSolrCoreCache transientSolrCoreCache = getTransientCacheHandler(); |
| // Release observer |
| if (transientSolrCoreCache != null) { |
| transientSolrCoreCache.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. |
| |
| // make a copy of the cores then clear the map so the core isn't handed out to a request again |
| // MRM TODO: transient needs work |
| if (transientSolrCoreCache != null) { |
| coreList.addAll(transientSolrCoreCache.prepareForShutdown()); |
| } |
| |
| cores.forEach((s, solrCore) -> { |
| try { |
| container.solrCoreExecutor.submit(() -> { |
| MDCLoggingContext.setCoreName(solrCore.getName()); |
| try { |
| solrCore.closeAndWait(); |
| } catch (Throwable e) { |
| log.error("Error closing SolrCore", e); |
| ParWork.propagateInterrupt("Error shutting down core", e); |
| } finally { |
| MDCLoggingContext.clear(); |
| } |
| return solrCore; |
| }); |
| } catch (RejectedExecutionException e) { |
| solrCore.closeAndWait(); |
| } |
| }); |
| |
| currentlyLoadingCores.clear(); |
| } |
| |
| // 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) { |
| // if (cd.isTransient()) { |
| // if (getTransientCacheHandler() != null) { |
| // return getTransientCacheHandler().addCore(cd.getName(), core); |
| // } |
| // } else { |
| residentDesciptors.put(cd.getName(), cd); |
| SolrCore c = cores.put(cd.getName(), core); |
| synchronized (loadingSignal) { |
| loadingSignal.notifyAll(); |
| } |
| return c; |
| // } |
| // |
| // return null; |
| } |
| |
| /** |
| * |
| * @return A list of "permanent" cores, i.e. cores that may not be swapped out and are currently loaded. |
| * |
| * A core may be non-transient but still lazily loaded. If it is "permanent" and lazy-load _and_ |
| * not yet loaded it will _not_ be returned by this call. |
| * |
| * 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() { |
| List<SolrCore> lst = new ArrayList<>(cores.values()); |
| return lst; |
| } |
| |
| /** |
| * 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. |
| * |
| * Put another way, this will not return any names of cores that are lazily loaded but have not been called for yet |
| * or are transient and either not loaded or have been swapped out. |
| * |
| * @return List of currently loaded cores. |
| */ |
| public Set<String> getLoadedCoreNames() { |
| Set<String> set; |
| set = new TreeSet<>(cores.keySet()); |
| if (getTransientCacheHandler() != null) { |
| set.addAll(getTransientCacheHandler().getLoadedCoreNames()); |
| } |
| return set; |
| } |
| |
| /** This method is currently experimental. |
| * |
| * @return a Collection of the names that a specific core object is mapped to, there are more than one. |
| */ |
| @Experimental |
| List<String> getNamesForCore(SolrCore core) { |
| List<String> lst = new ArrayList<>(); |
| |
| for (Map.Entry<String, SolrCore> entry : cores.entrySet()) { |
| if (core == entry.getValue()) { |
| lst.add(entry.getKey()); |
| } |
| } |
| if (getTransientCacheHandler() != null) { |
| lst.addAll(getTransientCacheHandler().getNamesForCore(core)); |
| } |
| |
| return lst; |
| } |
| |
| /** |
| * Gets a list of all cores, loaded and unloaded |
| * |
| * @return all cores names, whether loaded or unloaded, transient or permanent. |
| */ |
| public Collection<String> getAllCoreNames() { |
| Set<String> set; |
| set = new TreeSet<>(); |
| if (getTransientCacheHandler() != null) { |
| set.addAll(getTransientCacheHandler().getAllCoreNames()); |
| } |
| set.addAll(residentDesciptors.keySet()); |
| set.addAll(currentlyLoadingCores); |
| |
| return set; |
| } |
| |
| public Collection<String> getRegisteredCoreNames() { |
| Set<String> set; |
| set = new TreeSet<>(); |
| if (getTransientCacheHandler() != null) { |
| set.addAll(getTransientCacheHandler().getAllCoreNames()); |
| } |
| set.addAll(residentDesciptors.keySet()); |
| |
| return set; |
| } |
| |
| // SolrCore getCore(String name) { |
| // return cores.get(name); |
| // } |
| |
| protected void swap(String n0, String n1) { |
| if (isClosed() || container.isShutDown()) { |
| throw new AlreadyClosedException(); |
| } |
| synchronized (cores) { |
| SolrCore c0 = cores.get(n0); |
| SolrCore c1 = cores.get(n1); |
| if (c0 == null) { // Might be an unloaded transient core |
| c0 = container.getCore(n0); |
| if (c0 == null) { |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n0); |
| } |
| } |
| if (c1 == null) { // Might be an unloaded transient core |
| c1 = container.getCore(n1); |
| if (c1 == null) { |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n1); |
| } |
| } |
| // When we swap the cores, we also need to swap the associated core descriptors. Note, this changes the |
| // name of the coreDescriptor by virtue of the c-tor |
| CoreDescriptor cd1 = c1.getCoreDescriptor(); |
| addCoreDescriptor(new CoreDescriptor(n1, c0.getCoreDescriptor())); |
| addCoreDescriptor(new CoreDescriptor(n0, cd1)); |
| cores.put(n0, c1); |
| cores.put(n1, c0); |
| c0.setName(n1); |
| c1.setName(n0); |
| |
| container.getMetricManager().swapRegistries( |
| c0.getCoreMetricManager().getRegistryName(), |
| c1.getCoreMetricManager().getRegistryName()); |
| } |
| |
| } |
| |
| boolean isClosed() { |
| return closed; |
| } |
| |
| protected SolrCore remove(String name) { |
| currentlyLoadingCores.remove(name); |
| |
| if (name == null) { |
| throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Cannot unload non-existent core [null]"); |
| } |
| |
| if (log.isDebugEnabled()) log.debug("remove core from solrcores {}", name); |
| |
| SolrCore ret = cores.remove(name); |
| residentDesciptors.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. |
| TransientSolrCoreCache transientHandler = getTransientCacheHandler(); |
| if (ret == null && transientHandler != null) { |
| ret = transientHandler.removeCore(name); |
| } |
| return ret; |
| } |
| |
| /* If you don't increment the reference count, someone could close the core before you use it. */ |
| SolrCore getCoreFromAnyList(String name) { |
| CoreDescriptor cd = residentDesciptors.get(name); |
| |
| SolrCore core = cores.get(name); |
| |
| if (core != null) { |
| core.open(); |
| return core; |
| } |
| |
| // MRM TODO: |
| // if (core == null && residentDesciptors.get(name) != null && residentDesciptors.get(name).isTransient() && getTransientCacheHandler() != null) { |
| // core = getTransientCacheHandler().getCore(name); |
| // } |
| // if (core != null) { |
| // core.open(); |
| // } |
| |
| if (core == null && cd != null) { |
| throw new IllegalStateException("found a descriptor but no core"); |
| } |
| return core; |
| } |
| |
| protected boolean isLoaded(String name) { |
| if (cores.containsKey(name)) { |
| return true; |
| } |
| if (getTransientCacheHandler() != null && getTransientCacheHandler().containsCore(name)) { |
| return true; |
| } |
| |
| return false; |
| |
| } |
| |
| protected CoreDescriptor getUnloadedCoreDescriptor(String cname) { |
| CoreDescriptor desc = residentDesciptors.get(cname); |
| if (desc == null) { |
| if (getTransientCacheHandler() == null) return null; |
| desc = getTransientCacheHandler().getTransientDescriptor(cname); |
| if (desc == null) { |
| return null; |
| } |
| } |
| return new CoreDescriptor(cname, desc); |
| } |
| |
| /** |
| * Return the CoreDescriptor corresponding to a given core name. |
| * Blocks if the SolrCore is still loading until it is ready. |
| * @param coreName the name of the core |
| * @return the CoreDescriptor |
| */ |
| public CoreDescriptor getCoreDescriptor(String coreName) { |
| if (coreName == null) return null; |
| |
| CoreDescriptor cd = residentDesciptors.get(coreName); |
| if (cd != null) { |
| return cd; |
| } |
| TransientSolrCoreCache transientHandler = getTransientCacheHandler(); |
| if (transientHandler != null) { |
| return getTransientCacheHandler().getTransientDescriptor(coreName); |
| } |
| return null; |
| } |
| |
| /** |
| * Get the CoreDescriptors for every SolrCore managed here |
| * @return a List of CoreDescriptors |
| */ |
| public List<CoreDescriptor> getCoreDescriptors() { |
| List<CoreDescriptor> cds = Lists.newArrayList(); |
| for (String coreName : getAllCoreNames()) { |
| // TODO: This null check is a bit suspicious - it seems that |
| // getAllCoreNames might return deleted cores as well? |
| CoreDescriptor cd = getCoreDescriptor(coreName); |
| if (cd != null) |
| cds.add(cd); |
| } |
| |
| return cds; |
| } |
| |
| public void markCoreAsLoading(String name) { |
| if (getAllCoreNames().contains(name)) { |
| log.warn("Creating a core with existing name is not allowed {}", name); |
| |
| throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Core with name '" + name + "' already exists."); |
| } |
| |
| currentlyLoadingCores.add(name); |
| } |
| |
| //cores marked as loading will block on getCore |
| public void markCoreAsNotLoading(CoreDescriptor cd) { |
| markCoreAsNotLoading(cd.getName()); |
| } |
| |
| public void markCoreAsNotLoading(String name) { |
| currentlyLoadingCores.remove(name); |
| synchronized (loadingSignal) { |
| loadingSignal.notifyAll(); |
| } |
| } |
| |
| // returns when no cores are marked as loading |
| public void waitForLoadingCoresToFinish(long timeoutMs) { |
| long time = System.nanoTime(); |
| long timeout = time + TimeUnit.NANOSECONDS.convert(timeoutMs, TimeUnit.MILLISECONDS); |
| while (!currentlyLoadingCores.isEmpty()) { |
| synchronized (loadingSignal) { |
| try { |
| loadingSignal.wait(500); |
| } catch (InterruptedException e) { |
| return; |
| } |
| } |
| if (System.nanoTime() >= timeout) { |
| log.info("Timed out waiting for SolrCores to finish loading."); |
| // throw new RuntimeException("Timed out waiting for SolrCores to finish loading."); |
| } |
| } |
| } |
| |
| // returns when core is finished loading, throws exception if no such core loading or loaded |
| public void waitForLoadingCoreToFinish(String core, long timeoutMs) { |
| long time = System.nanoTime(); |
| long timeout = time + TimeUnit.NANOSECONDS.convert(timeoutMs, TimeUnit.MILLISECONDS); |
| |
| while (isCoreLoading(core)) { |
| synchronized (loadingSignal) { |
| try { |
| loadingSignal.wait(500); |
| } catch (InterruptedException e) { |
| ParWork.propagateInterrupt(e); |
| return; |
| } |
| } |
| if (System.nanoTime() >= timeout) { |
| log.warn("Timed out waiting for SolrCore, {}, to finish loading.", core); |
| } |
| } |
| } |
| |
| public boolean isCoreLoading(String name) { |
| return (currentlyLoadingCores.contains(name)); |
| } |
| |
| public TransientSolrCoreCache getTransientCacheHandler() { |
| |
| if (transientCoreCache == null) { |
| // log.error("No transient handler has been defined. Check solr.xml to see if an attempt to provide a custom {}" |
| // , "TransientSolrCoreCacheFactory was done incorrectly since the default should have been used otherwise."); |
| return null; |
| } |
| return transientCoreCache.getTransientSolrCoreCache(); |
| } |
| |
| public void closing() { |
| this.closed = true; |
| } |
| } |