Coverage for Lib/asyncio/unix_events.py: 87%
647 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:31 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:31 +0000
1"""Selector event loop for Unix with signal handling."""
3import errno
4import io
5import itertools
6import os
7import selectors
8import signal
9import socket
10import stat
11import subprocess
12import sys
13import threading
14import warnings
15import inspect
17from . import base_events
18from . import base_subprocess
19from . import constants
20from . import coroutines
21from . import events
22from . import exceptions
23from . import futures
24from . import selector_events
25from . import tasks
26from . import transports
27from .log import logger
30__all__ = (
31 'SelectorEventLoop',
32 'EventLoop',
33)
36if sys.platform == 'win32': # pragma: no cover
37 raise ImportError('Signals are not really supported on Windows')
40def _sighandler_noop(signum, frame):
41 """Dummy signal handler."""
42 pass
45def waitstatus_to_exitcode(status):
46 try:
47 return os.waitstatus_to_exitcode(status)
48 except ValueError:
49 # The child exited, but we don't understand its status.
50 # This shouldn't happen, but if it does, let's just
51 # return that status; perhaps that helps debug it.
52 return status
55class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
56 """Unix event loop.
58 Adds signal handling and UNIX Domain Socket support to
59 SelectorEventLoop.
60 """
62 def __init__(self, selector=None):
63 super().__init__(selector)
64 self._signal_handlers = {}
65 self._unix_server_sockets = {}
66 if can_use_pidfd():
67 self._watcher = _PidfdChildWatcher()
68 else:
69 self._watcher = _ThreadedChildWatcher()
71 def close(self):
72 super().close()
73 if not sys.is_finalizing():
74 for sig in list(self._signal_handlers):
75 self.remove_signal_handler(sig)
76 else:
77 if self._signal_handlers: 77 ↛ exitline 77 didn't return from function 'close' because the condition on line 77 was always true
78 warnings.warn(f"Closing the loop {self!r} "
79 f"on interpreter shutdown "
80 f"stage, skipping signal handlers removal",
81 ResourceWarning,
82 source=self)
83 self._signal_handlers.clear()
85 def _process_self_data(self, data):
86 for signum in data:
87 if not signum:
88 # ignore null bytes written by _write_to_self()
89 continue
90 self._handle_signal(signum)
92 def add_signal_handler(self, sig, callback, *args):
93 """Add a handler for a signal. UNIX only.
95 Raise ValueError if the signal number is invalid or uncatchable.
96 Raise RuntimeError if there is a problem setting up the handler.
97 """
98 if (coroutines.iscoroutine(callback) or
99 inspect.iscoroutinefunction(callback)):
100 raise TypeError("coroutines cannot be used "
101 "with add_signal_handler()")
102 self._check_signal(sig)
103 self._check_closed()
104 try:
105 # set_wakeup_fd() raises ValueError if this is not the
106 # main thread. By calling it early we ensure that an
107 # event loop running in another thread cannot add a signal
108 # handler.
109 signal.set_wakeup_fd(self._csock.fileno())
110 except (ValueError, OSError) as exc:
111 raise RuntimeError(str(exc))
113 handle = events.Handle(callback, args, self, None)
114 self._signal_handlers[sig] = handle
116 try:
117 # Register a dummy signal handler to ask Python to write the signal
118 # number in the wakeup file descriptor. _process_self_data() will
119 # read signal numbers from this file descriptor to handle signals.
120 signal.signal(sig, _sighandler_noop)
122 # Set SA_RESTART to limit EINTR occurrences.
123 signal.siginterrupt(sig, False)
124 except OSError as exc:
125 del self._signal_handlers[sig]
126 if not self._signal_handlers:
127 try:
128 signal.set_wakeup_fd(-1)
129 except (ValueError, OSError) as nexc:
130 logger.info('set_wakeup_fd(-1) failed: %s', nexc)
132 if exc.errno == errno.EINVAL:
133 raise RuntimeError(f'sig {sig} cannot be caught')
134 else:
135 raise
137 def _handle_signal(self, sig):
138 """Internal helper that is the actual signal handler."""
139 handle = self._signal_handlers.get(sig)
140 if handle is None:
141 return # Assume it's some race condition.
142 if handle._cancelled:
143 self.remove_signal_handler(sig) # Remove it properly.
144 else:
145 self._add_callback_signalsafe(handle)
147 def remove_signal_handler(self, sig):
148 """Remove a handler for a signal. UNIX only.
150 Return True if a signal handler was removed, False if not.
151 """
152 self._check_signal(sig)
153 try:
154 del self._signal_handlers[sig]
155 except KeyError:
156 return False
158 if sig == signal.SIGINT:
159 handler = signal.default_int_handler
160 else:
161 handler = signal.SIG_DFL
163 try:
164 signal.signal(sig, handler)
165 except OSError as exc:
166 if exc.errno == errno.EINVAL:
167 raise RuntimeError(f'sig {sig} cannot be caught')
168 else:
169 raise
171 if not self._signal_handlers:
172 try:
173 signal.set_wakeup_fd(-1)
174 except (ValueError, OSError) as exc:
175 logger.info('set_wakeup_fd(-1) failed: %s', exc)
177 return True
179 def _check_signal(self, sig):
180 """Internal helper to validate a signal.
182 Raise ValueError if the signal number is invalid or uncatchable.
183 Raise RuntimeError if there is a problem setting up the handler.
184 """
185 if not isinstance(sig, int):
186 raise TypeError(f'sig must be an int, not {sig!r}')
188 if sig not in signal.valid_signals():
189 raise ValueError(f'invalid signal number {sig}')
191 def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
192 extra=None):
193 return _UnixReadPipeTransport(self, pipe, protocol, waiter, extra)
195 def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
196 extra=None):
197 return _UnixWritePipeTransport(self, pipe, protocol, waiter, extra)
199 async def _make_subprocess_transport(self, protocol, args, shell,
200 stdin, stdout, stderr, bufsize,
201 extra=None, **kwargs):
202 watcher = self._watcher
203 waiter = self.create_future()
204 transp = _UnixSubprocessTransport(self, protocol, args, shell,
205 stdin, stdout, stderr, bufsize,
206 waiter=waiter, extra=extra,
207 **kwargs)
208 watcher.add_child_handler(transp.get_pid(),
209 self._child_watcher_callback, transp)
210 try:
211 await waiter
212 except (SystemExit, KeyboardInterrupt):
213 raise
214 except BaseException:
215 transp.close()
216 await tasks.shield(transp._wait())
217 raise
219 return transp
221 def _child_watcher_callback(self, pid, returncode, transp):
222 transp._process_exited(returncode)
224 async def create_unix_connection(
225 self, protocol_factory, path=None, *,
226 ssl=None, sock=None,
227 server_hostname=None,
228 ssl_handshake_timeout=None,
229 ssl_shutdown_timeout=None):
230 assert server_hostname is None or isinstance(server_hostname, str)
231 if ssl:
232 if server_hostname is None:
233 raise ValueError(
234 'you have to pass server_hostname when using ssl')
235 else:
236 if server_hostname is not None:
237 raise ValueError('server_hostname is only meaningful with ssl')
238 if ssl_handshake_timeout is not None:
239 raise ValueError(
240 'ssl_handshake_timeout is only meaningful with ssl')
241 if ssl_shutdown_timeout is not None: 241 ↛ 242line 241 didn't jump to line 242 because the condition on line 241 was never true
242 raise ValueError(
243 'ssl_shutdown_timeout is only meaningful with ssl')
245 if path is not None:
246 if sock is not None:
247 raise ValueError(
248 'path and sock can not be specified at the same time')
250 path = os.fspath(path)
251 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
252 try:
253 sock.setblocking(False)
254 await self.sock_connect(sock, path)
255 except:
256 sock.close()
257 raise
259 else:
260 if sock is None:
261 raise ValueError('no path and sock were specified')
262 if (sock.family != socket.AF_UNIX or 262 ↛ 266line 262 didn't jump to line 266 because the condition on line 262 was always true
263 sock.type != socket.SOCK_STREAM):
264 raise ValueError(
265 f'A UNIX Domain Stream Socket was expected, got {sock!r}')
266 sock.setblocking(False)
268 transport, protocol = await self._create_connection_transport(
269 sock, protocol_factory, ssl, server_hostname,
270 ssl_handshake_timeout=ssl_handshake_timeout,
271 ssl_shutdown_timeout=ssl_shutdown_timeout)
272 return transport, protocol
274 async def create_unix_server(
275 self, protocol_factory, path=None, *,
276 sock=None, backlog=100, ssl=None,
277 ssl_handshake_timeout=None,
278 ssl_shutdown_timeout=None,
279 start_serving=True, cleanup_socket=True, mode=None):
280 if isinstance(ssl, bool):
281 raise TypeError('ssl argument must be an SSLContext or None')
283 if ssl_handshake_timeout is not None and not ssl:
284 raise ValueError(
285 'ssl_handshake_timeout is only meaningful with ssl')
287 if ssl_shutdown_timeout is not None and not ssl: 287 ↛ 288line 287 didn't jump to line 288 because the condition on line 287 was never true
288 raise ValueError(
289 'ssl_shutdown_timeout is only meaningful with ssl')
291 if path is not None:
292 if sock is not None:
293 raise ValueError(
294 'path and sock can not be specified at the same time')
296 path = os.fspath(path)
297 if mode is not None and path and path[0] in (0, '\x00'):
298 raise ValueError(
299 'mode is not supported for abstract sockets')
300 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
302 # Check for abstract socket. `str` and `bytes` paths are supported.
303 if path[0] not in (0, '\x00'): 303 ↛ 314line 303 didn't jump to line 314 because the condition on line 303 was always true
304 try:
305 if stat.S_ISSOCK(os.stat(path).st_mode):
306 os.remove(path)
307 except FileNotFoundError:
308 pass
309 except OSError as err:
310 # Directory may have permissions only to create socket.
311 logger.error('Unable to check or remove stale UNIX socket '
312 '%r: %r', path, err)
314 try:
315 sock.bind(path)
316 except OSError as exc:
317 sock.close()
318 if exc.errno == errno.EADDRINUSE:
319 # Let's improve the error message by adding
320 # with what exact address it occurs.
321 msg = f'Address {path!r} is already in use'
322 raise OSError(errno.EADDRINUSE, msg) from None
323 else:
324 raise
325 except:
326 sock.close()
327 raise
329 if mode is not None:
330 # The socket cannot accept connections until listen() is
331 # called, which happens later in Server._start_serving(),
332 # so no connection can be accepted while the socket still
333 # has the default permissions.
334 try:
335 os.chmod(path, mode)
336 except:
337 sock.close()
338 raise
339 else:
340 if sock is None:
341 raise ValueError(
342 'path was not specified, and no sock specified')
344 if mode is not None:
345 raise ValueError(
346 'mode is only meaningful with path')
348 if (sock.family != socket.AF_UNIX or
349 sock.type != socket.SOCK_STREAM):
350 raise ValueError(
351 f'A UNIX Domain Stream Socket was expected, got {sock!r}')
353 if cleanup_socket:
354 path = sock.getsockname()
355 # Check for abstract socket. `str` and `bytes` paths are supported.
356 if path[0] not in (0, '\x00'): 356 ↛ 362line 356 didn't jump to line 362 because the condition on line 356 was always true
357 try:
358 self._unix_server_sockets[sock] = os.stat(path).st_ino
359 except FileNotFoundError:
360 pass
362 sock.setblocking(False)
363 server = base_events.Server(self, [sock], protocol_factory,
364 ssl, backlog, ssl_handshake_timeout,
365 ssl_shutdown_timeout)
366 if start_serving:
367 server._start_serving()
368 # Skip one loop iteration so that all 'loop.add_reader'
369 # go through.
370 await tasks.sleep(0)
372 return server
374 async def _sock_sendfile_native(self, sock, file, offset, count):
375 try:
376 os.sendfile
377 except AttributeError:
378 raise exceptions.SendfileNotAvailableError(
379 "os.sendfile() is not available")
380 try:
381 fileno = file.fileno()
382 except (AttributeError, io.UnsupportedOperation):
383 raise exceptions.SendfileNotAvailableError("not a regular file")
384 try:
385 fsize = os.fstat(fileno).st_size
386 except OSError:
387 raise exceptions.SendfileNotAvailableError("not a regular file")
388 blocksize = count if count else fsize
389 if not blocksize:
390 return 0 # empty file
392 fut = self.create_future()
393 self._sock_sendfile_native_impl(fut, None, sock, fileno,
394 offset, count, blocksize, 0)
395 return await fut
397 def _sock_sendfile_native_impl(self, fut, registered_fd, sock, fileno,
398 offset, count, blocksize, total_sent):
399 fd = sock.fileno()
400 if registered_fd is not None:
401 # Remove the callback early. It should be rare that the
402 # selector says the fd is ready but the call still returns
403 # EAGAIN, and I am willing to take a hit in that case in
404 # order to simplify the common case.
405 self.remove_writer(registered_fd)
406 if fut.cancelled():
407 self._sock_sendfile_update_filepos(fileno, offset)
408 return
409 if count:
410 blocksize = count - total_sent
411 if blocksize <= 0:
412 self._sock_sendfile_update_filepos(fileno, offset)
413 fut.set_result(total_sent)
414 return
416 # On 32-bit architectures truncate to 1GiB to avoid OverflowError
417 blocksize = min(blocksize, sys.maxsize//2 + 1)
419 try:
420 sent = os.sendfile(fd, fileno, offset, blocksize)
421 except (BlockingIOError, InterruptedError):
422 if registered_fd is None: 422 ↛ 424line 422 didn't jump to line 424 because the condition on line 422 was always true
423 self._sock_add_cancellation_callback(fut, sock)
424 self.add_writer(fd, self._sock_sendfile_native_impl, fut,
425 fd, sock, fileno,
426 offset, count, blocksize, total_sent)
427 except OSError as exc:
428 if (registered_fd is not None and 428 ↛ 435line 428 didn't jump to line 435 because the condition on line 428 was never true
429 exc.errno == errno.ENOTCONN and
430 type(exc) is not ConnectionError):
431 # If we have an ENOTCONN and this isn't a first call to
432 # sendfile(), i.e. the connection was closed in the middle
433 # of the operation, normalize the error to ConnectionError
434 # to make it consistent across all Posix systems.
435 new_exc = ConnectionError(
436 "socket is not connected", errno.ENOTCONN)
437 new_exc.__cause__ = exc
438 exc = new_exc
439 if total_sent == 0:
440 # We can get here for different reasons, the main
441 # one being 'file' is not a regular mmap(2)-like
442 # file, in which case we'll fall back on using
443 # plain send().
444 err = exceptions.SendfileNotAvailableError(
445 "os.sendfile call failed")
446 self._sock_sendfile_update_filepos(fileno, offset)
447 fut.set_exception(err)
448 else:
449 self._sock_sendfile_update_filepos(fileno, offset)
450 fut.set_exception(exc)
451 except (SystemExit, KeyboardInterrupt):
452 raise
453 except BaseException as exc:
454 self._sock_sendfile_update_filepos(fileno, offset)
455 fut.set_exception(exc)
456 else:
457 if sent == 0:
458 # EOF
459 self._sock_sendfile_update_filepos(fileno, offset)
460 fut.set_result(total_sent)
461 else:
462 offset += sent
463 total_sent += sent
464 if registered_fd is None:
465 self._sock_add_cancellation_callback(fut, sock)
466 self.add_writer(fd, self._sock_sendfile_native_impl, fut,
467 fd, sock, fileno,
468 offset, count, blocksize, total_sent)
470 def _sock_sendfile_update_filepos(self, fileno, offset):
471 # After this helper runs, the source fd's lseek pointer is at offset."
472 os.lseek(fileno, offset, os.SEEK_SET)
474 def _sock_add_cancellation_callback(self, fut, sock):
475 def cb(fut):
476 if fut.cancelled():
477 fd = sock.fileno()
478 if fd != -1: 478 ↛ exitline 478 didn't return from function 'cb' because the condition on line 478 was always true
479 self.remove_writer(fd)
480 fut.add_done_callback(cb)
482 def _stop_serving(self, sock):
483 # Is this a unix socket that needs cleanup?
484 if sock in self._unix_server_sockets:
485 path = sock.getsockname()
486 else:
487 path = None
489 super()._stop_serving(sock)
491 if path is not None:
492 prev_ino = self._unix_server_sockets[sock]
493 del self._unix_server_sockets[sock]
494 try:
495 if os.stat(path).st_ino == prev_ino:
496 os.unlink(path)
497 except FileNotFoundError:
498 pass
499 except OSError as err:
500 logger.error('Unable to clean up listening UNIX socket '
501 '%r: %r', path, err)
504class _UnixReadPipeTransport(transports.ReadTransport):
506 max_size = 256 * 1024 # max bytes we read in one event loop iteration
508 def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
509 super().__init__(extra)
510 self._extra['pipe'] = pipe
511 self._loop = loop
512 self._pipe = pipe
513 self._fileno = pipe.fileno()
514 self._protocol = protocol
515 self._closing = False
516 self._paused = False
518 mode = os.fstat(self._fileno).st_mode
519 if not (stat.S_ISFIFO(mode) or
520 stat.S_ISSOCK(mode) or
521 stat.S_ISCHR(mode)):
522 self._pipe = None
523 self._fileno = None
524 self._protocol = None
525 raise ValueError("Pipe transport is for pipes/sockets only.")
527 os.set_blocking(self._fileno, False)
529 self._loop.call_soon(self._protocol.connection_made, self)
530 # only start reading when connection_made() has been called
531 self._loop.call_soon(self._add_reader,
532 self._fileno, self._read_ready)
533 if waiter is not None:
534 # only wake up the waiter when connection_made() has been called
535 self._loop.call_soon(futures._set_result_unless_cancelled,
536 waiter, None)
538 def _add_reader(self, fd, callback):
539 if not self.is_reading():
540 return
541 self._loop._add_reader(fd, callback)
543 def is_reading(self):
544 return not self._paused and not self._closing
546 def __repr__(self):
547 info = [self.__class__.__name__]
548 if self._pipe is None: 548 ↛ 549line 548 didn't jump to line 549 because the condition on line 548 was never true
549 info.append('closed')
550 elif self._closing: 550 ↛ 551line 550 didn't jump to line 551 because the condition on line 550 was never true
551 info.append('closing')
552 info.append(f'fd={self._fileno}')
553 selector = getattr(self._loop, '_selector', None)
554 if self._pipe is not None and selector is not None:
555 polling = selector_events._test_selector_event(
556 selector, self._fileno, selectors.EVENT_READ)
557 if polling: 557 ↛ 558line 557 didn't jump to line 558 because the condition on line 557 was never true
558 info.append('polling')
559 else:
560 info.append('idle')
561 elif self._pipe is not None: 561 ↛ 564line 561 didn't jump to line 564 because the condition on line 561 was always true
562 info.append('open')
563 else:
564 info.append('closed')
565 return '<{}>'.format(' '.join(info))
567 def _read_ready(self):
568 try:
569 data = os.read(self._fileno, self.max_size)
570 except (BlockingIOError, InterruptedError):
571 pass
572 except OSError as exc:
573 self._fatal_error(exc, 'Fatal read error on pipe transport')
574 else:
575 if data:
576 self._protocol.data_received(data)
577 else:
578 if self._loop.get_debug(): 578 ↛ 579line 578 didn't jump to line 579 because the condition on line 578 was never true
579 logger.info("%r was closed by peer", self)
580 self._closing = True
581 self._loop._remove_reader(self._fileno)
582 self._loop.call_soon(self._protocol.eof_received)
583 self._loop.call_soon(self._call_connection_lost, None)
585 def pause_reading(self):
586 if not self.is_reading():
587 return
588 self._paused = True
589 self._loop._remove_reader(self._fileno)
590 if self._loop.get_debug(): 590 ↛ 591line 590 didn't jump to line 591 because the condition on line 590 was never true
591 logger.debug("%r pauses reading", self)
593 def resume_reading(self):
594 if self._closing or not self._paused:
595 return
596 self._paused = False
597 self._loop._add_reader(self._fileno, self._read_ready)
598 if self._loop.get_debug(): 598 ↛ 599line 598 didn't jump to line 599 because the condition on line 598 was never true
599 logger.debug("%r resumes reading", self)
601 def set_protocol(self, protocol):
602 self._protocol = protocol
604 def get_protocol(self):
605 return self._protocol
607 def is_closing(self):
608 return self._closing
610 def close(self):
611 if not self._closing:
612 self._close(None)
614 def __del__(self, _warn=warnings.warn):
615 if self._pipe is not None: 615 ↛ 616line 615 didn't jump to line 616 because the condition on line 615 was never true
616 _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
617 self._pipe.close()
619 def _fatal_error(self, exc, message='Fatal error on pipe transport'):
620 # should be called by exception handler only
621 if (isinstance(exc, OSError) and exc.errno == errno.EIO): 621 ↛ 622line 621 didn't jump to line 622 because the condition on line 621 was never true
622 if self._loop.get_debug():
623 logger.debug("%r: %s", self, message, exc_info=True)
624 else:
625 self._loop.call_exception_handler({
626 'message': message,
627 'exception': exc,
628 'transport': self,
629 'protocol': self._protocol,
630 })
631 self._close(exc)
633 def _close(self, exc):
634 self._closing = True
635 self._loop._remove_reader(self._fileno)
636 self._loop.call_soon(self._call_connection_lost, exc)
638 def _call_connection_lost(self, exc):
639 try:
640 self._protocol.connection_lost(exc)
641 finally:
642 self._pipe.close()
643 self._pipe = None
644 self._protocol = None
645 self._loop = None
648class _UnixWritePipeTransport(transports._FlowControlMixin,
649 transports.WriteTransport):
651 def __init__(self, loop, pipe, protocol, waiter=None, extra=None):
652 super().__init__(extra, loop)
653 self._extra['pipe'] = pipe
654 self._pipe = pipe
655 self._fileno = pipe.fileno()
656 self._protocol = protocol
657 self._buffer = bytearray()
658 self._conn_lost = 0
659 self._closing = False # Set when close() or write_eof() called.
661 pipe_stat = os.fstat(self._fileno)
662 mode = pipe_stat.st_mode
663 is_char = stat.S_ISCHR(mode)
664 is_fifo = stat.S_ISFIFO(mode)
665 is_socket = stat.S_ISSOCK(mode)
666 if not (is_char or is_fifo or is_socket):
667 self._pipe = None
668 self._fileno = None
669 self._protocol = None
670 raise ValueError("Pipe transport is only for "
671 "pipes, sockets and character devices")
673 os.set_blocking(self._fileno, False)
674 self._loop.call_soon(self._protocol.connection_made, self)
676 # On AIX, the reader trick (to be notified when the read end of the
677 # socket is closed) only works for sockets. On other platforms it
678 # works for pipes and sockets. (Exception: OS X 10.4? Issue #19294.)
679 # On macOS and Solaris, the trick misfires for named FIFOs (but not for
680 # pipes created with os.pipe(), which have st_nlink == 0): the write
681 # end polls as readable whenever unread data sits in the FIFO, and no
682 # event is delivered when the read end is closed, so it can only
683 # ever report a false disconnection (gh-145030). The same XNU
684 # behaviour applies on iOS/tvOS/watchOS (sys.platform is not
685 # "darwin" there).
686 is_named_fifo_without_close_event = (
687 sys.platform in {"darwin", "ios", "tvos", "watchos", "sunos5"}
688 and is_fifo and pipe_stat.st_nlink > 0)
689 if is_socket or (is_fifo
690 and not sys.platform.startswith("aix")
691 and not is_named_fifo_without_close_event):
692 # only start reading when connection_made() has been called
693 self._loop.call_soon(self._loop._add_reader,
694 self._fileno, self._read_ready)
696 if waiter is not None:
697 # only wake up the waiter when connection_made() has been called
698 self._loop.call_soon(futures._set_result_unless_cancelled,
699 waiter, None)
701 def __repr__(self):
702 info = [self.__class__.__name__]
703 if self._pipe is None: 703 ↛ 704line 703 didn't jump to line 704 because the condition on line 703 was never true
704 info.append('closed')
705 elif self._closing: 705 ↛ 706line 705 didn't jump to line 706 because the condition on line 705 was never true
706 info.append('closing')
707 info.append(f'fd={self._fileno}')
708 selector = getattr(self._loop, '_selector', None)
709 if self._pipe is not None and selector is not None: 709 ↛ 710line 709 didn't jump to line 710 because the condition on line 709 was never true
710 polling = selector_events._test_selector_event(
711 selector, self._fileno, selectors.EVENT_WRITE)
712 if polling:
713 info.append('polling')
714 else:
715 info.append('idle')
717 bufsize = self.get_write_buffer_size()
718 info.append(f'bufsize={bufsize}')
719 elif self._pipe is not None: 719 ↛ 722line 719 didn't jump to line 722 because the condition on line 719 was always true
720 info.append('open')
721 else:
722 info.append('closed')
723 return '<{}>'.format(' '.join(info))
725 def get_write_buffer_size(self):
726 return len(self._buffer)
728 def _read_ready(self):
729 # Pipe was closed by peer.
730 if self._loop.get_debug(): 730 ↛ 731line 730 didn't jump to line 731 because the condition on line 730 was never true
731 logger.info("%r was closed by peer", self)
732 if self._buffer:
733 self._close(BrokenPipeError())
734 else:
735 self._close()
737 def write(self, data):
738 assert isinstance(data, (bytes, bytearray, memoryview)), repr(data)
739 if isinstance(data, bytearray): 739 ↛ 740line 739 didn't jump to line 740 because the condition on line 739 was never true
740 data = memoryview(data)
741 if not data:
742 return
744 if self._conn_lost or self._closing:
745 if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
746 logger.warning('pipe closed by peer or '
747 'os.write(pipe, data) raised exception.')
748 self._conn_lost += 1
749 return
751 if not self._buffer:
752 # Attempt to send it right away first.
753 try:
754 n = os.write(self._fileno, data)
755 except (BlockingIOError, InterruptedError):
756 n = 0
757 except (SystemExit, KeyboardInterrupt):
758 raise
759 except BaseException as exc:
760 self._conn_lost += 1
761 self._fatal_error(exc, 'Fatal write error on pipe transport')
762 return
763 if n == len(data):
764 return
765 elif n > 0:
766 data = memoryview(data)[n:]
767 self._loop._add_writer(self._fileno, self._write_ready)
769 self._buffer += data
770 self._maybe_pause_protocol()
772 def _write_ready(self):
773 assert self._buffer, 'Data should not be empty'
775 try:
776 n = os.write(self._fileno, self._buffer)
777 except (BlockingIOError, InterruptedError):
778 pass
779 except (SystemExit, KeyboardInterrupt):
780 raise
781 except BaseException as exc:
782 self._buffer.clear()
783 self._conn_lost += 1
784 # Remove writer here, _fatal_error() doesn't it
785 # because _buffer is empty.
786 self._loop._remove_writer(self._fileno)
787 self._fatal_error(exc, 'Fatal write error on pipe transport')
788 else:
789 if n == len(self._buffer):
790 self._buffer.clear()
791 self._loop._remove_writer(self._fileno)
792 self._maybe_resume_protocol() # May append to buffer.
793 if self._closing:
794 self._loop._remove_reader(self._fileno)
795 self._call_connection_lost(None)
796 return
797 elif n > 0:
798 del self._buffer[:n]
800 def can_write_eof(self):
801 return True
803 def write_eof(self):
804 if self._closing: 804 ↛ 805line 804 didn't jump to line 805 because the condition on line 804 was never true
805 return
806 assert self._pipe
807 self._closing = True
808 if not self._buffer:
809 self._loop._remove_reader(self._fileno)
810 self._loop.call_soon(self._call_connection_lost, None)
812 def set_protocol(self, protocol):
813 self._protocol = protocol
815 def get_protocol(self):
816 return self._protocol
818 def is_closing(self):
819 return self._closing
821 def close(self):
822 if self._pipe is not None and not self._closing:
823 # write_eof is all what we needed to close the write pipe
824 self.write_eof()
826 def __del__(self, _warn=warnings.warn):
827 if self._pipe is not None: 827 ↛ 828line 827 didn't jump to line 828 because the condition on line 827 was never true
828 _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
829 self._pipe.close()
831 def abort(self):
832 self._close(None)
834 def _fatal_error(self, exc, message='Fatal error on pipe transport'):
835 # should be called by exception handler only
836 if isinstance(exc, OSError): 836 ↛ 840line 836 didn't jump to line 840 because the condition on line 836 was always true
837 if self._loop.get_debug(): 837 ↛ 838line 837 didn't jump to line 838 because the condition on line 837 was never true
838 logger.debug("%r: %s", self, message, exc_info=True)
839 else:
840 self._loop.call_exception_handler({
841 'message': message,
842 'exception': exc,
843 'transport': self,
844 'protocol': self._protocol,
845 })
846 self._close(exc)
848 def _close(self, exc=None):
849 self._closing = True
850 if self._buffer:
851 self._loop._remove_writer(self._fileno)
852 self._buffer.clear()
853 self._loop._remove_reader(self._fileno)
854 self._loop.call_soon(self._call_connection_lost, exc)
856 def _call_connection_lost(self, exc):
857 try:
858 self._protocol.connection_lost(exc)
859 finally:
860 self._pipe.close()
861 self._pipe = None
862 self._protocol = None
863 self._loop = None
866class _UnixSubprocessTransport(base_subprocess.BaseSubprocessTransport):
868 def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
869 stdin_w = None
870 if (stdin == subprocess.PIPE 870 ↛ 876line 870 didn't jump to line 876 because the condition on line 870 was never true
871 and (sys.platform.startswith('aix') or sys.platform == 'cygwin')):
872 # Use a socket pair for stdin on AIX, since it does not
873 # support selecting read events on the write end of a
874 # socket (which we use in order to detect closing of the
875 # other end).
876 stdin, stdin_w = socket.socketpair()
877 try:
878 self._proc = subprocess.Popen(
879 args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
880 universal_newlines=False, bufsize=bufsize, **kwargs)
881 if stdin_w is not None: 881 ↛ 882line 881 didn't jump to line 882 because the condition on line 881 was never true
882 stdin.close()
883 self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize)
884 stdin_w = None
885 finally:
886 if stdin_w is not None: 886 ↛ 887line 886 didn't jump to line 887 because the condition on line 886 was never true
887 stdin.close()
888 stdin_w.close()
891class _PidfdChildWatcher:
892 """Child watcher implementation using Linux's pid file descriptors.
894 This child watcher polls process file descriptors (pidfds) to await child
895 process termination. In some respects, PidfdChildWatcher is a "Goldilocks"
896 child watcher implementation. It doesn't require signals or threads, doesn't
897 interfere with any processes launched outside the event loop, and scales
898 linearly with the number of subprocesses launched by the event loop. The
899 main disadvantage is that pidfds are specific to Linux, and only work on
900 recent (5.3+) kernels.
901 """
903 def add_child_handler(self, pid, callback, *args):
904 loop = events.get_running_loop()
905 pidfd = os.pidfd_open(pid)
906 loop._add_reader(pidfd, self._do_wait, pid, pidfd, callback, args)
908 def _do_wait(self, pid, pidfd, callback, args):
909 loop = events.get_running_loop()
910 loop._remove_reader(pidfd)
911 try:
912 _, status = os.waitpid(pid, 0)
913 except ChildProcessError:
914 # The child process is already reaped
915 # (may happen if waitpid() is called elsewhere).
916 returncode = 255
917 logger.warning(
918 "child process pid %d exit status already read: "
919 " will report returncode 255",
920 pid)
921 else:
922 returncode = waitstatus_to_exitcode(status)
923 finally:
924 os.close(pidfd)
925 callback(pid, returncode, *args)
927class _ThreadedChildWatcher:
928 """Threaded child watcher implementation.
930 The watcher uses a thread per process
931 for waiting for the process finish.
933 It doesn't require subscription on POSIX signal
934 but a thread creation is not free.
936 The watcher has O(1) complexity, its performance doesn't depend
937 on amount of spawn processes.
938 """
940 def __init__(self):
941 self._pid_counter = itertools.count(0)
942 self._threads = {}
944 def __del__(self, _warn=warnings.warn):
945 threads = [thread for thread in list(self._threads.values())
946 if thread.is_alive()]
947 if threads: 947 ↛ 948line 947 didn't jump to line 948 because the condition on line 947 was never true
948 _warn(f"{self.__class__} has registered but not finished child processes",
949 ResourceWarning,
950 source=self)
952 def add_child_handler(self, pid, callback, *args):
953 loop = events.get_running_loop()
954 thread = threading.Thread(target=self._do_waitpid,
955 name=f"asyncio-waitpid-{next(self._pid_counter)}",
956 args=(loop, pid, callback, args),
957 daemon=True)
958 self._threads[pid] = thread
959 thread.start()
961 def _do_waitpid(self, loop, expected_pid, callback, args):
962 assert expected_pid > 0
964 if hasattr(os, 'waitid'): 964 ↛ 993line 964 didn't jump to line 993 because the condition on line 964 was always true
965 # Wait for the child process using waitid() on platforms which support it.
966 # WNOWAIT is used to avoid reaping the child process, allowing the event loop to
967 # reap the child process with waitpid() later in event loop thread.
968 # This makes the reaping of the child and notification of the return code
969 # atomic with respect to the event loop thread.
970 try:
971 os.waitid(os.P_PID, expected_pid, os.WEXITED | os.WNOWAIT)
972 except ChildProcessError:
973 # The child process is already reaped
974 pass
975 if loop.is_closed(): 975 ↛ 977line 975 didn't jump to line 977 because the condition on line 975 was never true
976 # loop is already closed, reap the zombie here so that it is not leaked.
977 pid, _ = self._reap(loop, expected_pid)
978 logger.warning("Loop %r that handles pid %r is closed",
979 loop, pid)
980 else:
981 try:
982 loop.call_soon_threadsafe(
983 self._reap_and_notify, loop, expected_pid,
984 callback, args)
985 except RuntimeError:
986 # The event loop was closed concurrently.
987 pid, _ = self._reap(loop, expected_pid)
988 logger.warning("Loop %r that handles pid %r is closed",
989 loop, pid)
990 else:
991 # Fallback for platforms that don't support waitid(): we have to
992 # reap the child here, which is racy with respect to send_signal()
993 pid, returncode = self._reap(loop, expected_pid)
994 if loop.is_closed():
995 logger.warning("Loop %r that handles pid %r is closed",
996 loop, pid)
997 else:
998 loop.call_soon_threadsafe(callback, pid, returncode, *args)
1000 self._threads.pop(expected_pid)
1002 def _reap_and_notify(self, loop, expected_pid, callback, args):
1003 pid, returncode = self._reap(loop, expected_pid)
1004 callback(pid, returncode, *args)
1006 def _reap(self, loop, expected_pid):
1007 try:
1008 pid, status = os.waitpid(expected_pid, 0)
1009 except ChildProcessError:
1010 # The child process is already reaped
1011 # (may happen if waitpid() is called elsewhere).
1012 pid = expected_pid
1013 returncode = 255
1014 logger.warning(
1015 "Unknown child process pid %d, will report returncode 255",
1016 pid)
1017 else:
1018 returncode = waitstatus_to_exitcode(status)
1019 if loop.get_debug(): 1019 ↛ 1020line 1019 didn't jump to line 1020 because the condition on line 1019 was never true
1020 logger.debug('process %s exited with returncode %s',
1021 expected_pid, returncode)
1022 return pid, returncode
1024def can_use_pidfd():
1025 if not hasattr(os, 'pidfd_open'): 1025 ↛ 1026line 1025 didn't jump to line 1026 because the condition on line 1025 was never true
1026 return False
1027 try:
1028 pid = os.getpid()
1029 os.close(os.pidfd_open(pid, 0))
1030 except OSError:
1031 # blocked by security policy like SECCOMP
1032 return False
1033 return True
1036SelectorEventLoop = _UnixSelectorEventLoop
1037EventLoop = SelectorEventLoop