You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
19 KiB
510 lines
19 KiB
"""
|
|
Low-level operating system functions from :mod:`os`.
|
|
|
|
Cooperative I/O
|
|
===============
|
|
|
|
This module provides cooperative versions of :func:`os.read` and
|
|
:func:`os.write`. These functions are *not* monkey-patched; you
|
|
must explicitly call them or monkey patch them yourself.
|
|
|
|
POSIX functions
|
|
---------------
|
|
|
|
On POSIX, non-blocking IO is available.
|
|
|
|
- :func:`nb_read`
|
|
- :func:`nb_write`
|
|
- :func:`make_nonblocking`
|
|
|
|
All Platforms
|
|
-------------
|
|
|
|
On non-POSIX platforms (e.g., Windows), non-blocking IO is not
|
|
available. On those platforms (and on POSIX), cooperative IO can
|
|
be done with the threadpool.
|
|
|
|
- :func:`tp_read`
|
|
- :func:`tp_write`
|
|
|
|
Child Processes
|
|
===============
|
|
|
|
The functions :func:`fork` and (on POSIX) :func:`forkpty` and :func:`waitpid` can be used
|
|
to manage child processes.
|
|
|
|
.. warning::
|
|
|
|
Forking a process that uses greenlets does not eliminate all non-running
|
|
greenlets. Any that were scheduled in the hub of the forking thread in the parent
|
|
remain scheduled in the child; compare this to how normal threads operate. (This behaviour
|
|
may change is a subsequent major release.)
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import os
|
|
import sys
|
|
from gevent.hub import _get_hub_noargs as get_hub
|
|
from gevent.hub import reinit
|
|
from gevent._config import config
|
|
from gevent._compat import PY3
|
|
from gevent._util import copy_globals
|
|
import errno
|
|
|
|
EAGAIN = getattr(errno, 'EAGAIN', 11)
|
|
|
|
try:
|
|
import fcntl
|
|
except ImportError:
|
|
fcntl = None
|
|
|
|
__implements__ = ['fork']
|
|
__extensions__ = ['tp_read', 'tp_write']
|
|
|
|
_read = os.read
|
|
_write = os.write
|
|
|
|
|
|
ignored_errors = [EAGAIN, errno.EINTR]
|
|
|
|
|
|
if fcntl:
|
|
|
|
__extensions__ += ['make_nonblocking', 'nb_read', 'nb_write']
|
|
|
|
def make_nonblocking(fd):
|
|
"""Put the file descriptor *fd* into non-blocking mode if
|
|
possible.
|
|
|
|
:return: A boolean value that evaluates to True if successful.
|
|
"""
|
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
|
|
if not bool(flags & os.O_NONBLOCK):
|
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
return True
|
|
|
|
def nb_read(fd, n):
|
|
"""
|
|
Read up to *n* bytes from file descriptor *fd*. Return a
|
|
byte string containing the bytes read, which may be shorter than
|
|
*n*. If end-of-file is reached, an empty string is returned.
|
|
|
|
The descriptor must be in non-blocking mode.
|
|
"""
|
|
hub = None
|
|
event = None
|
|
try:
|
|
while 1:
|
|
try:
|
|
result = _read(fd, n)
|
|
return result
|
|
except OSError as e:
|
|
if e.errno not in ignored_errors:
|
|
raise
|
|
if not PY3:
|
|
sys.exc_clear()
|
|
if hub is None:
|
|
hub = get_hub()
|
|
event = hub.loop.io(fd, 1)
|
|
hub.wait(event)
|
|
finally:
|
|
if event is not None:
|
|
event.close()
|
|
event = None
|
|
hub = None
|
|
|
|
|
|
def nb_write(fd, buf):
|
|
"""
|
|
Write some number of bytes from buffer *buf* to file
|
|
descriptor *fd*. Return the number of bytes written, which may
|
|
be less than the length of *buf*.
|
|
|
|
The file descriptor must be in non-blocking mode.
|
|
"""
|
|
hub = None
|
|
event = None
|
|
try:
|
|
while 1:
|
|
try:
|
|
result = _write(fd, buf)
|
|
return result
|
|
except OSError as e:
|
|
if e.errno not in ignored_errors:
|
|
raise
|
|
if not PY3:
|
|
sys.exc_clear()
|
|
if hub is None:
|
|
hub = get_hub()
|
|
event = hub.loop.io(fd, 2)
|
|
hub.wait(event)
|
|
finally:
|
|
if event is not None:
|
|
event.close()
|
|
event = None
|
|
hub = None
|
|
|
|
|
|
def tp_read(fd, n):
|
|
"""Read up to *n* bytes from file descriptor *fd*. Return a string
|
|
containing the bytes read. If end-of-file is reached, an empty string
|
|
is returned.
|
|
|
|
Reading is done using the threadpool.
|
|
"""
|
|
return get_hub().threadpool.apply(_read, (fd, n))
|
|
|
|
|
|
def tp_write(fd, buf):
|
|
"""Write bytes from buffer *buf* to file descriptor *fd*. Return the
|
|
number of bytes written.
|
|
|
|
Writing is done using the threadpool.
|
|
"""
|
|
return get_hub().threadpool.apply(_write, (fd, buf))
|
|
|
|
|
|
if hasattr(os, 'fork'):
|
|
# pylint:disable=function-redefined,redefined-outer-name
|
|
|
|
_raw_fork = os.fork
|
|
|
|
def fork_gevent():
|
|
"""
|
|
Forks the process using :func:`os.fork` and prepares the
|
|
child process to continue using gevent before returning.
|
|
|
|
.. note::
|
|
|
|
The PID returned by this function may not be waitable with
|
|
either the original :func:`os.waitpid` or this module's
|
|
:func:`waitpid` and it may not generate SIGCHLD signals if
|
|
libev child watchers are or ever have been in use. For
|
|
example, the :mod:`gevent.subprocess` module uses libev
|
|
child watchers (which parts of gevent use libev child
|
|
watchers is subject to change at any time). Most
|
|
applications should use :func:`fork_and_watch`, which is
|
|
monkey-patched as the default replacement for
|
|
:func:`os.fork` and implements the ``fork`` function of
|
|
this module by default, unless the environment variable
|
|
``GEVENT_NOWAITPID`` is defined before this module is
|
|
imported.
|
|
|
|
.. versionadded:: 1.1b2
|
|
"""
|
|
result = _raw_fork()
|
|
if not result:
|
|
reinit()
|
|
return result
|
|
|
|
def fork():
|
|
"""
|
|
A wrapper for :func:`fork_gevent` for non-POSIX platforms.
|
|
"""
|
|
return fork_gevent()
|
|
|
|
if hasattr(os, 'forkpty'):
|
|
_raw_forkpty = os.forkpty
|
|
|
|
def forkpty_gevent():
|
|
"""
|
|
Forks the process using :func:`os.forkpty` and prepares the
|
|
child process to continue using gevent before returning.
|
|
|
|
Returns a tuple (pid, master_fd). The `master_fd` is *not* put into
|
|
non-blocking mode.
|
|
|
|
Availability: Some Unix systems.
|
|
|
|
.. seealso:: This function has the same limitations as :func:`fork_gevent`.
|
|
|
|
.. versionadded:: 1.1b5
|
|
"""
|
|
pid, master_fd = _raw_forkpty()
|
|
if not pid:
|
|
reinit()
|
|
return pid, master_fd
|
|
|
|
forkpty = forkpty_gevent
|
|
|
|
__implements__.append('forkpty')
|
|
__extensions__.append("forkpty_gevent")
|
|
|
|
if hasattr(os, 'WNOWAIT') or hasattr(os, 'WNOHANG'):
|
|
# We can only do this on POSIX
|
|
import time
|
|
|
|
_waitpid = os.waitpid
|
|
_WNOHANG = os.WNOHANG
|
|
|
|
# replaced by the signal module.
|
|
_on_child_hook = lambda: None
|
|
|
|
# {pid -> watcher or tuple(pid, rstatus, timestamp)}
|
|
_watched_children = {}
|
|
|
|
def _on_child(watcher, callback):
|
|
# XXX: Could handle tracing here by not stopping
|
|
# until the pid is terminated
|
|
watcher.stop()
|
|
try:
|
|
_watched_children[watcher.pid] = (watcher.pid, watcher.rstatus, time.time())
|
|
if callback:
|
|
callback(watcher)
|
|
# dispatch an "event"; used by gevent.signal.signal
|
|
_on_child_hook()
|
|
# now is as good a time as any to reap children
|
|
_reap_children()
|
|
finally:
|
|
watcher.close()
|
|
|
|
def _reap_children(timeout=60):
|
|
# Remove all the dead children that haven't been waited on
|
|
# for the *timeout* seconds.
|
|
# Some platforms queue delivery of SIGCHLD for all children that die;
|
|
# in that case, a well-behaved application should call waitpid() for each
|
|
# signal.
|
|
# Some platforms (linux) only guarantee one delivery if multiple children
|
|
# die. On that platform, the well-behave application calls waitpid() in a loop
|
|
# until it gets back -1, indicating no more dead children need to be waited for.
|
|
# In either case, waitpid should be called the same number of times as dead children,
|
|
# thus removing all the watchers when a SIGCHLD arrives. The (generous) timeout
|
|
# is to work with applications that neglect to call waitpid and prevent "unlimited"
|
|
# growth.
|
|
# Note that we don't watch for the case of pid wraparound. That is, we fork a new
|
|
# child with the same pid as an existing watcher, but the child is already dead,
|
|
# just not waited on yet.
|
|
now = time.time()
|
|
oldest_allowed = now - timeout
|
|
dead = [pid for pid, val
|
|
in _watched_children.items()
|
|
if isinstance(val, tuple) and val[2] < oldest_allowed]
|
|
for pid in dead:
|
|
del _watched_children[pid]
|
|
|
|
def waitpid(pid, options):
|
|
"""
|
|
Wait for a child process to finish.
|
|
|
|
If the child process was spawned using
|
|
:func:`fork_and_watch`, then this function behaves
|
|
cooperatively. If not, it *may* have race conditions; see
|
|
:func:`fork_gevent` for more information.
|
|
|
|
The arguments are as for the underlying
|
|
:func:`os.waitpid`. Some combinations of *options* may not
|
|
be supported cooperatively (as of 1.1 that includes
|
|
WUNTRACED). Using a *pid* of 0 to request waiting on only processes
|
|
from the current process group is not cooperative.
|
|
|
|
Availability: POSIX.
|
|
|
|
.. versionadded:: 1.1b1
|
|
.. versionchanged:: 1.2a1
|
|
More cases are handled in a cooperative manner.
|
|
"""
|
|
# pylint: disable=too-many-return-statements
|
|
# XXX Does not handle tracing children
|
|
|
|
# So long as libev's loop doesn't run, it's OK to add
|
|
# child watchers. The SIGCHLD handler only feeds events
|
|
# for the next iteration of the loop to handle. (And the
|
|
# signal handler itself is only called from the next loop
|
|
# iteration.)
|
|
|
|
if pid <= 0:
|
|
# magic functions for multiple children.
|
|
if pid == -1:
|
|
# Any child. If we have one that we're watching and that finished,
|
|
# we will use that one. Otherwise, let the OS take care of it.
|
|
for k, v in _watched_children.items():
|
|
if isinstance(v, tuple):
|
|
pid = k
|
|
break
|
|
if pid <= 0:
|
|
# We didn't have one that was ready. If there are
|
|
# no funky options set, and the pid was -1
|
|
# (meaning any process, not 0, which means process
|
|
# group--- libev doesn't know about process
|
|
# groups) then we can use a child watcher of pid 0; otherwise,
|
|
# pass through to the OS.
|
|
if pid == -1 and options == 0:
|
|
hub = get_hub()
|
|
with hub.loop.child(0, False) as watcher:
|
|
hub.wait(watcher)
|
|
return watcher.rpid, watcher.rstatus
|
|
# There were funky options/pid, so we must go to the OS.
|
|
return _waitpid(pid, options)
|
|
|
|
if pid in _watched_children:
|
|
# yes, we're watching it
|
|
|
|
# Note that the remainder of this code must be careful to NOT
|
|
# yield to the event loop except at well known times, or
|
|
# we have a race condition between the _on_child callback and the
|
|
# code here that could lead to a process to hang.
|
|
if options & _WNOHANG or isinstance(_watched_children[pid], tuple):
|
|
# We're either asked not to block, or it already finished, in which
|
|
# case blocking doesn't matter
|
|
result = _watched_children[pid]
|
|
if isinstance(result, tuple):
|
|
# it finished. libev child watchers
|
|
# are one-shot
|
|
del _watched_children[pid]
|
|
return result[:2]
|
|
# it's not finished
|
|
return (0, 0)
|
|
|
|
# Ok, we need to "block". Do so via a watcher so that we're
|
|
# cooperative. We know it's our child, etc, so this should work.
|
|
watcher = _watched_children[pid]
|
|
# We can't start a watcher that's already started,
|
|
# so we can't reuse the existing watcher. Notice that the
|
|
# old watcher must not have fired already, or during this time, but
|
|
# only after we successfully `start()` the watcher. So this must
|
|
# not yield to the event loop.
|
|
with watcher.loop.child(pid, False) as new_watcher:
|
|
get_hub().wait(new_watcher)
|
|
# Ok, so now the new watcher is done. That means
|
|
# the old watcher's callback (_on_child) should
|
|
# have fired, potentially taking this child out of
|
|
# _watched_children (but that could depend on how
|
|
# many callbacks there were to run, so use the
|
|
# watcher object directly; libev sets all the
|
|
# watchers at the same time).
|
|
return watcher.rpid, watcher.rstatus
|
|
|
|
# we're not watching it and it may not even be our child,
|
|
# so we must go to the OS to be sure to get the right semantics (exception)
|
|
# XXX
|
|
# libuv has a race condition because the signal
|
|
# handler is a Python function, so the InterruptedError
|
|
# is raised before the signal handler runs and calls the
|
|
# child watcher
|
|
# we're not watching it
|
|
return _waitpid(pid, options)
|
|
|
|
def fork_and_watch(callback=None, loop=None, ref=False, fork=fork_gevent):
|
|
"""
|
|
Fork a child process and start a child watcher for it in the parent process.
|
|
|
|
This call cooperates with :func:`waitpid` to enable cooperatively waiting
|
|
for children to finish. When monkey-patching, these functions are patched in as
|
|
:func:`os.fork` and :func:`os.waitpid`, respectively.
|
|
|
|
In the child process, this function calls :func:`gevent.hub.reinit` before returning.
|
|
|
|
Availability: POSIX.
|
|
|
|
:keyword callback: If given, a callable that will be called with the child watcher
|
|
when the child finishes.
|
|
:keyword loop: The loop to start the watcher in. Defaults to the
|
|
loop of the current hub.
|
|
:keyword fork: The fork function. Defaults to :func:`the one defined in this
|
|
module <gevent.os.fork_gevent>` (which automatically calls :func:`gevent.hub.reinit`).
|
|
Pass the builtin :func:`os.fork` function if you do not need to
|
|
initialize gevent in the child process.
|
|
|
|
.. versionadded:: 1.1b1
|
|
.. seealso::
|
|
:func:`gevent.monkey.get_original` To access the builtin :func:`os.fork`.
|
|
"""
|
|
pid = fork()
|
|
if pid:
|
|
# parent
|
|
loop = loop or get_hub().loop
|
|
watcher = loop.child(pid, ref=ref)
|
|
_watched_children[pid] = watcher
|
|
watcher.start(_on_child, watcher, callback)
|
|
return pid
|
|
|
|
__extensions__.append('fork_and_watch')
|
|
__extensions__.append('fork_gevent')
|
|
|
|
if 'forkpty' in __implements__:
|
|
def forkpty_and_watch(callback=None, loop=None, ref=False, forkpty=forkpty_gevent):
|
|
"""
|
|
Like :func:`fork_and_watch`, except using :func:`forkpty_gevent`.
|
|
|
|
Availability: Some Unix systems.
|
|
|
|
.. versionadded:: 1.1b5
|
|
"""
|
|
result = []
|
|
|
|
def _fork():
|
|
pid_and_fd = forkpty()
|
|
result.append(pid_and_fd)
|
|
return pid_and_fd[0]
|
|
fork_and_watch(callback, loop, ref, _fork)
|
|
return result[0]
|
|
|
|
__extensions__.append('forkpty_and_watch')
|
|
|
|
# Watch children by default
|
|
if not config.disable_watch_children:
|
|
# Broken out into separate functions instead of simple name aliases
|
|
# for documentation purposes.
|
|
def fork(*args, **kwargs):
|
|
"""
|
|
Forks a child process and starts a child watcher for it in the
|
|
parent process so that ``waitpid`` and SIGCHLD work as expected.
|
|
|
|
This implementation of ``fork`` is a wrapper for :func:`fork_and_watch`
|
|
when the environment variable ``GEVENT_NOWAITPID`` is *not* defined.
|
|
This is the default and should be used by most applications.
|
|
|
|
.. versionchanged:: 1.1b2
|
|
"""
|
|
# take any args to match fork_and_watch
|
|
return fork_and_watch(*args, **kwargs)
|
|
|
|
if 'forkpty' in __implements__:
|
|
def forkpty(*args, **kwargs):
|
|
"""
|
|
Like :func:`fork`, but using :func:`forkpty_gevent`.
|
|
|
|
This implementation of ``forkpty`` is a wrapper for :func:`forkpty_and_watch`
|
|
when the environment variable ``GEVENT_NOWAITPID`` is *not* defined.
|
|
This is the default and should be used by most applications.
|
|
|
|
.. versionadded:: 1.1b5
|
|
"""
|
|
# take any args to match fork_and_watch
|
|
return forkpty_and_watch(*args, **kwargs)
|
|
__implements__.append("waitpid")
|
|
else:
|
|
def fork():
|
|
"""
|
|
Forks a child process, initializes gevent in the child,
|
|
but *does not* prepare the parent to wait for the child or receive SIGCHLD.
|
|
|
|
This implementation of ``fork`` is a wrapper for :func:`fork_gevent`
|
|
when the environment variable ``GEVENT_NOWAITPID`` *is* defined.
|
|
This is not recommended for most applications.
|
|
"""
|
|
return fork_gevent()
|
|
|
|
if 'forkpty' in __implements__:
|
|
def forkpty():
|
|
"""
|
|
Like :func:`fork`, but using :func:`os.forkpty`
|
|
|
|
This implementation of ``forkpty`` is a wrapper for :func:`forkpty_gevent`
|
|
when the environment variable ``GEVENT_NOWAITPID`` *is* defined.
|
|
This is not recommended for most applications.
|
|
|
|
.. versionadded:: 1.1b5
|
|
"""
|
|
return forkpty_gevent()
|
|
__extensions__.append("waitpid")
|
|
|
|
else:
|
|
__implements__.remove('fork')
|
|
|
|
__imports__ = copy_globals(os, globals(),
|
|
names_to_ignore=__implements__ + __extensions__,
|
|
dunder_names_to_keep=())
|
|
|
|
__all__ = list(set(__implements__ + __extensions__))
|
|
|