Philipp Trulson created SOLR-18303:
--------------------------------------

             Summary: Incremental backup failure-cleanup path masks the real 
error with an unrelated `NoSuchFileException`
                 Key: SOLR-18303
                 URL: https://issues.apache.org/jira/browse/SOLR-18303
             Project: Solr
          Issue Type: Bug
          Components: Backup/Restore
    Affects Versions: 10.0
            Reporter: Philipp Trulson


Disclaimer: Bug report was created with Claude, but I proof-read it and made 
sure the issue is valid and well described.

When an incremental collection backup ({{{}action=BACKUP{}}}) fails during the 
shard-copy phase, {{BackupCmd.call()}} logs the real exception and then 
attempts to clean up the partially-created backup via 
{{CollectionHandlingUtils.cleanBackup()}} → 
{{{}DeleteBackupCmd.deleteBackupIds(){}}}. That cleanup code tries to delete 
the backup's {{zk_backup_<id>}} directory defensively:
{code:java}
// DeleteBackupCmd.java:210-217 (10.0.0), :211-215 (main)
try {
  for (BackupId backupId : backupIdsDeletes) {
    repository.deleteDirectory(
        repository.resolveDirectory(backupUri, 
BackupFilePaths.getZkStateDir(backupId)));
  }
} catch (FileNotFoundException e) {
  // ignore this
}
{code}
{{zk_backup_<id>}} is only created later in {{BackupCmd.call()}} (via 
{{{}backupMgr.createZkStateDir(){}}}, which runs *after* the shard-copy step). 
If the shard-copy step fails, that directory was never created, so the delete 
attempt above is expected to be a no-op — but 
{{LocalFileSystemRepository.deleteDirectory()}} (via commons-io's 
{{PathUtils.deleteDirectory()}} / NIO {{{}Files.walkFileTree(){}}}) throws 
{{{}java.nio.file.NoSuchFileException{}}}, *not* 
{{{}java.io.FileNotFoundException{}}}. These are unrelated exception types 
({{{}NoSuchFileException extends FileSystemException extends IOException{}}}; 
it does not extend {{{}FileNotFoundException{}}}), so the catch clause never 
fires.

The result: the {{NoSuchFileException}} propagates out of 
{{{}cleanBackup(){}}}, which is itself uncaught in {{{}BackupCmd.call(){}}}'s 
catch block, so it replaces the original {{throw e;}} that would have surfaced 
the real {{{}SolrException{}}}. The client — and the server log — only ever see:
{noformat}
java.nio.file.NoSuchFileException: <location>/<name>/<collection>/zk_backup_0
{noformat}
with no indication of what actually failed. The real error is logged one line 
earlier via {{{}log.error("Error happened during incremental backup for 
collection: {}", ...){}}}, but is otherwise discarded from the API response and 
easy to miss in the logs.
h3. Steps to Reproduce

Minimal, reliable repro — forces the shard-copy phase to fail for any reason:
 # Fresh SolrCloud node, default {{solr.security.allow.paths}} (i.e. not set to 
{{*}} and not covering the target location).
 # Create a collection: {{citibike}} (or any collection with at least one 
document).
 # {{GET 
/solr/admin/collections?action=BACKUP&name=citibike&collection=citibike&location=/tmp/some-backup-dir}}
 where {{/tmp/some-backup-dir}} is outside the default allowed paths 
({{{}SOLR_HOME{}}}/{{{}SOLR_DATA_HOME{}}}/{{{}coreRootDirectory{}}}).

h3. Expected Result

HTTP 500 with the real cause, e.g.:
{quote}Path ... must be relative to SOLR_HOME, SOLR_DATA_HOME 
coreRootDirectory. Set system property 'solr.security.allow.paths' to add other 
allowed paths.
{quote}
h3. Actual Result

HTTP 500 with {{msg: "<location>/citibike/citibike/zk_backup_0"}} (bare 
{{{}NoSuchFileException{}}}, no explanation). The real cause above only appears 
in the server log, one ERROR line earlier, and is easy to overlook since the 
visible top-level exception is unrelated.

*Note:* any other failure during shard-copy reproduces the same masking — e.g. 
this is effectively what happened in SOLR-15673 / SOLR-15696 for a 
shard-split-related parsing bug; fixing that trigger did not fix the masking 
itself, which is still present verbatim on {{main}} today.
h3. Suggested Fix

Two options, not mutually exclusive:
 # *Minimal:* also catch {{java.nio.file.NoSuchFileException}} (or better, 
check {{repository.exists(...)}} before calling {{{}deleteDirectory{}}}) at 
{{{}DeleteBackupCmd.java:214{}}}.
 # *More robust:* in {{{}BackupCmd.call(){}}}'s catch block, wrap the 
{{cleanBackup()}} call in its own try/catch that logs-and-swallows *any* 
exception from best-effort cleanup, so a secondary failure during rollback can 
never suppress the original {{{}throw e;{}}}. This closes the whole class of 
bug rather than just this one exception-type mismatch, and would have also 
prevented the confusion in SOLR-15673/15696.

h3. Related

SOLR-15673, SOLR-15696 — same symptom ({{{}NoSuchFileException{}}} on 
{{{}zk_backup_N{}}}), different root trigger (shard-split {{ShardBackupId}} 
parsing), masking mechanism left unfixed.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to