| 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)
|