def _shutdown_resources(self): log.debug("Kernel %r shutting down", self) if self._notify_sock: self._notify_sock.close() self._notify_sock = None self._wait_sock.close() self._wait_sock = None if self._signal_sets: signal.set_wakeup_fd(-1) self._signal_sets = None self._default_signals = None if self._selector: self._selector.close() self._selector = None if self._thread_pool: self._thread_pool.shutdown() self._thread_pool = None if self._process_pool: self._process_pool.shutdown() self._process_pool = None if self._monitor: self._monitor.close()
def remove_signal_handler(self, sig): """Remove a handler for a signal. UNIX only. Return True if a signal handler was removed, False if not. """ self._check_signal(sig) try: del self._signal_handlers[sig] except KeyError: return False if sig == signal.SIGINT: handler = signal.default_int_handler else: handler = signal.SIG_DFL try: signal.signal(sig, handler) except OSError as exc: if exc.errno == errno.EINVAL: raise RuntimeError('sig {} cannot be caught'.format(sig)) else: raise if not self._signal_handlers: try: signal.set_wakeup_fd(-1) except (ValueError, OSError) as exc: logger.info('set_wakeup_fd(-1) failed: %s', exc) return True
def main(): pipe_r, pipe_w = os.pipe() flags = fcntl.fcntl(pipe_w, fcntl.F_GETFL, 0) flags = flags | os.O_NONBLOCK fcntl.fcntl(pipe_w, fcntl.F_SETFL, flags) signal.signal(signal.SIGCHLD, lambda x,y: None) signal.signal(signal.SIGALRM, lambda x,y: None) signal.siginterrupt(signal.SIGCHLD,False) #makes no difference signal.siginterrupt(signal.SIGALRM,False) #makes no difference signal.set_wakeup_fd(pipe_w) signal.setitimer(signal.ITIMER_REAL, 2, 2) poller = select.epoll() poller.register(pipe_r, select.EPOLLIN) poller.register(sys.stdin, select.EPOLLIN) print "Main screen turn on" while True: events=[] try: events = poller.poll() try: for fd, flags in events: ch=os.read(fd, 1) if fd==pipe_r: sys.stdout.write( "We get Signal" ) if fd==sys.stdin.fileno(): sys.stdout.write( ch ) sys.stdout.flush() except IOError as e: print "exception loop" + str(e) except IOError as e: print "exception poll" + str(e)
def _setup_signals(self): """Set up signal handlers. On Windows this uses a QTimer to periodically hand control over to Python so it can handle signals. On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get notified. """ signal.signal(signal.SIGINT, self.interrupt) signal.signal(signal.SIGTERM, self.interrupt) if os.name == 'posix' and hasattr(signal, 'set_wakeup_fd'): import fcntl read_fd, write_fd = os.pipe() for fd in (read_fd, write_fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) self._signal_notifier = QSocketNotifier( read_fd, QSocketNotifier.Read, self) self._signal_notifier.activated.connect(self._handle_signal_wakeup) signal.set_wakeup_fd(write_fd) else: self._signal_timer = usertypes.Timer(self, 'python_hacks') self._signal_timer.start(1000) self._signal_timer.timeout.connect(lambda: None)
def add_signal_handler(self, sig, callback, *args): """Add a handler for a signal. UNIX only. Raise ValueError if the signal number is invalid or uncatchable. Raise RuntimeError if there is a problem setting up the handler. """ if (coroutines.iscoroutine(callback) or coroutines.iscoroutinefunction(callback)): raise TypeError("coroutines cannot be used " "with add_signal_handler()") self._check_signal(sig) self._check_closed() try: # set_wakeup_fd() raises ValueError if this is not the # main thread. By calling it early we ensure that an # event loop running in another thread cannot add a signal # handler. signal.set_wakeup_fd(self._csock.fileno()) except (ValueError, OSError) as exc: raise RuntimeError(str(exc)) handle = events.Handle(callback, args, self) self._signal_handlers[sig] = handle try: if compat.PY33: # On Python 3.3 and newer, the C signal handler writes the # signal number into the wakeup file descriptor and then calls # Py_AddPendingCall() to schedule the Python signal handler. # # Register a dummy signal handler to ask Python to write the # signal number into the wakup file descriptor. # _process_self_data() will read signal numbers from this file # descriptor to handle signals. signal.signal(sig, _sighandler_noop) else: # On Python 3.2 and older, the C signal handler first calls # Py_AddPendingCall() to schedule the Python signal handler, # and then write a null byte into the wakeup file descriptor. signal.signal(sig, self._handle_signal) # Set SA_RESTART to limit EINTR occurrences. signal.siginterrupt(sig, False) except (RuntimeError, OSError) as exc: # On Python 2, signal.signal(signal.SIGKILL, signal.SIG_IGN) raises # RuntimeError(22, 'Invalid argument'). On Python 3, # OSError(22, 'Invalid argument') is raised instead. exc_type, exc_value, tb = sys.exc_info() del self._signal_handlers[sig] if not self._signal_handlers: try: signal.set_wakeup_fd(-1) except (ValueError, OSError) as nexc: logger.info('set_wakeup_fd(-1) failed: %s', nexc) if isinstance(exc, RuntimeError) or exc.errno == errno.EINVAL: raise RuntimeError('sig {0} cannot be caught'.format(sig)) else: reraise(exc_type, exc_value, tb)
def _shutdown(): nonlocal njobs for task in sorted(tasks.values(), key=lambda t: t.id, reverse=True): if task.id == self._kernel_task_id: continue # If the task is daemonic, force it to non-daemon status and cancel it if task.daemon: njobs += 1 task.daemon = False assert _cancel_task(task) # Run all of the daemon tasks through cancellation if ready: self.run() # Cancel the kernel loopback task (if any) task = tasks.pop(self._kernel_task_id, None) if task: task.cancel_func() self._notify_sock.close() self._notify_sock = None self._wait_sock.close() self._wait_sock = None self._kernel_task_id = None # Remove the signal handling file descriptor (if any) if self._signal_sets: signal.set_wakeup_fd(-1) self._signal_sets = None self._default_signals = None
def add_signal_handler(self, sig, callback, *args): """Add a handler for a signal. UNIX only. Raise ValueError if the signal number is invalid or uncatchable. Raise RuntimeError if there is a problem setting up the handler. """ self._check_signal(sig) try: # set_wakeup_fd() raises ValueError if this is not the # main thread. By calling it early we ensure that an # event loop running in another thread cannot add a signal # handler. signal.set_wakeup_fd(self._csock.fileno()) except ValueError as exc: raise RuntimeError(str(exc)) handle = events.make_handle(callback, args) self._signal_handlers[sig] = handle try: signal.signal(sig, self._handle_signal) # Set SA_RESTART to limit EINTR occurrences. signal.siginterrupt(sig, False) except OSError as exc: del self._signal_handlers[sig] if not self._signal_handlers: try: signal.set_wakeup_fd(-1) except ValueError as nexc: logger.info('set_wakeup_fd(-1) failed: %s', nexc) if exc.errno == errno.EINVAL: raise RuntimeError('sig {} cannot be caught'.format(sig)) else: raise
def register(): ''' This function creates a select.poll object that can be used in the same manner as signal.pause(). The poll object returns each time a signal was received by the process. This function has to be called from the main thread. ''' global _signal_poller global _signal_read_fd if _signal_poller is not None: raise RuntimeError('register was already called') read_fd, write_fd = os.pipe() # Python c-level signal handler requires that the write end will be in # non blocking mode filecontrol.set_non_blocking(write_fd) # Set the read pipe end to non-blocking too, just in case. filecontrol.set_non_blocking(read_fd) # Prevent subproccesses we execute from inheriting the pipes. filecontrol.set_close_on_exec(write_fd) filecontrol.set_close_on_exec(read_fd) signal.set_wakeup_fd(write_fd) poller = select.poll() poller.register(read_fd, select.POLLIN) _signal_poller = poller _signal_read_fd = read_fd
def _handleSignals(self): # Bypass installing the child waker, for now _SignalReactorMixin._handleSignals(self) try: signal.set_wakeup_fd(self._signal_fds.writer_fileno()) except ValueError: pass
def _signal_pipe(self): # Set up a pipe for SIGCHLD notifications wakeup_r, wakeup_w = os.pipe() fcntl.fcntl(wakeup_w, fcntl.F_SETFL, # Make the pipe non-blocking fcntl.fcntl(wakeup_w, fcntl.F_GETFL, 0) | os.O_NONBLOCK) signal.set_wakeup_fd(wakeup_w) # Tell Python to send a byte to this pipe on signal signal.signal(signal.SIGCHLD, lambda x,y: None) # Stop ignoring SIGCHLD return wakeup_r, wakeup_w
def test_invalid_call(self): # First parameter is positional-only with self.assertRaises(TypeError): signal.set_wakeup_fd(signum=signal.SIGINT) # warn_on_full_buffer is a keyword-only parameter with self.assertRaises(TypeError): signal.set_wakeup_fd(signal.SIGINT, False)
def init(cls): """ Creates a pipe for waking up a select call when a signal has been received. """ cls.__wake_up_pipe = os.pipe() fcntl.fcntl(cls.__wake_up_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) signal.set_wakeup_fd(EventQueueEmptyEventHandler.__wake_up_pipe[1])
def __init__(self): self.lock = threading.RLock() self.condition = threading.Condition(self.lock) # this lock and conditions are used for: # # - mutual exclusion and synchronization beetween sections of # code in _Conductor.__io_loop (conductor thread), and in # Process.start() and Process.wait() (main thread) # # - mutual exclusion beetween sections of code in # _Conductor.__io_loop() (conductor thread) and in # _Conductor.__reaper_thread_func() (reaper thread) self.__io_thread = threading.Thread(target = self.__io_thread_func, name = "I/O") self.__io_thread.setDaemon(True) # thread will terminate automatically when the main thread # exits. once in a while, this can trigger an exception, but # this seems to be safe and to be related to this issue: # http://bugs.python.org/issue1856 self.__rpipe, self.__wpipe = os.pipe() # pipe used to wakeup # the conductor thread # from the main thread # when needed _set_fd_nonblocking(self.__rpipe) # the reading function # _read_asmuch() relies on # file descriptors to be non # blocking _set_fd_nonblocking(self.__wpipe) # because we call # signal.set_wakeup_fd on this pipe self.__poller = poll() # asynchronous I/O with all # subprocesses filehandles self.__poller.register(self.__rpipe, POLLIN | POLLERR) self.__processes = set() # the set of `Process` handled by # this `_Conductor` self.__fds = dict() # keys: the file descriptors currently polled by # this `_Conductor` # # values: tuples (`Process`, `Process`'s # function to handle activity for this # descriptor) self.__pids = dict() # keys: the pids of the subprocesses # launched by this `_Conductor` # # values: their `Process` self.__timeline = [] # heapq of `Process` with a timeout date self.__process_actions = queue.Queue() # thread-safe FIFO used to send requests # from main thread and conductor thread: # we enqueue tuples (function to call, # tuple of parameters to pass to this # function)) self.__reaper_thread_running = False # to keep track wether reaper thread is # running signal.set_wakeup_fd(self.__wpipe) self.pgrp = self.__start_pgrp()
def __init__(self): self.readmap = {} self.writemap = {} # Setup the wakeup file descriptor to avoid hanging on lost signals. wakeup_readfd, wakeup_writefd = os.pipe() fcntl.fcntl(wakeup_writefd, fcntl.F_SETFL, os.O_NONBLOCK) self.register_read(wakeup_readfd, self.wakeup_handler) signal.set_wakeup_fd(wakeup_writefd)
def dispose(self, exc=None): if self.current[0] == self: signal.signal(signal.SIGCHLD, signal.SIG_DFL) signal.set_wakeup_fd(-1) self.current[0] = None error = Result.from_exception(exc or CanceledError('process queue has been disposed')) pids, self.pids = self.pids, {} for ret in self.pids.values(): ret(error)
def close(self): if not self._closed: self._closed = True _check(kernel32.CloseHandle(self._iocp)) if self._iocp_thread is not None: self._iocp_thread.join() self._main_thread_waker.close() if is_main_thread(): signal.set_wakeup_fd(self._old_signal_wakeup_fd)
def main(): writer, reader = socket.socketpair() writer.setblocking(False) reader.setblocking(False) signal.set_wakeup_fd(writer.fileno()) # Keep trying until we lose the race... for attempt in itertools.count(): print(f"Attempt {attempt}: start") # Make sure the socket is empty drained = drain(reader) if drained: print(f"Attempt {attempt}: ({drained} residual bytes discarded)") # Arrange for SIGINT to be delivered 1 second from now thread = threading.Thread(target=raise_SIGINT_soon) thread.start() # Fake an IO loop that's trying to sleep for 10 seconds (but will # hopefully get interrupted after just 1 second) start = time.monotonic() target = start + 10 try: select_calls = 0 drained = 0 while True: now = time.monotonic() if now > target: break select_calls += 1 r, _, _ = select.select([reader], [], [], target - now) if r: # In theory we should loop to fully drain the socket but # honestly there's 1 byte in there at most and it'll be # fine. drained += drain(reader) except KeyboardInterrupt: pass else: print(f"Attempt {attempt}: no KeyboardInterrupt?!") # We expect a successful run to take 1 second, and a failed run to # take 10 seconds, so 2 seconds is a reasonable cutoff to distinguish # them. duration = time.monotonic() - start if duration < 2: print(f"Attempt {attempt}: OK, trying again " f"(select_calls = {select_calls}, drained = {drained})") else: print(f"Attempt {attempt}: FAILED, took {duration} seconds") print(f"select_calls = {select_calls}, drained = {drained}") break thread.join()
def __init__(self, zkservers, config_path): self._signal_notifier = os.pipe() signal.set_wakeup_fd(self._signal_notifier[1]) signal.signal(signal.SIGCHLD, self._sigchld) zh = zkwrapper.ZKWrapper(zkservers) core.set_default_zkwrapper(zh) core.set_default_ping_fd(self._signal_notifier[1]) self._inotify_watcher = InotifyWatcher([config_path], ConfigFile, self._is_config_file) self._sigchld_received = False
def start(self): if self._running: raise RuntimeError('IOLoop is already running') if not logging.getLogger().handlers: # The IOLoop catches and logs exceptions, so it's # important that log output be visible. However, python's # default behavior for non-root loggers (prior to python # 3.2) is to print an unhelpful "no handlers could be # found" message rather than the actual log entry, so we # must explicitly configure logging if we've made it this # far without anything. logging.basicConfig() if self._stopped: self._stopped = False return old_current = getattr(IOLoop._current, "instance", None) IOLoop._current.instance = self self._thread_ident = thread.get_ident() # pyuv won't interate the loop if the poll is interrupted by # a signal, so make sure we can wake it up to catch signals # registered with the signal module # # If someone has already set a wakeup fd, we don't want to # disturb it. This is an issue for twisted, which does its # SIGCHILD processing in response to its own wakeup fd being # written to. As long as the wakeup fd is registered on the IOLoop, # the loop will still wake up and everything should work. old_wakeup_fd = None self._signal_checker.stop() if hasattr(signal, 'set_wakeup_fd') and os.name == 'posix': # requires python 2.6+, unix. set_wakeup_fd exists but crashes # the python process on windows. try: old_wakeup_fd = signal.set_wakeup_fd(self._fdwaker.writer.fileno()) if old_wakeup_fd != -1: # Already set, restore previous value. This is a little racy, # but there's no clean get_wakeup_fd and in real use the # IOLoop is just started once at the beginning. signal.set_wakeup_fd(old_wakeup_fd) old_wakeup_fd = None else: self._signal_checker.start() except ValueError: # non-main thread pass self._running = True self._loop.run(pyuv.UV_RUN_DEFAULT) # reset the stopped flag so another start/stop pair can be issued self._running = False self._stopped = False IOLoop._current.instance = old_current if old_wakeup_fd is not None: signal.set_wakeup_fd(old_wakeup_fd)
def _set_signal_handlers(self): """Set the signal handlers.""" def noopSignalHandler(*args): pass self._sigchld_handler = signal.signal(signal.SIGCHLD, noopSignalHandler) self._sigint_handler = signal.signal(signal.SIGINT, noopSignalHandler) self._sigterm_handler = signal.signal(signal.SIGTERM, noopSignalHandler) sig_r_fd, sig_w_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) signal.set_wakeup_fd(sig_w_fd) self._add_read_fd_handler(sig_r_fd, self._handle_sig_fd, None)
def add_signal_watch(signal_action, _sockets=[]): """Catches signals which should exit the program and calls `signal_action` after the main loop has started, even if the signal occurred before the main loop has started. """ # See https://bugzilla.gnome.org/show_bug.cgi?id=622084 for details sig_names = ["SIGINT", "SIGTERM", "SIGHUP"] if os.name == "nt": sig_names = ["SIGINT", "SIGTERM"] signals = {} for name in sig_names: id_ = getattr(signal, name, None) if id_ is None: continue signals[id_] = name for signum, name in signals.items(): # Before the mainloop starts we catch signals in python # directly and idle_add the app.quit def idle_handler(signum, frame): print_d("Python signal handler activated: %s" % signals[signum]) GLib.idle_add(signal_action, priority=GLib.PRIORITY_HIGH) print_d("Register Python signal handler: %r" % name) signal.signal(signum, idle_handler) read_socket, write_socket = socket.socketpair() for sock in [read_socket, write_socket]: sock.setblocking(False) # prevent it from being GCed and leak it _sockets.append(sock) def signal_notify(source, condition): if condition & GLib.IOCondition.IN: try: return bool(read_socket.recv(1)) except EnvironmentError: return False else: return False if os.name == "nt": channel = GLib.IOChannel.win32_new_socket(read_socket.fileno()) else: channel = GLib.IOChannel.unix_new(read_socket.fileno()) io_add_watch(channel, GLib.PRIORITY_HIGH, (GLib.IOCondition.IN | GLib.IOCondition.HUP | GLib.IOCondition.NVAL | GLib.IOCondition.ERR), signal_notify) signal.set_wakeup_fd(write_socket.fileno())
def _shutdown_resources(self): if self._notify_sock: self._notify_sock.close() self._notify_sock = None self._wait_sock.close() self._wait_sock = None if self._signal_sets: signal.set_wakeup_fd(-1) self._signal_sets = None self._default_signals = None
def __init__(self, proactor): super().__init__() logger.debug('Using proactor: %s', proactor.__class__.__name__) self._proactor = proactor self._selector = proactor # convenient alias self._self_reading_future = None self._accept_futures = {} # socket file descriptor => Future proactor.set_loop(self) self._make_self_pipe() self_no = self._csock.fileno() signal.set_wakeup_fd(self_no)
def __init__(self, sockfile, args, executable=None, use_path=True, env=None, restart=False): """ Start a new daemon. The child will be started and Unix socket will be opened. Connections are not yet accepted, call the run method to start handling connection and hand over the program execution to the SignalProxyDaemon. :param sockfile: Path to the Unix to listen on :param Sequence[str] args: Args of the process to exec :param str executable: Optional, if given this executable instead of the zeroth argument is used as executable. :param bool use_path: Use the PATH variable to find the executable, defaults to True :param dict[str,str] env: If given set the child process's environment, otherwise use the environment of the current process. :param bool restart: If True, restart the child process if it died, otherwise the SignalProxyDaemon will shut itself down, if the child dies. """ if not args: raise ValueError("Empty argument list") if executable is None: executable = args[0] self.sockfile = sockfile self.restart = restart self.args = args self.executable = executable self.use_path = use_path self.env = env self.pid = None self.last_forkexec = -1 try: options = os.O_CLOEXEC | os.O_NONBLOCK self.sig_read_fd, self.sig_write_fd = os.pipe2(options) signal.set_wakeup_fd(self.sig_write_fd) for signo in self.sigset: signal.signal(signo, self._noop_handler) logger.info('Listening on %s', sockfile) if os.path.exists(sockfile): os.unlink(sockfile) self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.server.bind(sockfile) self.server.setblocking(False) self.server.listen(0) self.poll = select.poll() self.connections = {} self._forkexec() self.state = DaemonState.started except: self._restore_signals() self._close_files() raise
def test_set_wakeup_fd_result(self): r1, w1 = os.pipe() self.addCleanup(os.close, r1) self.addCleanup(os.close, w1) r2, w2 = os.pipe() self.addCleanup(os.close, r2) self.addCleanup(os.close, w2) signal.set_wakeup_fd(w1) self.assertEqual(signal.set_wakeup_fd(w2), w1) self.assertEqual(signal.set_wakeup_fd(-1), w2) self.assertEqual(signal.set_wakeup_fd(-1), -1)
def init(signals, purge=False): """ Initialize the core threading/mainloop functionality by creating the thread notifier and signal wakeup pipes, and registering them with the notifier. :param signals: the main loop Signals object (passed by main.py) :param purge: if True, any pending callbacks queued for execution in the mainloop will be removed. This is useful when we have forked and want to wipe the slate clean. This function also installs a SIGCHLD handler, mainly for lack of a better place. If this function is called multiple times, it must recreate the pipes and cleanup after previous invocations. """ log.debug('Creating thread notifier and signal wakeup pipes (purge=%s)', purge) if CoreThreading._pipe: # There is an existing pipe already, so stop monitoring it. notifier.socket_remove(CoreThreading._pipe[0]) CoreThreading._pipe = CoreThreading._create_nonblocking_pipe() notifier.socket_add(CoreThreading._pipe[0], CoreThreading.run_queue) if purge: with CoreThreading._queue_lock: del CoreThreading._queue[:] elif CoreThreading._queue: # A thread is already running and wanted to run something in the # mainloop before the mainloop is started. In that case we need # to wakeup the loop ASAP to handle the requests. CoreThreading._wakeup() # Create wakeup fd pipe (Python 2.6) and install SIGCHLD handler. if hasattr(signal, 'set_wakeup_fd'): # Python 2.6+, so setup the signal wake pipe. if CoreThreading._signal_wake_pipe: # Stop monitoring old signal wake pipe. notifier.socket_remove(CoreThreading._signal_wake_pipe[0]) pipe = CoreThreading._create_nonblocking_pipe() notifier.socket_add(pipe[0], lambda fd: os.read(fd, 4096) and signals['unix-signal'].emit()) CoreThreading._signal_wake_pipe = pipe signal.signal(signal.SIGCHLD, lambda sig, frame: None) signal.set_wakeup_fd(pipe[1]) else: # With Python 2.5-, we can't wakeup the main loop. Use emit() # directly as the handler. signal.signal(signal.SIGCHLD, signals['unix-signal'].emit) # Emit now to reap processes that may have terminated before we set the # handler. process.py connects to this signal. signals['unix-signal'].emit()
def _trap_sigwatch(self, current, sigset): # Initialize the signal handling part of the kernel if not done already # Note: This only works if running in the main thread if self._signals is None: self._signals = defaultdict(list) signal.set_wakeup_fd(self._notify_sock.fileno()) for signo in sigset.signos: if not self._signals[signo]: self._default_signals[signo] = signal.signal(signo, lambda signo, frame:None) self._signals[signo].append(sigset) self._reschedule_task(current)
def test_set_wakeup_fd_socket_result(self): sock1 = socket.socket() self.addCleanup(sock1.close) fd1 = sock1.fileno() sock2 = socket.socket() self.addCleanup(sock2.close) fd2 = sock2.fileno() signal.set_wakeup_fd(fd1) self.assertEqual(signal.set_wakeup_fd(fd2), fd1) self.assertEqual(signal.set_wakeup_fd(-1), fd2) self.assertEqual(signal.set_wakeup_fd(-1), -1)
def child_start(self, gc_old_one=False): if not self.poller: self.poller, (r, w) = select.epoll(), os.pipe() signal.set_wakeup_fd(w) self.wakeup_fd = os.fdopen(r, 'rb', 0) self.poller.register(self.wakeup_fd, select.EPOLLIN) if self._child and gc_old_one: self._child.wait() self._child = None if not self.child_cmd or self._child: return self._child = subprocess.Popen( self.child_cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, close_fds=True ) self._child_readline(init_line=True) # wait until it's ready
def __init__(self): self.readmap = {} self.writemap = {} # Setup the wakeup file descriptor to avoid hanging on lost signals. wakeup_readfd, wakeup_writefd = os.pipe() self.register_read(wakeup_readfd, self.wakeup_handler) # TODO: remove test when we stop supporting Python <2.5 if hasattr(signal, 'set_wakeup_fd'): signal.set_wakeup_fd(wakeup_writefd) self.wakeup_writefd = None else: self.wakeup_writefd = wakeup_writefd