Skip to content

fix: one unreadable path no longer stops the file cache tracking anything - #1981

Merged
rmcrackan merged 1 commit into
masterfrom
cursor/keep-file-cache-alive-past-a-vanished-path-a274
Aug 19, 2026
Merged

fix: one unreadable path no longer stops the file cache tracking anything#1981
rmcrackan merged 1 commit into
masterfrom
cursor/keep-file-cache-alive-past-a-vanished-path-a274

Conversation

@rmcrackan

Copy link
Copy Markdown
Owner

Fixes the Windows CI failures on master after #1976.

What failed

All three Windows legs of run 32296036630 failed while the six Linux and macOS legs passed, and not on an assertion. Every test in FileLiberator.Tests' PDF path suite failed in TestInitialize:

Initialization method FileLiberator.Tests.DownloadPdfPathTests.Initialize threw exception.
System.AggregateException: One or more errors occurred.
  (Could not find file '\\?\C:\Users\runneradmin\AppData\Local\Temp\libation-pdf-path-tests-91af...\Books\üüüü...')
  ---> System.IO.FileNotFoundException
     at System.IO.FileSystem.GetAttributeData(String fullPath, Boolean returnErrorOnNotFound)

The path named is one no test in that class ever creates, and the failure is in setup rather than in any assertion — the signature of process-wide state left broken by something that ran earlier.

Why

BackgroundFileSystem.AddPath asked two questions and let the second trust the first:

if (Path.GetFileName(path).Contains("LibationContext.db") || !File.Exists(path) && !Directory.Exists(path))
    return;
if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))

The watcher had raised Created for a folder an earlier test's cleanup then deleted. Exists said yes and GetAttributes threw — those two disagree over a long \\?\ path, and the answer to the first can stop being true before the second is asked in any case, which is what the comment about temp files was already acknowledging.

That exception cost two things, and the second is what made it look unrelated:

  1. It ended BackgroundScanner's loop, so nothing further reached the cache for the rest of the process.
  2. It was stored on the task, so the next Stop()backgroundScanner?.Wait()rethrew it as an AggregateException at whoever called Refresh(). In the tests that was DownloadPdfPathTests.Initialize. In the app it is AudibleFileStorage.Audio.Refresh(), which runs after every download.

So a user moving or deleting a book folder while Libation is running could silently stop file tracking, and then surface as a failure attributed to their next download. This is the same class of fragility as d27445a1 and b9aa51cd, which hardened the watcher callback; this is the scanner task behind it.

The fix

Three changes, smallest first:

  • The attribute read is guarded and returns "nothing to add" exactly where the existence check used to say it. This removes the race rather than narrowing it: one question instead of two.
  • The scanner survives an event it cannot apply, logging and carrying on, so no future throw inside UpdateLocalCache can end tracking or be stored for an unrelated caller to receive.
  • Stop() waits on a scanner that has already failed without raising that failure at a caller which is about to replace it anyway.

Verification

a_path_that_is_not_there_reads_as_nothing_rather_than_throwing fails against the old code with the same exception CI produced, and passes with the fix:

failed a_path_that_is_not_there_reads_as_nothing_rather_than_throwing (8ms)
  System.IO.FileNotFoundException: Could not find file
    '/tmp/libation-bfs-vanishing-ce08f72f0cfd40f491b15e29a64bffca/never-existed.m4b'.

The trigger — Exists and GetAttributes disagreeing over a long \\?\ path — cannot be staged on Linux, so the guard is asserted directly and the surrounding tests cover what its failure used to cost: a cache that stops tracking new files, and Dispose() handing the exception to its caller. a_path_that_is_there_still_reads_as_what_it_is keeps the guard honest, since a guard that swallowed everything would also pass the others.

1507 tests pass locally, and both the CLI and the Avalonia app build clean.

Open in Web Open in Cursor 

…hing

All three Windows CI legs failed on master while the other six passed, and not on
an assertion: every test in FileLiberator.Tests' PDF path suite failed in
TestInitialize with an AggregateException wrapping FileNotFoundException, naming a
path none of those tests had anything to do with.

The watcher had raised Created for a folder an earlier test's cleanup then
deleted. AddPath asked whether the path existed, was told yes, asked what it was,
and got an exception - Exists and GetAttributes can disagree over a long \\?\
path, and the answer to the first can stop being true before the second is asked
anyway. That exception ended the background scanner, so nothing further reached
the cache, and it was stored on the task, so the next Stop() rethrew it as an
AggregateException at whoever had called Refresh(). In the app that caller is the
Books directory refresh after every download.

Three changes, smallest first: the attribute read is guarded and returns 'nothing
to add' where the existence check used to say it, which also removes the race
rather than narrowing it; the scanner survives an event it cannot apply; and
Stop() waits on a scanner that has already failed without handing the failure to
a caller that is about to replace it.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
@github-actions github-actions Bot added the exempt Excluded from automatic stale closure (e.g. maintainer-opened) label Aug 19, 2026
@rmcrackan
rmcrackan marked this pull request as ready for review August 19, 2026 20:27
@rmcrackan
rmcrackan merged commit 819a096 into master Aug 19, 2026
12 checks passed
@rmcrackan
rmcrackan deleted the cursor/keep-file-cache-alive-past-a-vanished-path-a274 branch August 19, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

exempt Excluded from automatic stale closure (e.g. maintainer-opened)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants