blob: f7a8cc00bdc08653ccfaa86f9ec9871b246e9a8f [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001"""
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
33try:
34 import sun.misc.Signal
35except ImportError:
36 raise ImportError("signal module requires sun.misc.Signal, which is not available on this platform")
37
38import os
39import sun.misc.SignalHandler
40import sys
41import threading
42import time
43from java.lang import IllegalArgumentException
44from java.util.concurrent.atomic import AtomicReference
45
46debug = 0
47
48def _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()
102NSIG = max(_signals.iterkeys()) + 1
103SIG_DFL = sun.misc.SignalHandler.SIG_DFL # default system handler
104SIG_IGN = sun.misc.SignalHandler.SIG_IGN # handler to ignore a signal
105
106class 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
115def 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!
148def 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
173def 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
182def pause():
183 raise NotImplementedError
184
185_alarm_timer_holder = AtomicReference()
186
187def _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)!
193try:
194 SIGALRM
195 signal(SIGALRM, _alarm_handler)
196except NameError:
197 pass
198
199class _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
218def 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