Imported Upstream version 3.28.3
[platform/upstream/python-gobject.git] / gi / _ossighelper.py
1 # -*- coding: utf-8 -*-
2 # Copyright 2017 Christoph Reiter
3 #
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.
8 #
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.
13 #
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/>.
16
17 from __future__ import print_function
18
19 import os
20 import sys
21 import socket
22 import signal
23 import ctypes
24 import threading
25 from contextlib import closing, contextmanager
26
27
28 def ensure_socket_not_inheritable(sock):
29     """Ensures that the socket is not inherited by child processes
30
31     Raises:
32         EnvironmentError
33         NotImplementedError: With Python <3.4 on Windows
34     """
35
36     if hasattr(sock, "set_inheritable"):
37         sock.set_inheritable(False)
38     else:
39         try:
40             import fcntl
41         except ImportError:
42             raise NotImplementedError(
43                 "Not implemented for older Python on Windows")
44         else:
45             fd = sock.fileno()
46             flags = fcntl.fcntl(fd, fcntl.F_GETFD)
47             fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
48
49
50 _wakeup_fd_is_active = False
51 """Since we can't check if set_wakeup_fd() is already used for nested event
52 loops without introducing a race condition we keep track of it globally.
53 """
54
55
56 @contextmanager
57 def wakeup_on_signal():
58     """A decorator for functions which create a glib event loop to keep
59     Python signal handlers working while the event loop is idling.
60
61     In case an OS signal is received will wake the default event loop up
62     shortly so that any registered Python signal handlers registered through
63     signal.signal() can run.
64
65     Works on Windows but needs Python 3.5+.
66
67     In case the wrapped function is not called from the main thread it will be
68     called as is and it will not wake up the default loop for signals.
69     """
70
71     global _wakeup_fd_is_active
72
73     if _wakeup_fd_is_active:
74         yield
75         return
76
77     from gi.repository import GLib
78
79     # On Windows only Python 3.5+ supports passing sockets to set_wakeup_fd
80     set_wakeup_fd_supports_socket = (
81         os.name != "nt" or sys.version_info[:2] >= (3, 5))
82     # On Windows only Python 3 has an implementation of socketpair()
83     has_socketpair = hasattr(socket, "socketpair")
84
85     if not has_socketpair or not set_wakeup_fd_supports_socket:
86         yield
87         return
88
89     read_socket, write_socket = socket.socketpair()
90     with closing(read_socket), closing(write_socket):
91
92         for sock in [read_socket, write_socket]:
93             sock.setblocking(False)
94             ensure_socket_not_inheritable(sock)
95
96         try:
97             orig_fd = signal.set_wakeup_fd(write_socket.fileno())
98         except ValueError:
99             # Raised in case this is not the main thread -> give up.
100             yield
101             return
102         else:
103             _wakeup_fd_is_active = True
104
105         def signal_notify(source, condition):
106             if condition & GLib.IO_IN:
107                 try:
108                     return bool(read_socket.recv(1))
109                 except EnvironmentError as e:
110                     print(e)
111                     return False
112                 return True
113             else:
114                 return False
115
116         try:
117             if os.name == "nt":
118                 channel = GLib.IOChannel.win32_new_socket(
119                     read_socket.fileno())
120             else:
121                 channel = GLib.IOChannel.unix_new(read_socket.fileno())
122
123             source_id = GLib.io_add_watch(
124                 channel,
125                 GLib.PRIORITY_DEFAULT,
126                 (GLib.IOCondition.IN | GLib.IOCondition.HUP |
127                  GLib.IOCondition.NVAL | GLib.IOCondition.ERR),
128                 signal_notify)
129             try:
130                 yield
131             finally:
132                 GLib.source_remove(source_id)
133         finally:
134             write_fd = signal.set_wakeup_fd(orig_fd)
135             if write_fd != write_socket.fileno():
136                 # Someone has called set_wakeup_fd while func() was active,
137                 # so let's re-revert again.
138                 signal.set_wakeup_fd(write_fd)
139             _wakeup_fd_is_active = False
140
141
142 def create_pythonapi():
143     # We need our own instance of ctypes.pythonapi so we don't modify the
144     # global shared one. Adapted from the ctypes source.
145     if os.name == "nt":
146         return ctypes.PyDLL("python dll", None, sys.dllhandle)
147     elif sys.platform == "cygwin":
148         return ctypes.PyDLL("libpython%d.%d.dll" % sys.version_info[:2])
149     else:
150         return ctypes.PyDLL(None)
151
152
153 pydll = create_pythonapi()
154 PyOS_getsig = pydll.PyOS_getsig
155 PyOS_getsig.restype = ctypes.c_void_p
156 PyOS_getsig.argtypes = [ctypes.c_int]
157
158 # We save the signal pointer so we can detect if glib has changed the
159 # signal handler behind Python's back (GLib.unix_signal_add)
160 if signal.getsignal(signal.SIGINT) is signal.default_int_handler:
161     startup_sigint_ptr = PyOS_getsig(signal.SIGINT)
162 else:
163     # Something has set the handler before import, we can't get a ptr
164     # for the default handler so make sure the pointer will never match.
165     startup_sigint_ptr = -1
166
167
168 def sigint_handler_is_default():
169     """Returns if on SIGINT the default Python handler would be called"""
170
171     return (signal.getsignal(signal.SIGINT) is signal.default_int_handler and
172             PyOS_getsig(signal.SIGINT) == startup_sigint_ptr)
173
174
175 @contextmanager
176 def sigint_handler_set_and_restore_default(handler):
177     """Context manager for saving/restoring the SIGINT handler default state.
178
179     Will only restore the default handler again if the handler is not changed
180     while the context is active.
181     """
182
183     assert sigint_handler_is_default()
184
185     signal.signal(signal.SIGINT, handler)
186     sig_ptr = PyOS_getsig(signal.SIGINT)
187     try:
188         yield
189     finally:
190         if signal.getsignal(signal.SIGINT) is handler and \
191                 PyOS_getsig(signal.SIGINT) == sig_ptr:
192             signal.signal(signal.SIGINT, signal.default_int_handler)
193
194
195 def is_main_thread():
196     """Returns True in case the function is called from the main thread"""
197
198     return threading.current_thread().name == "MainThread"
199
200
201 _callback_stack = []
202 _sigint_called = False
203
204
205 @contextmanager
206 def register_sigint_fallback(callback):
207     """Installs a SIGINT signal handler in case the default Python one is
208     active which calls 'callback' in case the signal occurs.
209
210     Only does something if called from the main thread.
211
212     In case of nested context managers the signal handler will be only
213     installed once and the callbacks will be called in the reverse order
214     of their registration.
215
216     The old signal handler will be restored in case no signal handler is
217     registered while the context is active.
218     """
219
220     # To handle multiple levels of event loops we need to call the last
221     # callback first, wait until the inner most event loop returns control
222     # and only then call the next callback, and so on... until we
223     # reach the outer most which manages the signal handler and raises
224     # in the end
225
226     global _callback_stack, _sigint_called
227
228     if not is_main_thread():
229         yield
230         return
231
232     if not sigint_handler_is_default():
233         if _callback_stack:
234             # This is an inner event loop, append our callback
235             # to the stack so the parent context can call it.
236             _callback_stack.append(callback)
237             try:
238                 yield
239             finally:
240                 cb = _callback_stack.pop()
241                 if _sigint_called:
242                     cb()
243         else:
244             # There is a signal handler set by the user, just do nothing
245             yield
246         return
247
248     _sigint_called = False
249
250     def sigint_handler(sig_num, frame):
251         global _callback_stack, _sigint_called
252
253         if _sigint_called:
254             return
255         _sigint_called = True
256         _callback_stack.pop()()
257
258     _callback_stack.append(callback)
259     try:
260         with sigint_handler_set_and_restore_default(sigint_handler):
261             yield
262     finally:
263         if _sigint_called:
264             signal.default_int_handler(signal.SIGINT, None)
265         else:
266             _callback_stack.pop()