Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
title: Remove the deprecated CoreContainer.getCores() and SolrCores.getCores() methods, which handed out cores without reserving them. Use getLoadedCoreNames() with getCore(String) and close each core, or getLoadedCoreNames().size() when only a count is needed.
type: removed
authors:
- name: Serhiy Bzhezytskyy
links:
- name: SOLR-18378
url: https://issues.apache.org/jira/browse/SOLR-18378
56 changes: 22 additions & 34 deletions solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1382,13 +1382,12 @@ public void shutdown() {
}

public void cancelCoreRecoveries() {

List<SolrCore> cores = solrCores.getCores();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably, SolrCores.getCores() shouldn't be deprecated because it's hidden one layer deep on a class only used by CoreContainer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair -- checked, and SolrCores is package-private and only ever referenced from CoreContainer, so the deprecation was arguably unnecessary ceremony from the start. Not making a case for the original deprecation though -- the ticket scope was to remove both getCores() methods together, so that's what this PR does.

AI-assisted (Claude Sonnet 5)


// we must cancel without holding the cores sync
// make sure we wait for any recoveries to stop
for (SolrCore core : cores) {
try {
for (String coreName : solrCores.getLoadedCoreNames()) {
// getCoreFromAnyList, not getCore: never loads, safe during shutdown
try (SolrCore core = solrCores.getCoreFromAnyList(coreName, true)) {
Comment on lines +1388 to +1389

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how could this be correct, using try-with-resources on a getCore that has not been inc-ref'ed, and thus we shouldn't close it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCoreFromAnyList(name, true) does inc-ref -- the second param is literally named incRefCount, and its body calls core.open(), whose own javadoc says "expert: increments the core reference count". So try-with-resources's close() here releases exactly that reference, not an un-reserved one. Same pattern is already used elsewhere in this file (SolrCore.java:3447/3482).

AI-assisted (Claude Sonnet 5)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went further and wrote a concurrency stress test for this exact concern (production, not just the single-threaded mechanism) -- 3 threads acquiring/releasing via getCoreFromAnyList(name, true) while a 4th concurrently unloads/reloads the same core. 0 failures across ~730k acquisitions in 4 runs. Added as TestCoreContainer.testGetCoreFromAnyListSafeUnderConcurrentUnload, pushed.

AI-assisted (Claude Sonnet 5)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I misunderstood your other PR comments where I thought you/AI communicated solrCores.getCoreFromAnyList doesn't incref which is why you were using it. Now I actually looked at our code to confirm.

if (core == null) continue; // unloaded since getLoadedCoreNames
core.getSolrCoreState().cancelRecovery();
} catch (Exception e) {
log.error("Error canceling recovery for core", e);
Expand All @@ -1408,21 +1407,25 @@ public void cancelCoreRecoveries() {
* <p>We do not need to unpause ever because the node is being shut down.
*/
private void pauseUpdatesAndAwaitInflightRequests() {
getCores().parallelStream()
solrCores.getLoadedCoreNames().parallelStream()
.forEach(
solrCore -> {
SolrCoreState solrCoreState = solrCore.getSolrCoreState();
try {
solrCoreState.pauseUpdatesAndAwaitInflightRequests();
} catch (TimeoutException e) {
log.warn(
"Timed out waiting for in-flight update requests to complete for core: {}",
solrCore.getName());
} catch (InterruptedException e) {
log.warn(
"Interrupted while waiting for in-flight update requests to complete for core: {}",
solrCore.getName());
Thread.currentThread().interrupt();
coreName -> {
// see cancelCoreRecoveries: reserve without loading, we are shutting down
try (SolrCore solrCore = solrCores.getCoreFromAnyList(coreName, true)) {
if (solrCore == null) return; // unloaded since getLoadedCoreNames
SolrCoreState solrCoreState = solrCore.getSolrCoreState();
try {
solrCoreState.pauseUpdatesAndAwaitInflightRequests();
} catch (TimeoutException e) {
log.warn(
"Timed out waiting for in-flight update requests to complete for core: {}",
solrCore.getName());
} catch (InterruptedException e) {
log.warn(
"Interrupted while waiting for in-flight update requests to complete for core: {}",
solrCore.getName());
Thread.currentThread().interrupt();
}
}
});
}
Expand Down Expand Up @@ -1838,21 +1841,6 @@ private void resetIndexDirectory(CoreDescriptor dcore, ConfigSet coreConfig) {
}
}

/**
* 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). Don't need to close them.
*/
@Deprecated
public List<SolrCore> getCores() {
return solrCores.getCores();
}

/**
* Gets the cores that are currently loaded, i.e. cores that have 1: loadOnStartup=true and have
* been loaded and 2: loadOnStartup=false and have been subsequently loaded.
Expand Down
16 changes: 1 addition & 15 deletions solr/core/src/java/org/apache/solr/core/SolrCores.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,6 @@ public SolrCore putCore(CoreDescriptor cd, SolrCore core) {
}
}

/**
* @return A list of "permanent" cores, i.e. cores that may not be swapped out and are currently
* loaded.
* <p>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.
* <p>This list is a new copy, it can be modified by the caller (e.g. it can be sorted).
*/
@Deprecated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe shouldn't be deprecated after all (as I say above

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the CoreContainer.java:1386 thread -- fair point, no argument.

AI-assisted (Claude Sonnet 5)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either we bring this back, or we add the method I suggested to CoreContainer forEachLoadedCore(Consumer<SolrCore>))

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:
Expand Down Expand Up @@ -189,7 +175,7 @@ public List<String> getAllCoreNames() {

/**
* Gets the number of currently loaded permanent (non transient) cores. Faster equivalent for
* {@link #getCores()}.size().
* {@link #getLoadedCoreNames()}.size().
*/
public int getNumLoadedPermanentCores() {
synchronized (modifyLock) {
Expand Down

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice; this is safer as we avoid race condition on a core closing with inspecting its health

Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,16 @@ private void healthCheckStandaloneMode(NodeHealthResponse response, Integer maxG
return;
}

for (SolrCore core : coreContainer.getCores()) {
ReplicationHandler replicationHandler =
(ReplicationHandler) core.getRequestHandler(ReplicationHandler.PATH);
if (replicationHandler.isFollower()) {
boolean isCoreInSync =
isWithinGenerationLag(core, replicationHandler, maxGenerationLag, laggingCoresInfo);
allCoresAreInSync &= isCoreInSync;
for (String coreName : coreContainer.getLoadedCoreNames()) {
try (SolrCore core = coreContainer.getCore(coreName)) {
if (core == null) continue; // unloaded since getLoadedCoreNames
ReplicationHandler replicationHandler =
(ReplicationHandler) core.getRequestHandler(ReplicationHandler.PATH);
if (replicationHandler.isFollower()) {
boolean isCoreInSync =
isWithinGenerationLag(core, replicationHandler, maxGenerationLag, laggingCoresInfo);
allCoresAreInSync &= isCoreInSync;
}
}
}

Expand Down
14 changes: 10 additions & 4 deletions solr/core/src/java/org/apache/solr/pkg/SolrPackageLoader.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again; added safety :-)

Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,11 @@ public void refreshPackageConf() {
}
}
}
for (SolrCore core : coreContainer.getCores()) {
core.getPackageListeners().packagesUpdated(updated);
for (String coreName : coreContainer.getLoadedCoreNames()) {
try (SolrCore core = coreContainer.getCore(coreName)) {
if (core == null) continue; // unloaded since getLoadedCoreNames
core.getPackageListeners().packagesUpdated(updated);
}
}
myCopy = packageAPI.pkgs;
}
Expand Down Expand Up @@ -146,8 +149,11 @@ public void notifyListeners(String pkg) {
SolrPackage p = packageClassLoaders.get(pkg);
if (p != null) {
List<SolrPackage> l = List.of(p);
for (SolrCore core : coreContainer.getCores()) {
core.getPackageListeners().packagesUpdated(l);
for (String coreName : coreContainer.getLoadedCoreNames()) {
try (SolrCore core = coreContainer.getCore(coreName)) {
if (core == null) continue; // unloaded since getLoadedCoreNames
core.getPackageListeners().packagesUpdated(l);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void test() throws Exception {

// start the freshNode
restartNodes(List.of(freshNode));
String coreName = freshNode.jetty.getCoreContainer().getCores().iterator().next().getName();
String coreName = freshNode.jetty.getCoreContainer().getLoadedCoreNames().get(0);
Path replicationProperties =
Path.of(freshNode.jetty.getSolrHome(), "cores", coreName, "data", "replication.properties");
String md5 = DigestUtils.md5Hex(Files.readAllBytes(replicationProperties));
Expand Down
20 changes: 12 additions & 8 deletions solr/core/src/test/org/apache/solr/cloud/TestCloudRecovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,18 @@ public void corruptedLogTest() throws Exception {
int logHeaderSize = Integer.MAX_VALUE;
Map<String, byte[]> contentFiles = new HashMap<>();
for (JettySolrRunner solrRunner : cluster.getJettySolrRunners()) {
for (SolrCore solrCore : solrRunner.getCoreContainer().getCores()) {
Path tlogFolder = Path.of(solrCore.getUpdateHandler().getUpdateLog().getTlogDir());
try (Stream<Path> tLogFiles = Files.list(tlogFolder)) {
Path lastTLogFile =
tlogFolder.resolve(tLogFiles.sorted().toList().getLast().getFileName());
byte[] tlogBytes = Files.readAllBytes(lastTLogFile);
contentFiles.put(lastTLogFile.toString(), tlogBytes);
logHeaderSize = Math.min(tlogBytes.length, logHeaderSize);
CoreContainer coreContainer = solrRunner.getCoreContainer();
for (String coreName : coreContainer.getLoadedCoreNames()) {
try (SolrCore solrCore = coreContainer.getCore(coreName)) {
if (solrCore == null) continue; // unloaded since getLoadedCoreNames
Path tlogFolder = Path.of(solrCore.getUpdateHandler().getUpdateLog().getTlogDir());
try (Stream<Path> tLogFiles = Files.list(tlogFolder)) {
Path lastTLogFile =
tlogFolder.resolve(tLogFiles.sorted().toList().getLast().getFileName());
byte[] tlogBytes = Files.readAllBytes(lastTLogFile);
contentFiles.put(lastTLogFile.toString(), tlogBytes);
logHeaderSize = Math.min(tlogBytes.length, logHeaderSize);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.embedded.JettySolrRunner;
import org.apache.solr.util.SocketProxy;
Expand Down Expand Up @@ -251,16 +252,19 @@ public void testCloseHooksDeletedOnReconnect() throws Exception {
DocCollection docCollection = assertNumberOfReplicas(1, 0, 1, false, true);
Slice s = docCollection.getSlices().iterator().next();
JettySolrRunner jetty = getJettyForReplica(s.getReplicas(EnumSet.of(Replica.Type.PULL)).get(0));
SolrCore core = jetty.getCoreContainer().getCores().iterator().next();
CoreContainer coreContainer = jetty.getCoreContainer();

for (int i = 0; i < (TEST_NIGHTLY ? 5 : 2); i++) {
cluster.expireZkSession(jetty);
waitForState(
"Expecting node to be disconnected", collectionName, activeReplicaCount(1, 0, 0));
waitForState("Expecting node to reconnect", collectionName, activeReplicaCount(1, 0, 1));
// We have two active ReplicationHandler with two close hooks each, one for triggering
// recovery and one for doing interval polling
assertEquals(5, core.getCloseHooks().size());
// held open across the reconnects below, so the same core instance is checked each time
try (SolrCore core = coreContainer.getCore(coreContainer.getLoadedCoreNames().get(0))) {
for (int i = 0; i < (TEST_NIGHTLY ? 5 : 2); i++) {
cluster.expireZkSession(jetty);
waitForState(
"Expecting node to be disconnected", collectionName, activeReplicaCount(1, 0, 0));
waitForState("Expecting node to reconnect", collectionName, activeReplicaCount(1, 0, 1));
// We have two active ReplicationHandler with two close hooks each, one for triggering
// recovery and one for doing interval polling
assertEquals(5, core.getCloseHooks().size());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,24 @@ private void testRequestTracking() throws Exception {

ZkStateReader.from(cloudClient).forceUpdateCollection("b1x1");

// get direct access to the SolrCore objects for each core/replica we're interested to monitor
final Map<String, SolrCore> cores = new LinkedHashMap<>();
// name + container, not the SolrCore itself: reserved only long enough to read it below
final Map<String, CoreContainer> cores = new LinkedHashMap<>();
for (JettySolrRunner runner : jettys) {
CoreContainer container = runner.getCoreContainer();
for (SolrCore core : container.getCores()) {
if ("a1x2".equals(core.getCoreDescriptor().getCollectionName())) {
cores.put(core.getName(), core);
for (String coreName : container.getLoadedCoreNames()) {
try (SolrCore core = container.getCore(coreName)) {
if (core == null) continue; // unloaded since getLoadedCoreNames
if ("a1x2".equals(core.getCoreDescriptor().getCollectionName())) {
cores.put(core.getName(), container);
}
}
}
}
assertEquals("Sanity Check: we know there should be 2 replicas", 2, cores.size());

// Sanity check - all cores should start with 0 requests
for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
double initialCount = getSelectRequestCount(entry.getValue());
for (Map.Entry<String, CoreContainer> entry : cores.entrySet()) {
double initialCount = getSelectRequestCount(entry.getValue(), entry.getKey());
assertEquals(entry.getKey() + " has already received some requests?", 0L, initialCount, 0.0);
}

Expand All @@ -123,8 +126,8 @@ private void testRequestTracking() throws Exception {
client.query("a1x2", new SolrQuery("*:*"));

double actualTotalRequests = 0;
for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
final double coreCount = getSelectRequestCount(entry.getValue());
for (Map.Entry<String, CoreContainer> entry : cores.entrySet()) {
final double coreCount = getSelectRequestCount(entry.getValue(), entry.getKey());
actualTotalRequests += coreCount;
if (0 < coreCount) {
uniqueCoreNames.add(entry.getKey());
Expand Down Expand Up @@ -225,17 +228,16 @@ private void testQueryAgainstDownReplica() throws Exception {
.withIdleTimeout(5000, TimeUnit.MILLISECONDS)
.build()) {

SolrCore leaderCore = null;
String leaderCoreName = leader.getStr(ZkStateReader.CORE_NAME_PROP);
CoreContainer leaderContainer = null;
for (JettySolrRunner jetty : jettys) {
CoreContainer container = jetty.getCoreContainer();
for (SolrCore core : container.getCores()) {
if (core.getName().equals(leader.getStr(ZkStateReader.CORE_NAME_PROP))) {
leaderCore = core;
break;
}
if (container.getLoadedCoreNames().contains(leaderCoreName)) {
leaderContainer = container;
break;
}
}
assertNotNull(leaderCore);
assertNotNull(leaderContainer);

// All queries should be served by the active replica to make sure that's true we keep
// querying the down replica. If queries are getting processed by the down replica then the
Expand All @@ -246,7 +248,7 @@ private void testQueryAgainstDownReplica() throws Exception {
count++;
client.query(new SolrQuery("*:*"));

double c = getSelectRequestCount(leaderCore);
double c = getSelectRequestCount(leaderContainer, leaderCoreName);

if (c == 1) {
break; // cluster state has got update locally
Expand All @@ -267,13 +269,21 @@ private void testQueryAgainstDownReplica() throws Exception {
client.query(new SolrQuery("*:*"));
count++;

double c = getSelectRequestCount(leaderCore);
double c = getSelectRequestCount(leaderContainer, leaderCoreName);

assertEquals("Query wasn't served by leader", count, (long) c);
}
}
}

/** Reserves the named core just long enough to read its /select request count. */
private double getSelectRequestCount(CoreContainer container, String coreName) {
try (SolrCore core = container.getCore(coreName)) {
assertNotNull("Core " + coreName + " is no longer loaded", core);
return getSelectRequestCount(core);
}
}

private Double getSelectRequestCount(SolrCore core) {
var labels =
SolrMetricTestUtils.newCloudLabelsBuilder(core)
Expand Down
Loading