| """ |
| This module provides mechanisms to use signal handlers in Python. |
| |
| Functions: |
| |
| signal(sig,action) -- set the action for a given signal (done) |
| pause(sig) -- wait until a signal arrives [Unix only] |
| alarm(seconds) -- cause SIGALRM after a specified time [Unix only] |
| getsignal(sig) -- get the signal action for a given signal |
| default_int_handler(action) -- default SIGINT handler (done, but acts string) |
| |
| Constants: |
| |
| SIG_DFL -- used to refer to the system default handler |
| SIG_IGN -- used to ignore the signal |
| NSIG -- number of defined signals |
| |
| SIGINT, SIGTERM, etc. -- signal numbers |
| |
| *** IMPORTANT NOTICES *** |
| A signal handler function is called with two arguments: |
| the first is the signal number, the second is the interrupted stack frame. |
| |
| According to http://java.sun.com/products/jdk/faq/faq-sun-packages.html |
| 'writing java programs that rely on sun.* is risky: they are not portable, and are not supported.' |
| |
| However, in Jython, like Python, we let you decide what makes |
| sense for your application. If sun.misc.Signal is not available, |
| an ImportError is raised. |
| """ |
| |
| |
| try: |
| import sun.misc.Signal |
| except ImportError: |
| raise ImportError("signal module requires sun.misc.Signal, which is not available on this platform") |
| |
| import os |
| import sun.misc.SignalHandler |
| import sys |
| import threading |
| import time |
| from java.lang import IllegalArgumentException |
| from java.util.concurrent.atomic import AtomicReference |
| |
| debug = 0 |
| |
| def _init_signals(): |
| # install signals by checking for standard names |
| # using IllegalArgumentException to diagnose |
| |
| possible_signals = """ |
| SIGABRT |
| SIGALRM |
| SIGBUS |
| SIGCHLD |
| SIGCONT |
| SIGFPE |
| SIGHUP |
| SIGILL |
| SIGINFO |
| SIGINT |
| SIGIOT |
| SIGKILL |
| SIGPIPE |
| SIGPOLL |
| SIGPROF |
| SIGQUIT |
| SIGSEGV |
| SIGSTOP |
| SIGSYS |
| SIGTERM |
| SIGTRAP |
| SIGTSTP |
| SIGTTIN |
| SIGTTOU |
| SIGURG |
| SIGUSR1 |
| SIGUSR2 |
| SIGVTALRM |
| SIGWINCH |
| SIGXCPU |
| SIGXFSZ |
| """.split() |
| |
| _module = __import__(__name__) |
| signals = {} |
| signals_by_name = {} |
| for signal_name in possible_signals: |
| try: |
| java_signal = sun.misc.Signal(signal_name[3:]) |
| except IllegalArgumentException: |
| continue |
| |
| signal_number = java_signal.getNumber() |
| signals[signal_number] = java_signal |
| signals_by_name[signal_name] = java_signal |
| setattr(_module, signal_name, signal_number) # install as a module constant |
| return signals |
| |
| _signals = _init_signals() |
| NSIG = max(_signals.iterkeys()) + 1 |
| SIG_DFL = sun.misc.SignalHandler.SIG_DFL # default system handler |
| SIG_IGN = sun.misc.SignalHandler.SIG_IGN # handler to ignore a signal |
| |
| class JythonSignalHandler(sun.misc.SignalHandler): |
| def __init__(self, action): |
| self.action = action |
| |
| def handle(self, signal): |
| # passing a frame here probably don't make sense in a threaded system, |
| # but perhaps revisit |
| self.action(signal.getNumber(), None) |
| |
| def signal(sig, action): |
| """ |
| signal(sig, action) -> action |
| |
| Set the action for the given signal. The action can be SIG_DFL, |
| SIG_IGN, or a callable Python object. The previous action is |
| returned. See getsignal() for possible return values. |
| |
| *** IMPORTANT NOTICE *** |
| A signal handler function is called with two arguments: |
| the first is the signal number, the second is the interrupted stack frame. |
| """ |
| # maybe keep a weak ref map of handlers we have returned? |
| |
| try: |
| signal = _signals[sig] |
| except KeyError: |
| raise ValueError("signal number out of range") |
| |
| if callable(action): |
| prev = sun.misc.Signal.handle(signal, JythonSignalHandler(action)) |
| elif action in (SIG_IGN, SIG_DFL) or isinstance(action, sun.misc.SignalHandler): |
| prev = sun.misc.Signal.handle(signal, action) |
| else: |
| raise TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object") |
| |
| if isinstance(prev, JythonSignalHandler): |
| return prev.action |
| else: |
| return prev |
| |
| |
| # dangerous! don't use! |
| def getsignal(sig): |
| """getsignal(sig) -> action |
| |
| Return the current action for the given signal. The return value can be: |
| SIG_IGN -- if the signal is being ignored |
| SIG_DFL -- if the default action for the signal is in effect |
| None -- if an unknown handler is in effect |
| anything else -- the callable Python object used as a handler |
| |
| Note for Jython: this function is NOT threadsafe. The underlying |
| Java support only enables getting the current signal handler by |
| setting a new one. So this is completely prone to race conditions. |
| """ |
| try: |
| signal = _signals[sig] |
| except KeyError: |
| raise ValueError("signal number out of range") |
| current = sun.misc.Signal.handle(signal, SIG_DFL) |
| sun.misc.Signal.handle(signal, current) # and reinstall |
| |
| if isinstance(current, JythonSignalHandler): |
| return current.action |
| else: |
| return current |
| |
| def default_int_handler(sig, frame): |
| """ |
| default_int_handler(...) |
| |
| The default handler for SIGINT installed by Python. |
| It raises KeyboardInterrupt. |
| """ |
| raise KeyboardInterrupt |
| |
| def pause(): |
| raise NotImplementedError |
| |
| _alarm_timer_holder = AtomicReference() |
| |
| def _alarm_handler(sig, frame): |
| print "Alarm clock" |
| os._exit(0) |
| |
| # install a default alarm handler, the one we get by default doesn't |
| # work terribly well since it throws a bus error (at least on OS X)! |
| try: |
| SIGALRM |
| signal(SIGALRM, _alarm_handler) |
| except NameError: |
| pass |
| |
| class _Alarm(object): |
| def __init__(self, interval, task): |
| self.interval = interval |
| self.task = task |
| self.scheduled = None |
| self.timer = threading.Timer(self.interval, self.task) |
| |
| def start(self): |
| self.timer.start() |
| self.scheduled = time.time() + self.interval |
| |
| def cancel(self): |
| self.timer.cancel() |
| now = time.time() |
| if self.scheduled and self.scheduled > now: |
| return self.scheduled - now |
| else: |
| return 0 |
| |
| def alarm(time): |
| try: |
| SIGALRM |
| except NameError: |
| raise NotImplementedError("alarm not implemented on this platform") |
| |
| def raise_alarm(): |
| sun.misc.Signal.raise(_signals[SIGALRM]) |
| |
| if time > 0: |
| new_alarm_timer = _Alarm(time, raise_alarm) |
| else: |
| new_alarm_timer = None |
| old_alarm_timer = _alarm_timer_holder.getAndSet(new_alarm_timer) |
| if old_alarm_timer: |
| scheduled = int(old_alarm_timer.cancel()) |
| else: |
| scheduled = 0 |
| |
| if new_alarm_timer: |
| new_alarm_timer.start() |
| return scheduled |