/* * 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 org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.handler.ReplicationHandler; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.component.QueryComponent; import org.apache.solr.handler.component.SpellCheckComponent; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.util.DefaultSolrThreadFactory; import org.apache.solr.util.RefCounted; import org.apache.solr.util.plugin.SolrCoreAware; import org.junit.Test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class SolrCoreTest extends SolrTestCaseJ4 { private static final String COLLECTION1 = "collection1"; @Override public void setUp() throws Exception { super.setUp(); initCore("solrconfig.xml", "schema.xml"); } @Override public void tearDown() throws Exception { deleteCore(); super.tearDown(); } @Test public void testRequestHandlerRegistry() { SolrCore core = h.getCore(); EmptyRequestHandler handler1 = new EmptyRequestHandler(); EmptyRequestHandler handler2 = new EmptyRequestHandler(); String path = "/this/is A path /that won't be registered!"; SolrRequestHandler old = core.registerRequestHandler( path, handler1 ); assertNull( old ); // should not be anything... assertEquals( core.getRequestHandlers().get( path ), handler1 ); old = core.registerRequestHandler( path, handler2 ); assertEquals( old, handler1 ); // should pop out the old one assertEquals( core.getRequestHandlers().get( path ), handler2 ); } @Test public void testImplicitPlugins() { final SolrCore core = h.getCore(); final List<PluginInfo> implicitHandlers = core.getImplicitHandlers(); final Map<String,String> pathToClassMap = new HashMap<>(implicitHandlers.size()); for (PluginInfo implicitHandler : implicitHandlers) { assertEquals("wrong type for "+implicitHandler.toString(), SolrRequestHandler.TYPE, implicitHandler.type); pathToClassMap.put(implicitHandler.name, implicitHandler.className); } int ihCount = 0; { ++ihCount; assertEquals(pathToClassMap.get("/admin/file"), "solr.ShowFileRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/logging"), "solr.LoggingHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/luke"), "solr.LukeRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/mbeans"), "solr.SolrInfoMBeanHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/ping"), "solr.PingRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/plugins"), "solr.PluginInfoHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/properties"), "solr.PropertiesRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/segments"), "solr.SegmentsInfoRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/system"), "solr.SystemInfoHandler"); ++ihCount; assertEquals(pathToClassMap.get("/admin/threads"), "solr.ThreadDumpHandler"); ++ihCount; assertEquals(pathToClassMap.get("/config"), "solr.SolrConfigHandler"); ++ihCount; assertEquals(pathToClassMap.get("/export"), "solr.ExportHandler"); ++ihCount; assertEquals(pathToClassMap.get("/terms"), "solr.SearchHandler"); ++ihCount; assertEquals(pathToClassMap.get("/get"), "solr.RealTimeGetHandler"); ++ihCount; assertEquals(pathToClassMap.get(ReplicationHandler.PATH), "solr.ReplicationHandler"); ++ihCount; assertEquals(pathToClassMap.get("/schema"), "solr.SchemaHandler"); ++ihCount; assertEquals(pathToClassMap.get("/sql"), "solr.SQLHandler"); ++ihCount; assertEquals(pathToClassMap.get("/stream"), "solr.StreamHandler"); ++ihCount; assertEquals(pathToClassMap.get("/graph"), "solr.GraphHandler"); ++ihCount; assertEquals(pathToClassMap.get("/update"), "solr.UpdateRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/update/csv"), "solr.UpdateRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/update/json"), "solr.UpdateRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/update/json/docs"), "solr.UpdateRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/analysis/document"), "solr.DocumentAnalysisRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/analysis/field"), "solr.FieldAnalysisRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("/debug/dump"), "solr.DumpRequestHandler"); ++ihCount; assertEquals(pathToClassMap.get("update"), "solr.UpdateRequestHandlerApi"); } assertEquals("wrong number of implicit handlers", ihCount, implicitHandlers.size()); } @Test public void testClose() throws Exception { final CoreContainer cores = h.getCoreContainer(); SolrCore core = cores.getCore(SolrTestCaseJ4.DEFAULT_TEST_CORENAME); ClosingRequestHandler handler1 = new ClosingRequestHandler(); handler1.inform( core ); String path = "/this/is A path /that won't be registered 2!!!!!!!!!!!"; SolrRequestHandler old = core.registerRequestHandler( path, handler1 ); assertNull( old ); // should not be anything... assertEquals( core.getRequestHandlers().get( path ), handler1 ); core.close(); cores.shutdown(); assertTrue("Handler not closed", handler1.closed == true); } @Test public void testRefCount() throws Exception { SolrCore core = h.getCore(); assertTrue("Refcount != 1", core.getOpenCount() == 1); final CoreContainer cores = h.getCoreContainer(); SolrCore c1 = cores.getCore(SolrTestCaseJ4.DEFAULT_TEST_CORENAME); assertTrue("Refcount != 2", core.getOpenCount() == 2); ClosingRequestHandler handler1 = new ClosingRequestHandler(); handler1.inform( core ); String path = "/this/is A path /that won't be registered!"; SolrRequestHandler old = core.registerRequestHandler( path, handler1 ); assertNull( old ); // should not be anything... assertEquals( core.getRequestHandlers().get( path ), handler1 ); SolrCore c2 = cores.getCore(SolrTestCaseJ4.DEFAULT_TEST_CORENAME); c1.close(); assertTrue("Refcount < 1", core.getOpenCount() >= 1); assertTrue("Handler is closed", handler1.closed == false); c1 = cores.getCore(SolrTestCaseJ4.DEFAULT_TEST_CORENAME); assertTrue("Refcount < 2", core.getOpenCount() >= 2); assertTrue("Handler is closed", handler1.closed == false); c2.close(); assertTrue("Refcount < 1", core.getOpenCount() >= 1); assertTrue("Handler is closed", handler1.closed == false); c1.close(); cores.shutdown(); assertTrue("Refcount != 0", core.getOpenCount() == 0); assertTrue("Handler not closed", core.isClosed() && handler1.closed == true); } @Test public void testRefCountMT() throws Exception { SolrCore core = h.getCore(); assertTrue("Refcount != 1", core.getOpenCount() == 1); final ClosingRequestHandler handler1 = new ClosingRequestHandler(); handler1.inform(core); String path = "/this/is A path /that won't be registered!"; SolrRequestHandler old = core.registerRequestHandler(path, handler1); assertNull(old); // should not be anything... assertEquals(core.getRequestHandlers().get(path), handler1); final int LOOP = 100; final int MT = 16; ExecutorService service = ExecutorUtil.newMDCAwareFixedThreadPool(MT, new DefaultSolrThreadFactory("refCountMT")); List<Callable<Integer>> callees = new ArrayList<>(MT); final CoreContainer cores = h.getCoreContainer(); for (int i = 0; i < MT; ++i) { Callable<Integer> call = new Callable<Integer>() { void yield(int n) { try { Thread.sleep(0, (n % 13 + 1) * 10); } catch (InterruptedException xint) { } } @Override public Integer call() { SolrCore core = null; int r = 0; try { for (int l = 0; l < LOOP; ++l) { r += 1; core = cores.getCore(SolrTestCaseJ4.DEFAULT_TEST_CORENAME); // sprinkle concurrency hinting... yield(l); assertTrue("Refcount < 1", core.getOpenCount() >= 1); yield(l); assertTrue("Refcount > 17", core.getOpenCount() <= 17); yield(l); assertTrue("Handler is closed", handler1.closed == false); yield(l); core.close(); core = null; yield(l); } return r; } finally { if (core != null) core.close(); } } }; callees.add(call); } List<Future<Integer>> results = service.invokeAll(callees); for (Future<Integer> result : results) { assertTrue("loop=" + result.get() +" < " + LOOP, result.get() >= LOOP); } cores.shutdown(); assertTrue("Refcount != 0", core.getOpenCount() == 0); assertTrue("Handler not closed", core.isClosed() && handler1.closed == true); service.shutdown(); assertTrue("Running for too long...", service.awaitTermination(60, TimeUnit.SECONDS)); } @Test public void testInfoRegistry() throws Exception { //TEst that SolrInfoMBeans are registered, including SearchComponents SolrCore core = h.getCore(); Map<String, SolrInfoBean> infoRegistry = core.getInfoRegistry(); assertTrue("infoRegistry Size: " + infoRegistry.size() + " is not greater than: " + 0, infoRegistry.size() > 0); //try out some that we know are in the config SolrInfoBean bean = infoRegistry.get(SpellCheckComponent.COMPONENT_NAME); assertNotNull("bean not registered", bean); //try a default one bean = infoRegistry.get(QueryComponent.COMPONENT_NAME); assertNotNull("bean not registered", bean); //try a Req Handler, which are stored by name, not clas bean = infoRegistry.get("standard"); assertNotNull("bean not registered", bean); } @Test public void testConfiguration() throws Exception { assertEquals("wrong config for slowQueryThresholdMillis", 2000, solrConfig.slowQueryThresholdMillis); assertEquals("wrong config for maxBooleanClauses", 1024, solrConfig.booleanQueryMaxClauseCount); assertEquals("wrong config for enableLazyFieldLoading", true, solrConfig.enableLazyFieldLoading); assertEquals("wrong config for queryResultWindowSize", 10, solrConfig.queryResultWindowSize); } /** * Test that's meant to be run with many iterations to expose a leak of SolrIndexSearcher when a core is closed * due to a reload. Without the fix, this test fails with most iters=1000 runs. */ @Test public void testReloadLeak() throws Exception { final ExecutorService executor = ExecutorUtil.newMDCAwareFixedThreadPool(1, new DefaultSolrThreadFactory("testReloadLeak")); // Continuously open new searcher while core is not closed, and reload core to try to reproduce searcher leak. // While in practice we never continuously open new searchers, this is trying to make up for the fact that opening // a searcher in this empty core is very fast by opening new searchers continuously to increase the likelihood // for race. SolrCore core = h.getCore(); assertTrue("Refcount != 1", core.getOpenCount() == 1); executor.execute(new NewSearcherRunnable(core)); // Since we called getCore() vs getCoreInc() and don't own a refCount, the container should decRef the core // and close it when we call reload. h.reload(); executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); // Check that all cores are closed and no searcher references are leaked. assertTrue("SolrCore " + core + " is not closed", core.isClosed()); assertTrue(core.areAllSearcherReferencesEmpty()); } private static class NewSearcherRunnable implements Runnable { private final SolrCore core; NewSearcherRunnable(SolrCore core) { this.core = core; } @Override public void run() { while (!core.isClosed()) { try { RefCounted<SolrIndexSearcher> newSearcher = null; try { newSearcher = core.openNewSearcher(true, true); } finally { if (newSearcher != null) { newSearcher.decref(); } } } catch (SolrException e) { if (!core.isClosed()) { throw e; } } } } } } class ClosingRequestHandler extends EmptyRequestHandler implements SolrCoreAware { boolean closed = false; @Override public void inform(SolrCore core) { core.addCloseHook( new CloseHook() { @Override public void preClose(SolrCore core) { closed = true; } @Override public void postClose(SolrCore core) {} }); } } /** * An empty handler for testing */ class EmptyRequestHandler extends RequestHandlerBase { @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { // nothing! } @Override public String getDescription() { return null; } }