A regular file replaced by a FIFO before the hash phase reads it hangs the scan for good #100

Closed
opened 2026-10-13 00:54:14 +02:00 by clawbot · 1 comment
Collaborator

The walk records a path as a regular file. If it is a FIFO (or a device node whose open blocks) by the time a hash worker opens it, os.Open blocks until a writer appears, which never happens. The worker never returns a result, so the phase waits forever, the lock stays held, and every later cron scan fails with another scan is running. The first SIGINT does not end it: readRuns returns, but the deferred pool.stop() waits for the blocked worker, so the hashed records still in the batch are never committed; only a second signal (uncaught, status 130) ends the process, leaving the database in WAL mode. The README says per-file errors never abort the run and that the first signal stops the scan cleanly.

Where (v1.0.0-rc1):

Reproduce: 302 same-size 9 MiB files, scan --workers 1; when the hash: line appears, rm the two files with the highest inode numbers and mkfifo them in their place. The bar stops at [300/302]; one thread sleeps in wait_for_partner; SIGINT prints nothing more; a second SIGINT ends it with status 130, an empty database and -wal/-shm files left behind.

Definition of done

  • The hash and content phases open without blocking (O_NONBLOCK or equivalent) and check with fstat that the open file is regular; anything else is a per-path error: warned about, counted as skipped, no record written.
  • A test that swaps a walked regular file for a FIFO before it is read fails before the fix (hangs until the test timeout) and passes after (one warning, exit 0).

Severity: minor. It needs a regular file replaced by a FIFO or blocking device between the walk and the read; the outcome is a scan that never ends and holds the lock until killed.

Model: fable-5-1

The walk records a path as a regular file. If it is a FIFO (or a device node whose open blocks) by the time a hash worker opens it, `os.Open` blocks until a writer appears, which never happens. The worker never returns a result, so the phase waits forever, the lock stays held, and every later cron scan fails with `another scan is running`. The first SIGINT does not end it: `readRuns` returns, but the deferred `pool.stop()` waits for the blocked worker, so the hashed records still in the batch are never committed; only a second signal (uncaught, status 130) ends the process, leaving the database in WAL mode. The README says per-file errors never abort the run and that the first signal stops the scan cleanly. Where (v1.0.0-rc1): - https://git.eeqj.de/sneak/sfdupes/src/tag/v1.0.0-rc1/scan.go#L1335 and https://git.eeqj.de/sneak/sfdupes/src/tag/v1.0.0-rc1/scan.go#L1367 (blocking open; no check that the open file is still regular) - https://git.eeqj.de/sneak/sfdupes/src/tag/v1.0.0-rc1/scan.go#L1257 (`stop` waits for every worker) Reproduce: 302 same-size 9 MiB files, `scan --workers 1`; when the `hash:` line appears, `rm` the two files with the highest inode numbers and `mkfifo` them in their place. The bar stops at `[300/302]`; one thread sleeps in `wait_for_partner`; SIGINT prints nothing more; a second SIGINT ends it with status 130, an empty database and `-wal`/`-shm` files left behind. **Definition of done** - The hash and content phases open without blocking (`O_NONBLOCK` or equivalent) and check with `fstat` that the open file is regular; anything else is a per-path error: warned about, counted as skipped, no record written. - A test that swaps a walked regular file for a FIFO before it is read fails before the fix (hangs until the test timeout) and passes after (one warning, exit 0). Severity: minor. It needs a regular file replaced by a FIFO or blocking device between the walk and the read; the outcome is a scan that never ends and holds the lock until killed. Model: fable-5-1
Author
Collaborator

Plan: one PR on next, built as the definition of done says.

  • Both places that open a file to read it (the hash phase and the content phase) open it with O_NONBLOCK and then check with fstat on the open file that it is still a regular file. Anything else is a per-path error: one warning, counted as skipped, no record written, and the scan goes on.
  • The same code serves Linux and macOS; nothing platform-specific is added.
  • The test replaces a walked regular file with a FIFO before it is read, and on the current next hangs until the test timeout.

Model: opus-5-5

Plan: one PR on `next`, built as the definition of done says. - Both places that open a file to read it (the hash phase and the content phase) open it with `O_NONBLOCK` and then check with `fstat` on the open file that it is still a regular file. Anything else is a per-path error: one warning, counted as skipped, no record written, and the scan goes on. - The same code serves Linux and macOS; nothing platform-specific is added. - The test replaces a walked regular file with a FIFO before it is read, and on the current `next` hangs until the test timeout. Model: opus-5-5
clawbot self-assigned this 2026-10-14 19:40:25 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/sfdupes#100