Refactor run*() family, rename Handler->Handle, updated intros.

This commit is contained in:
Guido van Rossum 2013-03-24 13:34:39 -07:00
parent ca459300f3
commit 06c6f63510
1 changed files with 61 additions and 84 deletions

View File

@ -17,8 +17,8 @@ Python 3.3. Consider this the concrete proposal that is missing from
PEP 3153. The proposal includes a pluggable event loop API, transport
and protocol abstractions similar to those in Twisted, and a
higher-level scheduler based on ``yield from`` (PEP 380). A reference
implementation is in the works under the code name Tulip (the Tulip
repo is linked from the References section at the end).
implementation is in the works under the code name Tulip. The Tulip
repo is linked from the References section at the end.
Introduction
@ -26,24 +26,33 @@ Introduction
The event loop is the place where most interoperability occurs. It
should be easy for (Python 3.3 ports of) frameworks like Twisted,
Tornado, or ZeroMQ to either adapt the default event loop
Tornado, or even gevents to either adapt the default event loop
implementation to their needs using a lightweight wrapper or proxy, or
to replace the default event loop implementation with an adaptation of
their own event loop implementation. (Some frameworks, like Twisted,
have multiple event loop implementations. This should not be a
problem since these all have the same interface.)
It should even be possible for two different third-party frameworks to
interoperate, either by sharing the default event loop implementation
(each using its own adapter), or by sharing the event loop
implementation of either framework. In the latter case two levels of
adaptation would occur (from framework A's event loop to the standard
event loop interface, and from there to framework B's event loop).
Which event loop implementation is used should be under control of the
main program (though a default policy for event loop selection is
provided).
In most cases it should be possible for two different third-party
frameworks to interoperate, either by sharing the default event loop
implementation (each using its own adapter), or by sharing the event
loop implementation of either framework. In the latter case two
levels of adaptation would occur (from framework A's event loop to the
standard event loop interface, and from there to framework B's event
loop). Which event loop implementation is used should be under
control of the main program (though a default policy for event loop
selection is provided).
Thus, two separate APIs are defined:
For this interoperability to be effective, the preferred direction of
adaptation in third party frameworks is to keep the default event loop
and adapt it to the framework's API. Ideally all third party
frameworks would give up their own event loop implementation in favor
of the standard implementation. But not all frameworks may be
satisfied with the functionality provided by the standard
implementation.
In order to support both directions of adaptation, two separate APIs
are defined:
- getting and setting the current event loop object
- the interface of a conforming event loop and its minimum guarantees
@ -118,13 +127,6 @@ Details of the interfaces between transports and protocols are given
later.
Non-goals
=========
Interoperability with systems like Stackless Python or
greenlets/gevent is not a goal of this PEP.
Specification
=============
@ -195,35 +197,18 @@ delays are measured in seconds, and may be ints or floats. The
accuracy and precision of the clock are up to the implementation; the
default implementation uses ``time.monotonic()``.
A note about callbacks and Handlers: any function that takes a
A note about callbacks and Handles: any function that takes a
callback and a variable number of arguments for it can also be given a
Handler object instead of the callback. Then no arguments should be
given, and the Handler should represent an immediate callback (as
Handle object instead of the callback. Then no arguments should be
given, and the Handle should represent an immediate callback (as
returned from ``call_soon()``), not a delayed callback (as returned
from ``call_later()``). If the Handler is already cancelled, the
from ``call_later()``). If the Handle is already cancelled, the
call is a no-op.
A conforming event loop object has the following methods:
- ``run()``. Runs the event loop until there is nothing left to do.
This means, in particular:
- No more calls scheduled with ``call_later()``,
``call_repeatedly()``, ``call_soon()``, or
``call_soon_threadsafe()``, except for cancelled calls.
- No more registered file descriptors. It is up to the registering
party to unregister a file descriptor when it is closed.
Note: ``run()`` blocks until the termination condition is met,
or until ``stop()`` is called.
Note: if you schedule a call with ``call_repeatedly()``, ``run()``
will not exit until you cancel it.
TBD: How many variants of this do we really need?
- ``run_forever()``. Runs the event loop until ``stop()`` is called.
- ``run()``. Runs the event loop until ``stop()`` is called. This
cannot be called when the event loop is already running.
- ``run_until_complete(future, timeout=None)``. Runs the event loop
until the Future is done. If a timeout is given, it waits at most
@ -232,28 +217,12 @@ A conforming event loop object has the following methods:
done, or if ``stop()`` is called, ``TimeoutError`` is raised (but
the Future is not cancelled). This cannot be called when the event
loop is already running.
Note: This API is most useful for tests and the like. It should not
be used as a substitute for ``yield from future`` or other ways to
wait for a Future (e.g. registering a done callback).
- ``run_once(timeout=None)``. Run the event loop for a little while.
If a timeout is given, an I/O poll made will block at most that
long; otherwise, an I/O poll is not constrained in time.
Note: Exactlly how much work this does is up to the implementation.
One constraint: if a callback immediately schedules itself using
``call_soon()``, causing an infinite loop, ``run_once()`` should
still return.
- ``stop()``. Stops the event loop as soon as it is convenient. It
is fine to restart the loop with ``run()`` (or one of its variants)
subsequently.
is fine to restart the loop with ``run()`` or ``run_until_complete()``
subsequently; no scheduled callbacks will be lost if this happens.
Note: How soon exactly is up to the implementation. All immediate
callbacks that were already scheduled to run before ``stop()`` is
called must still be run, but callbacks scheduled after it is called
(or scheduled to be run later) will not be run.
Note: How soon the event loop stops is up to the implementation.
- ``close()``. Closes the event loop, releasing any resources it may
hold, such as the file descriptor used by ``epoll()`` or
@ -263,16 +232,23 @@ A conforming event loop object has the following methods:
- ``call_later(delay, callback, *args)``. Arrange for
``callback(*args)`` to be called approximately ``delay`` seconds in
the future, once, unless cancelled. Returns
a ``Handler`` object representing the callback, whose
a ``Handle`` object representing the callback, whose
``cancel()`` method can be used to cancel the callback.
If ``delay`` is <= 0, this acts like ``call_soon()`` instead.
Otherwise, callbacks scheduled for exactly the same time will be
called in an undefined order.
- ``call_repeatedly(interval, callback, **args)``. Like ``call_later()``
but calls the callback repeatedly, every ``interval`` seconds,
until the ``Handler`` returned is cancelled. The first call is in
``interval`` seconds.
- ``call_repeatedly(interval, callback, **args)``. Like
``call_later()`` but calls the callback repeatedly, every (approximately)
``interval`` seconds, until the ``Handle`` returned is cancelled or
the callback raises an exception. The first call is in
approximately ``interval`` seconds. If for whatever reason the
callback happens later than scheduled, subsequent callbacks will be
delayed for (at least) the same amount. The ``interval`` must be > 0.
- ``call_soon(callback, *args)``. Equivalent to ``call_later(0,
callback, *args)``.
- ``call_soon(callback, *args)``. This schedules a callback to be
called as soon as possible. It guarantees that callbacks are called
in the order in which they were scheduled.
- ``call_soon_threadsafe(callback, *args)``. Like
``call_soon(callback, *args)``, but when called from another thread
@ -286,8 +262,8 @@ A conforming event loop object has the following methods:
- ``add_signal_handler(sig, callback, *args). Whenever signal ``sig``
is received, arrange for ``callback(*args)`` to be called. Returns
a ``Handler`` which can be used to cancel the signal callback.
(Cancelling the handler causes ``remove_signal_handler()`` to be
a ``Handle`` which can be used to cancel the signal callback.
(Cancelling the handle causes ``remove_signal_handler()`` to be
called the next time the signal arrives. Explicitly calling
``remove_signal_handler()`` is preferred.)
Specifying another callback for the same signal replaces the
@ -298,13 +274,13 @@ A conforming event loop object has the following methods:
signale (e.g. ``SIGKILL``), ``RuntimeError`` if this particular event
loop instance cannot handle signals (since signals are global per
process, only an event loop associated with the main thread can
handle signals).
handle signals). (TBD: Rename to ``set_signal_handler()``?)
- ``remove_signal_handler(sig)``. Removes the handler for signal
``sig``, if one is set. Raises the same exceptions as
``add_signal_handler()`` (except that it may return ``False``
instead raising ``RuntimeError`` for uncatchable signals). Returns
``True``e\ if a handler was removed successfully, ``False`` if no
``True`` if a handler was removed successfully, ``False`` if no
handler was set.
Some methods in the standard conforming interface return Futures:
@ -417,25 +393,26 @@ i.e. no disk files.
- ``add_reader(fd, callback, *args)``. Arrange for
``callback(*args)`` to be called whenever file descriptor ``fd`` is
ready for reading. Returns a ``Handler`` object which can be
ready for reading. Returns a ``Handle`` object which can be
used to cancel the callback. Note that, unlike ``call_later()``,
the callback may be called many times. Calling ``add_reader()``
again for the same file descriptor implicitly cancels the previous
callback for that file descriptor. Note: cancelling the handler
may be delayed until the handler would be called. If you plan to
callback for that file descriptor. Note: cancelling the handle
may be delayed until the handle would be called. If you plan to
close ``fd``, you should use ``remove_reader(fd)`` instead.
(TBD: Change this to raise an exception if a handler
is already set.)
(TBD: Change this to raise an exception if a handle
is already set.) (TBD: Rename to ``set_reader()``?)
- ``add_writer(fd, callback, *args)``. Like ``add_reader()``,
but registers the callback for writing instead of for reading.
(TBD: Rename to ``set_reader()``?)
- ``remove_reader(fd)``. Cancels the current read callback for file
descriptor ``fd``, if one is set. A no-op if no callback is
currently set for the file descriptor. (The reason for providing
this alternate interface is that it is often more convenient to
remember the file descriptor than to remember the ``Handler``
object.) Returns ``True`` if a handler was removed, ``False``
remember the file descriptor than to remember the ``Handle``
object.) Returns ``True`` if a callback was removed, ``False``
if not.
- ``remove_writer(fd)``. This is to ``add_writer()`` as
@ -522,13 +499,13 @@ traceback. (Examples of this category include ``KeyboardInterrupt``
and ``SystemExit``; it is usually unwise to treat these the same as
most other exceptions.)
The Handler Class
-----------------
The Handle Class
----------------
The various methods for registering callbacks (e.g. ``call_later()``)
all return an object representing the registration that can be used to
cancel the callback. For want of a better name this object is called
a ``Handler``, although the user never needs to instantiate
a ``Handle``, although the user never needs to instantiate
instances of this class. There is one public method:
- ``cancel()``. Attempt to cancel the callback.
@ -597,7 +574,7 @@ public API is as follows, indicating the differences with PEP 3148:
``call_soon()``. Note that the callback (unlike all other callbacks
defined in this PEP, and ignoring the convention from the section
"Callback Style" below) is always called with a single argument, the
Future object, and should not be a Handler object.
Future object, and should not be a Handle object.
- ``set_result(result)``. The Future must not be done (nor cancelled)
already. This makes the Future done and schedules the callbacks.