-
Notifications
You must be signed in to change notification settings - Fork 0
/
signals.py
76 lines (62 loc) · 2.29 KB
/
signals.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# Signal utilities
# Imports
import os
import signal
# Context manager for defering signals until the end of a block of code
# Adapted from: David Evans (2013), MIT License, https://gist.github.com/evansd/2375136
class DeferSignals:
"""
Context manager to defer signal handling until context exits.
Takes optional list of signals to defer (default: SIGHUP, SIGINT, SIGTERM).
Signals can be identified by number or by name.
Allows you to wrap instruction sequences that ought to be atomic and ensure
that they don't get interrupted mid-way.
"""
def __init__(self, signal_list=None):
# Default list of signals to defer
if signal_list is None:
signal_list = [signal.SIGHUP, signal.SIGINT, signal.SIGTERM]
# Accept either signal numbers or string identifiers
self.signal_list = [getattr(signal, sig_id) if isinstance(sig_id, str) else sig_id for sig_id in signal_list]
self.deferred = []
self.previous_handlers = {}
# noinspection PyUnusedLocal
def defer_signal(self, sig_num, stack_frame):
# Temporary signal handler just records which signals occurred
self.deferred.append(sig_num)
def __enter__(self):
# Replace existing handlers with deferred handler
for sig_num in self.signal_list:
# signal.signal returns None when no handler has been set in Python,
# which is the same as the default handler (SIG_DFL) being set
self.previous_handlers[sig_num] = (signal.signal(sig_num, self.defer_signal) or signal.SIG_DFL)
return self
def __exit__(self, *args):
# Restore handlers
for sig_num, handler in self.previous_handlers.items():
signal.signal(sig_num, handler)
# Send deferred signals
while self.deferred:
sig_num = self.deferred.pop(0)
os.kill(os.getpid(), sig_num)
def __call__(self):
"""
If there are any deferred signals pending, trigger them now
This means that instead of this code:
for item in collection:
with defer_signals():
item.process()
You can write this:
with defer_signals() as handle_signals:
for item in collection:
item.process()
handle_signals()
Which has the same effect but avoids having to embed the context
manager in the loop
"""
if self.deferred:
# Reattach the signal handlers and fire signals
self.__exit__()
# Put our deferred signal handlers back in place
self.__enter__()
# EOF