1 | """
|
2 | mylib.py: Python stubs/interfaces that are reimplemented in C++, not directly
|
3 | translated.
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | import signal
|
8 |
|
9 | from typing import List, Any
|
10 |
|
11 | UNTRAPPED_SIGWINCH = -1
|
12 |
|
13 |
|
14 | class SignalSafe(object):
|
15 | """State that is shared between the main thread and signal handlers.
|
16 |
|
17 | See C++ implementation in cpp/core.h
|
18 | """
|
19 |
|
20 | def __init__(self):
|
21 | # type: () -> None
|
22 | self.pending_signals = [] # type: List[int]
|
23 | self.last_sig_num = 0 # type: int
|
24 | self.sigint_trapped = False
|
25 | self.received_sigint = False
|
26 | self.received_sigwinch = False
|
27 | self.sigwinch_code = UNTRAPPED_SIGWINCH
|
28 |
|
29 | def UpdateFromSignalHandler(self, sig_num, unused_frame):
|
30 | # type: (int, Any) -> None
|
31 | """Receive the given signal, and update shared state.
|
32 |
|
33 | This method is registered as a Python signal handler.
|
34 | """
|
35 | self.pending_signals.append(sig_num)
|
36 |
|
37 | if sig_num == signal.SIGINT:
|
38 | self.received_sigint = True
|
39 |
|
40 | if sig_num == signal.SIGWINCH:
|
41 | self.received_sigwinch = True
|
42 | sig_num = self.sigwinch_code # mutate param
|
43 |
|
44 | self.last_sig_num = sig_num
|
45 |
|
46 | def LastSignal(self):
|
47 | # type: () -> int
|
48 | """Return the number of the last signal received."""
|
49 | return self.last_sig_num
|
50 |
|
51 | def PollSigInt(self):
|
52 | # type: () -> bool
|
53 | """Has SIGINT received since the last time PollSigInt() was called?"""
|
54 | result = self.received_sigint
|
55 | self.received_sigint = False
|
56 | return result
|
57 |
|
58 | def PollUntrappedSigInt(self):
|
59 | # type: () -> bool
|
60 | """Has SIGINT received since the last time PollSigInt() was called?"""
|
61 | received = self.PollSigInt()
|
62 | return received and not self.sigint_trapped
|
63 |
|
64 | if 0:
|
65 |
|
66 | def SigIntTrapped(self):
|
67 | # type: () -> bool
|
68 | return self.sigint_trapped
|
69 |
|
70 | def SetSigIntTrapped(self, b):
|
71 | # type: (bool) -> None
|
72 | """Set a flag to tell us whether sigint is trapped by the user."""
|
73 | self.sigint_trapped = b
|
74 |
|
75 | def SetSigWinchCode(self, code):
|
76 | # type: (int) -> None
|
77 | """Depending on whether or not SIGWINCH is trapped by a user, it is
|
78 | expected to report a different code to `wait`.
|
79 |
|
80 | SetSigWinchCode() lets us set which code is reported.
|
81 | """
|
82 | self.sigwinch_code = code
|
83 |
|
84 | def PollSigWinch(self):
|
85 | # type: () -> bool
|
86 | """Has SIGWINCH been received since the last time PollSigWinch() was
|
87 | called?"""
|
88 | result = self.received_sigwinch
|
89 | self.received_sigwinch = False
|
90 | return result
|
91 |
|
92 | def TakePendingSignals(self):
|
93 | # type: () -> List[int]
|
94 | """Transfer ownership of queue of pending signals to caller."""
|
95 |
|
96 | # A note on signal-safety here. The main loop might be calling this function
|
97 | # at the same time a signal is firing and appending to
|
98 | # `self.pending_signals`. We can forgoe using a lock here
|
99 | # (which would be problematic for the signal handler) because mutual
|
100 | # exclusivity should be maintained by the atomic nature of pointer
|
101 | # assignment (i.e. word-sized writes) on most modern platforms.
|
102 | # The replacement run list is allocated before the swap, so it can be
|
103 | # interrupted at any point without consequence.
|
104 | # This means the signal handler always has exclusive access to
|
105 | # `self.pending_signals`. In the worst case the signal handler might write to
|
106 | # `new_queue` and the corresponding trap handler won't get executed
|
107 | # until the main loop calls this function again.
|
108 | # NOTE: It's important to distinguish between signal-safety an
|
109 | # thread-safety here. Signals run in the same process context as the main
|
110 | # loop, while concurrent threads do not and would have to worry about
|
111 | # cache-coherence and instruction reordering.
|
112 | new_queue = [] # type: List[int]
|
113 | ret = self.pending_signals
|
114 | self.pending_signals = new_queue
|
115 | return ret
|
116 |
|
117 | def ReuseEmptyList(self, empty_list):
|
118 | # type: (List[int]) -> None
|
119 | """This optimization only happens in C++."""
|
120 | pass
|
121 |
|
122 |
|
123 | gSignalSafe = None # type: SignalSafe
|
124 |
|
125 | gOrigSigIntHandler = None # type: Any
|
126 |
|
127 |
|
128 | def InitSignalSafe():
|
129 | # type: () -> SignalSafe
|
130 | """Set global instance so the signal handler can access it."""
|
131 | global gSignalSafe
|
132 | gSignalSafe = SignalSafe()
|
133 |
|
134 | # See
|
135 | # - demo/cpython/keyboard_interrupt.py
|
136 | # - pyos::InitSignalSafe()
|
137 |
|
138 | # In C++, we do
|
139 | # RegisterSignalInterest(signal.SIGINT)
|
140 |
|
141 | global gOrigSigIntHandler
|
142 | gOrigSigIntHandler = signal.signal(signal.SIGINT,
|
143 | gSignalSafe.UpdateFromSignalHandler)
|
144 |
|
145 | return gSignalSafe
|
146 |
|
147 |
|
148 | def RegisterSignalInterest(sig_num):
|
149 | # type: (int) -> None
|
150 | """Have the kernel notify the main loop about the given signal."""
|
151 | #log('RegisterSignalInterest %d', sig_num)
|
152 |
|
153 | assert gSignalSafe is not None
|
154 | signal.signal(sig_num, gSignalSafe.UpdateFromSignalHandler)
|
155 |
|
156 |
|
157 | def sigaction(sig_num, handler):
|
158 | # type: (int, Any) -> None
|
159 | """
|
160 | Handle a signal with SIG_DFL or SIG_IGN, not our own signal handler.
|
161 | """
|
162 | # SIGINT and SIGWINCH must be registered through SignalSafe
|
163 | assert sig_num != signal.SIGINT
|
164 | assert sig_num != signal.SIGWINCH
|
165 | signal.signal(sig_num, handler)
|