Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/ext/pselect.pyx
8817 views
1
"""
2
Interface to the ``pselect()`` system call
3
4
This module defines a class :class:`PSelecter` which can be used to
5
call the system call ``pselect()`` and which can also be used in a
6
``with`` statement to block given signals until
7
:meth:`PSelecter.pselect` is called.
8
9
AUTHORS:
10
11
- Jeroen Demeyer (2013-02-07): initial version (:trac:`14079`)
12
13
Waiting for subprocesses
14
------------------------
15
16
One possible use is to wait with a **timeout** until **any child process**
17
exits, as opposed to ``os.wait()`` which doesn't have a timeout or
18
``multiprocessing.Process.join()`` which waits for one specific process.
19
20
Since ``SIGCHLD`` is ignored by default, we first need to install a
21
signal handler for ``SIGCHLD``. It doesn't matter what it does, as long
22
as the signal isn't ignored::
23
24
sage: import signal
25
sage: def dummy_handler(sig, frame):
26
....: pass
27
....:
28
sage: _ = signal.signal(signal.SIGCHLD, dummy_handler)
29
30
We wait for a child created using the ``subprocess`` module::
31
32
sage: from sage.ext.pselect import PSelecter
33
sage: from subprocess import *
34
sage: with PSelecter([signal.SIGCHLD]) as sel:
35
....: p = Popen(["sleep", "1"])
36
....: _ = sel.sleep()
37
....:
38
sage: p.poll() # p should be finished
39
0
40
41
Now using the ``multiprocessing`` module::
42
43
sage: from sage.ext.pselect import PSelecter
44
sage: from multiprocessing import *
45
sage: import time
46
sage: with PSelecter([signal.SIGCHLD]) as sel:
47
....: p = Process(target=time.sleep, args=(1,))
48
....: p.start()
49
....: _ = sel.sleep()
50
....: p.is_alive() # p should be finished
51
....:
52
False
53
"""
54
#*****************************************************************************
55
# Copyright (C) 2013 Jeroen Demeyer <[email protected]>
56
#
57
# Distributed under the terms of the GNU General Public License (GPL)
58
# as published by the Free Software Foundation; either version 2 of
59
# the License, or (at your option) any later version.
60
# http://www.gnu.org/licenses/
61
#*****************************************************************************
62
63
include "cdefs.pxi"
64
include "signals.pxi"
65
cimport libc.errno
66
67
68
cpdef int get_fileno(f) except -1:
69
"""
70
Return the file descriptor of ``f``.
71
72
INPUT:
73
74
- ``f`` -- an object with a ``.fileno`` method or an integer,
75
which is a file descriptor.
76
77
OUTPUT: A C ``long`` representing the file descriptor.
78
79
EXAMPLES::
80
81
sage: from sage.ext.pselect import get_fileno
82
sage: get_fileno(open(os.devnull)) # random
83
5
84
sage: get_fileno(42)
85
42
86
sage: get_fileno(None)
87
Traceback (most recent call last):
88
...
89
TypeError: an integer is required
90
sage: get_fileno(-1)
91
Traceback (most recent call last):
92
...
93
ValueError: Invalid file descriptor
94
sage: get_fileno(2^30)
95
Traceback (most recent call last):
96
...
97
ValueError: Invalid file descriptor
98
"""
99
cdef int n
100
try:
101
n = f.fileno()
102
except AttributeError:
103
n = f
104
if n < 0 or n >= FD_SETSIZE:
105
raise ValueError("Invalid file descriptor")
106
return n
107
108
109
cdef class PSelecter:
110
"""
111
This class gives an interface to the ``pselect`` system call.
112
113
It can be used in a ``with`` statement to block given signals
114
such that they can only occur during the :meth:`pselect()` or
115
:meth:`sleep()` calls.
116
117
As an example, we block the ``SIGHUP`` and ``SIGALRM`` signals and
118
then raise a ``SIGALRM`` signal. The interrupt will only be seen
119
during the :meth:`sleep` call::
120
121
sage: from sage.ext.pselect import PSelecter
122
sage: import signal, time
123
sage: with PSelecter([signal.SIGHUP, signal.SIGALRM]) as sel:
124
....: os.kill(os.getpid(), signal.SIGALRM)
125
....: time.sleep(0.5) # Simply sleep, no interrupt detected
126
....: try:
127
....: _ = sel.sleep(1) # Interrupt seen here
128
....: except AlarmInterrupt:
129
....: print("Interrupt OK")
130
....:
131
Interrupt OK
132
133
.. WARNING::
134
135
If ``SIGCHLD`` is blocked inside the ``with`` block, then you
136
should not use ``Popen().wait()`` or ``Process().join()``
137
because those might block, even if the process has actually
138
exited. Use non-blocking alternatives such as ``Popen.poll()``
139
or ``multiprocessing.active_children()`` instead.
140
"""
141
cdef sigset_t oldset
142
cdef sigset_t blockset
143
144
def __cinit__(self):
145
"""
146
Store old signal mask, needed if this class is used *without*
147
a ``with`` statement.
148
149
EXAMPLES::
150
151
sage: from sage.ext.pselect import PSelecter
152
sage: PSelecter()
153
<sage.ext.pselect.PSelecter ...>
154
"""
155
cdef sigset_t emptyset
156
sigemptyset(&emptyset)
157
sigprocmask(SIG_BLOCK, &emptyset, &self.oldset)
158
159
def __init__(self, block=[]):
160
"""
161
Store list of signals to block during ``pselect()``.
162
163
EXAMPLES::
164
165
sage: from sage.ext.pselect import PSelecter
166
sage: from signal import *
167
sage: PSelecter([SIGINT, SIGSEGV])
168
<sage.ext.pselect.PSelecter ...>
169
"""
170
sigemptyset(&self.blockset)
171
for sig in block:
172
sigaddset(&self.blockset, sig)
173
174
def __enter__(self):
175
"""
176
Block signals chosen during :meth:`__init__` in this ``with`` block.
177
178
OUTPUT: ``self``
179
180
TESTS:
181
182
Test nesting, where the inner ``with`` statements should have no
183
influence, in particular they should not unblock signals which
184
were already blocked upon entering::
185
186
sage: from sage.ext.pselect import PSelecter
187
sage: import signal
188
sage: with PSelecter([signal.SIGALRM]) as sel:
189
....: os.kill(os.getpid(), signal.SIGALRM)
190
....: with PSelecter([signal.SIGFPE]) as sel2:
191
....: _ = sel2.sleep(0.1)
192
....: with PSelecter([signal.SIGALRM]) as sel3:
193
....: _ = sel3.sleep(0.1)
194
....: try:
195
....: _ = sel.sleep(0.1)
196
....: except AlarmInterrupt:
197
....: print("Interrupt OK")
198
....:
199
Interrupt OK
200
"""
201
sigprocmask(SIG_BLOCK, &self.blockset, &self.oldset)
202
return self
203
204
def __exit__(self, type, value, traceback):
205
"""
206
Reset signal mask to what it was before :meth:`__enter__`.
207
208
EXAMPLES:
209
210
Install a ``SIGCHLD`` handler::
211
212
sage: import signal
213
sage: def child_handler(sig, frame):
214
....: global got_child
215
....: got_child = 1
216
....:
217
sage: _ = signal.signal(signal.SIGCHLD, child_handler)
218
sage: got_child = 0
219
220
Start a process which will cause a ``SIGCHLD`` signal::
221
222
sage: import time
223
sage: from multiprocessing import *
224
sage: from sage.ext.pselect import PSelecter
225
sage: w = PSelecter([signal.SIGCHLD])
226
sage: with w:
227
....: p = Process(target=time.sleep, args=(0.25,))
228
....: t0 = walltime()
229
....: p.start()
230
....:
231
232
This ``sleep`` should be interruptible now::
233
234
sage: time.sleep(1)
235
sage: t = walltime(t0)
236
sage: (0.2 <= t <= 0.9) or t
237
True
238
sage: got_child
239
1
240
sage: p.join()
241
"""
242
sigprocmask(SIG_SETMASK, &self.oldset, NULL)
243
244
def pselect(self, rlist=[], wlist=[], xlist=[], timeout=None):
245
"""
246
Wait until one of the given files is ready, or a signal has
247
been received, or until ``timeout`` seconds have past.
248
249
INPUT:
250
251
- ``rlist`` -- (default: ``[]``) a list of files to wait for
252
reading.
253
254
- ``wlist`` -- (default: ``[]``) a list of files to wait for
255
writing.
256
257
- ``xlist`` -- (default: ``[]``) a list of files to wait for
258
exceptions.
259
260
- ``timeout`` -- (default: ``None``) a timeout in seconds,
261
where ``None`` stands for no timeout.
262
263
OUTPUT: A 4-tuple ``(rready, wready, xready, tmout)`` where the
264
first three are lists of file descriptors which are ready,
265
that is a subset of ``(rlist, wlist, xlist)``. The fourth is a
266
boolean which is ``True`` if and only if the command timed out.
267
If ``pselect`` was interrupted by a signal, the output is
268
``([], [], [], False)``.
269
270
.. SEEALSO::
271
272
Use the :meth:`sleep` method instead if you don't care about
273
file descriptors.
274
275
EXAMPLES:
276
277
The file ``/dev/null`` should always be available for reading
278
and writing::
279
280
sage: from sage.ext.pselect import PSelecter
281
sage: f = open(os.devnull, "r+")
282
sage: sel = PSelecter()
283
sage: sel.pselect(rlist=[f])
284
([<open file '/dev/null', mode 'r+' at ...>], [], [], False)
285
sage: sel.pselect(wlist=[f])
286
([], [<open file '/dev/null', mode 'r+' at ...>], [], False)
287
288
A list of various files, all of them should be ready for
289
reading. Also create a pipe, which should be ready for
290
writing, but not reading (since nothing has been written)::
291
292
sage: f = open(os.devnull, "r")
293
sage: g = open(os.path.join(SAGE_LOCAL, 'bin', 'python'), "r")
294
sage: (pr, pw) = os.pipe()
295
sage: r, w, x, t = PSelecter().pselect([f,g,pr,pw], [pw], [pr,pw])
296
sage: len(r), len(w), len(x), t
297
(2, 1, 0, False)
298
299
Checking for exceptions on the pipe should simply time out::
300
301
sage: sel.pselect(xlist=[pr,pw], timeout=0.2)
302
([], [], [], True)
303
304
TESTS:
305
306
It is legal (but silly) to list the same file multiple times::
307
308
sage: r, w, x, t = PSelecter().pselect([f,g,f,f,g])
309
sage: len(r)
310
5
311
312
Invalid input::
313
314
sage: PSelecter().pselect([None])
315
Traceback (most recent call last):
316
...
317
TypeError: an integer is required
318
319
Open a file and close it, but save the (invalid) file
320
descriptor::
321
322
sage: f = open(os.devnull, "r")
323
sage: n = f.fileno()
324
sage: f.close()
325
sage: PSelecter().pselect([n])
326
Traceback (most recent call last):
327
...
328
IOError: ...
329
"""
330
# Convert given lists to fd_set
331
cdef fd_set rfds, wfds, xfds
332
FD_ZERO(&rfds)
333
FD_ZERO(&wfds)
334
FD_ZERO(&xfds)
335
336
cdef int nfds = 0
337
cdef int n
338
for f in rlist:
339
n = get_fileno(f)
340
if (n >= nfds): nfds = n + 1
341
FD_SET(n, &rfds)
342
for f in wlist:
343
n = get_fileno(f)
344
if (n >= nfds): nfds = n + 1
345
FD_SET(n, &wfds)
346
for f in xlist:
347
n = get_fileno(f)
348
if (n >= nfds): nfds = n + 1
349
FD_SET(n, &xfds)
350
351
cdef double tm
352
cdef timespec tv
353
cdef timespec *ptv = NULL
354
cdef int ret
355
if timeout is not None:
356
tm = timeout
357
if tm < 0:
358
tm = 0
359
tv.tv_sec = <long>tm
360
tv.tv_nsec = <long>(1e9 * (tm - <double>tv.tv_sec))
361
ptv = &tv
362
363
ret = pselect(nfds, &rfds, &wfds, &xfds, ptv, &self.oldset)
364
# No file descriptors ready => timeout
365
if ret == 0:
366
return ([], [], [], True)
367
368
# Error?
369
cdef int err
370
if ret < 0:
371
# Save this value in case Python statements change it.
372
err = libc.errno.errno
373
if err == libc.errno.EINTR:
374
return ([], [], [], False)
375
import os
376
if err == libc.errno.EBADF:
377
raise IOError(err, os.strerror(err))
378
else:
379
raise OSError(err, os.strerror(err))
380
381
# Figure out which file descriptors to return
382
rready = []
383
wready = []
384
xready = []
385
for f in rlist:
386
n = get_fileno(f)
387
if FD_ISSET(n, &rfds):
388
rready.append(f)
389
for f in wlist:
390
n = get_fileno(f)
391
if FD_ISSET(n, &wfds):
392
wready.append(f)
393
for f in xlist:
394
n = get_fileno(f)
395
if FD_ISSET(n, &xfds):
396
xready.append(f)
397
398
return (rready, wready, xready, False)
399
400
def sleep(self, timeout=None):
401
"""
402
Wait until a signal has been received, or until ``timeout``
403
seconds have past.
404
405
This is implemented as a special case of :meth:`pselect` with
406
empty lists of file descriptors.
407
408
INPUT:
409
410
- ``timeout`` -- (default: ``None``) a timeout in seconds,
411
where ``None`` stands for no timeout.
412
413
OUTPUT: A boolean which is ``True`` if the call timed out,
414
False if it was interrupted.
415
416
EXAMPLES:
417
418
A simple wait with timeout::
419
420
sage: from sage.ext.pselect import PSelecter
421
sage: sel = PSelecter()
422
sage: sel.sleep(timeout=0.1)
423
True
424
425
0 or negative time-outs are allowed, ``sleep`` should then
426
return immediately::
427
428
sage: sel.sleep(timeout=0)
429
True
430
sage: sel.sleep(timeout=-123.45)
431
True
432
"""
433
return self.pselect(timeout=timeout)[3]
434
435