From b4504de92945f89993ff31e9ba48439629afc368 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 26 Apr 2026 09:56:23 -0400 Subject: [PATCH 1/4] Fix HTTP 500 in filestore getMetadata racing with concurrent delete ClusterFileStore.getMetadata could return a 500 when a file was deleted between fileStore.list(...) returning the entry and convertToResponse calling FileDetails.size()/getTimeStamp() on it. The underlying Files.size()/Files.getLastModifiedTime() would throw NoSuchFileException, which DistribFileStore wrapped as SolrException(SERVER_ERROR). Reproducible via beasting TestDistribFileStore.testFileStoreManagement (seen ~1/20 with seed 30453DE51CF44AB7, US-ASCII, security manager). Fix: - DistribFileStore.FileInfo: catch NoSuchFileException in size() (returns -1) and getTimeStamp() (returns null) so callers can distinguish a concurrent-delete race from a real I/O failure. - ClusterFileStore.convertToResponse: returns null when size() < 0, signalling the entry vanished mid-call. - ClusterFileStore.getMetadata: filters null entries from the DIRECTORY listing; the FILE/METADATA branch already maps an empty result to null, which now also covers the race case so the response matches NOFILE semantics (singletonMap(path, null)). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../filestore-getmetadata-delete-race.yml | 6 ++++++ .../org/apache/solr/filestore/ClusterFileStore.java | 13 ++++++++++--- .../org/apache/solr/filestore/DistribFileStore.java | 7 +++++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/filestore-getmetadata-delete-race.yml diff --git a/changelog/unreleased/filestore-getmetadata-delete-race.yml b/changelog/unreleased/filestore-getmetadata-delete-race.yml new file mode 100644 index 000000000000..0d310d48b920 --- /dev/null +++ b/changelog/unreleased/filestore-getmetadata-delete-race.yml @@ -0,0 +1,6 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Filestore metadata API no longer returns HTTP 500 when a file is deleted concurrently with a metadata read; the response now matches the not-found case (null entry). +type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other +authors: + - name: Eric Pugh +links: [] diff --git a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java index c405fcbcfe95..5049d3022f0c 100644 --- a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java +++ b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.codec.digest.DigestUtils; import org.apache.solr.api.JerseyResource; @@ -228,13 +229,14 @@ public static FileStoreDirectoryListingResponse getMetadata( String parentPath = path.substring(0, path.lastIndexOf('/')); List l = fileStore.list(parentPath, s -> s.equals(fileName)); - dirListingResponse.files = - Collections.singletonMap(path, l.isEmpty() ? null : convertToResponse(l.get(0))); + FileStoreEntryMetadata entry = l.isEmpty() ? null : convertToResponse(l.get(0)); + dirListingResponse.files = Collections.singletonMap(path, entry); break; case DIRECTORY: final var directoryContents = fileStore.list(path, null).stream() .map(details -> convertToResponse(details)) + .filter(Objects::nonNull) .collect(Collectors.toList()); dirListingResponse.files = Map.of(path, directoryContents); break; @@ -254,7 +256,12 @@ private static FileStoreEntryMetadata convertToResponse(FileStore.FileDetails de return entryMetadata; } - entryMetadata.size = details.size(); + long size = details.size(); + if (size < 0) { + // File was deleted concurrently between listing and reading its attributes. + return null; + } + entryMetadata.size = size; entryMetadata.timestamp = details.getTimeStamp(); if (details.getMetaData() != null) { details.getMetaData().toMap(entryMetadata.unknownProperties()); diff --git a/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java b/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java index 398075663a4c..7fffd9ebf08a 100644 --- a/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java +++ b/solr/core/src/java/org/apache/solr/filestore/DistribFileStore.java @@ -30,6 +30,7 @@ import java.nio.channels.SeekableByteChannel; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; @@ -288,6 +289,9 @@ public MetaData getMetaData() { public Date getTimeStamp() { try { return new Date(Files.getLastModifiedTime(realPath()).toMillis()); + } catch (NoSuchFileException e) { + // File was deleted concurrently between listing and reading its attributes. + return null; } catch (IOException e) { throw new SolrException( SERVER_ERROR, "Failed to retrieve the last modified time for: " + realPath(), e); @@ -303,6 +307,9 @@ public boolean isDir() { public long size() { try { return Files.size(realPath()); + } catch (NoSuchFileException e) { + // File was deleted concurrently between listing and reading its attributes. + return -1; } catch (IOException e) { throw new SolrException( SERVER_ERROR, "Failed to retrieve the file size for: " + realPath(), e); From e1059e203abcd31af4f273d7c85d7e40f24adbdf Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sun, 26 Apr 2026 16:36:15 -0400 Subject: [PATCH 2/4] add pr number to changelog --- ...ete-race.yml => PR#4367-filestore-getmetadata-delete-race.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/unreleased/{filestore-getmetadata-delete-race.yml => PR#4367-filestore-getmetadata-delete-race.yml} (100%) diff --git a/changelog/unreleased/filestore-getmetadata-delete-race.yml b/changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml similarity index 100% rename from changelog/unreleased/filestore-getmetadata-delete-race.yml rename to changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml From 1be58e1b2bc688f25e37f102b55e44cc900da172 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 2 May 2026 09:37:28 -0400 Subject: [PATCH 3/4] Update solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../java/org/apache/solr/filestore/ClusterFileStore.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java index 5049d3022f0c..bcd4e5620534 100644 --- a/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java +++ b/solr/core/src/java/org/apache/solr/filestore/ClusterFileStore.java @@ -261,8 +261,13 @@ private static FileStoreEntryMetadata convertToResponse(FileStore.FileDetails de // File was deleted concurrently between listing and reading its attributes. return null; } + final var timestamp = details.getTimeStamp(); + if (timestamp == null) { + // File was deleted concurrently between reading its size and timestamp. + return null; + } entryMetadata.size = size; - entryMetadata.timestamp = details.getTimeStamp(); + entryMetadata.timestamp = timestamp; if (details.getMetaData() != null) { details.getMetaData().toMap(entryMetadata.unknownProperties()); } From 0d018395511ef6e6dc31ae1ace2d8ef14af183b9 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Sat, 2 May 2026 09:39:42 -0400 Subject: [PATCH 4/4] add chnagelog --- .../unreleased/PR#4367-filestore-getmetadata-delete-race.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml b/changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml index 0d310d48b920..96be57260aa1 100644 --- a/changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml +++ b/changelog/unreleased/PR#4367-filestore-getmetadata-delete-race.yml @@ -3,4 +3,5 @@ title: Filestore metadata API no longer returns HTTP 500 when a file is deleted type: fixed # added, changed, fixed, deprecated, removed, dependency_update, security, other authors: - name: Eric Pugh -links: [] +links: + - https://github.com/apache/solr/pull/4367