More about event loops.

This commit is contained in:
Guido van Rossum 2012-12-12 22:47:17 -08:00
parent 8e31cc29eb
commit 53018c7e85
1 changed files with 172 additions and 3 deletions

View File

@ -111,8 +111,8 @@ Until the boring name is chosen, this PEP will use 'tulip' as the
toplevel package name. Classes and functions given without a module
name are assumed to be accessed via the toplevel package.
Getting and Setting the Event Loop
----------------------------------
Event Loop Policy: Getting and Setting the Event Loop
-----------------------------------------------------
To get the current event loop, use ``get_event_loop()``. This returns
an instance of the ``EventLoop`` class defined below or an equivalent
@ -131,14 +131,183 @@ To change the way ``get_event_loop()`` and ``set_event_loop()`` work
policy object. The policy object can be any object that has methods
``get_event_loop()`` and ``set_event_loop(eventloop)`` behaving like
the functions described above. The default event loop policy is an
instance of the class ``EventLoopPolicy``. The current event loop
instance of the class ``DefaultEventLoopPolicy``. The current event loop
policy object can be retrieved by calling ``get_event_loop_policy()``.
An event loop policy may but does not have to enforce that there is
only one event loop in existence. The default event loop policy does
not enforce this, but it does enforce that there is only one event
loop per thread.
Event Loop Interface
--------------------
A conforming event loop object has the following methods:
..
Look for a better way to format method docs. PEP 12 doesn't
seem to have one. PEP 418 uses ^^^, which makes sub-headings.
- ``run()``. Runs the event loop until there is nothing left to do.
This means, in particular:
- No more calls scheduled with ``call_later()`` (except for canceled
calls).
- No more registered file descriptors. It is up to the registering
party to unregister a file descriptor when it is closed.
- ``call_later(when, callback, *args)``. Arrange for
``callback(*args)`` to be called approximately ``when`` seconds in
the future, once, unless canceled. As usual in Python, ``when`` may
be a floating point number to represent smaller intervals. Returns
a ``DelayedCall`` object representing the callback, whose
``cancel()`` method can be used to cancel the callback.
- ``call_soon(callback, *args)``. Equivalent to ``call_later(0,
callback, *args)``.
- ``call_soon_threadsafe(callback, *args)``. Like
``call_soon(callback, *args)``, but when called from another thread
while the event loop is blocked waiting for I/O, unblocks the event
loop. This is the _only_ method that is safe to call from another
thread or from a signal handler. (To schedule a callback for a
later time in a threadsafe manner, you can use
``ev.call_soon_threadsafe(ev.call_later, when, callback, *args)``.)
- ``add_reader(fd, callback, *args)``. Arrange for
``callback(*args)`` to be called whenever file descriptor ``fd`` is
ready for reading. Returns a ``DelayedCall`` 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.
- ``add_writer(fd, callback, *args)``. Like ``add_reader()``,
but registers the callback for writing instead of for reading.
- ``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 ``DelayedCall``
object.)
- ``remove_writer(fd)``. This is to ``add_writer()`` as
``remove_reader()`` is to ``add_reader()``.
- TBD: A method to submit a call to a PEP 3148 executor. Or a method
to wait for a PEP 3148 Future. Or both.
- TBD: Methods that return ``Futures``, in particular to make a
connection and to set up a listener.
Callback Sequencing
-------------------
When two callbacks are scheduled for the same time, they are run
in the order in which they are registered. For example::
ev.call_soon(foo)
ev.call_soon(bar)
guarantees that ``foo()`` is called before ``bar()``.
If ``call_soon()`` is used, this guarantee is even true if the system
clock were to run backwards. This is also the case for
``call_later(0, callback, *args)``. However, if ``call_later()`` is
used with a nonzero ``when`` argument, all bets are off if the system
clock were to runs backwards. (A good event loop implementation
should use ``time.monotonic()`` to avoid problems when the clock runs
backward. See PEP 418.)
Context
-------
All event loops have a notion of context. For the default event loop
implementation, the context is a thread. An event loop implementation
should run all callbacks in the same context. An event loop
implementation should run only one callback at a time, so callbacks
can assume automatic mutual exclusion with other callbacks scheduled
in the same event loop.
The DelayedCall Class
---------------------
TBD. (Only one method, ``cancel()``, and a read-only property,
``canceled``. Perhaps also ``callback`` and ``args`` properties.)
Futures
-------
TBD.
Transports
----------
TBD.
Protocols
---------
TBD.
Coroutines and the Scheduler
----------------------------
TBD.
Callback Style
--------------
Most interfaces taking a callback also take positional arguments. For
instance, to arrange for ``foo("abc", 42)`` to be called soon, you
call ``ev.call_soon(foo, "abc", 42)``. To schedule the call
``foo()``, use ``ev.call_soon(foo)``. This convention greatly reduces
the number of small lambdas required in typical callback programming.
This convention specifically does _not_ support keyword arguments.
Keyword arguments are used to pass optional extra information about
the callback. This allows graceful evolution of the API without
having to worry about whether a keyword might be significant to a
callee somewhere. If you have a callback that _must_ be called with a
keyword argument, you can use a lambda or ``functools.partial``. For
example::
ev.call_soon(functools.partial(foo, "abc", repeat=42))
Choosing an Event Loop Implementation
-------------------------------------
TBD. (This is about the choice to use e.g. select vs. poll vs. epoll,
and how to override the choice. Probably belongs in the event loop
policy.)
Open Issues
===========
- What have I missed that hasn't been marked with TBD yet?
- A better name for ``DelayedCall`` (I really don't like adjectives. :-)
- Do we need an API for stopping the event loop, given that we have
the termination condition? Is the termination condition compatible
with other frameworks?
- Do we need an API to run the event loop for a little while?
- Should we have ``future.add_callback(callback, *args)``, using the
convention from the section "Callback Style" above, or should we
stick with the PEP 3148 specification of
``future.add_done_callback(callback)`` which calls
``callback(future)``? (Glyph suggested using a different method
name since add_done_callback() does not guarantee that the callback
will be called in the right context.)
- Do we need introspection APIs? E.g. asking for the read callback
given a file descriptor.
Acknowledgments
===============