Skip to content

cleanup: don't flush the write buffer from a signal handler - #1129

Merged
steadytao merged 2 commits into
RsyncProject:masterfrom
peterochek:cleanup-no-flush-in-signal-handler
Oct 13, 2026
Merged

steadytao merged 2 commits into
RsyncProject:masterfrom
peterochek:cleanup-no-flush-in-signal-handler

Conversation

@peterochek

@peterochek peterochek commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1128.

If rsync is stopped in the middle of an --append or --inplace transfer, the receiver can write its last 256 KiB buffer twice. With --append the duplicate stays in the file for good. #1128 has the full story and a reproduction script.

The change

When a transfer aborts, the receiver gets SIGUSR1 and runs its exit cleanup inside the signal handler. The handler may have interrupted flush_write_file() right after a write() returned, before the buffer's byte count was updated, so flushing the buffer again from there can write the same data twice.

This PR skips that flush when the cleanup runs from a signal handler. The same function already checks called_from_signal_handler for a similar reason: to call _exit() instead of exit().

Effect on behaviour

  • An aborted transfer no longer duplicates data.
  • If the receiver itself gets SIGINT, SIGTERM or SIGHUP once multiplexed I/O has started, it still shuts down from normal code through got_kill_signal, and that path still flushes.
  • When the abort arrives as SIGUSR1, up to 256 KiB that was received but not yet written is dropped. The file stays a clean prefix, and the next run sends that part again.
  • Completed transfers are unaffected: the receiver clears its cleanup state after every finished file.

If you'd rather keep the flush whenever it is safe, a guard inside flush_write_file() that skips only an interrupted flush also works, and I can switch the PR to that.

Tests

New testsuite/append-abort-mid-write_test.py:

  • An LD_PRELOAD shim holds the receiver right after its first write to the destination, with signals pending. The test sends SIGUSR1, lets the receiver go, and checks that the destination is still a prefix of the source and that a rerun completes it. It covers both --append and --inplace.
  • It fails on master and passes with the fix, also with --protocol=29 and --protocol=30 and in a clang ASan+UBSan build.
  • Like source-change-size-continues, it builds its shim at run time and is Linux-only, so it is added to the macOS and Cygwin skip lists.

make check (unprivileged build without ACL support): 268 passed, 99 skipped. The per-test results match master apart from the new test.

When SIGUSR1 aborts an --inplace/--append transfer, _exit_cleanup()
runs inside the handler and flushes the receiver's write buffer.  If
the handler interrupted flush_write_file() after write() returned but
before wf_writeBufCnt was updated, the same bytes are written a second
time, and with --append the file keeps a duplicated 256 KiB region.

Skip that flush when called from a signal handler.  A SIGINT, SIGTERM
or SIGHUP that reaches the receiver itself still exits from normal code
through got_kill_signal, so its flush is unaffected.
Hold the receiver right after its first write() to the destination,
with every signal pending, send it SIGUSR1 and check that the
destination is still a prefix of the source.  This fails without the
previous commit.  The hold is an LD_PRELOAD shim, so the test is
Linux-only and is expected to skip on macOS and Cygwin.
@peterochek
peterochek force-pushed the cleanup-no-flush-in-signal-handler branch from 78e0e9b to a9b52df Compare October 13, 2026 11:23

@steadytao steadytao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pretty minimal and good fix. Thank you. LGTM.

@steadytao steadytao added the bug Something isn't working label Oct 13, 2026
@steadytao
steadytao merged commit 96705c2 into RsyncProject:master Oct 13, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--append/--inplace: an interrupted transfer can write the same 256 KiB twice

2 participants