Repository: borgbackup/borg

Issue:
unable to interrupt tests
I run py.test in a pycharm window and sometimes I am not able to interrupt it:

```
collected 600 items 

borg/testsuite/archive.py ........
borg/testsuite/archiver.py .s...F.F....^C.....^C..s^C.^C.^C^C.^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C.^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C..^C.ss..^C^C.^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C
Received signal, again. I'm not deaf.
^C
Received signal, again. I'm not deaf.
^C
Received signal, again. I'm not deaf.
F^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C.^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C.^C......^[.xFF.FFFFFFFF.FFF.........sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss.....F...F.F....s......s
```



Discussion:
Looks like the recreate signal handler. Reset/clean-up signal handlers in ArchiverBaseTestBlubb . tearDown?


Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -40,6 +40,7 @@
 from .helpers import dir_is_tagged, is_slow_msgpack, yes, sysinfo
 from .helpers import log_multi
 from .helpers import parse_pattern, PatternMatcher, PathPrefixPattern
+from .helpers import signal_handler
 from .item import Item
 from .key import key_creator, RepoKey, PassphraseKey
 from .platform import get_flags
@@ -938,27 +939,26 @@ def interrupt(signal_num, stack_frame):
                                      file_status_printer=self.print_file_status,
                                      dry_run=args.dry_run)
 
-        signal.signal(signal.SIGTERM, interrupt)
-        signal.signal(signal.SIGINT, interrupt)
-
-        if args.location.archive:
-            name = args.location.archive
-            if recreater.is_temporary_archive(name):
-                self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
-                return self.exit_code
-            recreater.recreate(name, args.comment)
-        else:
-            for archive in manifest.list_archive_infos(sort_by='ts'):
-                name = archive.name
+        with signal_handler(signal.SIGTERM, interrupt), \
+             signal_handler(signal.SIGINT, interrupt):
+            if args.location.archive:
+                name = args.location.archive
                 if recreater.is_temporary_archive(name):
-                    continue
-                print('Processing', name)
-                if not recreater.recreate(name, args.comment):
-                    break
-        manifest.write()
-        repository.commit()
-        cache.commit()
-        return self.exit_code
+                    self.print_error('Refusing to work on temporary archive of prior recreate: %s', name)
+                    return self.exit_code
+                recreater.recreate(name, args.comment)
+            else:
+                for archive in manifest.list_archive_infos(sort_by='ts'):
+                    name = archive.name
+                    if recreater.is_temporary_archive(name):
+                        continue
+                    print('Processing', name)
+                    if not recreater.recreate(name, args.comment):
+                        break
+            manifest.write()
+            repository.commit()
+            cache.commit()
+            return self.exit_code
 
     @with_repository(manifest=False)
     def do_with_lock(self, args, repository):
diff --git a/src/borg/helpers.py b/src/borg/helpers.py
--- a/src/borg/helpers.py
+++ b/src/borg/helpers.py
@@ -7,6 +7,7 @@
 import platform
 import pwd
 import re
+import signal
 import socket
 import sys
 import stat
@@ -16,6 +17,7 @@
 import uuid
 from binascii import hexlify
 from collections import namedtuple, deque
+from contextlib import contextmanager
 from datetime import datetime, timezone, timedelta
 from fnmatch import translate
 from functools import wraps, partial
@@ -1535,3 +1537,12 @@ def heuristic_lz4(self, compr_args, chunk):
         compr_args.update(compr_spec)
         logger.debug("len(data) == %d, len(lz4(data)) == %d, choosing %s", data_len, cdata_len, compr_spec)
         return compr_args, Chunk(data, **meta)
+
+
+@contextmanager
+def signal_handler(signo, handler):
+    old_signal_handler = signal.signal(signo, handler)
+    try:
+        yield
+    finally:
+        signal.signal(signo, old_signal_handler)


Test patch: