functions: revert the function init order to make pylint happy again. See #217
[pygobject.git] / gi / _ossighelper.py
blob213f0965be6a97ddb80fa7fbba2a0b34a7fa9378
1 # -*- coding: utf-8 -*-
2 # Copyright 2017 Christoph Reiter
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 from __future__ import print_function
19 import os
20 import sys
21 import socket
22 import signal
23 import threading
24 from contextlib import closing, contextmanager
26 from . import _gi
29 def ensure_socket_not_inheritable(sock):
30 """Ensures that the socket is not inherited by child processes
32 Raises:
33 EnvironmentError
34 NotImplementedError: With Python <3.4 on Windows
35 """
37 if hasattr(sock, "set_inheritable"):
38 sock.set_inheritable(False)
39 else:
40 try:
41 import fcntl
42 except ImportError:
43 raise NotImplementedError(
44 "Not implemented for older Python on Windows")
45 else:
46 fd = sock.fileno()
47 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
48 fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
51 _wakeup_fd_is_active = False
52 """Since we can't check if set_wakeup_fd() is already used for nested event
53 loops without introducing a race condition we keep track of it globally.
54 """
57 @contextmanager
58 def wakeup_on_signal():
59 """A decorator for functions which create a glib event loop to keep
60 Python signal handlers working while the event loop is idling.
62 In case an OS signal is received will wake the default event loop up
63 shortly so that any registered Python signal handlers registered through
64 signal.signal() can run.
66 Works on Windows but needs Python 3.5+.
68 In case the wrapped function is not called from the main thread it will be
69 called as is and it will not wake up the default loop for signals.
70 """
72 global _wakeup_fd_is_active
74 if _wakeup_fd_is_active:
75 yield
76 return
78 from gi.repository import GLib
80 # On Windows only Python 3.5+ supports passing sockets to set_wakeup_fd
81 set_wakeup_fd_supports_socket = (
82 os.name != "nt" or sys.version_info[:2] >= (3, 5))
83 # On Windows only Python 3 has an implementation of socketpair()
84 has_socketpair = hasattr(socket, "socketpair")
86 if not has_socketpair or not set_wakeup_fd_supports_socket:
87 yield
88 return
90 read_socket, write_socket = socket.socketpair()
91 with closing(read_socket), closing(write_socket):
93 for sock in [read_socket, write_socket]:
94 sock.setblocking(False)
95 ensure_socket_not_inheritable(sock)
97 try:
98 orig_fd = signal.set_wakeup_fd(write_socket.fileno())
99 except ValueError:
100 # Raised in case this is not the main thread -> give up.
101 yield
102 return
103 else:
104 _wakeup_fd_is_active = True
106 def signal_notify(source, condition):
107 if condition & GLib.IO_IN:
108 try:
109 return bool(read_socket.recv(1))
110 except EnvironmentError as e:
111 print(e)
112 return False
113 return True
114 else:
115 return False
117 try:
118 if os.name == "nt":
119 channel = GLib.IOChannel.win32_new_socket(
120 read_socket.fileno())
121 else:
122 channel = GLib.IOChannel.unix_new(read_socket.fileno())
124 source_id = GLib.io_add_watch(
125 channel,
126 GLib.PRIORITY_DEFAULT,
127 (GLib.IOCondition.IN | GLib.IOCondition.HUP |
128 GLib.IOCondition.NVAL | GLib.IOCondition.ERR),
129 signal_notify)
130 try:
131 yield
132 finally:
133 GLib.source_remove(source_id)
134 finally:
135 write_fd = signal.set_wakeup_fd(orig_fd)
136 if write_fd != write_socket.fileno():
137 # Someone has called set_wakeup_fd while func() was active,
138 # so let's re-revert again.
139 signal.set_wakeup_fd(write_fd)
140 _wakeup_fd_is_active = False
143 PyOS_getsig = _gi.pyos_getsig
145 # We save the signal pointer so we can detect if glib has changed the
146 # signal handler behind Python's back (GLib.unix_signal_add)
147 if signal.getsignal(signal.SIGINT) is signal.default_int_handler:
148 startup_sigint_ptr = PyOS_getsig(signal.SIGINT)
149 else:
150 # Something has set the handler before import, we can't get a ptr
151 # for the default handler so make sure the pointer will never match.
152 startup_sigint_ptr = -1
155 def sigint_handler_is_default():
156 """Returns if on SIGINT the default Python handler would be called"""
158 return (signal.getsignal(signal.SIGINT) is signal.default_int_handler and
159 PyOS_getsig(signal.SIGINT) == startup_sigint_ptr)
162 @contextmanager
163 def sigint_handler_set_and_restore_default(handler):
164 """Context manager for saving/restoring the SIGINT handler default state.
166 Will only restore the default handler again if the handler is not changed
167 while the context is active.
170 assert sigint_handler_is_default()
172 signal.signal(signal.SIGINT, handler)
173 sig_ptr = PyOS_getsig(signal.SIGINT)
174 try:
175 yield
176 finally:
177 if signal.getsignal(signal.SIGINT) is handler and \
178 PyOS_getsig(signal.SIGINT) == sig_ptr:
179 signal.signal(signal.SIGINT, signal.default_int_handler)
182 def is_main_thread():
183 """Returns True in case the function is called from the main thread"""
185 return threading.current_thread().name == "MainThread"
188 _callback_stack = []
189 _sigint_called = False
192 @contextmanager
193 def register_sigint_fallback(callback):
194 """Installs a SIGINT signal handler in case the default Python one is
195 active which calls 'callback' in case the signal occurs.
197 Only does something if called from the main thread.
199 In case of nested context managers the signal handler will be only
200 installed once and the callbacks will be called in the reverse order
201 of their registration.
203 The old signal handler will be restored in case no signal handler is
204 registered while the context is active.
207 # To handle multiple levels of event loops we need to call the last
208 # callback first, wait until the inner most event loop returns control
209 # and only then call the next callback, and so on... until we
210 # reach the outer most which manages the signal handler and raises
211 # in the end
213 global _callback_stack, _sigint_called
215 if not is_main_thread():
216 yield
217 return
219 if not sigint_handler_is_default():
220 if _callback_stack:
221 # This is an inner event loop, append our callback
222 # to the stack so the parent context can call it.
223 _callback_stack.append(callback)
224 try:
225 yield
226 finally:
227 cb = _callback_stack.pop()
228 if _sigint_called:
229 cb()
230 else:
231 # There is a signal handler set by the user, just do nothing
232 yield
233 return
235 _sigint_called = False
237 def sigint_handler(sig_num, frame):
238 global _callback_stack, _sigint_called
240 if _sigint_called:
241 return
242 _sigint_called = True
243 _callback_stack.pop()()
245 _callback_stack.append(callback)
246 try:
247 with sigint_handler_set_and_restore_default(sigint_handler):
248 yield
249 finally:
250 if _sigint_called:
251 signal.default_int_handler(signal.SIGINT, None)
252 else:
253 _callback_stack.pop()