| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 1 | """ |
| 2 | This module provides mechanisms to use signal handlers in Python. |
| 3 | |
| 4 | Functions: |
| 5 | |
| 6 | signal(sig,action) -- set the action for a given signal (done) |
| 7 | pause(sig) -- wait until a signal arrives [Unix only] |
| 8 | alarm(seconds) -- cause SIGALRM after a specified time [Unix only] |
| 9 | getsignal(sig) -- get the signal action for a given signal |
| 10 | default_int_handler(action) -- default SIGINT handler (done, but acts string) |
| 11 | |
| 12 | Constants: |
| 13 | |
| 14 | SIG_DFL -- used to refer to the system default handler |
| 15 | SIG_IGN -- used to ignore the signal |
| 16 | NSIG -- number of defined signals |
| 17 | |
| 18 | SIGINT, SIGTERM, etc. -- signal numbers |
| 19 | |
| 20 | *** IMPORTANT NOTICES *** |
| 21 | A signal handler function is called with two arguments: |
| 22 | the first is the signal number, the second is the interrupted stack frame. |
| 23 | |
| 24 | According to http://java.sun.com/products/jdk/faq/faq-sun-packages.html |
| 25 | 'writing java programs that rely on sun.* is risky: they are not portable, and are not supported.' |
| 26 | |
| 27 | However, in Jython, like Python, we let you decide what makes |
| 28 | sense for your application. If sun.misc.Signal is not available, |
| 29 | an ImportError is raised. |
| 30 | """ |
| 31 | |
| 32 | |
| 33 | try: |
| 34 | import sun.misc.Signal |
| 35 | except ImportError: |
| 36 | raise ImportError("signal module requires sun.misc.Signal, which is not available on this platform") |
| 37 | |
| 38 | import os |
| 39 | import sun.misc.SignalHandler |
| 40 | import sys |
| 41 | import threading |
| 42 | import time |
| 43 | from java.lang import IllegalArgumentException |
| 44 | from java.util.concurrent.atomic import AtomicReference |
| 45 | |
| 46 | debug = 0 |
| 47 | |
| 48 | def _init_signals(): |
| 49 | # install signals by checking for standard names |
| 50 | # using IllegalArgumentException to diagnose |
| 51 | |
| 52 | possible_signals = """ |
| 53 | SIGABRT |
| 54 | SIGALRM |
| 55 | SIGBUS |
| 56 | SIGCHLD |
| 57 | SIGCONT |
| 58 | SIGFPE |
| 59 | SIGHUP |
| 60 | SIGILL |
| 61 | SIGINFO |
| 62 | SIGINT |
| 63 | SIGIOT |
| 64 | SIGKILL |
| 65 | SIGPIPE |
| 66 | SIGPOLL |
| 67 | SIGPROF |
| 68 | SIGQUIT |
| 69 | SIGSEGV |
| 70 | SIGSTOP |
| 71 | SIGSYS |
| 72 | SIGTERM |
| 73 | SIGTRAP |
| 74 | SIGTSTP |
| 75 | SIGTTIN |
| 76 | SIGTTOU |
| 77 | SIGURG |
| 78 | SIGUSR1 |
| 79 | SIGUSR2 |
| 80 | SIGVTALRM |
| 81 | SIGWINCH |
| 82 | SIGXCPU |
| 83 | SIGXFSZ |
| 84 | """.split() |
| 85 | |
| 86 | _module = __import__(__name__) |
| 87 | signals = {} |
| 88 | signals_by_name = {} |
| 89 | for signal_name in possible_signals: |
| 90 | try: |
| 91 | java_signal = sun.misc.Signal(signal_name[3:]) |
| 92 | except IllegalArgumentException: |
| 93 | continue |
| 94 | |
| 95 | signal_number = java_signal.getNumber() |
| 96 | signals[signal_number] = java_signal |
| 97 | signals_by_name[signal_name] = java_signal |
| 98 | setattr(_module, signal_name, signal_number) # install as a module constant |
| 99 | return signals |
| 100 | |
| 101 | _signals = _init_signals() |
| 102 | NSIG = max(_signals.iterkeys()) + 1 |
| 103 | SIG_DFL = sun.misc.SignalHandler.SIG_DFL # default system handler |
| 104 | SIG_IGN = sun.misc.SignalHandler.SIG_IGN # handler to ignore a signal |
| 105 | |
| 106 | class JythonSignalHandler(sun.misc.SignalHandler): |
| 107 | def __init__(self, action): |
| 108 | self.action = action |
| 109 | |
| 110 | def handle(self, signal): |
| 111 | # passing a frame here probably don't make sense in a threaded system, |
| 112 | # but perhaps revisit |
| 113 | self.action(signal.getNumber(), None) |
| 114 | |
| 115 | def signal(sig, action): |
| 116 | """ |
| 117 | signal(sig, action) -> action |
| 118 | |
| 119 | Set the action for the given signal. The action can be SIG_DFL, |
| 120 | SIG_IGN, or a callable Python object. The previous action is |
| 121 | returned. See getsignal() for possible return values. |
| 122 | |
| 123 | *** IMPORTANT NOTICE *** |
| 124 | A signal handler function is called with two arguments: |
| 125 | the first is the signal number, the second is the interrupted stack frame. |
| 126 | """ |
| 127 | # maybe keep a weak ref map of handlers we have returned? |
| 128 | |
| 129 | try: |
| 130 | signal = _signals[sig] |
| 131 | except KeyError: |
| 132 | raise ValueError("signal number out of range") |
| 133 | |
| 134 | if callable(action): |
| 135 | prev = sun.misc.Signal.handle(signal, JythonSignalHandler(action)) |
| 136 | elif action in (SIG_IGN, SIG_DFL) or isinstance(action, sun.misc.SignalHandler): |
| 137 | prev = sun.misc.Signal.handle(signal, action) |
| 138 | else: |
| 139 | raise TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object") |
| 140 | |
| 141 | if isinstance(prev, JythonSignalHandler): |
| 142 | return prev.action |
| 143 | else: |
| 144 | return prev |
| 145 | |
| 146 | |
| 147 | # dangerous! don't use! |
| 148 | def getsignal(sig): |
| 149 | """getsignal(sig) -> action |
| 150 | |
| 151 | Return the current action for the given signal. The return value can be: |
| 152 | SIG_IGN -- if the signal is being ignored |
| 153 | SIG_DFL -- if the default action for the signal is in effect |
| 154 | None -- if an unknown handler is in effect |
| 155 | anything else -- the callable Python object used as a handler |
| 156 | |
| 157 | Note for Jython: this function is NOT threadsafe. The underlying |
| 158 | Java support only enables getting the current signal handler by |
| 159 | setting a new one. So this is completely prone to race conditions. |
| 160 | """ |
| 161 | try: |
| 162 | signal = _signals[sig] |
| 163 | except KeyError: |
| 164 | raise ValueError("signal number out of range") |
| 165 | current = sun.misc.Signal.handle(signal, SIG_DFL) |
| 166 | sun.misc.Signal.handle(signal, current) # and reinstall |
| 167 | |
| 168 | if isinstance(current, JythonSignalHandler): |
| 169 | return current.action |
| 170 | else: |
| 171 | return current |
| 172 | |
| 173 | def default_int_handler(sig, frame): |
| 174 | """ |
| 175 | default_int_handler(...) |
| 176 | |
| 177 | The default handler for SIGINT installed by Python. |
| 178 | It raises KeyboardInterrupt. |
| 179 | """ |
| 180 | raise KeyboardInterrupt |
| 181 | |
| 182 | def pause(): |
| 183 | raise NotImplementedError |
| 184 | |
| 185 | _alarm_timer_holder = AtomicReference() |
| 186 | |
| 187 | def _alarm_handler(sig, frame): |
| 188 | print "Alarm clock" |
| 189 | os._exit(0) |
| 190 | |
| 191 | # install a default alarm handler, the one we get by default doesn't |
| 192 | # work terribly well since it throws a bus error (at least on OS X)! |
| 193 | try: |
| 194 | SIGALRM |
| 195 | signal(SIGALRM, _alarm_handler) |
| 196 | except NameError: |
| 197 | pass |
| 198 | |
| 199 | class _Alarm(object): |
| 200 | def __init__(self, interval, task): |
| 201 | self.interval = interval |
| 202 | self.task = task |
| 203 | self.scheduled = None |
| 204 | self.timer = threading.Timer(self.interval, self.task) |
| 205 | |
| 206 | def start(self): |
| 207 | self.timer.start() |
| 208 | self.scheduled = time.time() + self.interval |
| 209 | |
| 210 | def cancel(self): |
| 211 | self.timer.cancel() |
| 212 | now = time.time() |
| 213 | if self.scheduled and self.scheduled > now: |
| 214 | return self.scheduled - now |
| 215 | else: |
| 216 | return 0 |
| 217 | |
| 218 | def alarm(time): |
| 219 | try: |
| 220 | SIGALRM |
| 221 | except NameError: |
| 222 | raise NotImplementedError("alarm not implemented on this platform") |
| 223 | |
| 224 | def raise_alarm(): |
| 225 | sun.misc.Signal.raise(_signals[SIGALRM]) |
| 226 | |
| 227 | if time > 0: |
| 228 | new_alarm_timer = _Alarm(time, raise_alarm) |
| 229 | else: |
| 230 | new_alarm_timer = None |
| 231 | old_alarm_timer = _alarm_timer_holder.getAndSet(new_alarm_timer) |
| 232 | if old_alarm_timer: |
| 233 | scheduled = int(old_alarm_timer.cancel()) |
| 234 | else: |
| 235 | scheduled = 0 |
| 236 | |
| 237 | if new_alarm_timer: |
| 238 | new_alarm_timer.start() |
| 239 | return scheduled |