borg icon indicating copy to clipboard operation
borg copied to clipboard

Ctrl+C does not work during y/N questions

Open sophie-h opened this issue 1 year ago • 3 comments

Borg 1.4

Ctrl+C and SIGINT are not working if borg is asking a y/N question.

It would be convenient for API usage and command line usage, if aborting would work during questions as well.

sophie-h avatar Nov 04 '24 14:11 sophie-h

borg does rather special stuff with Ctrl-C / SIGINT, see SigIntManager and raising_signal_handler, also main() wrapping everything with special signal handling.

Interactive usage: usually just pressing ctrl-c multiple times should work.

Usage by a wrapper: need more details about the purpose, how about sending SIGTERM?

ThomasWaldmann avatar Dec 24 '24 21:12 ThomasWaldmann

We have a "gracefully" abort feature in the borg backend for Pika Backup. It's used in all kind of places. This sends SIGINT. But if borg is in the state of asking a question, this does not work. So currently we are also writing "\n" to STDIN of borg and sending SIGINT. The sending of "\n" to hopefully abort by giving the default answer doesn't sound very robust to me.

sophie-h avatar Oct 14 '25 20:10 sophie-h

I let Junie generate this, IIRC it is correct:

Summary

Below is a concise comparison of how Borg reacts to common Unix signals, based on the current code paths in src/borg/helpers/process.py, src/borg/archiver.py, and src/borg/archive.py.

Comparison table

Signal Catchable by Borg What Borg does Exit code Repository/locks impact Special cases
SIGINT (Ctrl-C) Yes Handled via SigIntManager. First SIGINT: triggers a checkpoint (for operations that support it, e.g. create/recreate/chunk processing) and then aborts; Borg avoids saving the final archive and raises an error like "Got Ctrl-C / SIGINT.". Second SIGINT (after a debounce interval): original handler restored and a KeyboardInterrupt is raised immediately (like default behavior). EXIT_SIGNAL_BASE + 2 (reported as "Keyboard interrupt" in archiver.main) Designed to be orderly: checkpoints are written where supported, contexts unwind, repository and cache are closed, avoiding orphan locks. - For borg create (and related chunking in archive.ChunksProcessor), first SIGINT forces a checkpoint part and aborts; final archive is not saved.
- For delete/prune paths that use checkpointing, first SIGINT aborts after ensuring a checkpoint; no extra final commit is done.
- Subprocesses used by Borg (e.g., ssh, path generators) are started with preexec_fn=ignore_sigint so Ctrl-C doesn’t kill them prematurely; they are needed for clean shutdown.
- borg mount in foreground: SIGINT cleanly unmounts.
SIGHUP Yes Converted to exception SigHup via raising_signal_handler, leading to orderly exit at top-level with a log message "Received SIGHUP." EXIT_SIGNAL_BASE + 1 Orderly exit: normal context unwinding (repository/cache contexts close), avoiding orphan locks. No special checkpoint is triggered by SIGHUP. Important for systemd/logind sessions that deliver SIGHUP when sessions end.
SIGTERM Yes Converted to exception SigTerm via raising_signal_handler, leading to orderly exit at top-level with a log message "Received SIGTERM". EXIT_SIGNAL_BASE + 15 Orderly exit designed to avoid orphan lock files (explicitly noted in project history). No special checkpoint is triggered by SIGTERM. Used for graceful shutdowns (e.g., kill without -9).
SIGKILL No (uncatchable) Immediate termination by the OS; Borg cannot run cleanup or handlers. Not set by Borg; shell commonly reports 137 (128 + 9) when a process dies due to SIGKILL. Unclean exit: contexts do not unwind, so temporary files, locks, or partial state may remain until corrected; repository is designed for transactional commits, but any in-flight operation stops abruptly; no checkpoint or final commit is performed. Should be avoided if you want Borg to cleanly release locks or write checkpoints. Use SIGTERM or SIGINT instead.

What is the same across signals

  • SIGINT, SIGHUP, SIGTERM are all hooked in archiver.main() via signal_handler(..., raising_signal_handler(...)), causing Borg to exit through a controlled path with a specific exit code and log message.
  • For those three, normal Python context managers run on the way out, so caches and repository handles are closed, reducing the chance of orphan locks.

What is different

  • SIGINT has special in-operation semantics via SigIntManager:
    • First SIGINT: request a checkpoint and abort safely where supported; do not finalize the archive/save.
    • Second SIGINT (after debounce): immediate KeyboardInterrupt (like default), no additional checkpoint.
  • SIGHUP/SIGTERM do not trigger special checkpointing; they raise exceptions that lead to an orderly exit without forcing a checkpoint.
  • SIGKILL cannot be handled; Borg performs no cleanup, may leave orphan locks or partial states, and no checkpoint is written.

Pointers to relevant code

  • Signal registration and exit mapping: src/borg/archiver.py around main() (signal_handler('SIGINT'| 'SIGHUP'| 'SIGTERM', raising_signal_handler(...))), exception handling, and exit codes (EXIT_SIGNAL_BASE + {1,2,15}).
  • SIGINT management: src/borg/helpers/process.py (class SigIntManager, global sig_int), including debounce and two-stage behavior; ignore_sigint() for subprocesses.
  • Checkpointing behavior on SIGINT during chunk processing: src/borg/archive.py (ChunksProcessor.maybe_checkpoint, uses sig_int.action_triggered() and then sig_int.action_completed()).
  • Create and delete command integrations with sig_int: src/borg/archiver.py around calls that check if sig_int: and raise Error("Got Ctrl-C / SIGINT.") to avoid final save/extra commit.
  • Mount special case: src/borg/fuse.py and docs/man/borg-mount.1 state that SIGINT cleanly unmounts when running in the foreground.

Practical recommendations

  • Prefer SIGTERM for automation to allow Borg to exit cleanly (no orphan locks) without forcing a checkpoint.
  • Use SIGINT when you explicitly want Borg to checkpoint the current work (e.g., during borg create) and abort safely; press Ctrl-C only once and wait for the checkpoint to finish.
  • Avoid SIGKILL unless the process is irrecoverably stuck; run borg break-lock or borg check --repair if needed afterward.

ThomasWaldmann avatar Oct 14 '25 21:10 ThomasWaldmann