mirror of
https://github.com/borgbackup/borg.git
synced 2025-02-24 07:01:59 +00:00
special behaviour on first ctrl-c, fixes #4606
like: - try saving a checkpoint if borg create is ctrl-c-ed
This commit is contained in:
parent
373bd8abd3
commit
9732fe4965
3 changed files with 75 additions and 7 deletions
|
@ -40,6 +40,7 @@
|
|||
from .helpers import ellipsis_truncate, ProgressIndicatorPercent, log_multi
|
||||
from .helpers import os_open, flags_normal
|
||||
from .helpers import msgpack
|
||||
from .helpers import sig_int
|
||||
from .patterns import PathPrefixPattern, FnmatchPattern, IECommand
|
||||
from .item import Item, ArchiveItem, ItemDiff
|
||||
from .platform import acl_get, acl_set, set_flags, get_flags, swidth, hostname
|
||||
|
@ -1095,6 +1096,19 @@ def write_part_file(self, item, from_chunk, number):
|
|||
self.write_checkpoint()
|
||||
return length, number
|
||||
|
||||
def maybe_checkpoint(self, item, from_chunk, part_number, forced=False):
|
||||
sig_int_triggered = sig_int and sig_int.action_triggered()
|
||||
if forced or sig_int_triggered or \
|
||||
self.checkpoint_interval and time.monotonic() - self.last_checkpoint > self.checkpoint_interval:
|
||||
if sig_int_triggered:
|
||||
logger.info('checkpoint requested: starting checkpoint creation...')
|
||||
from_chunk, part_number = self.write_part_file(item, from_chunk, part_number)
|
||||
self.last_checkpoint = time.monotonic()
|
||||
if sig_int_triggered:
|
||||
sig_int.action_completed()
|
||||
logger.info('checkpoint requested: finished checkpoint creation!')
|
||||
return from_chunk, part_number
|
||||
|
||||
def process_file_chunks(self, item, cache, stats, show_progress, chunk_iter, chunk_processor=None):
|
||||
if not chunk_processor:
|
||||
def chunk_processor(data):
|
||||
|
@ -1113,17 +1127,14 @@ def chunk_processor(data):
|
|||
item.chunks.append(chunk_processor(data))
|
||||
if show_progress:
|
||||
stats.show_progress(item=item, dt=0.2)
|
||||
if self.checkpoint_interval and time.monotonic() - self.last_checkpoint > self.checkpoint_interval:
|
||||
from_chunk, part_number = self.write_part_file(item, from_chunk, part_number)
|
||||
self.last_checkpoint = time.monotonic()
|
||||
from_chunk, part_number = self.maybe_checkpoint(item, from_chunk, part_number, forced=False)
|
||||
else:
|
||||
if part_number > 1:
|
||||
if item.chunks[from_chunk:]:
|
||||
# if we already have created a part item inside this file, we want to put the final
|
||||
# chunks (if any) into a part item also (so all parts can be concatenated to get
|
||||
# the complete file):
|
||||
from_chunk, part_number = self.write_part_file(item, from_chunk, part_number)
|
||||
self.last_checkpoint = time.monotonic()
|
||||
from_chunk, part_number = self.maybe_checkpoint(item, from_chunk, part_number, forced=True)
|
||||
|
||||
# if we created part files, we have referenced all chunks from the part files,
|
||||
# but we also will reference the same chunks also from the final, complete file:
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
from .helpers import umount
|
||||
from .helpers import flags_root, flags_dir, flags_special_follow, flags_special
|
||||
from .helpers import msgpack
|
||||
from .helpers import sig_int
|
||||
from .nanorst import rst_to_terminal
|
||||
from .patterns import ArgparsePatternAction, ArgparseExcludeFileAction, ArgparsePatternFileAction, parse_exclude_pattern
|
||||
from .patterns import PatternMatcher
|
||||
|
@ -531,7 +532,12 @@ def create_inner(archive, cache, fso):
|
|||
if args.progress:
|
||||
archive.stats.show_progress(final=True)
|
||||
archive.stats += fso.stats
|
||||
archive.save(comment=args.comment, timestamp=args.timestamp, stats=archive.stats)
|
||||
if sig_int:
|
||||
# do not save the archive if the user ctrl-c-ed - it is valid, but incomplete.
|
||||
# we already have a checkpoint archive in this case.
|
||||
self.print_error("Got Ctrl-C / SIGINT.")
|
||||
else:
|
||||
archive.save(comment=args.comment, timestamp=args.timestamp, stats=archive.stats)
|
||||
args.stats |= args.json
|
||||
if args.stats:
|
||||
if args.json:
|
||||
|
@ -587,6 +593,10 @@ def _process(self, *, path, parent_fd=None, name=None,
|
|||
|
||||
This should only raise on critical errors. Per-item errors must be handled within this method.
|
||||
"""
|
||||
if sig_int and sig_int.action_done():
|
||||
# the user says "get out of here!" and we have already completed the desired action.
|
||||
return
|
||||
|
||||
try:
|
||||
recurse_excluded_dir = False
|
||||
if matcher.match(path):
|
||||
|
@ -4431,7 +4441,8 @@ def main(): # pragma: no cover
|
|||
print(tb, file=sys.stderr)
|
||||
sys.exit(e.exit_code)
|
||||
try:
|
||||
exit_code = archiver.run(args)
|
||||
with sig_int:
|
||||
exit_code = archiver.run(args)
|
||||
except Error as e:
|
||||
msg = e.get_message()
|
||||
msgid = type(e).__qualname__
|
||||
|
|
|
@ -86,6 +86,52 @@ def handler(sig_no, frame):
|
|||
return handler
|
||||
|
||||
|
||||
class SigIntManager:
|
||||
def __init__(self):
|
||||
self._sig_int_triggered = False
|
||||
self._action_triggered = False
|
||||
self._action_done = False
|
||||
self.ctx = signal_handler('SIGINT', self.handler)
|
||||
|
||||
def __bool__(self):
|
||||
# this will be True (and stay True) after the first Ctrl-C/SIGINT
|
||||
return self._sig_int_triggered
|
||||
|
||||
def action_triggered(self):
|
||||
# this is True to indicate that the action shall be done
|
||||
return self._action_triggered
|
||||
|
||||
def action_done(self):
|
||||
# this will be True after the action has completed
|
||||
return self._action_done
|
||||
|
||||
def action_completed(self):
|
||||
# this must be called when the action triggered is completed,
|
||||
# to avoid that the action is repeatedly triggered.
|
||||
self._action_triggered = False
|
||||
self._action_done = True
|
||||
|
||||
def handler(self, sig_no, stack):
|
||||
# handle the first ctrl-c / SIGINT.
|
||||
self.__exit__(None, None, None)
|
||||
self._sig_int_triggered = True
|
||||
self._action_triggered = True
|
||||
|
||||
def __enter__(self):
|
||||
self.ctx.__enter__()
|
||||
|
||||
def __exit__(self, exception_type, exception_value, traceback):
|
||||
# restore the original ctrl-c handler, so the next ctrl-c / SIGINT does the normal thing:
|
||||
if self.ctx:
|
||||
self.ctx.__exit__(exception_type, exception_value, traceback)
|
||||
self.ctx = None
|
||||
|
||||
|
||||
# global flag which might trigger some special behaviour on first ctrl-c / SIGINT,
|
||||
# e.g. if this is interrupting "borg create", it shall try to create a checkpoint.
|
||||
sig_int = SigIntManager()
|
||||
|
||||
|
||||
def popen_with_error_handling(cmd_line: str, log_prefix='', **kwargs):
|
||||
"""
|
||||
Handle typical errors raised by subprocess.Popen. Return None if an error occurred,
|
||||
|
|
Loading…
Reference in a new issue