Merge
This commit is contained in:
26
NEWS
Normal file
26
NEWS
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
0.7.x
|
||||||
|
=====
|
||||||
|
Fix a major memory leak when using the libevent or libev hubs. Timers were not being removed from the hub after they fired. (Thanks Agusto Becciu and the grugq). Also, make it possible to call wrap_socket_with_coroutine_socket without using the threadpool to make dns operations non-blocking (Thanks the grugq).
|
||||||
|
|
||||||
|
It's now possible to use eventlet's SSL client to talk to eventlet's SSL server. (Thanks to Ryan Williams)
|
||||||
|
|
||||||
|
Fixed a major CPU leak when using select hub. When adding a descriptor to the hub, entries were made in all three dictionaries, readers, writers, and exc, even if the callback is None. Thus every fd would be passed into all three lists when calling select regardless of whether there was a callback for that event or not. When reading the next request out of a keepalive socket, the socket would come back as ready for writing, the hub would notice the callback is None and ignore it, and then loop as fast as possible consuming CPU.
|
||||||
|
|
||||||
|
0.6.x
|
||||||
|
=====
|
||||||
|
|
||||||
|
Fixes some long-standing bugs where sometimes failures in accept() or connect() would cause the coroutine that was waiting to be double-resumed, most often resulting in SwitchingToDeadGreenlet exceptions as well as weird tuple-unpacking exceptions in the CoroutinePool main loop.
|
||||||
|
|
||||||
|
0.6.1: Added eventlet.tpool.killall. Blocks until all of the threadpool threads have been told to exit and join()ed. Meant to be used to clean up the threadpool on exit or if calling execv. Used by Spawning.
|
||||||
|
|
||||||
|
0.5.x
|
||||||
|
=====
|
||||||
|
|
||||||
|
"The Pycon 2008 Refactor": The first release which incorporates libevent support. Also comes with significant refactoring and code cleanup, especially to the eventlet.wsgi http server. Docstring coverage is much higher and there is new extensive documentation: http://wiki.secondlife.com/wiki/Eventlet/Documentation
|
||||||
|
|
||||||
|
The point releases of 0.5.x fixed some bugs in the wsgi server, most notably handling of Transfer-Encoding: chunked; previously, it would happily send chunked encoding to clients which asked for HTTP/1.0, which isn't legal.
|
||||||
|
|
||||||
|
0.2
|
||||||
|
=====
|
||||||
|
|
||||||
|
Initial re-release of forked linden branch.
|
6
README
6
README
@@ -13,18 +13,14 @@ https://lists.secondlife.com/cgi-bin/mailman/listinfo/eventletdev
|
|||||||
== requirements ===
|
== requirements ===
|
||||||
|
|
||||||
Eventlet runs on Python version 2.3 or greater, with the following dependenceis:
|
Eventlet runs on Python version 2.3 or greater, with the following dependenceis:
|
||||||
* [http://cheeseshop.python.org/pypi/greenlet
|
* http://cheeseshop.python.org/pypi/greenlet
|
||||||
* (if running python versions < 2.4) collections.py from the 2.4 distribution or later
|
* (if running python versions < 2.4) collections.py from the 2.4 distribution or later
|
||||||
|
|
||||||
== limitations ==
|
== limitations ==
|
||||||
|
|
||||||
* Sorely lacking in documentation
|
|
||||||
* Not enough test coverage -- the goal is 100%, but we are not there yet.
|
* Not enough test coverage -- the goal is 100%, but we are not there yet.
|
||||||
* Eventlet does not currently run on stackless using tasklets, though
|
* Eventlet does not currently run on stackless using tasklets, though
|
||||||
it is a goal to do so in the future.
|
it is a goal to do so in the future.
|
||||||
* The SSL client does not properly connect to the SSL server, though
|
|
||||||
both client and server interoperate with other SSL implementations
|
|
||||||
(e.g. curl and apache).
|
|
||||||
|
|
||||||
== getting started ==
|
== getting started ==
|
||||||
|
|
||||||
|
257
eventlet/api.py
257
eventlet/api.py
@@ -34,53 +34,65 @@ try:
|
|||||||
import greenlet
|
import greenlet
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
import pylibsupport
|
import support.pylib
|
||||||
pylibsupport.emulate()
|
support.pylib.emulate()
|
||||||
greenlet = sys.modules['greenlet']
|
greenlet = sys.modules['greenlet']
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
import stacklesssupport
|
import support.stacklesspypys
|
||||||
stacklesssupport.emulate()
|
support.stacklesspypys.emulate()
|
||||||
greenlet = sys.modules['greenlet']
|
greenlet = sys.modules['greenlet']
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import support.stacklesss
|
||||||
|
support.stacklesss.emulate()
|
||||||
|
greenlet = sys.modules['greenlet']
|
||||||
|
except ImportError, e:
|
||||||
raise ImportError("Unable to find an implementation of greenlet.")
|
raise ImportError("Unable to find an implementation of greenlet.")
|
||||||
|
|
||||||
|
|
||||||
from eventlet import greenlib, tls
|
from eventlet import greenlib, tls
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'use_hub', 'get_hub', 'sleep', 'spawn', 'kill',
|
'call_after', 'exc_after', 'getcurrent', 'get_default_hub', 'get_hub',
|
||||||
'call_after', 'exc_after', 'trampoline', 'tcp_listener', 'tcp_server',
|
'GreenletExit', 'kill', 'sleep', 'spawn', 'spew', 'switch',
|
||||||
|
'ssl_listener', 'tcp_listener', 'tcp_server', 'trampoline',
|
||||||
|
'unspew', 'use_hub', 'with_timeout',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TimeoutError(Exception):
|
class TimeoutError(Exception):
|
||||||
|
"""Exception raised if an asynchronous operation times out"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
_threadlocal = tls.local()
|
_threadlocal = tls.local()
|
||||||
|
|
||||||
def tcp_listener(address):
|
def tcp_listener(address):
|
||||||
"""
|
"""
|
||||||
Listen on the given (ip, port) address with a TCP socket.
|
Listen on the given (ip, port) *address* with a TCP socket.
|
||||||
Returns a socket object which one should call accept() on to
|
Returns a socket object on which one should call ``accept()`` to
|
||||||
accept a connection on the newly bound socket.
|
accept a connection on the newly bound socket.
|
||||||
|
|
||||||
Generally, the returned socket will be passed to tcp_server,
|
Generally, the returned socket will be passed to ``tcp_server()``,
|
||||||
which accepts connections forever and spawns greenlets for
|
which accepts connections forever and spawns greenlets for
|
||||||
each incoming connection.
|
each incoming connection.
|
||||||
"""
|
"""
|
||||||
from eventlet import wrappedfd, util
|
from eventlet import greenio, util
|
||||||
socket = wrappedfd.wrapped_fd(util.tcp_socket())
|
socket = greenio.GreenSocket(util.tcp_socket())
|
||||||
util.socket_bind_and_listen(socket, address)
|
util.socket_bind_and_listen(socket, address)
|
||||||
return socket
|
return socket
|
||||||
|
|
||||||
def ssl_listener(address, certificate, private_key):
|
def ssl_listener(address, certificate, private_key):
|
||||||
"""Listen on the given (ip, port) address with a TCP socket that
|
"""Listen on the given (ip, port) *address* with a TCP socket that
|
||||||
can do SSL.
|
can do SSL.
|
||||||
|
|
||||||
Returns a socket object which one should call accept() on to
|
*certificate* and *private_key* should be the filenames of the appropriate
|
||||||
|
certificate and private key files to use with the SSL socket.
|
||||||
|
|
||||||
|
Returns a socket object on which one should call ``accept()`` to
|
||||||
accept a connection on the newly bound socket.
|
accept a connection on the newly bound socket.
|
||||||
|
|
||||||
Generally, the returned socket will be passed to tcp_server,
|
Generally, the returned socket will be passed to ``tcp_server()``,
|
||||||
which accepts connections forever and spawns greenlets for
|
which accepts connections forever and spawns greenlets for
|
||||||
each incoming connection.
|
each incoming connection.
|
||||||
"""
|
"""
|
||||||
@@ -90,32 +102,32 @@ def ssl_listener(address, certificate, private_key):
|
|||||||
socket.is_secure = True
|
socket.is_secure = True
|
||||||
return socket
|
return socket
|
||||||
|
|
||||||
def connect_tcp(address):
|
def connect_tcp(address, localaddr=None):
|
||||||
"""
|
"""
|
||||||
Create a TCP connection to address (host, port) and return the socket.
|
Create a TCP connection to address (host, port) and return the socket.
|
||||||
|
Optionally, bind to localaddr (host, port) first.
|
||||||
"""
|
"""
|
||||||
from eventlet import wrappedfd, util
|
from eventlet import greenio, util
|
||||||
desc = wrappedfd.wrapped_fd(util.tcp_socket())
|
desc = greenio.GreenSocket(util.tcp_socket())
|
||||||
|
if localaddr is not None:
|
||||||
|
desc.bind(localaddr)
|
||||||
desc.connect(address)
|
desc.connect(address)
|
||||||
return desc
|
return desc
|
||||||
|
|
||||||
def tcp_server(listensocket, server, *args, **kw):
|
def tcp_server(listensocket, server, *args, **kw):
|
||||||
"""
|
"""
|
||||||
Given a socket, accept connections forever, spawning greenlets
|
Given a socket, accept connections forever, spawning greenlets
|
||||||
and executing "server" for each new incoming connection.
|
and executing *server* for each new incoming connection.
|
||||||
When listensocket is closed, the tcp_server greenlet will end.
|
When *listensocket* is closed, the ``tcp_server()`` greenlet will end.
|
||||||
|
|
||||||
listensocket:
|
listensocket
|
||||||
The socket to accept connections from.
|
The socket from which to accept connections.
|
||||||
|
server
|
||||||
server:
|
|
||||||
The callable to call when a new connection is made.
|
The callable to call when a new connection is made.
|
||||||
|
\*args
|
||||||
*args:
|
The positional arguments to pass to *server*.
|
||||||
The arguments to pass to the call to server.
|
\*\*kw
|
||||||
|
The keyword arguments to pass to *server*.
|
||||||
**kw:
|
|
||||||
The keyword arguments to pass to the call to server.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -128,7 +140,19 @@ def tcp_server(listensocket, server, *args, **kw):
|
|||||||
finally:
|
finally:
|
||||||
listensocket.close()
|
listensocket.close()
|
||||||
|
|
||||||
def trampoline(fd, read=None, write=None, timeout=None):
|
def trampoline(fd, read=False, write=False, timeout=None):
|
||||||
|
"""Suspend the current coroutine until the given socket object or file
|
||||||
|
descriptor is ready to *read*, ready to *write*, or the specified
|
||||||
|
*timeout* elapses, depending on arguments specified.
|
||||||
|
|
||||||
|
To wait for *fd* to be ready to read, pass *read* ``=True``; ready to
|
||||||
|
write, pass *write* ``=True``. To specify a timeout, pass the *timeout*
|
||||||
|
argument in seconds.
|
||||||
|
|
||||||
|
If the specified *timeout* elapses before the socket is ready to read or
|
||||||
|
write, ``TimeoutError`` will be raised instead of ``trampoline()``
|
||||||
|
returning normally.
|
||||||
|
"""
|
||||||
t = None
|
t = None
|
||||||
hub = get_hub()
|
hub = get_hub()
|
||||||
self = greenlet.getcurrent()
|
self = greenlet.getcurrent()
|
||||||
@@ -136,8 +160,8 @@ def trampoline(fd, read=None, write=None, timeout=None):
|
|||||||
def _do_close(fn):
|
def _do_close(fn):
|
||||||
hub.remove_descriptor(fn)
|
hub.remove_descriptor(fn)
|
||||||
greenlib.switch(self, exc=socket.error(32, 'Broken pipe'))
|
greenlib.switch(self, exc=socket.error(32, 'Broken pipe'))
|
||||||
def _do_timeout(fn):
|
def _do_timeout():
|
||||||
hub.remove_descriptor(fn)
|
hub.remove_descriptor(fileno)
|
||||||
greenlib.switch(self, exc=TimeoutError())
|
greenlib.switch(self, exc=TimeoutError())
|
||||||
def cb(_fileno):
|
def cb(_fileno):
|
||||||
if t is not None:
|
if t is not None:
|
||||||
@@ -163,45 +187,144 @@ def _spawn(g):
|
|||||||
greenlib.switch(g)
|
greenlib.switch(g)
|
||||||
|
|
||||||
|
|
||||||
def spawn(cb, *args, **kw):
|
def spawn(function, *args, **kwds):
|
||||||
|
"""Create a new coroutine, or cooperative thread of control, within which
|
||||||
|
to execute *function*.
|
||||||
|
|
||||||
|
The *function* will be called with the given *args* and keyword arguments
|
||||||
|
*kwds* and will remain in control unless it cooperatively yields by
|
||||||
|
calling a socket method or ``sleep()``.
|
||||||
|
|
||||||
|
``spawn()`` returns control to the caller immediately, and *function* will
|
||||||
|
be called in a future main loop iteration.
|
||||||
|
|
||||||
|
An uncaught exception in *function* or any child will terminate the new
|
||||||
|
coroutine with a log message.
|
||||||
|
"""
|
||||||
# killable
|
# killable
|
||||||
t = None
|
t = None
|
||||||
g = greenlib.tracked_greenlet()
|
g = greenlib.tracked_greenlet()
|
||||||
t = get_hub().schedule_call(0, _spawn, g)
|
t = get_hub().schedule_call(0, _spawn, g)
|
||||||
greenlib.switch(g, (_spawn_startup, cb, args, kw, t.cancel))
|
greenlib.switch(g, (_spawn_startup, function, args, kwds, t.cancel))
|
||||||
return g
|
return g
|
||||||
|
|
||||||
|
|
||||||
kill = greenlib.kill
|
kill = greenlib.kill
|
||||||
|
|
||||||
def call_after(seconds, cb, *args, **kw):
|
|
||||||
|
def call_after(seconds, function, *args, **kwds):
|
||||||
|
"""Schedule *function* to be called after *seconds* have elapsed.
|
||||||
|
|
||||||
|
*seconds* may be specified as an integer, or a float if fractional seconds
|
||||||
|
are desired. The *function* will be called with the given *args* and
|
||||||
|
keyword arguments *kwds*, and will be executed within the main loop's
|
||||||
|
coroutine.
|
||||||
|
|
||||||
|
Its return value is discarded. Any uncaught exception will be logged.
|
||||||
|
"""
|
||||||
# cancellable
|
# cancellable
|
||||||
def startup():
|
def startup():
|
||||||
g = greenlib.tracked_greenlet()
|
g = greenlib.tracked_greenlet()
|
||||||
greenlib.switch(g, (_spawn_startup, cb, args, kw))
|
greenlib.switch(g, (_spawn_startup, function, args, kwds))
|
||||||
greenlib.switch(g)
|
greenlib.switch(g)
|
||||||
return get_hub().schedule_call(seconds, startup)
|
return get_hub().schedule_call(seconds, startup)
|
||||||
|
|
||||||
|
|
||||||
def exc_after(seconds, exc):
|
def with_timeout(seconds, func, *args, **kwds):
|
||||||
return call_after(seconds, switch, getcurrent(), None, exc)
|
"""Wrap a call to some (yielding) function with a timeout; if the called
|
||||||
|
function fails to return before the timeout, cancel it and return a flag
|
||||||
|
value.
|
||||||
|
|
||||||
|
seconds
|
||||||
|
(int or float) seconds before timeout occurs
|
||||||
|
func
|
||||||
|
the callable to execute with a timeout; must be one of the functions
|
||||||
|
that implicitly or explicitly yields
|
||||||
|
\*args, \*\*kwds
|
||||||
|
(positional, keyword) arguments to pass to *func*
|
||||||
|
timeout_value=
|
||||||
|
value to return if timeout occurs (default None)
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
|
||||||
|
Value returned by *func* if *func* returns before *seconds*, else *timeout_value*
|
||||||
|
|
||||||
|
**Raises**:
|
||||||
|
|
||||||
|
Any exception raised by *func*, except ``TimeoutError``
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
data = with_timeout(30, httpc.get, 'http://www.google.com/', timeout_value="")
|
||||||
|
|
||||||
|
Here *data* is either the result of the ``get()`` call, or the empty string if
|
||||||
|
it took too long to return. Any exception raised by the ``get()`` call is
|
||||||
|
passed through to the caller.
|
||||||
|
"""
|
||||||
|
# Recognize a specific keyword argument, while also allowing pass-through
|
||||||
|
# of any other keyword arguments accepted by func. Use pop() so we don't
|
||||||
|
# pass timeout_value through to func().
|
||||||
|
timeout_value = kwds.pop("timeout_value", None)
|
||||||
|
timeout = api.exc_after(time, TimeoutError())
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return func(*args, **kwds)
|
||||||
|
except TimeoutError:
|
||||||
|
return timeout_value
|
||||||
|
finally:
|
||||||
|
timeout.cancel()
|
||||||
|
|
||||||
|
def exc_after(seconds, exception_object):
|
||||||
|
"""Schedule *exception_object* to be raised into the current coroutine
|
||||||
|
after *seconds* have elapsed.
|
||||||
|
|
||||||
|
This only works if the current coroutine is yielding, and is generally
|
||||||
|
used to set timeouts after which a network operation or series of
|
||||||
|
operations will be canceled.
|
||||||
|
|
||||||
|
Returns a timer object with a ``cancel()`` method which should be used to
|
||||||
|
prevent the exception if the operation completes successfully.
|
||||||
|
|
||||||
|
See also ``with_timeout()`` that encapsulates the idiom below.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
def read_with_timeout():
|
||||||
|
timer = api.exc_after(30, RuntimeError())
|
||||||
|
try:
|
||||||
|
httpc.get('http://www.google.com/')
|
||||||
|
except RuntimeError:
|
||||||
|
print "Timed out!"
|
||||||
|
else:
|
||||||
|
timer.cancel()
|
||||||
|
"""
|
||||||
|
hub = get_hub()
|
||||||
|
return call_after(seconds, hub.exc_greenlet, getcurrent(), exception_object)
|
||||||
|
|
||||||
|
|
||||||
def get_default_hub():
|
def get_default_hub():
|
||||||
|
"""Select the default hub implementation based on what multiplexing
|
||||||
|
libraries are installed. Tries libevent first, then poll, then select.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
import eventlet.kqueuehub
|
import eventlet.hubs.libevent
|
||||||
|
return eventlet.hubs.libevent
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
|
||||||
return eventlet.kqueuehub
|
|
||||||
import select
|
import select
|
||||||
if hasattr(select, 'poll'):
|
if False: #hasattr(select, 'poll'): pollhub is broken at the moment
|
||||||
import eventlet.pollhub
|
import eventlet.hubs.poll
|
||||||
return eventlet.pollhub
|
return eventlet.hubs.poll
|
||||||
else:
|
else:
|
||||||
import eventlet.selecthub
|
import eventlet.hubs.selects
|
||||||
return eventlet.selecthub
|
return eventlet.hubs.selects
|
||||||
|
|
||||||
|
|
||||||
def use_hub(mod=None):
|
def use_hub(mod=None):
|
||||||
|
"""Use the module *mod*, containing a class called Hub, as the
|
||||||
|
event hub. Usually not required; the default hub is usually fine.
|
||||||
|
"""
|
||||||
if mod is None:
|
if mod is None:
|
||||||
mod = get_default_hub()
|
mod = get_default_hub()
|
||||||
if hasattr(_threadlocal, 'hub'):
|
if hasattr(_threadlocal, 'hub'):
|
||||||
@@ -212,6 +335,8 @@ def use_hub(mod=None):
|
|||||||
_threadlocal.Hub = mod
|
_threadlocal.Hub = mod
|
||||||
|
|
||||||
def get_hub():
|
def get_hub():
|
||||||
|
"""Get the current event hub singleton object.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
hub = _threadlocal.hub
|
hub = _threadlocal.hub
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -223,9 +348,19 @@ def get_hub():
|
|||||||
return hub
|
return hub
|
||||||
|
|
||||||
|
|
||||||
def sleep(timeout=0):
|
def sleep(seconds=0):
|
||||||
|
"""Yield control to another eligible coroutine until at least *seconds* have
|
||||||
|
elapsed.
|
||||||
|
|
||||||
|
*seconds* may be specified as an integer, or a float if fractional seconds
|
||||||
|
are desired. Calling sleep with *seconds* of 0 is the canonical way of
|
||||||
|
expressing a cooperative yield. For example, if one is looping over a
|
||||||
|
large list performing an expensive calculation without calling any socket
|
||||||
|
methods, it's a good idea to call ``sleep(0)`` occasionally; otherwise
|
||||||
|
nothing else will run.
|
||||||
|
"""
|
||||||
hub = get_hub()
|
hub = get_hub()
|
||||||
hub.schedule_call(timeout, greenlib.switch, greenlet.getcurrent())
|
hub.schedule_call(seconds, greenlib.switch, greenlet.getcurrent())
|
||||||
hub.switch()
|
hub.switch()
|
||||||
|
|
||||||
|
|
||||||
@@ -235,8 +370,11 @@ GreenletExit = greenlet.GreenletExit
|
|||||||
|
|
||||||
|
|
||||||
class Spew(object):
|
class Spew(object):
|
||||||
def __init__(self, trace_names=None):
|
"""
|
||||||
|
"""
|
||||||
|
def __init__(self, trace_names=None, show_values=True):
|
||||||
self.trace_names = trace_names
|
self.trace_names = trace_names
|
||||||
|
self.show_values = show_values
|
||||||
|
|
||||||
def __call__(self, frame, event, arg):
|
def __call__(self, frame, event, arg):
|
||||||
if event == 'line':
|
if event == 'line':
|
||||||
@@ -258,6 +396,8 @@ class Spew(object):
|
|||||||
frame.f_code.co_name, frame.f_lasti)
|
frame.f_code.co_name, frame.f_lasti)
|
||||||
if self.trace_names is None or name in self.trace_names:
|
if self.trace_names is None or name in self.trace_names:
|
||||||
print '%s:%s: %s' % (name, lineno, line.rstrip())
|
print '%s:%s: %s' % (name, lineno, line.rstrip())
|
||||||
|
if not self.show_values:
|
||||||
|
return self
|
||||||
details = '\t'
|
details = '\t'
|
||||||
tokens = line.translate(
|
tokens = line.translate(
|
||||||
string.maketrans(' ,.()', '\0' * 5)).split('\0')
|
string.maketrans(' ,.()', '\0' * 5)).split('\0')
|
||||||
@@ -271,19 +411,28 @@ class Spew(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def spew(trace_names=None):
|
def spew(trace_names=None, show_values=False):
|
||||||
sys.settrace(Spew(trace_names))
|
"""Install a trace hook which writes incredibly detailed logs
|
||||||
|
about what code is being executed to stdout.
|
||||||
|
"""
|
||||||
|
sys.settrace(Spew(trace_names, show_values))
|
||||||
|
|
||||||
|
|
||||||
def unspew():
|
def unspew():
|
||||||
|
"""Remove the trace hook installed by spew.
|
||||||
|
"""
|
||||||
sys.settrace(None)
|
sys.settrace(None)
|
||||||
|
|
||||||
|
|
||||||
def named(name):
|
def named(name):
|
||||||
"""Return an object given its name. The name uses a module-like
|
"""Return an object given its name.
|
||||||
syntax, eg:
|
|
||||||
|
The name uses a module-like syntax, eg::
|
||||||
|
|
||||||
os.path.join
|
os.path.join
|
||||||
or
|
|
||||||
|
or::
|
||||||
|
|
||||||
mulib.mu.Resource
|
mulib.mu.Resource
|
||||||
"""
|
"""
|
||||||
toimport = name
|
toimport = name
|
||||||
|
@@ -22,24 +22,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from eventlet import tests
|
import os
|
||||||
from eventlet import api, wrappedfd, util
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from eventlet import api
|
||||||
|
from eventlet import greenio
|
||||||
|
from eventlet import tests
|
||||||
|
from eventlet import util
|
||||||
|
|
||||||
|
|
||||||
def check_hub():
|
def check_hub():
|
||||||
# Clear through the descriptor queue
|
# Clear through the descriptor queue
|
||||||
api.sleep(0)
|
api.sleep(0)
|
||||||
api.sleep(0)
|
api.sleep(0)
|
||||||
assert not api.get_hub().descriptors, repr(api.get_hub().descriptors)
|
hub = api.get_hub()
|
||||||
|
for nm in 'readers', 'writers', 'excs':
|
||||||
|
dct = getattr(hub, nm)
|
||||||
|
assert not dct, "hub.%s not empty: %s" % (nm, dct)
|
||||||
# Stop the runloop
|
# Stop the runloop
|
||||||
api.get_hub().runloop.abort()
|
api.get_hub().abort()
|
||||||
api.sleep(0)
|
api.sleep(0)
|
||||||
assert not api.get_hub().runloop.running
|
assert not api.get_hub().running
|
||||||
|
|
||||||
|
|
||||||
class TestApi(tests.TestCase):
|
class TestApi(tests.TestCase):
|
||||||
mode = 'static'
|
mode = 'static'
|
||||||
|
|
||||||
|
certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt')
|
||||||
|
private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key')
|
||||||
|
|
||||||
def test_tcp_listener(self):
|
def test_tcp_listener(self):
|
||||||
socket = api.tcp_listener(('0.0.0.0', 0))
|
socket = api.tcp_listener(('0.0.0.0', 0))
|
||||||
assert socket.getsockname()[0] == '0.0.0.0'
|
assert socket.getsockname()[0] == '0.0.0.0'
|
||||||
@@ -47,15 +58,14 @@ class TestApi(tests.TestCase):
|
|||||||
|
|
||||||
check_hub()
|
check_hub()
|
||||||
|
|
||||||
def dont_test_connect_tcp(self):
|
def test_connect_tcp(self):
|
||||||
"""This test is broken. Please name it test_connect_tcp and fix
|
|
||||||
the bug (or the test) so it passes.
|
|
||||||
"""
|
|
||||||
def accept_once(listenfd):
|
def accept_once(listenfd):
|
||||||
try:
|
try:
|
||||||
conn, addr = listenfd.accept()
|
conn, addr = listenfd.accept()
|
||||||
conn.write('hello\n')
|
fd = conn.makefile()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
fd.write('hello\n')
|
||||||
|
fd.close()
|
||||||
finally:
|
finally:
|
||||||
listenfd.close()
|
listenfd.close()
|
||||||
|
|
||||||
@@ -63,20 +73,45 @@ class TestApi(tests.TestCase):
|
|||||||
api.spawn(accept_once, server)
|
api.spawn(accept_once, server)
|
||||||
|
|
||||||
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
|
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
|
||||||
assert client.readline() == 'hello\n'
|
fd = client.makefile()
|
||||||
|
|
||||||
assert client.read() == ''
|
|
||||||
client.close()
|
client.close()
|
||||||
|
assert fd.readline() == 'hello\n'
|
||||||
|
|
||||||
|
assert fd.read() == ''
|
||||||
|
fd.close()
|
||||||
|
|
||||||
check_hub()
|
check_hub()
|
||||||
|
|
||||||
|
def test_connect_ssl(self):
|
||||||
|
def accept_once(listenfd):
|
||||||
|
try:
|
||||||
|
conn, addr = listenfd.accept()
|
||||||
|
fl = conn.makefile('w')
|
||||||
|
fl.write('hello\r\n')
|
||||||
|
fl.close()
|
||||||
|
conn.close()
|
||||||
|
finally:
|
||||||
|
listenfd.close()
|
||||||
|
|
||||||
|
server = api.ssl_listener(('0.0.0.0', 0),
|
||||||
|
self.certificate_file,
|
||||||
|
self.private_key_file)
|
||||||
|
api.spawn(accept_once, server)
|
||||||
|
|
||||||
|
client = util.wrap_ssl(
|
||||||
|
api.connect_tcp(('127.0.0.1', server.getsockname()[1])))
|
||||||
|
client = client.makefile()
|
||||||
|
|
||||||
|
assert client.readline() == 'hello\r\n'
|
||||||
|
assert client.read() == ''
|
||||||
|
client.close()
|
||||||
|
|
||||||
def test_server(self):
|
def test_server(self):
|
||||||
|
connected = []
|
||||||
server = api.tcp_listener(('0.0.0.0', 0))
|
server = api.tcp_listener(('0.0.0.0', 0))
|
||||||
bound_port = server.getsockname()[1]
|
bound_port = server.getsockname()[1]
|
||||||
connected = []
|
|
||||||
|
|
||||||
def accept_twice((conn, addr)):
|
def accept_twice((conn, addr)):
|
||||||
print 'connected'
|
|
||||||
connected.append(True)
|
connected.append(True)
|
||||||
conn.close()
|
conn.close()
|
||||||
if len(connected) == 2:
|
if len(connected) == 2:
|
||||||
@@ -90,15 +125,12 @@ class TestApi(tests.TestCase):
|
|||||||
|
|
||||||
check_hub()
|
check_hub()
|
||||||
|
|
||||||
def dont_test_trampoline_timeout(self):
|
def test_001_trampoline_timeout(self):
|
||||||
"""This test is broken. Please change it's name to test_trampoline_timeout,
|
|
||||||
and fix the bug (or fix the test)
|
|
||||||
"""
|
|
||||||
server = api.tcp_listener(('0.0.0.0', 0))
|
server = api.tcp_listener(('0.0.0.0', 0))
|
||||||
bound_port = server.getsockname()[1]
|
bound_port = server.getsockname()[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
desc = wrappedfd.wrapped_fd(util.tcp_socket())
|
desc = greenio.GreenSocket(util.tcp_socket())
|
||||||
api.trampoline(desc, read=True, write=True, timeout=0.1)
|
api.trampoline(desc, read=True, write=True, timeout=0.1)
|
||||||
except api.TimeoutError:
|
except api.TimeoutError:
|
||||||
pass # test passed
|
pass # test passed
|
||||||
@@ -117,7 +149,7 @@ class TestApi(tests.TestCase):
|
|||||||
def go():
|
def go():
|
||||||
client = util.tcp_socket()
|
client = util.tcp_socket()
|
||||||
|
|
||||||
desc = wrappedfd.wrapped_fd(client)
|
desc = greenio.GreenSocket(client)
|
||||||
desc.connect(('127.0.0.1', bound_port))
|
desc.connect(('127.0.0.1', bound_port))
|
||||||
try:
|
try:
|
||||||
api.trampoline(desc, read=True, write=True, timeout=0.1)
|
api.trampoline(desc, read=True, write=True, timeout=0.1)
|
||||||
@@ -133,10 +165,7 @@ class TestApi(tests.TestCase):
|
|||||||
|
|
||||||
check_hub()
|
check_hub()
|
||||||
|
|
||||||
def dont_test_explicit_hub(self):
|
def test_explicit_hub(self):
|
||||||
"""This test is broken. please change it's name to test_explicit_hub
|
|
||||||
and make it pass (or fix the test)
|
|
||||||
"""
|
|
||||||
api.use_hub(Foo)
|
api.use_hub(Foo)
|
||||||
assert isinstance(api.get_hub(), Foo), api.get_hub()
|
assert isinstance(api.get_hub(), Foo), api.get_hub()
|
||||||
|
|
||||||
|
@@ -76,7 +76,9 @@ class SocketConsole(greenlib.GreenletContext):
|
|||||||
def backdoor((conn, addr), locals=None):
|
def backdoor((conn, addr), locals=None):
|
||||||
host, port = addr
|
host, port = addr
|
||||||
print "backdoor to %s:%s" % (host, port)
|
print "backdoor to %s:%s" % (host, port)
|
||||||
ctx = SocketConsole(conn)
|
fl = conn.makefile("rw")
|
||||||
|
fl.newlines = '\n'
|
||||||
|
ctx = SocketConsole(fl)
|
||||||
ctx.register()
|
ctx.register()
|
||||||
try:
|
try:
|
||||||
console = InteractiveConsole(locals)
|
console = InteractiveConsole(locals)
|
||||||
|
40
eventlet/bench.py
Normal file
40
eventlet/bench.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import collections, time, Queue
|
||||||
|
|
||||||
|
qt = 10000
|
||||||
|
|
||||||
|
l1 = collections.deque()
|
||||||
|
l2 = []
|
||||||
|
l3 = Queue.Queue()
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for i in range(1,qt):
|
||||||
|
l1.append(i)
|
||||||
|
|
||||||
|
for i in range(1,qt):
|
||||||
|
l1.popleft()
|
||||||
|
|
||||||
|
mid = time.time()
|
||||||
|
|
||||||
|
for i in range(1,qt):
|
||||||
|
l2.append(i)
|
||||||
|
|
||||||
|
for i in range(1,qt):
|
||||||
|
l2.pop(0)
|
||||||
|
|
||||||
|
mid2 = time.time()
|
||||||
|
|
||||||
|
for i in range(1,qt):
|
||||||
|
l3.put_nowait(i)
|
||||||
|
|
||||||
|
for i in range(1,qt):
|
||||||
|
l3.get_nowait()
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
|
||||||
|
dtime = mid - start
|
||||||
|
ltime = mid2 - mid
|
||||||
|
qtime = end - mid2
|
||||||
|
|
||||||
|
print "deque:", dtime
|
||||||
|
print " list:", ltime
|
||||||
|
print "queue:", qtime
|
30
eventlet/corolocal.py
Normal file
30
eventlet/corolocal.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
|
||||||
|
def get_ident():
|
||||||
|
return id(api.getcurrent())
|
||||||
|
|
||||||
|
|
||||||
|
class local(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__dict__['__objs'] = {}
|
||||||
|
|
||||||
|
def __getattr__(self, attr, g=get_ident):
|
||||||
|
print "getattr", self, attr, g
|
||||||
|
try:
|
||||||
|
return self.__dict__['__objs'][g()][attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
"No variable %s defined for the thread %s"
|
||||||
|
% (attr, g()))
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value, g=get_ident):
|
||||||
|
self.__dict__['__objs'].setdefault(g(), {})[attr] = value
|
||||||
|
|
||||||
|
def __delattr__(self, attr, g=get_ident):
|
||||||
|
try:
|
||||||
|
del self.__dict__['__objs'][g()][attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
"No variable %s defined for thread %s"
|
||||||
|
% (attr, g()))
|
@@ -44,6 +44,11 @@ class Cancelled(RuntimeError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionWrapper(object):
|
||||||
|
def __init__(self, e):
|
||||||
|
self.e = e
|
||||||
|
|
||||||
|
|
||||||
NOT_USED = object()
|
NOT_USED = object()
|
||||||
|
|
||||||
|
|
||||||
@@ -260,8 +265,13 @@ class CoroutinePool(pools.Pool):
|
|||||||
foo 4
|
foo 4
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, min_size=0, max_size=4):
|
def __init__(self, min_size=0, max_size=4, track_events=False):
|
||||||
self._greenlets = set()
|
self._greenlets = set()
|
||||||
|
if track_events:
|
||||||
|
self._tracked_events = []
|
||||||
|
self._next_event = None
|
||||||
|
else:
|
||||||
|
self._tracked_events = None
|
||||||
super(CoroutinePool, self).__init__(min_size, max_size)
|
super(CoroutinePool, self).__init__(min_size, max_size)
|
||||||
|
|
||||||
def _main_loop(self, sender):
|
def _main_loop(self, sender):
|
||||||
@@ -272,7 +282,7 @@ class CoroutinePool(pools.Pool):
|
|||||||
sender = event()
|
sender = event()
|
||||||
(evt, func, args, kw) = recvd
|
(evt, func, args, kw) = recvd
|
||||||
self._safe_apply(evt, func, args, kw)
|
self._safe_apply(evt, func, args, kw)
|
||||||
api.get_hub().runloop.cancel_timers(api.getcurrent())
|
api.get_hub().cancel_timers(api.getcurrent())
|
||||||
self.put(sender)
|
self.put(sender)
|
||||||
finally:
|
finally:
|
||||||
# if we get here, something broke badly, and all we can really
|
# if we get here, something broke badly, and all we can really
|
||||||
@@ -286,6 +296,14 @@ class CoroutinePool(pools.Pool):
|
|||||||
result = func(*args, **kw)
|
result = func(*args, **kw)
|
||||||
if evt is not None:
|
if evt is not None:
|
||||||
evt.send(result)
|
evt.send(result)
|
||||||
|
if self._tracked_events is not None:
|
||||||
|
if self._next_event is None:
|
||||||
|
self._tracked_events.append(result)
|
||||||
|
else:
|
||||||
|
|
||||||
|
ne = self._next_event
|
||||||
|
self._next_event = None
|
||||||
|
ne.send(result)
|
||||||
except api.GreenletExit, e:
|
except api.GreenletExit, e:
|
||||||
# we're printing this out to see if it ever happens
|
# we're printing this out to see if it ever happens
|
||||||
# in practice
|
# in practice
|
||||||
@@ -298,6 +316,13 @@ class CoroutinePool(pools.Pool):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
if evt is not None:
|
if evt is not None:
|
||||||
evt.send(exc=e)
|
evt.send(exc=e)
|
||||||
|
if self._tracked_events is not None:
|
||||||
|
if self._next_event is None:
|
||||||
|
self._tracked_events.append(ExceptionWrapper(e))
|
||||||
|
else:
|
||||||
|
ne = self._next_event
|
||||||
|
self._next_event = None
|
||||||
|
ne.send(exc=e)
|
||||||
|
|
||||||
def _execute(self, evt, func, args, kw):
|
def _execute(self, evt, func, args, kw):
|
||||||
""" Private implementation of the execute methods.
|
""" Private implementation of the execute methods.
|
||||||
@@ -354,6 +379,34 @@ class CoroutinePool(pools.Pool):
|
|||||||
"""
|
"""
|
||||||
self._execute(None, func, args, kw)
|
self._execute(None, func, args, kw)
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Wait for the next execute in the pool to complete,
|
||||||
|
and return the result.
|
||||||
|
|
||||||
|
You must pass track_events=True to the CoroutinePool constructor
|
||||||
|
in order to use this method.
|
||||||
|
"""
|
||||||
|
assert self._tracked_events is not None, (
|
||||||
|
"Must pass track_events=True to the constructor to use CoroutinePool.wait()")
|
||||||
|
if self._next_event is not None:
|
||||||
|
return self._next_event.wait()
|
||||||
|
|
||||||
|
if not self._tracked_events:
|
||||||
|
self._next_event = event()
|
||||||
|
return self._next_event.wait()
|
||||||
|
|
||||||
|
result = self._tracked_events.pop(0)
|
||||||
|
if isinstance(result, ExceptionWrapper):
|
||||||
|
raise result.e
|
||||||
|
|
||||||
|
if not self._tracked_events:
|
||||||
|
self._next_event = event()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def killall(self):
|
||||||
|
for g in self._greenlets:
|
||||||
|
api.kill(g)
|
||||||
|
|
||||||
|
|
||||||
class pipe(object):
|
class pipe(object):
|
||||||
""" Implementation of pipe using events. Not tested! Not used, either."""
|
""" Implementation of pipe using events. Not tested! Not used, either."""
|
||||||
|
@@ -110,6 +110,7 @@ class TestEvent(tests.TestCase):
|
|||||||
api.exc_after(0.001, api.TimeoutError)
|
api.exc_after(0.001, api.TimeoutError)
|
||||||
self.assertRaises(api.TimeoutError, evt.wait)
|
self.assertRaises(api.TimeoutError, evt.wait)
|
||||||
|
|
||||||
|
|
||||||
class TestCoroutinePool(tests.TestCase):
|
class TestCoroutinePool(tests.TestCase):
|
||||||
mode = 'static'
|
mode = 'static'
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -207,6 +208,20 @@ class TestCoroutinePool(tests.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
sys.stderr = normal_err
|
sys.stderr = normal_err
|
||||||
|
|
||||||
|
def test_track_events(self):
|
||||||
|
pool = coros.CoroutinePool(track_events=True)
|
||||||
|
for x in range(6):
|
||||||
|
pool.execute(lambda n: n, x)
|
||||||
|
for y in range(6):
|
||||||
|
pool.wait()
|
||||||
|
|
||||||
|
def test_track_slow_event(self):
|
||||||
|
pool = coros.CoroutinePool(track_events=True)
|
||||||
|
def slow():
|
||||||
|
api.sleep(0.1)
|
||||||
|
return 'ok'
|
||||||
|
pool.execute(slow)
|
||||||
|
self.assertEquals(pool.wait(), 'ok')
|
||||||
|
|
||||||
|
|
||||||
class IncrActor(coros.Actor):
|
class IncrActor(coros.Actor):
|
||||||
@@ -214,6 +229,7 @@ class IncrActor(coros.Actor):
|
|||||||
self.value = getattr(self, 'value', 0) + 1
|
self.value = getattr(self, 'value', 0) + 1
|
||||||
if evt: evt.send()
|
if evt: evt.send()
|
||||||
|
|
||||||
|
|
||||||
class TestActor(tests.TestCase):
|
class TestActor(tests.TestCase):
|
||||||
mode = 'static'
|
mode = 'static'
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -278,7 +294,6 @@ class TestActor(tests.TestCase):
|
|||||||
evt.wait()
|
evt.wait()
|
||||||
self.assertEqual(msgs, [1,2,3,4,5])
|
self.assertEqual(msgs, [1,2,3,4,5])
|
||||||
|
|
||||||
|
|
||||||
def test_raising_received(self):
|
def test_raising_received(self):
|
||||||
msgs = []
|
msgs = []
|
||||||
def received( (message, evt) ):
|
def received( (message, evt) ):
|
||||||
@@ -325,5 +340,6 @@ class TestActor(tests.TestCase):
|
|||||||
self.assertEqual(total[0], 3)
|
self.assertEqual(total[0], 3)
|
||||||
self.assertEqual(self.actor._pool.free(), 2)
|
self.assertEqual(self.actor._pool.free(), 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
tests.main()
|
tests.main()
|
||||||
|
476
eventlet/greenio.py
Normal file
476
eventlet/greenio.py
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
"""\
|
||||||
|
@file greenio.py
|
||||||
|
|
||||||
|
Copyright (c) 2005-2006, Bob Ippolito
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from eventlet.api import exc_after, TimeoutError, trampoline, get_hub
|
||||||
|
from eventlet import util
|
||||||
|
|
||||||
|
|
||||||
|
BUFFER_SIZE = 4096
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
|
||||||
|
from errno import EWOULDBLOCK, EAGAIN
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['GreenSocket', 'GreenFile', 'GreenPipe']
|
||||||
|
|
||||||
|
def higher_order_recv(recv_func):
|
||||||
|
def recv(self, buflen):
|
||||||
|
buf = self.recvbuffer
|
||||||
|
if buf:
|
||||||
|
chunk, self.recvbuffer = buf[:buflen], buf[buflen:]
|
||||||
|
return chunk
|
||||||
|
fd = self.fd
|
||||||
|
bytes = recv_func(fd, buflen)
|
||||||
|
while bytes is None:
|
||||||
|
try:
|
||||||
|
trampoline(fd, read=True)
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EPIPE:
|
||||||
|
bytes = ''
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
bytes = recv_func(fd, buflen)
|
||||||
|
self.recvcount += len(bytes)
|
||||||
|
return bytes
|
||||||
|
return recv
|
||||||
|
|
||||||
|
|
||||||
|
def higher_order_send(send_func):
|
||||||
|
def send(self, data):
|
||||||
|
count = send_func(self.fd, data)
|
||||||
|
if not count:
|
||||||
|
return 0
|
||||||
|
self.sendcount += count
|
||||||
|
return count
|
||||||
|
return send
|
||||||
|
|
||||||
|
|
||||||
|
CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)
|
||||||
|
CONNECT_SUCCESS = (0, errno.EISCONN)
|
||||||
|
def socket_connect(descriptor, address):
|
||||||
|
err = descriptor.connect_ex(address)
|
||||||
|
if err in CONNECT_ERR:
|
||||||
|
return None
|
||||||
|
if err not in CONNECT_SUCCESS:
|
||||||
|
raise socket.error(err, errno.errorcode[err])
|
||||||
|
return descriptor
|
||||||
|
|
||||||
|
|
||||||
|
def socket_accept(descriptor):
|
||||||
|
try:
|
||||||
|
return descriptor.accept()
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EWOULDBLOCK:
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def socket_send(descriptor, data):
|
||||||
|
timeout = descriptor.gettimeout()
|
||||||
|
if timeout:
|
||||||
|
cancel = exc_after(timeout, TimeoutError)
|
||||||
|
else:
|
||||||
|
cancel = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return descriptor.send(data)
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EWOULDBLOCK or e[0] == errno.ENOTCONN:
|
||||||
|
return 0
|
||||||
|
raise
|
||||||
|
except util.SSL.WantWriteError:
|
||||||
|
return 0
|
||||||
|
except util.SSL.WantReadError:
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
if cancel:
|
||||||
|
cancel.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
# winsock sometimes throws ENOTCONN
|
||||||
|
SOCKET_CLOSED = (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN)
|
||||||
|
def socket_recv(descriptor, buflen):
|
||||||
|
timeout = descriptor.gettimeout()
|
||||||
|
if timeout:
|
||||||
|
cancel = exc_after(timeout, TimeoutError)
|
||||||
|
else:
|
||||||
|
cancel = None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return descriptor.recv(buflen)
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EWOULDBLOCK:
|
||||||
|
return None
|
||||||
|
if e[0] in SOCKET_CLOSED:
|
||||||
|
return ''
|
||||||
|
raise
|
||||||
|
except util.SSL.WantReadError:
|
||||||
|
return None
|
||||||
|
except util.SSL.ZeroReturnError:
|
||||||
|
return ''
|
||||||
|
except util.SSL.SysCallError, e:
|
||||||
|
if e[0] == -1 or e[0] > 0:
|
||||||
|
return ''
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if cancel:
|
||||||
|
cancel.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
def file_recv(fd, buflen):
|
||||||
|
try:
|
||||||
|
return fd.read(buflen)
|
||||||
|
except IOError, e:
|
||||||
|
if e[0] == EAGAIN:
|
||||||
|
return None
|
||||||
|
return ''
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EPIPE:
|
||||||
|
return ''
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def file_send(fd, data):
|
||||||
|
try:
|
||||||
|
fd.write(data)
|
||||||
|
fd.flush()
|
||||||
|
return len(data)
|
||||||
|
except IOError, e:
|
||||||
|
if e[0] == EAGAIN:
|
||||||
|
return 0
|
||||||
|
except ValueError, e:
|
||||||
|
written = 0
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] == errno.EPIPE:
|
||||||
|
written = 0
|
||||||
|
|
||||||
|
|
||||||
|
def set_nonblocking(fd):
|
||||||
|
## Socket
|
||||||
|
if hasattr(fd, 'setblocking'):
|
||||||
|
fd.setblocking(0)
|
||||||
|
## File
|
||||||
|
else:
|
||||||
|
fileno = fd.fileno()
|
||||||
|
flags = fcntl.fcntl(fileno, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fileno, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenSocket(object):
|
||||||
|
is_secure = False
|
||||||
|
timeout = None
|
||||||
|
def __init__(self, fd):
|
||||||
|
set_nonblocking(fd)
|
||||||
|
self.fd = fd
|
||||||
|
self._fileno = fd.fileno()
|
||||||
|
self.sendcount = 0
|
||||||
|
self.recvcount = 0
|
||||||
|
self.recvbuffer = ''
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
fd = self.fd
|
||||||
|
while True:
|
||||||
|
res = socket_accept(fd)
|
||||||
|
if res is not None:
|
||||||
|
client, addr = res
|
||||||
|
set_nonblocking(client)
|
||||||
|
return type(self)(client), addr
|
||||||
|
trampoline(fd, read=True)
|
||||||
|
|
||||||
|
def bind(self, *args, **kw):
|
||||||
|
fn = self.bind = self.fd.bind
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def close(self, *args, **kw):
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
self.closed = True
|
||||||
|
if self.is_secure:
|
||||||
|
# *NOTE: This is not quite the correct SSL shutdown sequence.
|
||||||
|
# We should actually be checking the return value of shutdown.
|
||||||
|
# Note also that this is not the same as calling self.shutdown().
|
||||||
|
self.fd.shutdown()
|
||||||
|
|
||||||
|
fn = self.close = self.fd.close
|
||||||
|
try:
|
||||||
|
res = fn(*args, **kw)
|
||||||
|
finally:
|
||||||
|
# This will raise socket.error(32, 'Broken pipe') if there's
|
||||||
|
# a caller waiting on trampoline (e.g. server on .accept())
|
||||||
|
get_hub().exc_descriptor(self._fileno)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def connect(self, address):
|
||||||
|
fd = self.fd
|
||||||
|
connect = socket_connect
|
||||||
|
while not connect(fd, address):
|
||||||
|
trampoline(fd, write=True)
|
||||||
|
|
||||||
|
def connect_ex(self, *args, **kw):
|
||||||
|
fn = self.connect_ex = self.fd.connect_ex
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def dup(self, *args, **kw):
|
||||||
|
sock = self.fd.dup(*args, **kw)
|
||||||
|
set_nonblocking(sock)
|
||||||
|
return type(self)(sock)
|
||||||
|
|
||||||
|
def fileno(self, *args, **kw):
|
||||||
|
fn = self.fileno = self.fd.fileno
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def getpeername(self, *args, **kw):
|
||||||
|
fn = self.getpeername = self.fd.getpeername
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def getsockname(self, *args, **kw):
|
||||||
|
fn = self.getsockname = self.fd.getsockname
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def getsockopt(self, *args, **kw):
|
||||||
|
fn = self.getsockopt = self.fd.getsockopt
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def listen(self, *args, **kw):
|
||||||
|
fn = self.listen = self.fd.listen
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def old_makefile(self, *args, **kw):
|
||||||
|
self._refcount.increment()
|
||||||
|
new_sock = type(self)(self.fd, self._refcount)
|
||||||
|
return GreenFile(new_sock)
|
||||||
|
|
||||||
|
def makefile(self, mode = None, bufsize = None):
|
||||||
|
return GreenFile(self.dup())
|
||||||
|
|
||||||
|
recv = higher_order_recv(socket_recv)
|
||||||
|
|
||||||
|
def recvfrom(self, *args):
|
||||||
|
trampoline(self.fd, read=True)
|
||||||
|
return self.fd.recvfrom(*args)
|
||||||
|
|
||||||
|
# TODO recvfrom_into
|
||||||
|
# TODO recv_into
|
||||||
|
|
||||||
|
send = higher_order_send(socket_send)
|
||||||
|
|
||||||
|
def sendall(self, data):
|
||||||
|
fd = self.fd
|
||||||
|
tail = self.send(data)
|
||||||
|
while tail < len(data):
|
||||||
|
trampoline(self.fd, write=True)
|
||||||
|
tail += self.send(data[tail:])
|
||||||
|
|
||||||
|
def sendto(self, *args):
|
||||||
|
trampoline(self.fd, write=True)
|
||||||
|
return self.fd.sendto(*args)
|
||||||
|
|
||||||
|
def setblocking(self, *args, **kw):
|
||||||
|
fn = self.setblocking = self.fd.setblocking
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def setsockopt(self, *args, **kw):
|
||||||
|
fn = self.setsockopt = self.fd.setsockopt
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def shutdown(self, *args, **kw):
|
||||||
|
if self.is_secure:
|
||||||
|
fn = self.shutdown = self.fd.sock_shutdown
|
||||||
|
else:
|
||||||
|
fn = self.shutdown = self.fd.shutdown
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def settimeout(self, howlong):
|
||||||
|
self.timeout = howlong
|
||||||
|
|
||||||
|
def gettimeout(self):
|
||||||
|
return self.timeout
|
||||||
|
|
||||||
|
|
||||||
|
def read(self, size=None):
|
||||||
|
if size is not None and not isinstance(size, (int, long)):
|
||||||
|
raise TypeError('Expecting an int or long for size, got %s: %s' % (type(size), repr(size)))
|
||||||
|
buf, self.sock.recvbuffer = self.sock.recvbuffer, ''
|
||||||
|
lst = [buf]
|
||||||
|
if size is None:
|
||||||
|
while True:
|
||||||
|
d = self.sock.recv(BUFFER_SIZE)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
lst.append(d)
|
||||||
|
else:
|
||||||
|
buflen = len(buf)
|
||||||
|
while buflen < size:
|
||||||
|
d = self.sock.recv(BUFFER_SIZE)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
buflen += len(d)
|
||||||
|
lst.append(d)
|
||||||
|
else:
|
||||||
|
d = lst[-1]
|
||||||
|
overbite = buflen - size
|
||||||
|
if overbite:
|
||||||
|
lst[-1], self.sock.recvbuffer = d[:-overbite], d[-overbite:]
|
||||||
|
else:
|
||||||
|
lst[-1], self.sock.recvbuffer = d, ''
|
||||||
|
return ''.join(lst)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenFile(object):
|
||||||
|
newlines = '\r\n'
|
||||||
|
mode = 'wb+'
|
||||||
|
|
||||||
|
def __init__(self, fd):
|
||||||
|
if isinstance(fd, GreenSocket):
|
||||||
|
set_nonblocking(fd.fd)
|
||||||
|
else:
|
||||||
|
set_nonblocking(fd)
|
||||||
|
self.sock = fd
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.sock.close()
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
return self.sock.fileno()
|
||||||
|
|
||||||
|
# TODO next
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
return self.sock.sendall(data)
|
||||||
|
|
||||||
|
def readuntil(self, terminator, size=None):
|
||||||
|
buf, self.sock.recvbuffer = self.sock.recvbuffer, ''
|
||||||
|
checked = 0
|
||||||
|
if size is None:
|
||||||
|
while True:
|
||||||
|
found = buf.find(terminator, checked)
|
||||||
|
if found != -1:
|
||||||
|
found += len(terminator)
|
||||||
|
chunk, self.sock.recvbuffer = buf[:found], buf[found:]
|
||||||
|
return chunk
|
||||||
|
checked = max(0, len(buf) - (len(terminator) - 1))
|
||||||
|
d = self.sock.recv(BUFFER_SIZE)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
buf += d
|
||||||
|
return buf
|
||||||
|
while len(buf) < size:
|
||||||
|
found = buf.find(terminator, checked)
|
||||||
|
if found != -1:
|
||||||
|
found += len(terminator)
|
||||||
|
chunk, self.sock.recvbuffer = buf[:found], buf[found:]
|
||||||
|
return chunk
|
||||||
|
checked = len(buf)
|
||||||
|
d = self.sock.recv(BUFFER_SIZE)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
buf += d
|
||||||
|
chunk, self.sock.recvbuffer = buf[:size], buf[size:]
|
||||||
|
return chunk
|
||||||
|
|
||||||
|
def readline(self, size=None):
|
||||||
|
return self.readuntil(self.newlines, size=size)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.xreadlines()
|
||||||
|
|
||||||
|
def readlines(self, size=None):
|
||||||
|
return list(self.xreadlines(size=size))
|
||||||
|
|
||||||
|
def xreadlines(self, size=None):
|
||||||
|
if size is None:
|
||||||
|
while True:
|
||||||
|
line = self.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
yield line
|
||||||
|
else:
|
||||||
|
while size > 0:
|
||||||
|
line = self.readline(size)
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
yield line
|
||||||
|
size -= len(line)
|
||||||
|
|
||||||
|
def writelines(self, lines):
|
||||||
|
for line in lines:
|
||||||
|
self.write(line)
|
||||||
|
|
||||||
|
read = read
|
||||||
|
|
||||||
|
|
||||||
|
class GreenPipeSocket(GreenSocket):
|
||||||
|
""" This is a weird class that looks like a socket but expects a file descriptor as an argument instead of a socket.
|
||||||
|
"""
|
||||||
|
recv = higher_order_recv(file_recv)
|
||||||
|
|
||||||
|
send = higher_order_send(file_send)
|
||||||
|
|
||||||
|
|
||||||
|
class GreenPipe(GreenFile):
|
||||||
|
def __init__(self, fd):
|
||||||
|
set_nonblocking(fd)
|
||||||
|
self.fd = GreenPipeSocket(fd)
|
||||||
|
super(GreenPipe, self).__init__(self.fd)
|
||||||
|
|
||||||
|
def recv(self, *args, **kw):
|
||||||
|
fn = self.recv = self.fd.recv
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def send(self, *args, **kw):
|
||||||
|
fn = self.send = self.fd.send
|
||||||
|
return fn(*args, **kw)
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.fd.fd.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class GreenSSL(GreenSocket):
|
||||||
|
def __init__(self, fd):
|
||||||
|
GreenSocket.__init__(self, fd)
|
||||||
|
self.sock = self
|
||||||
|
|
||||||
|
read = read
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
return self.sendall(data)
|
||||||
|
|
||||||
|
def server(self):
|
||||||
|
return self.fd.server()
|
||||||
|
|
||||||
|
def issuer(self):
|
||||||
|
return self.fd.issuer()
|
||||||
|
|
105
eventlet/greenio_test.py
Normal file
105
eventlet/greenio_test.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"""\
|
||||||
|
@file greenio_test.py
|
||||||
|
|
||||||
|
Copyright (c) 2006-2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from eventlet import tests
|
||||||
|
from eventlet import api, greenio, util
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# TODO try and reuse unit tests from within Python itself
|
||||||
|
|
||||||
|
class TestGreenIo(tests.TestCase):
|
||||||
|
def test_close_with_makefile(self):
|
||||||
|
def accept_close_early(listener):
|
||||||
|
# verify that the makefile and the socket are truly independent
|
||||||
|
# by closing the socket prior to using the made file
|
||||||
|
try:
|
||||||
|
conn, addr = listener.accept()
|
||||||
|
fd = conn.makefile()
|
||||||
|
conn.close()
|
||||||
|
fd.write('hello\n')
|
||||||
|
fd.close()
|
||||||
|
self.assertRaises(socket.error, fd.write, 'a')
|
||||||
|
self.assertRaises(socket.error, conn.send, 'b')
|
||||||
|
finally:
|
||||||
|
listener.close()
|
||||||
|
|
||||||
|
def accept_close_late(listener):
|
||||||
|
# verify that the makefile and the socket are truly independent
|
||||||
|
# by closing the made file and then sending a character
|
||||||
|
try:
|
||||||
|
conn, addr = listener.accept()
|
||||||
|
fd = conn.makefile()
|
||||||
|
fd.write('hello')
|
||||||
|
fd.close()
|
||||||
|
conn.send('\n')
|
||||||
|
conn.close()
|
||||||
|
self.assertRaises(socket.error, fd.write, 'a')
|
||||||
|
self.assertRaises(socket.error, conn.send, 'b')
|
||||||
|
finally:
|
||||||
|
listener.close()
|
||||||
|
|
||||||
|
def did_it_work(server):
|
||||||
|
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
|
||||||
|
fd = client.makefile()
|
||||||
|
client.close()
|
||||||
|
assert fd.readline() == 'hello\n'
|
||||||
|
assert fd.read() == ''
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
server = api.tcp_listener(('0.0.0.0', 0))
|
||||||
|
killer = api.spawn(accept_close_early, server)
|
||||||
|
did_it_work(server)
|
||||||
|
api.kill(killer)
|
||||||
|
|
||||||
|
server = api.tcp_listener(('0.0.0.0', 0))
|
||||||
|
killer = api.spawn(accept_close_late, server)
|
||||||
|
did_it_work(server)
|
||||||
|
api.kill(killer)
|
||||||
|
|
||||||
|
|
||||||
|
def test_del_closes_socket(self):
|
||||||
|
timer = api.exc_after(0.5, api.TimeoutError)
|
||||||
|
def accept_once(listener):
|
||||||
|
# delete/overwrite the original conn
|
||||||
|
# object, only keeping the file object around
|
||||||
|
# closing the file object should close everything
|
||||||
|
try:
|
||||||
|
conn, addr = listener.accept()
|
||||||
|
conn = conn.makefile()
|
||||||
|
conn.write('hello\n')
|
||||||
|
conn.close()
|
||||||
|
self.assertRaises(socket.error, conn.write, 'a')
|
||||||
|
finally:
|
||||||
|
listener.close()
|
||||||
|
server = api.tcp_listener(('0.0.0.0', 0))
|
||||||
|
killer = api.spawn(accept_once, server)
|
||||||
|
client = api.connect_tcp(('127.0.0.1', server.getsockname()[1]))
|
||||||
|
fd = client.makefile()
|
||||||
|
client.close()
|
||||||
|
assert fd.read() == 'hello\n'
|
||||||
|
assert fd.read() == ''
|
||||||
|
|
||||||
|
timer.cancel()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
tests.main()
|
@@ -265,6 +265,7 @@ def greenlet_body(value, exc):
|
|||||||
|
|
||||||
Greenlets using this body must be greenlib.switch()'ed to
|
Greenlets using this body must be greenlib.switch()'ed to
|
||||||
"""
|
"""
|
||||||
|
from eventlet import api
|
||||||
if exc is not None:
|
if exc is not None:
|
||||||
if isinstance(exc, tuple):
|
if isinstance(exc, tuple):
|
||||||
raise exc[0], exc[1], exc[2]
|
raise exc[0], exc[1], exc[2]
|
||||||
@@ -282,12 +283,14 @@ def greenlet_body(value, exc):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
greenlet_id = 1
|
greenlet_id = 1
|
||||||
_threadlocal.next_greenlet_id = itertools.count(2)
|
_threadlocal.next_greenlet_id = itertools.count(2)
|
||||||
greenlets[greenlet.getcurrent()] = {'greenlet_id': greenlet_id}
|
cur = greenlet.getcurrent()
|
||||||
|
greenlets[cur] = {'greenlet_id': greenlet_id}
|
||||||
try:
|
try:
|
||||||
return cb(*args)
|
return cb(*args)
|
||||||
finally:
|
finally:
|
||||||
_greenlet_context_call('finalize')
|
_greenlet_context_call('finalize')
|
||||||
greenlets.pop(greenlet.getcurrent(), None)
|
greenlets.pop(cur, None)
|
||||||
|
api.get_hub().cancel_timers(cur, quiet=True)
|
||||||
|
|
||||||
|
|
||||||
class SwitchingToDeadGreenlet(RuntimeError):
|
class SwitchingToDeadGreenlet(RuntimeError):
|
||||||
|
135
eventlet/gthreadless.py
Normal file
135
eventlet/gthreadless.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import greenlet
|
||||||
|
greenlet.main = greenlet.getcurrent() # WTF did greenlet.main go?
|
||||||
|
from twisted.internet import defer, reactor
|
||||||
|
|
||||||
|
def _desc(g):
|
||||||
|
if isinstance(g, DebugGreenlet):
|
||||||
|
if hasattr(g, 'name'):
|
||||||
|
desc = "<%s %s" % (g.name, hex(id(g)))
|
||||||
|
else:
|
||||||
|
desc = "<NO NAME!? %s" % (hex(id(g)), )
|
||||||
|
else:
|
||||||
|
desc = "<%s" % (hex(id(g)),)
|
||||||
|
if g is greenlet.main:
|
||||||
|
desc += " (main)"
|
||||||
|
desc += ">"
|
||||||
|
return desc
|
||||||
|
|
||||||
|
|
||||||
|
class DebugGreenlet(greenlet.greenlet):
|
||||||
|
__slots__ = ('name',)
|
||||||
|
def __init__(self, func, name=None):
|
||||||
|
super(DebugGreenlet, self).__init__(func)
|
||||||
|
self.name = name
|
||||||
|
def switch(self, *args, **kwargs):
|
||||||
|
current = greenlet.getcurrent()
|
||||||
|
#print "%s -> %s" % (_desc(current), _desc(self))
|
||||||
|
return super(DebugGreenlet, self).switch(*args, **kwargs)
|
||||||
|
|
||||||
|
def deferredGreenlet(func):
|
||||||
|
"""
|
||||||
|
I am a function decorator for functions that call blockOn. The
|
||||||
|
function I return will call the original function inside of a
|
||||||
|
greenlet, and return a Deferred.
|
||||||
|
|
||||||
|
TODO: Do a hack so the name of 'replacement' is the name of 'func'.
|
||||||
|
"""
|
||||||
|
def replacement(*args, **kwargs):
|
||||||
|
d = defer.Deferred()
|
||||||
|
def greenfunc(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
d.callback(func(*args, **kwargs))
|
||||||
|
except:
|
||||||
|
d.errback()
|
||||||
|
g = greenlet.greenlet(greenfunc)
|
||||||
|
crap = g.switch(*args, **kwargs)
|
||||||
|
return d
|
||||||
|
return replacement
|
||||||
|
|
||||||
|
class CalledFromMain(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _IAmAnException(object):
|
||||||
|
def __init__(self, f):
|
||||||
|
self.f = f
|
||||||
|
|
||||||
|
def blockOn(d, desc=None):
|
||||||
|
"""
|
||||||
|
Use me in non-main greenlets to wait for a Deferred to fire.
|
||||||
|
"""
|
||||||
|
g = greenlet.getcurrent()
|
||||||
|
if g is greenlet.main:
|
||||||
|
raise CalledFromMain("You cannot call blockOn from the main greenlet.")
|
||||||
|
|
||||||
|
## Note ##
|
||||||
|
# Notice that this code catches and ignores GreenletExit. The
|
||||||
|
# greenlet mechanism sends a GreenletExit at a blocking greenlet if
|
||||||
|
# there is no chance that the greenlet will be fired by anyone
|
||||||
|
# else -- that is, no other greenlets have a reference to the one
|
||||||
|
# that's blocking.
|
||||||
|
|
||||||
|
# This is often the case with blockOn. When someone blocks on a
|
||||||
|
# Deferred, these callbacks are added to it. When the deferred
|
||||||
|
# fires, we make the blockOn() call finish -- we resume the
|
||||||
|
# blocker. At that point, the Deferred chain is irrelevant; it
|
||||||
|
# makes no sense for any other callbacks to be called. The
|
||||||
|
# Deferred, then, will likely be garbage collected and thus all
|
||||||
|
# references to our greenlet will be lost -- and thus it will have
|
||||||
|
# GreenletExit fired.
|
||||||
|
|
||||||
|
def cb(r):
|
||||||
|
try:
|
||||||
|
# This callback might be fired immediately when added
|
||||||
|
# and switching to the current greenlet seems to do nothing
|
||||||
|
# (ie. we will never actually return to the function we called
|
||||||
|
# blockOn from), so we make the call happen later in the main greenlet
|
||||||
|
# instead, if the current greenlet is the same as the one we are swithcing
|
||||||
|
# to.
|
||||||
|
|
||||||
|
if g == greenlet.getcurrent():
|
||||||
|
reactor.callLater(0, g.switch, r)
|
||||||
|
else:
|
||||||
|
g.switch(r)
|
||||||
|
except greenlet.GreenletExit:
|
||||||
|
pass
|
||||||
|
def eb(f):
|
||||||
|
try:
|
||||||
|
g.switch(_IAmAnException(f))
|
||||||
|
except greenlet.GreenletExit:
|
||||||
|
pass
|
||||||
|
|
||||||
|
d.addCallbacks(cb, eb)
|
||||||
|
|
||||||
|
x = g.parent.switch()
|
||||||
|
if isinstance(x, _IAmAnException):
|
||||||
|
x.f.raiseException()
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class GreenletWrapper(object):
|
||||||
|
"""Wrap an object which presents an asynchronous interface (via Deferreds).
|
||||||
|
|
||||||
|
The wrapped object will present the same interface, but all methods will
|
||||||
|
return results, rather than Deferreds.
|
||||||
|
|
||||||
|
When a Deferred would otherwise be returned, a greenlet is created and then
|
||||||
|
control is switched back to the main greenlet. When the Deferred fires,
|
||||||
|
control is switched back to the created greenlet and execution resumes with
|
||||||
|
the result.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wrappee):
|
||||||
|
self.wrappee = wrappee
|
||||||
|
|
||||||
|
def __getattribute__(self, name):
|
||||||
|
wrappee = super(GreenletWrapper, self).__getattribute__('wrappee')
|
||||||
|
original = getattr(wrappee, name)
|
||||||
|
if callable(original):
|
||||||
|
def wrapper(*a, **kw):
|
||||||
|
result = original(*a, **kw)
|
||||||
|
if isinstance(result, defer.Deferred):
|
||||||
|
return blockOn(result)
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
return original
|
||||||
|
|
@@ -45,9 +45,7 @@ try:
|
|||||||
def from_http_time(t, defaultdate=None):
|
def from_http_time(t, defaultdate=None):
|
||||||
return int(DateTime.Parser.DateTimeFromString(
|
return int(DateTime.Parser.DateTimeFromString(
|
||||||
t, defaultdate=defaultdate).gmticks())
|
t, defaultdate=defaultdate).gmticks())
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
parse_formats = (HTTP_TIME_FORMAT, # RFC 1123
|
parse_formats = (HTTP_TIME_FORMAT, # RFC 1123
|
||||||
'%A, %d-%b-%y %H:%M:%S GMT', # RFC 850
|
'%A, %d-%b-%y %H:%M:%S GMT', # RFC 850
|
||||||
@@ -276,7 +274,7 @@ class UnparseableResponse(ConnectionError):
|
|||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Could not parse the data at the URL %r of content-type %r\nData:\n%r)" % (
|
return "Could not parse the data at the URL %r of content-type %r\nData:\n%s" % (
|
||||||
self.url, self.content_type, self.response)
|
self.url, self.content_type, self.response)
|
||||||
|
|
||||||
__str__ = __repr__
|
__str__ = __repr__
|
||||||
@@ -519,7 +517,7 @@ class HttpSuite(object):
|
|||||||
self.loader = loader
|
self.loader = loader
|
||||||
self.fallback_content_type = fallback_content_type
|
self.fallback_content_type = fallback_content_type
|
||||||
|
|
||||||
def request_(self, params):
|
def request_(self, params, connection=None):
|
||||||
'''Make an http request to a url, for internal use mostly.'''
|
'''Make an http request to a url, for internal use mostly.'''
|
||||||
|
|
||||||
params = _LocalParams(params, instance=self)
|
params = _LocalParams(params, instance=self)
|
||||||
@@ -548,7 +546,7 @@ class HttpSuite(object):
|
|||||||
else:
|
else:
|
||||||
params.body = ''
|
params.body = ''
|
||||||
|
|
||||||
params.response, params.response_body = self._get_response_body(params)
|
params.response, params.response_body = self._get_response_body(params, connection)
|
||||||
response, body = params.response, params.response_body
|
response, body = params.response, params.response_body
|
||||||
|
|
||||||
if self.loader is not None:
|
if self.loader is not None:
|
||||||
@@ -567,7 +565,8 @@ class HttpSuite(object):
|
|||||||
klass = status_to_error_map.get(response.status, ConnectionError)
|
klass = status_to_error_map.get(response.status, ConnectionError)
|
||||||
raise klass(params)
|
raise klass(params)
|
||||||
|
|
||||||
def _get_response_body(self, params):
|
def _get_response_body(self, params, connection):
|
||||||
|
if connection is None:
|
||||||
connection = connect(params.url, params.use_proxy)
|
connection = connect(params.url, params.use_proxy)
|
||||||
connection.request(params.method, params.path, params.body,
|
connection.request(params.method, params.path, params.body,
|
||||||
params.headers)
|
params.headers)
|
||||||
@@ -578,25 +577,33 @@ class HttpSuite(object):
|
|||||||
|
|
||||||
return params.response, params.response_body
|
return params.response, params.response_body
|
||||||
|
|
||||||
def request(self, params):
|
def request(self, params, connection=None):
|
||||||
return self.request_(params)[-1]
|
return self.request_(params, connection=connection)[-1]
|
||||||
|
|
||||||
def head_(self, url, headers=None, use_proxy=False, ok=None, aux=None):
|
def head_(
|
||||||
return self.request_(_Params(url, 'HEAD', headers=headers,
|
self, url, headers=None, use_proxy=False,
|
||||||
|
ok=None, aux=None, connection=None):
|
||||||
|
return self.request_(
|
||||||
|
_Params(
|
||||||
|
url, 'HEAD', headers=headers,
|
||||||
loader=self.loader, dumper=self.dumper,
|
loader=self.loader, dumper=self.dumper,
|
||||||
use_proxy=use_proxy, ok=ok, aux=aux))
|
use_proxy=use_proxy, ok=ok, aux=aux),
|
||||||
|
connection)
|
||||||
|
|
||||||
def head(self, *args, **kwargs):
|
def head(self, *args, **kwargs):
|
||||||
return self.head_(*args, **kwargs)[-1]
|
return self.head_(*args, **kwargs)[-1]
|
||||||
|
|
||||||
def get_(self, url, headers=None, use_proxy=False, ok=None, aux=None, max_retries=8):
|
def get_(
|
||||||
|
self, url, headers=None, use_proxy=False, ok=None,
|
||||||
|
aux=None, max_retries=8, connection=None):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0'
|
headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0'
|
||||||
def req():
|
def req():
|
||||||
return self.request_(_Params(url, 'GET', headers=headers,
|
return self.request_(_Params(url, 'GET', headers=headers,
|
||||||
loader=self.loader, dumper=self.dumper,
|
loader=self.loader, dumper=self.dumper,
|
||||||
use_proxy=use_proxy, ok=ok, aux=aux))
|
use_proxy=use_proxy, ok=ok, aux=aux),
|
||||||
|
connection)
|
||||||
def retry_response(err):
|
def retry_response(err):
|
||||||
def doit():
|
def doit():
|
||||||
return err.retry_()
|
return err.retry_()
|
||||||
@@ -614,7 +621,7 @@ class HttpSuite(object):
|
|||||||
return self.get_(*args, **kwargs)[-1]
|
return self.get_(*args, **kwargs)[-1]
|
||||||
|
|
||||||
def put_(self, url, data, headers=None, content_type=None, ok=None,
|
def put_(self, url, data, headers=None, content_type=None, ok=None,
|
||||||
aux=None):
|
aux=None, connection=None):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
if 'content-type' not in headers:
|
if 'content-type' not in headers:
|
||||||
@@ -623,22 +630,29 @@ class HttpSuite(object):
|
|||||||
else:
|
else:
|
||||||
headers['content-type'] = content_type
|
headers['content-type'] = content_type
|
||||||
headers['accept'] = headers['content-type']+';q=1,*/*;q=0'
|
headers['accept'] = headers['content-type']+';q=1,*/*;q=0'
|
||||||
return self.request_(_Params(url, 'PUT', body=data, headers=headers,
|
return self.request_(
|
||||||
|
_Params(
|
||||||
|
url, 'PUT', body=data, headers=headers,
|
||||||
loader=self.loader, dumper=self.dumper,
|
loader=self.loader, dumper=self.dumper,
|
||||||
ok=ok, aux=aux))
|
ok=ok, aux=aux),
|
||||||
|
connection)
|
||||||
|
|
||||||
def put(self, *args, **kwargs):
|
def put(self, *args, **kwargs):
|
||||||
return self.put_(*args, **kwargs)[-1]
|
return self.put_(*args, **kwargs)[-1]
|
||||||
|
|
||||||
def delete_(self, url, ok=None, aux=None):
|
def delete_(self, url, ok=None, aux=None, connection=None):
|
||||||
return self.request_(_Params(url, 'DELETE', loader=self.loader,
|
return self.request_(
|
||||||
dumper=self.dumper, ok=ok, aux=aux))
|
_Params(
|
||||||
|
url, 'DELETE', loader=self.loader,
|
||||||
|
dumper=self.dumper, ok=ok, aux=aux),
|
||||||
|
connection)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
return self.delete_(*args, **kwargs)[-1]
|
return self.delete_(*args, **kwargs)[-1]
|
||||||
|
|
||||||
def post_(self, url, data='', headers=None, content_type=None, ok=None,
|
def post_(
|
||||||
aux=None):
|
self, url, data='', headers=None, content_type=None,ok=None,
|
||||||
|
aux=None, connection=None):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
if 'content-type' not in headers:
|
if 'content-type' not in headers:
|
||||||
@@ -647,9 +661,12 @@ class HttpSuite(object):
|
|||||||
else:
|
else:
|
||||||
headers['content-type'] = content_type
|
headers['content-type'] = content_type
|
||||||
headers['accept'] = headers['content-type']+';q=1,*/*;q=0'
|
headers['accept'] = headers['content-type']+';q=1,*/*;q=0'
|
||||||
return self.request_(_Params(url, 'POST', body=data,
|
return self.request_(
|
||||||
|
_Params(
|
||||||
|
url, 'POST', body=data,
|
||||||
headers=headers, loader=self.loader,
|
headers=headers, loader=self.loader,
|
||||||
dumper=self.dumper, ok=ok, aux=aux))
|
dumper=self.dumper, ok=ok, aux=aux),
|
||||||
|
connection)
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
return self.post_(*args, **kwargs)[-1]
|
return self.post_(*args, **kwargs)[-1]
|
||||||
|
@@ -369,10 +369,15 @@ class Request(object):
|
|||||||
if not hasattr(self, '_cached_parsed_body'):
|
if not hasattr(self, '_cached_parsed_body'):
|
||||||
body = self.read_body()
|
body = self.read_body()
|
||||||
if hasattr(self.site, 'parsers'):
|
if hasattr(self.site, 'parsers'):
|
||||||
parser = self.site.parsers.get(
|
ct = self.get_header('content-type')
|
||||||
self.get_header('content-type'))
|
parser = self.site.parsers.get(ct)
|
||||||
|
|
||||||
if parser is not None:
|
if parser is not None:
|
||||||
body = parser(body)
|
body = parser(body)
|
||||||
|
else:
|
||||||
|
ex = ValueError("Could not find parser for content-type: %s" % ct)
|
||||||
|
ex.body = body
|
||||||
|
raise ex
|
||||||
self._cached_parsed_body = body
|
self._cached_parsed_body = body
|
||||||
return self._cached_parsed_body
|
return self._cached_parsed_body
|
||||||
|
|
||||||
@@ -389,7 +394,7 @@ class Request(object):
|
|||||||
return self.protocol.request_version
|
return self.protocol.request_version
|
||||||
|
|
||||||
def request_protocol(self):
|
def request_protocol(self):
|
||||||
if self.protocol.socket.is_secure:
|
if self.protocol.is_secure:
|
||||||
return "https"
|
return "https"
|
||||||
return "http"
|
return "http"
|
||||||
|
|
||||||
@@ -412,12 +417,18 @@ class Timeout(RuntimeError):
|
|||||||
|
|
||||||
class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
def __init__(self, request, client_address, server):
|
def __init__(self, request, client_address, server):
|
||||||
self.socket = self.request = self.rfile = self.wfile = request
|
self.rfile = self.wfile = request.makefile()
|
||||||
|
self.is_secure = request.is_secure
|
||||||
|
request.close() # close this now so that when rfile and wfile are closed, the socket gets closed
|
||||||
self.client_address = client_address
|
self.client_address = client_address
|
||||||
self.server = server
|
self.server = server
|
||||||
self.set_response_code(None, 200, None)
|
self.set_response_code(None, 200, None)
|
||||||
self.protocol_version = server.max_http_version
|
self.protocol_version = server.max_http_version
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.rfile.close()
|
||||||
|
self.wfile.close()
|
||||||
|
|
||||||
def set_response_code(self, request, code, message):
|
def set_response_code(self, request, code, message):
|
||||||
self._code = code
|
self._code = code
|
||||||
if message is not None:
|
if message is not None:
|
||||||
@@ -501,7 +512,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
body=err.body)
|
body=err.body)
|
||||||
finally:
|
finally:
|
||||||
# clean up any timers that might have been left around by the handling code
|
# clean up any timers that might have been left around by the handling code
|
||||||
api.get_hub().runloop.cancel_timers(api.getcurrent())
|
api.get_hub().cancel_timers(api.getcurrent())
|
||||||
|
|
||||||
# throw an exception if it failed to write a body
|
# throw an exception if it failed to write a body
|
||||||
if not request.response_written():
|
if not request.response_written():
|
||||||
@@ -526,9 +537,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
if not request.response_written():
|
if not request.response_written():
|
||||||
request.response(500)
|
request.response(500)
|
||||||
request.write('Internal Server Error')
|
request.write('Internal Server Error')
|
||||||
self.socket.close()
|
self.close()
|
||||||
raise e # can't do a plain raise since exc_info might have been cleared
|
raise e # can't do a plain raise since exc_info might have been cleared
|
||||||
self.socket.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class Server(BaseHTTPServer.HTTPServer):
|
class Server(BaseHTTPServer.HTTPServer):
|
||||||
@@ -582,3 +593,14 @@ def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAU
|
|||||||
sock.close()
|
sock.close()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
class TestSite(object):
|
||||||
|
def handle_request(self, req):
|
||||||
|
req.write('hello')
|
||||||
|
|
||||||
|
server(
|
||||||
|
api.tcp_listener(('127.0.0.1', 8080)),
|
||||||
|
TestSite())
|
||||||
|
|
||||||
|
@@ -68,10 +68,11 @@ class ConnectionClosed(Exception):
|
|||||||
|
|
||||||
|
|
||||||
def read_http(sock):
|
def read_http(sock):
|
||||||
response_line = sock.readline()
|
fd = sock.makefile()
|
||||||
|
response_line = fd.readline()
|
||||||
if not response_line:
|
if not response_line:
|
||||||
raise ConnectionClosed
|
raise ConnectionClosed
|
||||||
raw_headers = sock.readuntil('\r\n\r\n').strip()
|
raw_headers = fd.readuntil('\r\n\r\n').strip()
|
||||||
#print "R", response_line, raw_headers
|
#print "R", response_line, raw_headers
|
||||||
headers = dict()
|
headers = dict()
|
||||||
for x in raw_headers.split('\r\n'):
|
for x in raw_headers.split('\r\n'):
|
||||||
@@ -81,7 +82,7 @@ def read_http(sock):
|
|||||||
|
|
||||||
if CONTENT_LENGTH in headers:
|
if CONTENT_LENGTH in headers:
|
||||||
num = int(headers[CONTENT_LENGTH])
|
num = int(headers[CONTENT_LENGTH])
|
||||||
body = sock.read(num)
|
body = fd.read(num)
|
||||||
#print body
|
#print body
|
||||||
else:
|
else:
|
||||||
body = None
|
body = None
|
||||||
@@ -104,9 +105,10 @@ class TestHttpd(tests.TestCase):
|
|||||||
sock = api.connect_tcp(
|
sock = api.connect_tcp(
|
||||||
('127.0.0.1', 12346))
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
sock.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
fd = sock.makefile()
|
||||||
result = sock.read()
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
||||||
sock.close()
|
result = fd.read()
|
||||||
|
fd.close()
|
||||||
## The server responds with the maximum version it supports
|
## The server responds with the maximum version it supports
|
||||||
self.assert_(result.startswith('HTTP'), result)
|
self.assert_(result.startswith('HTTP'), result)
|
||||||
self.assert_(result.endswith('hello world'))
|
self.assert_(result.endswith('hello world'))
|
||||||
@@ -115,34 +117,37 @@ class TestHttpd(tests.TestCase):
|
|||||||
sock = api.connect_tcp(
|
sock = api.connect_tcp(
|
||||||
('127.0.0.1', 12346))
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
read_http(sock)
|
read_http(sock)
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
read_http(sock)
|
read_http(sock)
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
def test_003_passing_non_int_to_read(self):
|
def test_003_passing_non_int_to_read(self):
|
||||||
# This should go in test_wrappedfd
|
# This should go in greenio_test
|
||||||
sock = api.connect_tcp(
|
sock = api.connect_tcp(
|
||||||
('127.0.0.1', 12346))
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
cancel = api.exc_after(1, RuntimeError)
|
cancel = api.exc_after(1, RuntimeError)
|
||||||
self.assertRaises(TypeError, sock.read, "This shouldn't work")
|
self.assertRaises(TypeError, fd.read, "This shouldn't work")
|
||||||
cancel.cancel()
|
cancel.cancel()
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
def test_004_close_keepalive(self):
|
def test_004_close_keepalive(self):
|
||||||
sock = api.connect_tcp(
|
sock = api.connect_tcp(
|
||||||
('127.0.0.1', 12346))
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
read_http(sock)
|
read_http(sock)
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
||||||
read_http(sock)
|
read_http(sock)
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
self.assertRaises(ConnectionClosed, read_http, sock)
|
self.assertRaises(ConnectionClosed, read_http, sock)
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
def skip_test_005_run_apachebench(self):
|
def skip_test_005_run_apachebench(self):
|
||||||
url = 'http://localhost:12346/'
|
url = 'http://localhost:12346/'
|
||||||
@@ -159,11 +164,12 @@ class TestHttpd(tests.TestCase):
|
|||||||
path_parts.append('path')
|
path_parts.append('path')
|
||||||
path = '/'.join(path_parts)
|
path = '/'.join(path_parts)
|
||||||
request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
|
request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
|
||||||
sock.write(request)
|
fd = sock.makefile()
|
||||||
result = sock.readline()
|
fd.write(request)
|
||||||
|
result = fd.readline()
|
||||||
status = result.split(' ')[1]
|
status = result.split(' ')[1]
|
||||||
self.assertEqual(status, '414')
|
self.assertEqual(status, '414')
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
def test_007_get_arg(self):
|
def test_007_get_arg(self):
|
||||||
# define a new handler that does a get_arg as well as a read_body
|
# define a new handler that does a get_arg as well as a read_body
|
||||||
@@ -181,26 +187,28 @@ class TestHttpd(tests.TestCase):
|
|||||||
'Content-Length: 3',
|
'Content-Length: 3',
|
||||||
'',
|
'',
|
||||||
'a=a'))
|
'a=a'))
|
||||||
sock.write(request)
|
fd = sock.makefile()
|
||||||
|
fd.write(request)
|
||||||
|
|
||||||
# send some junk after the actual request
|
# send some junk after the actual request
|
||||||
sock.write('01234567890123456789')
|
fd.write('01234567890123456789')
|
||||||
reqline, headers, body = read_http(sock)
|
reqline, headers, body = read_http(sock)
|
||||||
self.assertEqual(body, 'a is a, body is a=a')
|
self.assertEqual(body, 'a is a, body is a=a')
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
def test_008_correctresponse(self):
|
def test_008_correctresponse(self):
|
||||||
sock = api.connect_tcp(
|
sock = api.connect_tcp(
|
||||||
('127.0.0.1', 12346))
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
response_line_200,_,_ = read_http(sock)
|
response_line_200,_,_ = read_http(sock)
|
||||||
sock.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
response_line_404,_,_ = read_http(sock)
|
response_line_404,_,_ = read_http(sock)
|
||||||
sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
response_line_test,_,_ = read_http(sock)
|
response_line_test,_,_ = read_http(sock)
|
||||||
self.assertEqual(response_line_200,response_line_test)
|
self.assertEqual(response_line_200,response_line_test)
|
||||||
sock.close()
|
fd.close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@@ -1,12 +1,6 @@
|
|||||||
"""\
|
"""\
|
||||||
@file runloop.py
|
@file hub.py
|
||||||
@author Bob Ippolito
|
|
||||||
|
|
||||||
Defines the core eventlet runloop. The runloop keeps track of scheduled
|
|
||||||
events and observers which watch for specific portions of the runloop to
|
|
||||||
be executed.
|
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Bob Ippolito
|
|
||||||
Copyright (c) 2007, Linden Research, Inc.
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,26 +21,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
|
||||||
import bisect
|
import bisect
|
||||||
|
import weakref
|
||||||
import sys
|
import sys
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
import traceback
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
import greenlet
|
import greenlet
|
||||||
|
|
||||||
|
from eventlet import greenlib
|
||||||
from eventlet.timer import Timer
|
from eventlet.timer import Timer
|
||||||
|
|
||||||
|
_g_debug = True
|
||||||
|
|
||||||
|
class BaseHub(object):
|
||||||
|
""" Base hub class for easing the implementation of subclasses that are
|
||||||
|
specific to a particular underlying event architecture. """
|
||||||
|
|
||||||
class RunLoop(object):
|
|
||||||
SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit)
|
SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit)
|
||||||
|
|
||||||
def __init__(self, wait=None, clock=None):
|
def __init__(self, clock=time.time):
|
||||||
if clock is None:
|
self.readers = {}
|
||||||
clock = self.default_clock()
|
self.writers = {}
|
||||||
|
self.excs = {}
|
||||||
|
self.waiters_by_greenlet = {}
|
||||||
|
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
if wait is None:
|
self.greenlet = None
|
||||||
wait = self.default_wait
|
|
||||||
self.wait = wait
|
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
self.running = False
|
self.running = False
|
||||||
self.timers = []
|
self.timers = []
|
||||||
@@ -61,11 +64,84 @@ class RunLoop(object):
|
|||||||
'exit': [],
|
'exit': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
def default_wait(self, time):
|
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
||||||
return None
|
""" Signals an intent to read/write from a particular file descriptor.
|
||||||
|
|
||||||
def default_clock(self):
|
The fileno argument is the file number of the file of interest. The other
|
||||||
return time.time
|
arguments are either callbacks or None. If there is a callback for read
|
||||||
|
or write, the hub sets things up so that when the file descriptor is
|
||||||
|
ready to be read or written, the callback is called.
|
||||||
|
|
||||||
|
The exc callback is called when the socket represented by the file
|
||||||
|
descriptor is closed. The intent is that the the exc callbacks should
|
||||||
|
only be present when either a read or write callback is also present,
|
||||||
|
so the exc callback happens instead of the respective read or write
|
||||||
|
callback.
|
||||||
|
"""
|
||||||
|
read = read or self.readers.get(fileno)
|
||||||
|
if read is not None:
|
||||||
|
self.readers[fileno] = read
|
||||||
|
else:
|
||||||
|
self.readers.pop(fileno, None)
|
||||||
|
write = write or self.writers.get(fileno)
|
||||||
|
if write is not None:
|
||||||
|
self.writers[fileno] = write
|
||||||
|
else:
|
||||||
|
self.writers.pop(fileno, None)
|
||||||
|
exc = exc or self.excs.get(fileno)
|
||||||
|
if exc is not None:
|
||||||
|
self.excs[fileno] = exc
|
||||||
|
else:
|
||||||
|
self.excs.pop(fileno, None)
|
||||||
|
self.waiters_by_greenlet[greenlet.getcurrent()] = fileno
|
||||||
|
|
||||||
|
def remove_descriptor(self, fileno):
|
||||||
|
self.readers.pop(fileno, None)
|
||||||
|
self.writers.pop(fileno, None)
|
||||||
|
self.excs.pop(fileno, None)
|
||||||
|
self.waiters_by_greenlet.pop(greenlet.getcurrent(), None)
|
||||||
|
|
||||||
|
def exc_greenlet(self, gr, exception_object):
|
||||||
|
fileno = self.waiters_by_greenlet.pop(gr, None)
|
||||||
|
if fileno is not None:
|
||||||
|
self.remove_descriptor(fileno)
|
||||||
|
greenlib.switch(gr, None, exception_object)
|
||||||
|
|
||||||
|
def exc_descriptor(self, fileno):
|
||||||
|
exc = self.excs.get(fileno)
|
||||||
|
if exc is not None:
|
||||||
|
try:
|
||||||
|
exc(fileno)
|
||||||
|
except self.SYSTEM_EXCEPTIONS:
|
||||||
|
self.squelch_exception(fileno, sys.exc_info())
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.abort()
|
||||||
|
if self.greenlet is not greenlet.getcurrent():
|
||||||
|
self.switch()
|
||||||
|
|
||||||
|
def switch(self):
|
||||||
|
if not self.greenlet:
|
||||||
|
self.greenlet = greenlib.tracked_greenlet()
|
||||||
|
args = ((self.run,),)
|
||||||
|
else:
|
||||||
|
args = ()
|
||||||
|
try:
|
||||||
|
greenlet.getcurrent().parent = self.greenlet
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return greenlib.switch(self.greenlet, *args)
|
||||||
|
|
||||||
|
def squelch_exception(self, fileno, exc_info):
|
||||||
|
traceback.print_exception(*exc_info)
|
||||||
|
print >>sys.stderr, "Removing descriptor: %r" % (fileno,)
|
||||||
|
try:
|
||||||
|
self.remove_descriptor(fileno)
|
||||||
|
except Exception, e:
|
||||||
|
print >>sys.stderr, "Exception while removing descriptor! %r" % (e,)
|
||||||
|
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
raise NotImplementedError("Implement this in a subclass")
|
||||||
|
|
||||||
def default_sleep(self):
|
def default_sleep(self):
|
||||||
return 60.0
|
return 60.0
|
||||||
@@ -167,13 +243,26 @@ class RunLoop(object):
|
|||||||
def add_timer(self, timer):
|
def add_timer(self, timer):
|
||||||
scheduled_time = self.clock() + timer.seconds
|
scheduled_time = self.clock() + timer.seconds
|
||||||
self._add_absolute_timer(scheduled_time, timer)
|
self._add_absolute_timer(scheduled_time, timer)
|
||||||
current_greenlet = greenlet.getcurrent()
|
self.track_timer(timer)
|
||||||
if current_greenlet not in self.timers_by_greenlet:
|
|
||||||
self.timers_by_greenlet[current_greenlet] = {}
|
|
||||||
self.timers_by_greenlet[current_greenlet][timer] = True
|
|
||||||
timer.greenlet = current_greenlet
|
|
||||||
return scheduled_time
|
return scheduled_time
|
||||||
|
|
||||||
|
def track_timer(self, timer):
|
||||||
|
current_greenlet = greenlet.getcurrent()
|
||||||
|
timer.greenlet = current_greenlet
|
||||||
|
self.timers_by_greenlet.setdefault(
|
||||||
|
current_greenlet,
|
||||||
|
weakref.WeakKeyDictionary())[timer] = True
|
||||||
|
|
||||||
|
def timer_finished(self, timer):
|
||||||
|
try:
|
||||||
|
del self.timers_by_greenlet[timer.greenlet][timer]
|
||||||
|
if not self.timers_by_greenlet[timer.greenlet]:
|
||||||
|
del self.timers_by_greenlet[timer.greenlet]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def timer_canceled(self, timer):
|
||||||
|
self.timer_finished(timer)
|
||||||
|
|
||||||
def prepare_timers(self):
|
def prepare_timers(self):
|
||||||
ins = bisect.insort_right
|
ins = bisect.insort_right
|
||||||
@@ -208,21 +297,26 @@ class RunLoop(object):
|
|||||||
except:
|
except:
|
||||||
self.squelch_timer_exception(timer, sys.exc_info())
|
self.squelch_timer_exception(timer, sys.exc_info())
|
||||||
finally:
|
finally:
|
||||||
try:
|
self.timer_finished(timer)
|
||||||
del self.timers_by_greenlet[timer.greenlet][timer]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
del t[:last]
|
del t[:last]
|
||||||
|
|
||||||
def cancel_timers(self, greenlet):
|
def cancel_timers(self, greenlet, quiet=False):
|
||||||
if greenlet not in self.timers_by_greenlet:
|
if greenlet not in self.timers_by_greenlet:
|
||||||
return
|
return
|
||||||
for timer in self.timers_by_greenlet[greenlet]:
|
for timer in self.timers_by_greenlet[greenlet].keys():
|
||||||
if not timer.cancelled and timer.seconds:
|
if not timer.cancelled and not timer.called and timer.seconds:
|
||||||
## If timer.seconds is 0, this isn't a timer, it's
|
## If timer.seconds is 0, this isn't a timer, it's
|
||||||
## actually eventlet's silly way of specifying whether
|
## actually eventlet's silly way of specifying whether
|
||||||
## a coroutine is "ready to run" or not.
|
## a coroutine is "ready to run" or not.
|
||||||
|
try:
|
||||||
|
# this might be None due to weirdness with weakrefs
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
print 'Runloop cancelling left-over timer %s' % timer
|
except TypeError:
|
||||||
|
pass
|
||||||
|
if _g_debug and not quiet:
|
||||||
|
print 'Hub cancelling left-over timer %s' % timer
|
||||||
|
try:
|
||||||
del self.timers_by_greenlet[greenlet]
|
del self.timers_by_greenlet[greenlet]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
136
eventlet/hubs/libev.py
Normal file
136
eventlet/hubs/libev.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"""\
|
||||||
|
@file libev.py
|
||||||
|
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bisect
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
from eventlet import greenlib
|
||||||
|
from eventlet.timer import Timer
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
|
||||||
|
import greenlet
|
||||||
|
|
||||||
|
# XXX for debugging only
|
||||||
|
#raise ImportError()
|
||||||
|
|
||||||
|
import ev as libev
|
||||||
|
|
||||||
|
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def __init__(self, clock=time.time):
|
||||||
|
super(Hub, self).__init__(clock)
|
||||||
|
self.interrupted = False
|
||||||
|
self._evloop = libev.default_loop()
|
||||||
|
|
||||||
|
sig = libev.Signal(signal.SIGINT, self._evloop, self.signal_received, signal.SIGINT)
|
||||||
|
sig.start()
|
||||||
|
|
||||||
|
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
||||||
|
if read:
|
||||||
|
evt = libev.Io(fileno, libev.EV_READ, self._evloop, read, fileno)
|
||||||
|
evt.start()
|
||||||
|
self.readers[fileno] = evt, read
|
||||||
|
|
||||||
|
if write:
|
||||||
|
evt = libev.Io(fileno, libev.EV_WRITE, self._evloop, write, fileno)
|
||||||
|
evt.start()
|
||||||
|
self.writers[fileno] = evt, write
|
||||||
|
|
||||||
|
if exc:
|
||||||
|
self.excs[fileno] = exc
|
||||||
|
|
||||||
|
self.waiters_by_greenlet[greenlet.getcurrent()] = fileno
|
||||||
|
|
||||||
|
def remove_descriptor(self, fileno):
|
||||||
|
for queue in (self.readers, self.writers):
|
||||||
|
tpl = queue.pop(fileno, None)
|
||||||
|
if tpl is not None:
|
||||||
|
tpl[0].stop()
|
||||||
|
self.excs.pop(fileno, None)
|
||||||
|
self.waiters_by_greenlet.pop(greenlet.getcurrent(), None)
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
super(Hub, self).abort()
|
||||||
|
self._evloop.unloop()
|
||||||
|
|
||||||
|
def signal_received(self, signal):
|
||||||
|
# can't do more than set this flag here because the pyevent callback
|
||||||
|
# mechanism swallows exceptions raised here, so we have to raise in
|
||||||
|
# the 'main' greenlet (in wait()) to kill the program
|
||||||
|
self.interrupted = True
|
||||||
|
self._evloop.unloop()
|
||||||
|
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
# this timeout will cause us to return from the dispatch() call
|
||||||
|
# when we want to
|
||||||
|
timer = libev.Timer(seconds, 0, self._evloop, lambda *args: None)
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = self._evloop.loop()
|
||||||
|
except self.SYSTEM_EXCEPTIONS:
|
||||||
|
self.interrupted = True
|
||||||
|
except:
|
||||||
|
self.squelch_exception(-1, sys.exc_info())
|
||||||
|
|
||||||
|
# we are explicitly ignoring the status because in our experience it's
|
||||||
|
# harmless and there's nothing meaningful we could do with it anyway
|
||||||
|
|
||||||
|
timer.stop()
|
||||||
|
|
||||||
|
# raise any signals that deserve raising
|
||||||
|
if self.interrupted:
|
||||||
|
self.interrupted = False
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
def add_timer(self, timer):
|
||||||
|
# store the pyevent timer object so that we can cancel later
|
||||||
|
eventtimer = libev.Timer(timer.seconds, 0, self._evloop, timer)
|
||||||
|
timer.impltimer = eventtimer
|
||||||
|
eventtimer.start()
|
||||||
|
self.track_timer(timer)
|
||||||
|
|
||||||
|
def timer_finished(self, timer):
|
||||||
|
try:
|
||||||
|
timer.impltimer.stop()
|
||||||
|
del timer.impltimer
|
||||||
|
# XXX might this raise other errors?
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
super(Hub, self).timer_finished(timer)
|
||||||
|
|
||||||
|
def timer_canceled(self, timer):
|
||||||
|
""" Cancels the underlying libevent timer. """
|
||||||
|
try:
|
||||||
|
timer.impltimer.stop()
|
||||||
|
del timer.impltimer
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
super(Hub, self).timer_canceled(timer)
|
146
eventlet/hubs/libevent.py
Normal file
146
eventlet/hubs/libevent.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""\
|
||||||
|
@file libevent.py
|
||||||
|
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bisect
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
from eventlet import greenlib
|
||||||
|
from eventlet.timer import Timer
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
|
||||||
|
import greenlet
|
||||||
|
|
||||||
|
# XXX for debugging only
|
||||||
|
#raise ImportError()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# use rel if available
|
||||||
|
import rel
|
||||||
|
rel.initialize()
|
||||||
|
rel.override()
|
||||||
|
except ImportError:
|
||||||
|
# don't have rel, but might still have libevent
|
||||||
|
pass
|
||||||
|
|
||||||
|
import event
|
||||||
|
|
||||||
|
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def __init__(self, clock=time.time):
|
||||||
|
super(Hub, self).__init__(clock)
|
||||||
|
self.interrupted = False
|
||||||
|
event.init()
|
||||||
|
|
||||||
|
sig = event.signal(signal.SIGINT, self.signal_received, signal.SIGINT)
|
||||||
|
sig.add()
|
||||||
|
|
||||||
|
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
||||||
|
if read:
|
||||||
|
evt = event.read(fileno, read, fileno)
|
||||||
|
evt.add()
|
||||||
|
self.readers[fileno] = evt, read
|
||||||
|
|
||||||
|
if write:
|
||||||
|
evt = event.write(fileno, write, fileno)
|
||||||
|
evt.add()
|
||||||
|
self.writers[fileno] = evt, write
|
||||||
|
|
||||||
|
if exc:
|
||||||
|
self.excs[fileno] = exc
|
||||||
|
|
||||||
|
self.waiters_by_greenlet[greenlet.getcurrent()] = fileno
|
||||||
|
|
||||||
|
def remove_descriptor(self, fileno):
|
||||||
|
for queue in (self.readers, self.writers):
|
||||||
|
tpl = queue.pop(fileno, None)
|
||||||
|
if tpl is not None:
|
||||||
|
tpl[0].delete()
|
||||||
|
self.excs.pop(fileno, None)
|
||||||
|
self.waiters_by_greenlet.pop(greenlet.getcurrent(), None)
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
super(Hub, self).abort()
|
||||||
|
event.abort()
|
||||||
|
|
||||||
|
def signal_received(self, signal):
|
||||||
|
# can't do more than set this flag here because the pyevent callback
|
||||||
|
# mechanism swallows exceptions raised here, so we have to raise in
|
||||||
|
# the 'main' greenlet (in wait()) to kill the program
|
||||||
|
self.interrupted = True
|
||||||
|
event.abort()
|
||||||
|
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
# this timeout will cause us to return from the dispatch() call
|
||||||
|
# when we want to
|
||||||
|
timer = event.timeout(seconds, lambda: None)
|
||||||
|
timer.add()
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = event.dispatch()
|
||||||
|
except self.SYSTEM_EXCEPTIONS:
|
||||||
|
self.interrupted = True
|
||||||
|
except:
|
||||||
|
self.squelch_exception(-1, sys.exc_info())
|
||||||
|
|
||||||
|
# we are explicitly ignoring the status because in our experience it's
|
||||||
|
# harmless and there's nothing meaningful we could do with it anyway
|
||||||
|
|
||||||
|
timer.delete()
|
||||||
|
|
||||||
|
# raise any signals that deserve raising
|
||||||
|
if self.interrupted:
|
||||||
|
self.interrupted = False
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
def add_timer(self, timer):
|
||||||
|
# store the pyevent timer object so that we can cancel later
|
||||||
|
eventtimer = event.timeout(timer.seconds, timer)
|
||||||
|
timer.impltimer = eventtimer
|
||||||
|
eventtimer.add()
|
||||||
|
self.track_timer(timer)
|
||||||
|
|
||||||
|
def timer_finished(self, timer):
|
||||||
|
try:
|
||||||
|
timer.impltimer.delete()
|
||||||
|
del timer.impltimer
|
||||||
|
# XXX might this raise other exceptions? double delete?
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
super(Hub, self).timer_finished(timer)
|
||||||
|
|
||||||
|
def timer_canceled(self, timer):
|
||||||
|
""" Cancels the underlying libevent timer. """
|
||||||
|
try:
|
||||||
|
timer.impltimer.delete()
|
||||||
|
del timer.impltimer
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
super(Hub, self).timer_canceled(timer)
|
||||||
|
|
172
eventlet/hubs/nginx.py
Normal file
172
eventlet/hubs/nginx.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
"""\
|
||||||
|
@file nginx.py
|
||||||
|
@author Donovan Preston
|
||||||
|
|
||||||
|
Copyright (c) 2008, Linden Research, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from os.path import abspath, dirname
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
sys.stdout = sys.stderr
|
||||||
|
mydir = dirname(dirname(dirname(abspath(__file__))))
|
||||||
|
if mydir not in sys.path:
|
||||||
|
sys.path.append(mydir)
|
||||||
|
|
||||||
|
|
||||||
|
from eventlet import api
|
||||||
|
from eventlet import greenlib
|
||||||
|
from eventlet import httpc
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
from eventlet import util
|
||||||
|
|
||||||
|
|
||||||
|
util.wrap_socket_with_coroutine_socket()
|
||||||
|
|
||||||
|
|
||||||
|
def hello_world(env, start_response):
|
||||||
|
result = httpc.get('http://www.google.com/')
|
||||||
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||||
|
return [result]
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_application(master, env, start_response):
|
||||||
|
try:
|
||||||
|
real_application = api.named(env['eventlet_nginx_wsgi_app'])
|
||||||
|
except:
|
||||||
|
real_application = hello_world
|
||||||
|
result = real_application(env, start_response)
|
||||||
|
master.switch((result, None))
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
class StartResponse(object):
|
||||||
|
def __call__(self, *args):
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
|
||||||
|
pythonpath_already_set = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WSGI_POLLIN = 0x01
|
||||||
|
WSGI_POLLOUT = 0x04
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
hub.BaseHub.__init__(self, *args, **kw)
|
||||||
|
self._connection_wrappers = {}
|
||||||
|
|
||||||
|
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
||||||
|
print "ADD DESCRIPTOR", fileno, read, write, exc
|
||||||
|
traceback.print_stack()
|
||||||
|
|
||||||
|
super(Hub, self).add_descriptor(fileno, read, write, exc)
|
||||||
|
flag = 0
|
||||||
|
if read:
|
||||||
|
flag |= WSGI_POLLIN
|
||||||
|
if write:
|
||||||
|
flag |= WSGI_POLLOUT
|
||||||
|
conn = self.connection_wrapper(fileno)
|
||||||
|
self._connection_wrappers[fileno] = conn
|
||||||
|
print "POLL REGISTER", flag
|
||||||
|
self.poll_register(conn, flag)
|
||||||
|
|
||||||
|
def remove_descriptor(self, fileno):
|
||||||
|
super(Hub, self).remove_descriptor(fileno)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.poll_unregister(self._connection_wrappers[fileno])
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def wait(self, seconds=0):
|
||||||
|
to_call = getattr(self, 'to_call', None)
|
||||||
|
print "WAIT", self, to_call
|
||||||
|
if to_call:
|
||||||
|
print "CALL TOCALL"
|
||||||
|
result = to_call[0](to_call[1])
|
||||||
|
del self.to_call
|
||||||
|
return result
|
||||||
|
greenlib.switch(self.current_application, self.poll(int(seconds*1000)))
|
||||||
|
|
||||||
|
def application(self, env, start_response):
|
||||||
|
print "ENV",env
|
||||||
|
self.poll_register = env['ngx.poll_register']
|
||||||
|
self.poll_unregister = env['ngx.poll_unregister']
|
||||||
|
self.poll = env['ngx.poll']
|
||||||
|
self.connection_wrapper = env['ngx.connection_wrapper']
|
||||||
|
self.current_application = api.getcurrent()
|
||||||
|
|
||||||
|
slave = api.greenlet.greenlet(wrap_application)
|
||||||
|
response = StartResponse()
|
||||||
|
result = slave.switch(
|
||||||
|
api.getcurrent(), env, response)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.current_application = api.getcurrent()
|
||||||
|
print "RESULT", result, callable(result[0])
|
||||||
|
if result and callable(result[0]):
|
||||||
|
print "YIELDING!"
|
||||||
|
yield ''
|
||||||
|
print "AFTER YIELD!"
|
||||||
|
conn, flags = result[0]()
|
||||||
|
fileno = conn.fileno()
|
||||||
|
if flags & WSGI_POLLIN:
|
||||||
|
self.readers[fileno](fileno)
|
||||||
|
elif flags & WSGI_POLLOUT:
|
||||||
|
self.writers[fileno](fileno)
|
||||||
|
print "POLL STATE", conn, flags, dir(conn)
|
||||||
|
else:
|
||||||
|
start_response(*response.args)
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
for x in result[0]:
|
||||||
|
yield x
|
||||||
|
else:
|
||||||
|
for x in result:
|
||||||
|
yield x
|
||||||
|
return
|
||||||
|
result = self.switch()
|
||||||
|
if not isinstance(result, tuple):
|
||||||
|
result = (result, None) ## TODO Fix greenlib's return values
|
||||||
|
|
||||||
|
|
||||||
|
def application(env, start_response):
|
||||||
|
hub = api.get_hub()
|
||||||
|
|
||||||
|
if not isinstance(hub, Hub):
|
||||||
|
api.use_hub(sys.modules[Hub.__module__])
|
||||||
|
hub = api.get_hub()
|
||||||
|
|
||||||
|
global pythonpath_already_set
|
||||||
|
if not pythonpath_already_set:
|
||||||
|
pythonpath = env.get('eventlet_python_path', '').split(':')
|
||||||
|
for seg in pythonpath:
|
||||||
|
if seg not in sys.path:
|
||||||
|
sys.path.append(seg)
|
||||||
|
|
||||||
|
return hub.application(env, start_response)
|
||||||
|
|
94
eventlet/hubs/poll.py
Normal file
94
eventlet/hubs/poll.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
"""\
|
||||||
|
@file poll.py
|
||||||
|
@author Bob Ippolito
|
||||||
|
|
||||||
|
Copyright (c) 2005-2006, Bob Ippolito
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
import traceback
|
||||||
|
from time import sleep
|
||||||
|
import time
|
||||||
|
|
||||||
|
from eventlet import greenlib
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
|
||||||
|
EXC_MASK = select.POLLERR | select.POLLHUP | select.POLLNVAL
|
||||||
|
READ_MASK = select.POLLIN
|
||||||
|
WRITE_MASK = select.POLLOUT
|
||||||
|
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def __init__(self, clock=time.time):
|
||||||
|
super(Hub, self).__init__(clock)
|
||||||
|
self.poll = select.poll()
|
||||||
|
|
||||||
|
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
||||||
|
super(Hub, self).add_descriptor(fileno, read, write, exc)
|
||||||
|
|
||||||
|
mask = self.get_fn_mask(read, write)
|
||||||
|
oldmask = self.get_fn_mask(self.readers.get(fileno), self.writers.get(fileno))
|
||||||
|
if mask != oldmask:
|
||||||
|
# Only need to re-register this fileno if the mask changes
|
||||||
|
self.poll.register(fileno, mask)
|
||||||
|
|
||||||
|
def get_fn_mask(self, read, write):
|
||||||
|
mask = 0
|
||||||
|
if read is not None:
|
||||||
|
mask |= READ_MASK
|
||||||
|
if write is not None:
|
||||||
|
mask |= WRITE_MASK
|
||||||
|
return mask
|
||||||
|
|
||||||
|
|
||||||
|
def remove_descriptor(self, fileno):
|
||||||
|
super(Hub, self).remove_descriptor(fileno)
|
||||||
|
self.poll.unregister(fileno)
|
||||||
|
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
readers = self.readers
|
||||||
|
writers = self.writers
|
||||||
|
excs = self.excs
|
||||||
|
|
||||||
|
if not readers and not writers and not excs:
|
||||||
|
if seconds:
|
||||||
|
sleep(seconds)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
presult = self.poll.poll(seconds * 1000.0)
|
||||||
|
except select.error, e:
|
||||||
|
if e.args[0] == errno.EINTR:
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
|
||||||
|
|
||||||
|
for fileno, event in presult:
|
||||||
|
for dct, mask in ((readers, READ_MASK), (writers, WRITE_MASK), (excs, EXC_MASK)):
|
||||||
|
func = dct.get(fileno)
|
||||||
|
if func is not None and event & mask:
|
||||||
|
try:
|
||||||
|
func(fileno)
|
||||||
|
except SYSTEM_EXCEPTIONS:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.squelch_exception(fileno, sys.exc_info())
|
61
eventlet/hubs/selecthub.py
Normal file
61
eventlet/hubs/selecthub.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""\
|
||||||
|
@file selecthub.py
|
||||||
|
|
||||||
|
Copyright (c) 2005-2006, Bob Ippolito
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
import errno
|
||||||
|
import time
|
||||||
|
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
|
||||||
|
import greenlet
|
||||||
|
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
readers = self.readers
|
||||||
|
writers = self.writers
|
||||||
|
excs = self.excs
|
||||||
|
if not readers and not writers and not excs:
|
||||||
|
if seconds:
|
||||||
|
time.sleep(seconds)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
r, w, ig = select.select(readers.keys(), writers.keys(), [], seconds)
|
||||||
|
except select.error, e:
|
||||||
|
if e.args[0] == errno.EINTR:
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
|
||||||
|
for observed, events in ((readers, r), (writers, w)):
|
||||||
|
#print "events", r, " ", w
|
||||||
|
for fileno in events:
|
||||||
|
try:
|
||||||
|
cb = observed.get(fileno)
|
||||||
|
#print "cb", cb, " ", observed
|
||||||
|
if cb is not None:
|
||||||
|
cb(fileno)
|
||||||
|
except SYSTEM_EXCEPTIONS:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.squelch_exception(fileno, sys.exc_info())
|
61
eventlet/hubs/selects.py
Normal file
61
eventlet/hubs/selects.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""\
|
||||||
|
@file select.py
|
||||||
|
|
||||||
|
Copyright (c) 2005-2006, Bob Ippolito
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
import errno
|
||||||
|
import time
|
||||||
|
|
||||||
|
from eventlet.hubs import hub
|
||||||
|
|
||||||
|
import greenlet
|
||||||
|
|
||||||
|
class Hub(hub.BaseHub):
|
||||||
|
def wait(self, seconds=None):
|
||||||
|
readers = self.readers
|
||||||
|
writers = self.writers
|
||||||
|
excs = self.excs
|
||||||
|
if not readers and not writers and not excs:
|
||||||
|
if seconds:
|
||||||
|
time.sleep(seconds)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
r, w, ig = select.select(readers.keys(), writers.keys(), [], seconds)
|
||||||
|
except select.error, e:
|
||||||
|
if e.args[0] == errno.EINTR:
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS
|
||||||
|
for observed, events in ((readers, r), (writers, w)):
|
||||||
|
#print "events", r, " ", w
|
||||||
|
for fileno in events:
|
||||||
|
try:
|
||||||
|
cb = observed.get(fileno)
|
||||||
|
#print "cb", cb, " ", observed
|
||||||
|
if cb is not None:
|
||||||
|
cb(fileno)
|
||||||
|
except SYSTEM_EXCEPTIONS:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.squelch_exception(fileno, sys.exc_info())
|
@@ -1,219 +0,0 @@
|
|||||||
"""\
|
|
||||||
@file kqueuehub.py
|
|
||||||
@author Donovan Preston
|
|
||||||
|
|
||||||
Copyright (c) 2006-2007, Linden Research, Inc.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import select
|
|
||||||
import kqueue
|
|
||||||
import traceback
|
|
||||||
from errno import EBADF
|
|
||||||
|
|
||||||
from eventlet import greenlib
|
|
||||||
from eventlet.runloop import RunLoop, Timer
|
|
||||||
|
|
||||||
import greenlet
|
|
||||||
|
|
||||||
class Hub(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.runloop = RunLoop(self.wait)
|
|
||||||
self.descriptor_queue = {}
|
|
||||||
self.descriptors = {}
|
|
||||||
self.greenlet = None
|
|
||||||
self.kfd = None
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.process_queue()
|
|
||||||
self.descriptors, self.descriptor_queue = self.descriptor_queue, {}
|
|
||||||
os.close(self.kfd)
|
|
||||||
self.kfd = None
|
|
||||||
self.runloop.abort()
|
|
||||||
if self.greenlet is not greenlet.getcurrent():
|
|
||||||
self.switch()
|
|
||||||
|
|
||||||
def schedule_call(self, *args, **kw):
|
|
||||||
return self.runloop.schedule_call(*args, **kw)
|
|
||||||
|
|
||||||
def switch(self):
|
|
||||||
if not self.greenlet:
|
|
||||||
self.greenlet = greenlib.tracked_greenlet()
|
|
||||||
args = ((self.runloop.run,),)
|
|
||||||
else:
|
|
||||||
args = ()
|
|
||||||
try:
|
|
||||||
greenlet.getcurrent().parent = self.greenlet
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return greenlib.switch(self.greenlet, *args)
|
|
||||||
|
|
||||||
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
|
||||||
self.descriptor_queue[fileno] = read, write, exc
|
|
||||||
|
|
||||||
def remove_descriptor(self, fileno):
|
|
||||||
self.descriptor_queue[fileno] = None, None, None
|
|
||||||
|
|
||||||
def exc_descriptor(self, fileno):
|
|
||||||
# We must handle two cases here, the descriptor
|
|
||||||
# may be changing or removing its exc handler
|
|
||||||
# in the queue, or it may be waiting on the queue.
|
|
||||||
exc = None
|
|
||||||
try:
|
|
||||||
exc = self.descriptor_queue[fileno][2]
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
exc = self.descriptors[fileno][2]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if exc is not None:
|
|
||||||
try:
|
|
||||||
exc()
|
|
||||||
except self.runloop.SYSTEM_EXCEPTIONS:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
|
|
||||||
def squelch_exception(self, fileno, exc_info):
|
|
||||||
traceback.print_exception(*exc_info)
|
|
||||||
print >>sys.stderr, "Removing descriptor: %r" % (fileno,)
|
|
||||||
try:
|
|
||||||
self.remove_descriptor(fileno)
|
|
||||||
except Exception, e:
|
|
||||||
print >>sys.stderr, "Exception while removing descriptor! %r" % (e,)
|
|
||||||
|
|
||||||
def process_queue(self):
|
|
||||||
if self.kfd is None:
|
|
||||||
self.kfd = kqueue.kqueue()
|
|
||||||
d = self.descriptors
|
|
||||||
|
|
||||||
E_R = kqueue.EVFILT_READ
|
|
||||||
E_W = kqueue.EVFILT_WRITE
|
|
||||||
E = kqueue.Event
|
|
||||||
E_ADD = kqueue.EV_ADD
|
|
||||||
E_DEL = kqueue.EV_DELETE
|
|
||||||
|
|
||||||
kevent = kqueue.kevent
|
|
||||||
kfd = self.kfd
|
|
||||||
for fileno, rwe in self.descriptor_queue.iteritems():
|
|
||||||
read, write, exc = rwe
|
|
||||||
if read is None and write is None and exc is None:
|
|
||||||
try:
|
|
||||||
read, write, exc = d.pop(fileno)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
l = []
|
|
||||||
if read is not None:
|
|
||||||
l.append(E(fileno, E_R, E_DEL))
|
|
||||||
if write is not None:
|
|
||||||
l.append(E(fileno, E_W, E_DEL))
|
|
||||||
if l:
|
|
||||||
try:
|
|
||||||
kevent(kfd, l, 0, 0)
|
|
||||||
except OSError, e:
|
|
||||||
if e[0] != EBADF:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
l = []
|
|
||||||
try:
|
|
||||||
oldr, oldw, olde = d[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if oldr is not None:
|
|
||||||
if read is None:
|
|
||||||
l.append(E(fileno, E_R, E_DEL))
|
|
||||||
else:
|
|
||||||
read = None
|
|
||||||
if oldw is not None:
|
|
||||||
if write is None:
|
|
||||||
l.append(E(fileno, E_W, E_DEL))
|
|
||||||
else:
|
|
||||||
write = None
|
|
||||||
if read is not None:
|
|
||||||
l.append(E(fileno, E_R, E_ADD))
|
|
||||||
if write is not None:
|
|
||||||
l.append(E(fileno, E_W, E_ADD))
|
|
||||||
if l:
|
|
||||||
try:
|
|
||||||
kevent(kfd, l, 0, 0)
|
|
||||||
except OSError, e:
|
|
||||||
if e[0] != EBADF:
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
del d[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if exc is not None:
|
|
||||||
try:
|
|
||||||
exc(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
continue
|
|
||||||
d[fileno] = rwe
|
|
||||||
self.descriptor_queue.clear()
|
|
||||||
|
|
||||||
def wait(self, seconds=None):
|
|
||||||
|
|
||||||
self.process_queue()
|
|
||||||
|
|
||||||
if seconds is not None:
|
|
||||||
seconds *= 1000000000.0
|
|
||||||
dct = self.descriptors
|
|
||||||
events = kqueue.kevent(self.kfd, [], len(dct), seconds)
|
|
||||||
|
|
||||||
SYSTEM_EXCEPTIONS = self.runloop.SYSTEM_EXCEPTIONS
|
|
||||||
|
|
||||||
E_R = kqueue.EVFILT_READ
|
|
||||||
E_W = kqueue.EVFILT_WRITE
|
|
||||||
E_EOF = kqueue.EV_EOF
|
|
||||||
|
|
||||||
for e in events:
|
|
||||||
fileno = e.ident
|
|
||||||
event = e.filter
|
|
||||||
|
|
||||||
try:
|
|
||||||
read, write, exc = dct[fileno]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if read is not None and event == E_R:
|
|
||||||
try:
|
|
||||||
read(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
elif exc is not None and e.fflags & E_EOF:
|
|
||||||
try:
|
|
||||||
exc(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
|
|
||||||
if write is not None and event == E_W:
|
|
||||||
try:
|
|
||||||
write(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
@@ -1,189 +0,0 @@
|
|||||||
"""\
|
|
||||||
@file pollhub.py
|
|
||||||
@author Bob Ippolito
|
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Bob Ippolito
|
|
||||||
Copyright (c) 2007, Linden Research, Inc.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import select
|
|
||||||
import socket
|
|
||||||
import errno
|
|
||||||
import traceback
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
from eventlet import greenlib
|
|
||||||
from eventlet.runloop import RunLoop, Timer
|
|
||||||
|
|
||||||
import greenlet
|
|
||||||
|
|
||||||
EXC_MASK = select.POLLERR | select.POLLHUP | select.POLLNVAL
|
|
||||||
READ_MASK = select.POLLIN
|
|
||||||
WRITE_MASK = select.POLLOUT
|
|
||||||
|
|
||||||
class Hub(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.runloop = RunLoop(self.wait)
|
|
||||||
self.descriptor_queue = {}
|
|
||||||
self.descriptors = {}
|
|
||||||
self.greenlet = None
|
|
||||||
self.poll = select.poll()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.process_queue()
|
|
||||||
self.runloop.abort()
|
|
||||||
if self.greenlet is not greenlet.getcurrent():
|
|
||||||
self.switch()
|
|
||||||
|
|
||||||
def schedule_call(self, *args, **kw):
|
|
||||||
return self.runloop.schedule_call(*args, **kw)
|
|
||||||
|
|
||||||
def switch(self):
|
|
||||||
if not self.greenlet:
|
|
||||||
self.greenlet = greenlib.tracked_greenlet()
|
|
||||||
args = ((self.runloop.run,),)
|
|
||||||
else:
|
|
||||||
args = ()
|
|
||||||
try:
|
|
||||||
greenlet.getcurrent().parent = self.greenlet
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return greenlib.switch(self.greenlet, *args)
|
|
||||||
|
|
||||||
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
|
||||||
if fileno in self.descriptor_queue:
|
|
||||||
oread, owrite, oexc = self.descriptor_queue[fileno]
|
|
||||||
read, write, exc = read or oread, write or owrite, exc or oexc
|
|
||||||
self.descriptor_queue[fileno] = read, write, exc
|
|
||||||
|
|
||||||
def remove_descriptor(self, fileno):
|
|
||||||
self.descriptor_queue[fileno] = None, None, None
|
|
||||||
|
|
||||||
def exc_descriptor(self, fileno):
|
|
||||||
# We must handle two cases here, the descriptor
|
|
||||||
# may be changing or removing its exc handler
|
|
||||||
# in the queue, or it may be waiting on the queue.
|
|
||||||
exc = None
|
|
||||||
try:
|
|
||||||
exc = self.descriptor_queue[fileno][2]
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
exc = self.descriptors[fileno][2]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if exc is not None:
|
|
||||||
try:
|
|
||||||
exc(fileno)
|
|
||||||
except self.runloop.SYSTEM_EXCEPTIONS:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
|
|
||||||
def squelch_exception(self, fileno, exc_info):
|
|
||||||
traceback.print_exception(*exc_info)
|
|
||||||
print >>sys.stderr, "Removing descriptor: %r" % (fileno,)
|
|
||||||
try:
|
|
||||||
self.remove_descriptor(fileno)
|
|
||||||
except Exception, e:
|
|
||||||
print >>sys.stderr, "Exception while removing descriptor! %r" % (e,)
|
|
||||||
|
|
||||||
def process_queue(self):
|
|
||||||
d = self.descriptors
|
|
||||||
reg = self.poll.register
|
|
||||||
unreg = self.poll.unregister
|
|
||||||
rm = READ_MASK
|
|
||||||
wm = WRITE_MASK
|
|
||||||
for fileno, rwe in self.descriptor_queue.iteritems():
|
|
||||||
read, write, exc = rwe
|
|
||||||
if read is None and write is None and exc is None:
|
|
||||||
try:
|
|
||||||
del d[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
unreg(fileno)
|
|
||||||
except socket.error:
|
|
||||||
# print "squelched socket err on unreg", fileno
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
mask = 0
|
|
||||||
if read is not None:
|
|
||||||
mask |= rm
|
|
||||||
if write is not None:
|
|
||||||
mask |= wm
|
|
||||||
oldmask = 0
|
|
||||||
try:
|
|
||||||
oldr, oldw, olde = d[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if oldr is not None:
|
|
||||||
oldmask |= rm
|
|
||||||
if oldw is not None:
|
|
||||||
oldmask |= wm
|
|
||||||
if mask != oldmask:
|
|
||||||
reg(fileno, mask)
|
|
||||||
d[fileno] = rwe
|
|
||||||
self.descriptor_queue.clear()
|
|
||||||
|
|
||||||
def wait(self, seconds=None):
|
|
||||||
self.process_queue()
|
|
||||||
|
|
||||||
if not self.descriptors:
|
|
||||||
if seconds:
|
|
||||||
sleep(seconds)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
presult = self.poll.poll(seconds * 1000.0)
|
|
||||||
except select.error, e:
|
|
||||||
if e.args[0] == errno.EINTR:
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
SYSTEM_EXCEPTIONS = self.runloop.SYSTEM_EXCEPTIONS
|
|
||||||
dct = self.descriptors
|
|
||||||
|
|
||||||
for fileno, event in presult:
|
|
||||||
try:
|
|
||||||
read, write, exc = dct[fileno]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if read is not None and event & READ_MASK:
|
|
||||||
try:
|
|
||||||
read(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
elif exc is not None and event & EXC_MASK:
|
|
||||||
try:
|
|
||||||
exc(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
|
|
||||||
if write is not None and event & WRITE_MASK:
|
|
||||||
try:
|
|
||||||
write(fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
@@ -21,19 +21,64 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import popen2
|
import popen2
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from eventlet import coros
|
||||||
|
from eventlet import pools
|
||||||
|
from eventlet import greenio
|
||||||
|
from eventlet import util
|
||||||
|
|
||||||
from eventlet import util, pools
|
|
||||||
from eventlet import wrappedfd
|
|
||||||
|
|
||||||
class DeadProcess(RuntimeError):
|
class DeadProcess(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
CHILD_PIDS = []
|
||||||
|
|
||||||
|
CHILD_EVENTS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def sig_child(signal, frame):
|
||||||
|
for child_pid in CHILD_PIDS:
|
||||||
|
try:
|
||||||
|
pid, code = util.__original_waitpid__(child_pid, os.WNOHANG)
|
||||||
|
if not pid:
|
||||||
|
continue ## Wasn't this one that died
|
||||||
|
elif pid == -1:
|
||||||
|
print >> sys.stderr, "Got -1! Why didn't python raise?"
|
||||||
|
elif pid != child_pid:
|
||||||
|
print >> sys.stderr, "pid (%d) != child_pid (%d)" % (pid, child_pid)
|
||||||
|
|
||||||
|
# Defensively assume we could get a different pid back
|
||||||
|
if CHILD_EVENTS.get(pid):
|
||||||
|
event = CHILD_EVENTS.pop(pid)
|
||||||
|
event.send(code)
|
||||||
|
|
||||||
|
except OSError, e:
|
||||||
|
if e[0] != errno.ECHILD:
|
||||||
|
raise e
|
||||||
|
elif CHILD_EVENTS.get(child_pid):
|
||||||
|
# Already dead; signal, but assume success
|
||||||
|
event = CHILD_EVENTS.pop(child_pid)
|
||||||
|
event.send(0)
|
||||||
|
signal.signal(signal.SIGCHLD, sig_child)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_child_pid(pid):
|
||||||
|
"""Add the given integer 'pid' to the list of child
|
||||||
|
process ids we are tracking. Return an event object
|
||||||
|
that can be used to get the process' exit code.
|
||||||
|
"""
|
||||||
|
CHILD_PIDS.append(pid)
|
||||||
|
event = coros.event()
|
||||||
|
CHILD_EVENTS[pid] = event
|
||||||
|
return event
|
||||||
|
|
||||||
|
|
||||||
class Process(object):
|
class Process(object):
|
||||||
process_number = 0
|
process_number = 0
|
||||||
def __init__(self, command, args, dead_callback=lambda:None):
|
def __init__(self, command, args, dead_callback=lambda:None):
|
||||||
@@ -51,13 +96,14 @@ class Process(object):
|
|||||||
|
|
||||||
## We use popen4 so that read() will read from either stdout or stderr
|
## We use popen4 so that read() will read from either stdout or stderr
|
||||||
self.popen4 = popen2.Popen4([self.command] + self.args)
|
self.popen4 = popen2.Popen4([self.command] + self.args)
|
||||||
|
self.event = _add_child_pid(self.popen4.pid)
|
||||||
child_stdout_stderr = self.popen4.fromchild
|
child_stdout_stderr = self.popen4.fromchild
|
||||||
child_stdin = self.popen4.tochild
|
child_stdin = self.popen4.tochild
|
||||||
util.set_nonblocking(child_stdout_stderr)
|
greenio.set_nonblocking(child_stdout_stderr)
|
||||||
util.set_nonblocking(child_stdin)
|
greenio.set_nonblocking(child_stdin)
|
||||||
self.child_stdout_stderr = wrappedfd.wrapped_file(child_stdout_stderr)
|
self.child_stdout_stderr = greenio.GreenPipe(child_stdout_stderr)
|
||||||
self.child_stdout_stderr.newlines = '\n' # the default is \r\n, which aren't sent over pipes
|
self.child_stdout_stderr.newlines = '\n' # the default is \r\n, which aren't sent over pipes
|
||||||
self.child_stdin = wrappedfd.wrapped_file(child_stdin)
|
self.child_stdin = greenio.GreenPipe(child_stdin)
|
||||||
self.child_stdin.newlines = '\n'
|
self.child_stdin.newlines = '\n'
|
||||||
|
|
||||||
self.sendall = self.child_stdin.write
|
self.sendall = self.child_stdin.write
|
||||||
@@ -86,8 +132,9 @@ class Process(object):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def write(self, stuff):
|
def write(self, stuff):
|
||||||
written = self.child_stdin.send(stuff)
|
written = 0
|
||||||
try:
|
try:
|
||||||
|
written = self.child_stdin.write(stuff)
|
||||||
self.child_stdin.flush()
|
self.child_stdin.flush()
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
## File was closed
|
## File was closed
|
||||||
@@ -115,6 +162,9 @@ class Process(object):
|
|||||||
def getpid(self):
|
def getpid(self):
|
||||||
return self.popen4.pid
|
return self.popen4.pid
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
return self.event.wait()
|
||||||
|
|
||||||
|
|
||||||
class ProcessPool(pools.Pool):
|
class ProcessPool(pools.Pool):
|
||||||
def __init__(self, command, args=None, min_size=0, max_size=4):
|
def __init__(self, command, args=None, min_size=0, max_size=4):
|
||||||
|
@@ -99,9 +99,13 @@ class TestDyingProcessesLeavePool(tests.TestCase):
|
|||||||
|
|
||||||
def test_dead_process_not_inserted_into_pool(self):
|
def test_dead_process_not_inserted_into_pool(self):
|
||||||
proc = self.pool.get()
|
proc = self.pool.get()
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
result = proc.read()
|
result = proc.read()
|
||||||
self.assertEquals(result, 'hello\n')
|
self.assertEquals(result, 'hello\n')
|
||||||
|
result = proc.read()
|
||||||
|
except processes.DeadProcess:
|
||||||
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.pool.put(proc)
|
self.pool.put(proc)
|
||||||
proc2 = self.pool.get()
|
proc2 = self.pool.get()
|
||||||
@@ -111,7 +115,7 @@ class TestDyingProcessesLeavePool(tests.TestCase):
|
|||||||
class TestProcessLivesForever(tests.TestCase):
|
class TestProcessLivesForever(tests.TestCase):
|
||||||
mode = 'static'
|
mode = 'static'
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.pool = processes.ProcessPool('yes', max_size=1)
|
self.pool = processes.ProcessPool('python', ['-c', 'print "y"; import time; time.sleep(0.1); print "y"'], max_size=1)
|
||||||
|
|
||||||
def test_reading_twice_from_same_process(self):
|
def test_reading_twice_from_same_process(self):
|
||||||
proc = self.pool.get()
|
proc = self.pool.get()
|
||||||
|
@@ -1,157 +0,0 @@
|
|||||||
"""\
|
|
||||||
@file runloop_test.py
|
|
||||||
@author Donovan Preston
|
|
||||||
|
|
||||||
Copyright (c) 2006-2007, Linden Research, Inc.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import StringIO
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from eventlet import runloop
|
|
||||||
|
|
||||||
|
|
||||||
class TestRunloop(unittest.TestCase):
|
|
||||||
mode = 'static'
|
|
||||||
def test_empty(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
r.schedule_call(0, r.abort)
|
|
||||||
r.run()
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
|
|
||||||
def test_timer(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
r.schedule_call(0.125, r.abort)
|
|
||||||
start_time = time.time()
|
|
||||||
r.run()
|
|
||||||
assert time.time() - start_time >= 0.125
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
def test_observer(self):
|
|
||||||
observed = []
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
r.add_observer(lambda runloop, activity: observed.append(activity))
|
|
||||||
r.schedule_call(0, r.abort)
|
|
||||||
r.run()
|
|
||||||
assert observed == ['entry', 'before_timers', 'before_waiting', 'after_waiting', 'exit']
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
|
|
||||||
def test_remove_observer(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
|
|
||||||
observed = []
|
|
||||||
def observe(runloop, mode):
|
|
||||||
observed.append(mode)
|
|
||||||
r.remove_observer(observe)
|
|
||||||
|
|
||||||
looped = []
|
|
||||||
def run_loop_twice(runloop, mode):
|
|
||||||
if looped:
|
|
||||||
r.abort()
|
|
||||||
else:
|
|
||||||
looped.append(True)
|
|
||||||
|
|
||||||
r.add_observer(observe, 'before_timers')
|
|
||||||
r.add_observer(run_loop_twice, 'after_waiting')
|
|
||||||
r.run()
|
|
||||||
assert len(observed) == 1
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
def test_observer_exception(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
|
|
||||||
observed = []
|
|
||||||
def observe(runloop, mode):
|
|
||||||
observed.append(mode)
|
|
||||||
raise Exception("Squelch me please")
|
|
||||||
|
|
||||||
looped = []
|
|
||||||
def run_loop_twice(runloop, mode):
|
|
||||||
if looped:
|
|
||||||
r.abort()
|
|
||||||
else:
|
|
||||||
looped.append(True)
|
|
||||||
|
|
||||||
saved = sys.stderr
|
|
||||||
sys.stderr = err = StringIO.StringIO()
|
|
||||||
|
|
||||||
r.add_observer(observe, 'before_timers')
|
|
||||||
r.add_observer(run_loop_twice, 'after_waiting')
|
|
||||||
r.run()
|
|
||||||
|
|
||||||
err.seek(0)
|
|
||||||
sys.stderr = saved
|
|
||||||
|
|
||||||
assert len(observed) == 1
|
|
||||||
assert err.read()
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
def test_timer_exception(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
|
|
||||||
observed = []
|
|
||||||
def timer():
|
|
||||||
observed.append(True)
|
|
||||||
raise Exception("Squelch me please")
|
|
||||||
|
|
||||||
looped = []
|
|
||||||
def run_loop_twice(runloop, mode):
|
|
||||||
if looped:
|
|
||||||
r.abort()
|
|
||||||
else:
|
|
||||||
looped.append(True)
|
|
||||||
|
|
||||||
saved = sys.stderr
|
|
||||||
sys.stderr = err = StringIO.StringIO()
|
|
||||||
|
|
||||||
r.schedule_call(0, timer)
|
|
||||||
r.add_observer(run_loop_twice, 'after_waiting')
|
|
||||||
r.run()
|
|
||||||
|
|
||||||
err.seek(0)
|
|
||||||
sys.stderr = saved
|
|
||||||
|
|
||||||
assert len(observed) == 1
|
|
||||||
assert err.read()
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
def test_timer_system_exception(self):
|
|
||||||
r = runloop.RunLoop()
|
|
||||||
def timer():
|
|
||||||
raise SystemExit
|
|
||||||
|
|
||||||
r.schedule_call(0, timer)
|
|
||||||
|
|
||||||
caught = []
|
|
||||||
try:
|
|
||||||
r.run()
|
|
||||||
except SystemExit:
|
|
||||||
caught.append(True)
|
|
||||||
|
|
||||||
assert caught
|
|
||||||
assert not r.running
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
|
|
@@ -1,174 +0,0 @@
|
|||||||
"""\
|
|
||||||
@file selecthub.py
|
|
||||||
@author Bob Ippolito
|
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Bob Ippolito
|
|
||||||
Copyright (c) 2007, Linden Research, Inc.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import select
|
|
||||||
import errno
|
|
||||||
import traceback
|
|
||||||
import time
|
|
||||||
from bisect import insort, bisect_left
|
|
||||||
|
|
||||||
from eventlet import greenlib
|
|
||||||
from eventlet import util
|
|
||||||
from eventlet.runloop import RunLoop, Timer
|
|
||||||
|
|
||||||
import greenlet
|
|
||||||
|
|
||||||
class Hub(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.runloop = RunLoop(self.wait)
|
|
||||||
self.readers = {}
|
|
||||||
self.writers = {}
|
|
||||||
self.excs = {}
|
|
||||||
self.descriptors = {}
|
|
||||||
self.descriptor_queue = {}
|
|
||||||
self.greenlet = None
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.process_queue()
|
|
||||||
self.runloop.abort()
|
|
||||||
if self.greenlet is not greenlet.getcurrent():
|
|
||||||
self.switch()
|
|
||||||
|
|
||||||
def schedule_call(self, *args, **kw):
|
|
||||||
return self.runloop.schedule_call(*args, **kw)
|
|
||||||
|
|
||||||
def switch(self):
|
|
||||||
if not self.greenlet:
|
|
||||||
self.greenlet = greenlib.tracked_greenlet()
|
|
||||||
args = ((self.runloop.run,),)
|
|
||||||
else:
|
|
||||||
args = ()
|
|
||||||
try:
|
|
||||||
greenlet.getcurrent().parent = self.greenlet
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return greenlib.switch(self.greenlet, *args)
|
|
||||||
|
|
||||||
def add_descriptor(self, fileno, read=None, write=None, exc=None):
|
|
||||||
self.descriptor_queue[fileno] = read, write, exc
|
|
||||||
|
|
||||||
def remove_descriptor(self, fileno):
|
|
||||||
self.descriptor_queue[fileno] = None, None, None
|
|
||||||
|
|
||||||
def exc_descriptor(self, fileno):
|
|
||||||
# We must handle two cases here, the descriptor
|
|
||||||
# may be changing or removing its exc handler
|
|
||||||
# in the queue, or it may be waiting on the queue.
|
|
||||||
exc = None
|
|
||||||
try:
|
|
||||||
exc = self.descriptor_queue[fileno][2]
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
exc = self.excs[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if exc is not None:
|
|
||||||
try:
|
|
||||||
exc(fileno)
|
|
||||||
except self.runloop.SYSTEM_EXCEPTIONS:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
||||||
|
|
||||||
def squelch_exception(self, fileno, exc_info):
|
|
||||||
traceback.print_exception(*exc_info)
|
|
||||||
print >>sys.stderr, "Removing descriptor: %r" % (fileno,)
|
|
||||||
try:
|
|
||||||
self.remove_descriptor(fileno)
|
|
||||||
except Exception, e:
|
|
||||||
print >>sys.stderr, "Exception while removing descriptor! %r" % (e,)
|
|
||||||
|
|
||||||
def process_queue(self):
|
|
||||||
readers = self.readers
|
|
||||||
writers = self.writers
|
|
||||||
excs = self.excs
|
|
||||||
descriptors = self.descriptors
|
|
||||||
for fileno, rwe in self.descriptor_queue.iteritems():
|
|
||||||
read, write, exc = rwe
|
|
||||||
if read is None and write is None and exc is None:
|
|
||||||
try:
|
|
||||||
del descriptors[fileno]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
del readers[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
del writers[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
del excs[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if read is not None:
|
|
||||||
readers[fileno] = read
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
del readers[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if write is not None:
|
|
||||||
writers[fileno] = write
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
del writers[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if exc is not None:
|
|
||||||
excs[fileno] = exc
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
del excs[fileno]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
descriptors[fileno] = rwe
|
|
||||||
self.descriptor_queue.clear()
|
|
||||||
|
|
||||||
def wait(self, seconds=None):
|
|
||||||
self.process_queue()
|
|
||||||
if not self.descriptors:
|
|
||||||
if seconds:
|
|
||||||
time.sleep(seconds)
|
|
||||||
return
|
|
||||||
readers = self.readers
|
|
||||||
writers = self.writers
|
|
||||||
excs = self.excs
|
|
||||||
try:
|
|
||||||
r, w, ig = util.__original_select__(readers.keys(), writers.keys(), [], seconds)
|
|
||||||
except select.error, e:
|
|
||||||
if e.args[0] == errno.EINTR:
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
SYSTEM_EXCEPTIONS = self.runloop.SYSTEM_EXCEPTIONS
|
|
||||||
for observed, events in ((readers, r), (writers, w)):
|
|
||||||
for fileno in events:
|
|
||||||
try:
|
|
||||||
observed[fileno](fileno)
|
|
||||||
except SYSTEM_EXCEPTIONS:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.squelch_exception(fileno, sys.exc_info())
|
|
12
eventlet/support/__init__.py
Normal file
12
eventlet/support/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""\
|
||||||
|
@file __init__.py
|
||||||
|
@author Nat Goodspeed
|
||||||
|
@date 2008-03-18
|
||||||
|
@brief Make eventlet.support a subpackage
|
||||||
|
|
||||||
|
$LicenseInfo:firstyear=2008&license=internal$
|
||||||
|
Copyright (c) 2008, Linden Research, Inc.
|
||||||
|
$/LicenseInfo$
|
||||||
|
"""
|
||||||
|
|
103
eventlet/support/pycurls.py
Normal file
103
eventlet/support/pycurls.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""http client that uses pycurl
|
||||||
|
"""
|
||||||
|
|
||||||
|
from eventlet import api
|
||||||
|
|
||||||
|
import pycurl
|
||||||
|
|
||||||
|
|
||||||
|
CURL_POLL_NONE = 0
|
||||||
|
CURL_POLL_IN = 1
|
||||||
|
CURL_POLL_OUT = 2
|
||||||
|
CURL_POLL_INOUT = 3
|
||||||
|
CURL_POLL_REMOVE = 4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SUSPENDED_COROS = {}
|
||||||
|
LAST_SOCKET = None
|
||||||
|
LAST_SOCKET_DONE = False
|
||||||
|
|
||||||
|
|
||||||
|
def hub_callback(fileno):
|
||||||
|
print "HUB_CALLBACK", fileno
|
||||||
|
SUSPENDED_COROS[fileno].switch()
|
||||||
|
|
||||||
|
|
||||||
|
def socket_callback(action, socket, user_data, socket_data):
|
||||||
|
global LAST_SOCKET
|
||||||
|
global LAST_SOCKET_DONE
|
||||||
|
LAST_SOCKET = socket
|
||||||
|
LAST_SOCKET_DONE = False
|
||||||
|
print "SOCKET_CALLBACK", action, socket, user_data, socket_data
|
||||||
|
hub = api.get_hub()
|
||||||
|
if action == CURL_POLL_NONE:
|
||||||
|
# nothing to do
|
||||||
|
return
|
||||||
|
elif action == CURL_POLL_IN:
|
||||||
|
print "POLLIN"
|
||||||
|
hub.add_descriptor(socket, read=hub_callback)
|
||||||
|
elif action == CURL_POLL_OUT:
|
||||||
|
print "POLLOUT"
|
||||||
|
hub.add_descriptor(socket, write=hub_callback)
|
||||||
|
elif action == CURL_POLL_INOUT:
|
||||||
|
print "POLLINOUT"
|
||||||
|
hub.add_descriptor(socket, read=hub_callback, write=hub_callback)
|
||||||
|
elif action == CURL_POLL_REMOVE:
|
||||||
|
print "POLLREMOVE"
|
||||||
|
hub.remove_descriptor(socket)
|
||||||
|
LAST_SOCKET_DONE = True
|
||||||
|
|
||||||
|
|
||||||
|
THE_MULTI = pycurl.CurlMulti()
|
||||||
|
THE_MULTI.setopt(pycurl.M_SOCKETFUNCTION, socket_callback)
|
||||||
|
|
||||||
|
|
||||||
|
def read(*data):
|
||||||
|
print "READ", data
|
||||||
|
|
||||||
|
|
||||||
|
def write(*data):
|
||||||
|
print "WRITE", data
|
||||||
|
|
||||||
|
|
||||||
|
def runloop_observer(*_):
|
||||||
|
result, numhandles = THE_MULTI.socket_all()
|
||||||
|
print "PERFORM RESULT", result
|
||||||
|
while result == pycurl.E_CALL_MULTI_PERFORM:
|
||||||
|
result, numhandles = THE_MULTI.socket_all()
|
||||||
|
print "PERFORM RESULT2", result
|
||||||
|
|
||||||
|
|
||||||
|
def get(url):
|
||||||
|
hub = api.get_hub()
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(pycurl.URL, url)
|
||||||
|
#c.setopt(pycurl.M_SOCKETFUNCTION, socket_callback)
|
||||||
|
c.setopt(pycurl.WRITEFUNCTION, write)
|
||||||
|
c.setopt(pycurl.READFUNCTION, read)
|
||||||
|
c.setopt(pycurl.NOSIGNAL, 1)
|
||||||
|
THE_MULTI.add_handle(c)
|
||||||
|
hub.add_observer(runloop_observer, 'before_waiting')
|
||||||
|
while True:
|
||||||
|
print "TOP"
|
||||||
|
result, numhandles = THE_MULTI.socket_all()
|
||||||
|
print "PERFORM RESULT", result
|
||||||
|
while result == pycurl.E_CALL_MULTI_PERFORM:
|
||||||
|
result, numhandles = THE_MULTI.socket_all()
|
||||||
|
print "PERFORM RESULT2", result
|
||||||
|
|
||||||
|
if LAST_SOCKET_DONE:
|
||||||
|
break
|
||||||
|
|
||||||
|
SUSPENDED_COROS[LAST_SOCKET] = api.getcurrent()
|
||||||
|
print "SUSPENDED", SUSPENDED_COROS
|
||||||
|
api.get_hub().switch()
|
||||||
|
print "BOTTOM"
|
||||||
|
|
||||||
|
if not SUSPENDED_COROS:
|
||||||
|
hub.remove_observer(runloop_observer)
|
||||||
|
|
||||||
|
|
||||||
|
#from eventlet.support import pycurls
|
||||||
|
#reload(pycurls); from eventlet.support import pycurls; pycurls.get('http://localhost/')
|
@@ -1,5 +1,5 @@
|
|||||||
"""\
|
"""\
|
||||||
@file pylibsupport.py
|
@file support.pylib.py
|
||||||
@author Donovan Preston
|
@author Donovan Preston
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Donovan Preston
|
Copyright (c) 2005-2006, Donovan Preston
|
44
eventlet/support/stacklesspypys.py
Normal file
44
eventlet/support/stacklesspypys.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""\
|
||||||
|
@file stacklesspypysupport.py
|
||||||
|
@author Donovan Preston
|
||||||
|
|
||||||
|
Copyright (c) 2008, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from stackless import greenlet
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
def emulate():
|
||||||
|
module = types.ModuleType('greenlet')
|
||||||
|
sys.modules['greenlet'] = module
|
||||||
|
module.greenlet = greenlet
|
||||||
|
module.getcurrent = greenlet.getcurrent
|
||||||
|
module.GreenletExit = greenlet.GreenletExit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -48,7 +48,7 @@ class FirstSwitch(object):
|
|||||||
self.gr = gr
|
self.gr = gr
|
||||||
|
|
||||||
def __call__(self, *args, **kw):
|
def __call__(self, *args, **kw):
|
||||||
print "first call", args, kw
|
#print "first call", args, kw
|
||||||
gr = self.gr
|
gr = self.gr
|
||||||
del gr.switch
|
del gr.switch
|
||||||
run, gr.run = gr.run, None
|
run, gr.run = gr.run, None
|
||||||
@@ -72,7 +72,7 @@ class greenlet(object):
|
|||||||
self.switch = FirstSwitch(self)
|
self.switch = FirstSwitch(self)
|
||||||
|
|
||||||
def switch(self, *args):
|
def switch(self, *args):
|
||||||
print "switch", args
|
#print "switch", args
|
||||||
global caller
|
global caller
|
||||||
caller = stackless.getcurrent()
|
caller = stackless.getcurrent()
|
||||||
coro_args[self] = args
|
coro_args[self] = args
|
@@ -1,5 +1,5 @@
|
|||||||
"""\
|
"""\
|
||||||
@file twistedsupport.py
|
@file support.twisted.py
|
||||||
@author Donovan Preston
|
@author Donovan Preston
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Donovan Preston
|
Copyright (c) 2005-2006, Donovan Preston
|
||||||
@@ -82,37 +82,45 @@ class EventletReactor(posixbase.PosixReactorBase):
|
|||||||
self.running = True
|
self.running = True
|
||||||
self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None)
|
self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None)
|
||||||
## schedule a call way in the future, and cancel it in stop?
|
## schedule a call way in the future, and cancel it in stop?
|
||||||
api.get_hub().runloop.run()
|
api.get_hub().run()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._stopper.cancel()
|
self._stopper.cancel()
|
||||||
posixbase.PosixReactorBase.stop(self)
|
posixbase.PosixReactorBase.stop(self)
|
||||||
api.get_hub().remove_descriptor(self._readers.keys()[0])
|
api.get_hub().remove_descriptor(self._readers.keys()[0])
|
||||||
api.get_hub().runloop.abort()
|
api.get_hub().abort()
|
||||||
|
|
||||||
def addReader(self, reader):
|
def addReader(self, reader):
|
||||||
|
print "NEW READER", reader.fileno()
|
||||||
fileno = reader.fileno()
|
fileno = reader.fileno()
|
||||||
self._readers[fileno] = reader
|
self._readers[fileno] = reader
|
||||||
api.get_hub().add_descriptor(fileno, read=self._got_read)
|
api.get_hub().add_descriptor(fileno, read=self._got_read)
|
||||||
|
|
||||||
def _got_read(self, fileno):
|
def _got_read(self, fileno):
|
||||||
|
print "got read on", fileno, self._readers[fileno]
|
||||||
|
api.get_hub().add_descriptor(fileno, read=self._got_read)
|
||||||
self._readers[fileno].doRead()
|
self._readers[fileno].doRead()
|
||||||
|
|
||||||
def addWriter(self, writer):
|
def addWriter(self, writer):
|
||||||
|
print "NEW WRITER", writer.fileno()
|
||||||
fileno = writer.fileno()
|
fileno = writer.fileno()
|
||||||
self._writers[fileno] = writer
|
self._writers[fileno] = writer
|
||||||
api.get_hub().add_descriptor(fileno, write=self._got_write)
|
api.get_hub().add_descriptor(fileno, write=self._got_write)
|
||||||
|
|
||||||
def _got_write(self, fileno):
|
def _got_write(self, fileno):
|
||||||
|
print "got write on", fileno, self._writers[fileno]
|
||||||
|
api.get_hub().add_descriptor(fileno, write=self._got_write)
|
||||||
self._writers[fileno].doWrite()
|
self._writers[fileno].doWrite()
|
||||||
|
|
||||||
def removeReader(self, reader):
|
def removeReader(self, reader):
|
||||||
|
print "removing reader", reader.fileno()
|
||||||
fileno = reader.fileno()
|
fileno = reader.fileno()
|
||||||
if fileno in self._readers:
|
if fileno in self._readers:
|
||||||
self._readers.pop(fileno)
|
self._readers.pop(fileno)
|
||||||
api.get_hub().remove_descriptor(fileno)
|
api.get_hub().remove_descriptor(fileno)
|
||||||
|
|
||||||
def removeWriter(self, writer):
|
def removeWriter(self, writer):
|
||||||
|
print "removing writer", writer.fileno()
|
||||||
fileno = writer.fileno()
|
fileno = writer.fileno()
|
||||||
if fileno in self._writers:
|
if fileno in self._writers:
|
||||||
self._writers.pop(fileno)
|
self._writers.pop(fileno)
|
||||||
@@ -122,13 +130,13 @@ class EventletReactor(posixbase.PosixReactorBase):
|
|||||||
return self._removeAll(self._readers.values(), self._writers.values())
|
return self._removeAll(self._readers.values(), self._writers.values())
|
||||||
|
|
||||||
|
|
||||||
def emulate():
|
def install():
|
||||||
if not _working:
|
if not _working:
|
||||||
raise RuntimeError, "Can't use twistedsupport because zope.interface is not installed."
|
raise RuntimeError, "Can't use support.twisted because zope.interface is not installed."
|
||||||
reactor = EventletReactor()
|
reactor = EventletReactor()
|
||||||
from twisted.internet.main import installReactor
|
from twisted.internet.main import installReactor
|
||||||
installReactor(reactor)
|
installReactor(reactor)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['emulate']
|
__all__ = ['install']
|
||||||
|
|
15
eventlet/test_server.crt
Normal file
15
eventlet/test_server.crt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICYzCCAcwCCQD5jx1Aa0dytjANBgkqhkiG9w0BAQQFADB2MQswCQYDVQQGEwJU
|
||||||
|
UzENMAsGA1UECBMEVGVzdDENMAsGA1UEBxMEVGVzdDEWMBQGA1UEChMNVGVzdCBF
|
||||||
|
dmVudGxldDENMAsGA1UECxMEVGVzdDENMAsGA1UEAxMEVGVzdDETMBEGCSqGSIb3
|
||||||
|
DQEJARYEVGVzdDAeFw0wODA3MDgyMTExNDJaFw0xMDAyMDgwODE1MTBaMHYxCzAJ
|
||||||
|
BgNVBAYTAlRTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MRYwFAYDVQQK
|
||||||
|
Ew1UZXN0IEV2ZW50bGV0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwRUZXN0MRMw
|
||||||
|
EQYJKoZIhvcNAQkBFgRUZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDM
|
||||||
|
WcyeIiHQuEGQxgTIvu0aOW4iRFAyUEi8pLWNCxMEHglF8k6OxFVq7XWZMDnDFVnb
|
||||||
|
ZjmQh5Tc21Ae6cXzxXln578fROXHEzXo3Is8HUlq3ug1yYOGHjxw++Opjf1uoHwP
|
||||||
|
EBUKsz/flS7knuscgFM9FO05KSPn2wHnZeIDta4yTwIDAQABMA0GCSqGSIb3DQEB
|
||||||
|
BAUAA4GBAKM71aP0r26gEEEBzovfXm1IwKav6R9/xiWsJ4pFsUXVotcaIjcVBDG1
|
||||||
|
Z7tz688hokb+GNxsTI2gNfqanqUnfP9wZxnKRmfTSOvb5aWHIiaiMXSgjiPlqBcm
|
||||||
|
6mnSeEbSMM9cw479wWhh1YqY8tf3gYJa+sxznVWLSfVLpsjRMphe
|
||||||
|
-----END CERTIFICATE-----
|
15
eventlet/test_server.key
Normal file
15
eventlet/test_server.key
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQDMWcyeIiHQuEGQxgTIvu0aOW4iRFAyUEi8pLWNCxMEHglF8k6O
|
||||||
|
xFVq7XWZMDnDFVnbZjmQh5Tc21Ae6cXzxXln578fROXHEzXo3Is8HUlq3ug1yYOG
|
||||||
|
Hjxw++Opjf1uoHwPEBUKsz/flS7knuscgFM9FO05KSPn2wHnZeIDta4yTwIDAQAB
|
||||||
|
AoGBAKWfvq0IIvok7Ncm92ew/0D6/R1+2rT8xwdGQ/Nt31q98WwkqLEjxctlbKPd
|
||||||
|
J2PLIUomf0955BhhFH4JoSwjiHJQ6uishY7srjQQDX/Dxdi5wZAyxYCIVW/kAA9N
|
||||||
|
/u2s75hSD3s/rqAwOZ182DwAPIqJc4KQoYzvlKERSMDT1PJhAkEA5SUFsiSzBEMX
|
||||||
|
FyZ++ZMMs1vHrTu5oTK7WHznh9lk7dvsnp9BoUPqhiu8iJ7Q23zj0u5asz2czu11
|
||||||
|
nnczXgU6XwJBAORM5Ib4I7nAsoUWn9wDiTwVQeE+D9P1ac9p7EHm7XXuf8o2irRZ
|
||||||
|
wYYfpXXsjk496YfyQFcQRMk0tU0gegCP7hECQFWRWqwoajUoPIInnPjjwbVki48U
|
||||||
|
I4CfqjgkBG3Fb5wnKRgezmpDK1vJD1FRRRsBay4EVhhi5KCdKfPv/V2ZxC8CQQCu
|
||||||
|
U5SxBytofJ8UhxkcTErvaR/8GYLGi//21GAGVop+YdaMlydE3cCrZODYcgCb+CSp
|
||||||
|
nS7KDG8p4KiMMz9VzJGxAkEAv85K6Sa3H8g9h7LwopBZ5tFNZUaFWo7lEP7DDMH0
|
||||||
|
eckZTb1JVpyT/8zrDtsis4WlV9zVkVHxkIaad503BjqvEQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@@ -29,7 +29,7 @@ useful for debugging leaking timers, to find out where the timer was set up. """
|
|||||||
_g_debug = False
|
_g_debug = False
|
||||||
|
|
||||||
class Timer(object):
|
class Timer(object):
|
||||||
__slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback']
|
#__slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback', 'impltimer']
|
||||||
def __init__(self, seconds, cb, *args, **kw):
|
def __init__(self, seconds, cb, *args, **kw):
|
||||||
"""Create a timer.
|
"""Create a timer.
|
||||||
seconds: The minimum number of seconds to wait before calling
|
seconds: The minimum number of seconds to wait before calling
|
||||||
@@ -66,14 +66,17 @@ class Timer(object):
|
|||||||
"""Schedule this timer to run in the current runloop.
|
"""Schedule this timer to run in the current runloop.
|
||||||
"""
|
"""
|
||||||
self.called = False
|
self.called = False
|
||||||
self.scheduled_time = get_hub().runloop.add_timer(self)
|
self.scheduled_time = get_hub().add_timer(self)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self, *args):
|
||||||
if not self.called:
|
if not self.called:
|
||||||
self.called = True
|
self.called = True
|
||||||
cb, args, kw = self.tpl
|
cb, args, kw = self.tpl
|
||||||
|
try:
|
||||||
cb(*args, **kw)
|
cb(*args, **kw)
|
||||||
|
finally:
|
||||||
|
get_hub().timer_finished(self)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
"""Prevent this timer from being called. If the timer has already
|
"""Prevent this timer from being called. If the timer has already
|
||||||
@@ -81,3 +84,4 @@ class Timer(object):
|
|||||||
"""
|
"""
|
||||||
self.cancelled = True
|
self.cancelled = True
|
||||||
self.called = True
|
self.called = True
|
||||||
|
get_hub().timer_canceled(self)
|
||||||
|
@@ -24,7 +24,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from eventlet import api, runloop, tests, timer
|
from eventlet import api, tests, timer
|
||||||
|
|
||||||
class TestTimer(tests.TestCase):
|
class TestTimer(tests.TestCase):
|
||||||
mode = 'static'
|
mode = 'static'
|
||||||
@@ -35,32 +35,32 @@ class TestTimer(tests.TestCase):
|
|||||||
assert t.tpl == t2.tpl
|
assert t.tpl == t2.tpl
|
||||||
assert t.called == t2.called
|
assert t.called == t2.called
|
||||||
|
|
||||||
def test_cancel(self):
|
## def test_cancel(self):
|
||||||
r = runloop.RunLoop()
|
## r = runloop.RunLoop()
|
||||||
called = []
|
## called = []
|
||||||
t = timer.Timer(0, lambda: called.append(True))
|
## t = timer.Timer(0, lambda: called.append(True))
|
||||||
t.cancel()
|
## t.cancel()
|
||||||
r.add_timer(t)
|
## r.add_timer(t)
|
||||||
r.add_observer(lambda r, activity: r.abort(), 'after_waiting')
|
## r.add_observer(lambda r, activity: r.abort(), 'after_waiting')
|
||||||
r.run()
|
## r.run()
|
||||||
assert not called
|
## assert not called
|
||||||
assert not r.running
|
## assert not r.running
|
||||||
|
|
||||||
def test_schedule(self):
|
def test_schedule(self):
|
||||||
hub = api.get_hub()
|
hub = api.get_hub()
|
||||||
r = hub.runloop
|
## r = hub.runloop
|
||||||
# clean up the runloop, preventing side effects from previous tests
|
# clean up the runloop, preventing side effects from previous tests
|
||||||
# on this thread
|
# on this thread
|
||||||
if r.running:
|
if hub.running:
|
||||||
r.abort()
|
hub.abort()
|
||||||
api.sleep(0)
|
api.sleep(0)
|
||||||
called = []
|
called = []
|
||||||
t = timer.Timer(0, lambda: (called.append(True), hub.runloop.abort()))
|
t = timer.Timer(0, lambda: (called.append(True), hub.abort()))
|
||||||
t.schedule()
|
t.schedule()
|
||||||
r.default_sleep = lambda: 0.0
|
hub.default_sleep = lambda: 0.0
|
||||||
r.run()
|
hub.run()
|
||||||
assert called
|
assert called
|
||||||
assert not r.running
|
assert not hub.running
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -22,7 +22,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
import threading
|
import threading
|
||||||
|
except ImportError:
|
||||||
|
class Dummy(object): pass
|
||||||
|
|
||||||
|
the_thread = Dummy()
|
||||||
|
|
||||||
|
class threading(object):
|
||||||
|
def currentThread():
|
||||||
|
return the_thread
|
||||||
|
currentThread = staticmethod(currentThread)
|
||||||
|
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
__all__ = ['local']
|
__all__ = ['local']
|
||||||
|
@@ -17,22 +17,25 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os, socket, time, threading
|
import os, threading
|
||||||
import Queue
|
import Queue
|
||||||
|
|
||||||
from sys import stdout
|
from sys import stdout
|
||||||
from Queue import Empty, Queue
|
from Queue import Empty, Queue
|
||||||
|
|
||||||
from eventlet import api, coros, httpc, httpd, util, wrappedfd
|
from eventlet import api, coros, httpc, httpd, greenio
|
||||||
from eventlet.api import trampoline, get_hub
|
from eventlet.api import trampoline, get_hub
|
||||||
|
|
||||||
_rpipe, _wpipe = os.pipe()
|
_rpipe, _wpipe = os.pipe()
|
||||||
_rfile = os.fdopen(_rpipe,"r",0)
|
_rfile = os.fdopen(_rpipe,"r",0)
|
||||||
_wrap_rfile = wrappedfd.wrapped_file(_rfile)
|
## Work whether or not wrap_pipe_with_coroutine_pipe was called
|
||||||
util.set_nonblocking(_rfile)
|
if not isinstance(_rfile, greenio.GreenPipe):
|
||||||
|
_rfile = greenio.GreenPipe(_rfile)
|
||||||
|
|
||||||
|
|
||||||
def _signal_t2e():
|
def _signal_t2e():
|
||||||
nwritten = os.write(_wpipe,' ')
|
from eventlet import util
|
||||||
|
nwritten = util.__original_write__(_wpipe, ' ')
|
||||||
|
|
||||||
_reqq = Queue(maxsize=-1)
|
_reqq = Queue(maxsize=-1)
|
||||||
_rspq = Queue(maxsize=-1)
|
_rspq = Queue(maxsize=-1)
|
||||||
@@ -40,7 +43,7 @@ _rspq = Queue(maxsize=-1)
|
|||||||
def tpool_trampoline():
|
def tpool_trampoline():
|
||||||
global _reqq, _rspq
|
global _reqq, _rspq
|
||||||
while(True):
|
while(True):
|
||||||
_c = _wrap_rfile.recv(1)
|
_c = _rfile.recv(1)
|
||||||
assert(_c != "")
|
assert(_c != "")
|
||||||
while not _rspq.empty():
|
while not _rspq.empty():
|
||||||
try:
|
try:
|
||||||
@@ -58,7 +61,10 @@ def esend(meth,*args, **kwargs):
|
|||||||
def tworker():
|
def tworker():
|
||||||
global _reqq, _rspq
|
global _reqq, _rspq
|
||||||
while(True):
|
while(True):
|
||||||
(e,meth,args,kwargs) = _reqq.get()
|
msg = _reqq.get()
|
||||||
|
if msg is None:
|
||||||
|
return
|
||||||
|
(e,meth,args,kwargs) = msg
|
||||||
rv = None
|
rv = None
|
||||||
try:
|
try:
|
||||||
rv = meth(*args,**kwargs)
|
rv = meth(*args,**kwargs)
|
||||||
@@ -69,6 +75,7 @@ def tworker():
|
|||||||
_rspq.put((e,rv))
|
_rspq.put((e,rv))
|
||||||
_signal_t2e()
|
_signal_t2e()
|
||||||
|
|
||||||
|
|
||||||
def erecv(e):
|
def erecv(e):
|
||||||
rv = e.wait()
|
rv = e.wait()
|
||||||
if isinstance(rv,tuple) and len(rv) == 4 and isinstance(rv[0],Exception):
|
if isinstance(rv,tuple) and len(rv) == 4 and isinstance(rv[0],Exception):
|
||||||
@@ -79,11 +86,20 @@ def erecv(e):
|
|||||||
raise e
|
raise e
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def erpc(meth,*args, **kwargs):
|
|
||||||
|
def execute(meth,*args, **kwargs):
|
||||||
|
"""Execute method in a thread, blocking the current
|
||||||
|
coroutine until the method completes.
|
||||||
|
"""
|
||||||
e = esend(meth,*args,**kwargs)
|
e = esend(meth,*args,**kwargs)
|
||||||
rv = erecv(e)
|
rv = erecv(e)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
## TODO deprecate
|
||||||
|
erpc = execute
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Proxy(object):
|
class Proxy(object):
|
||||||
""" a simple proxy-wrapper of any object that comes with a methods-only interface,
|
""" a simple proxy-wrapper of any object that comes with a methods-only interface,
|
||||||
in order to forward every method invocation onto a thread in the native-thread pool.
|
in order to forward every method invocation onto a thread in the native-thread pool.
|
||||||
@@ -92,6 +108,8 @@ class Proxy(object):
|
|||||||
code only. """
|
code only. """
|
||||||
def __init__(self, obj,autowrap=()):
|
def __init__(self, obj,autowrap=()):
|
||||||
self._obj = obj
|
self._obj = obj
|
||||||
|
if isinstance(autowrap, (list, tuple)):
|
||||||
|
autowrap = dict([(x, True) for x in autowrap])
|
||||||
self._autowrap = autowrap
|
self._autowrap = autowrap
|
||||||
|
|
||||||
def __getattr__(self,attr_name):
|
def __getattr__(self,attr_name):
|
||||||
@@ -102,14 +120,15 @@ class Proxy(object):
|
|||||||
if kwargs.pop('nonblocking',False):
|
if kwargs.pop('nonblocking',False):
|
||||||
rv = f(*args, **kwargs)
|
rv = f(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
rv = erpc(f,*args,**kwargs)
|
rv = execute(f,*args,**kwargs)
|
||||||
if type(rv) in self._autowrap:
|
if type(rv) in self._autowrap:
|
||||||
return Proxy(rv)
|
return Proxy(rv, self._autowrap)
|
||||||
else:
|
else:
|
||||||
return rv
|
return rv
|
||||||
return doit
|
return doit
|
||||||
|
|
||||||
_nthreads = 20
|
|
||||||
|
_nthreads = int(os.environ.get('EVENTLET_THREADPOOL_SIZE', 20))
|
||||||
_threads = {}
|
_threads = {}
|
||||||
def setup():
|
def setup():
|
||||||
global _threads
|
global _threads
|
||||||
@@ -121,3 +140,11 @@ def setup():
|
|||||||
api.spawn(tpool_trampoline)
|
api.spawn(tpool_trampoline)
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
|
|
||||||
|
def killall():
|
||||||
|
for i in _threads:
|
||||||
|
_reqq.put(None)
|
||||||
|
for thr in _threads.values():
|
||||||
|
thr.join()
|
||||||
|
|
||||||
|
@@ -39,13 +39,16 @@ class yadda(object):
|
|||||||
def foo(self,when,n=None):
|
def foo(self,when,n=None):
|
||||||
assert(n is not None)
|
assert(n is not None)
|
||||||
prnt("foo: %s, %s" % (when,n))
|
prnt("foo: %s, %s" % (when,n))
|
||||||
time.sleep(r.random())
|
time.sleep(r.random()/20.0)
|
||||||
return n
|
return n
|
||||||
|
|
||||||
def sender_loop(pfx):
|
def sender_loop(pfx):
|
||||||
n = 0
|
n = 0
|
||||||
obj = tpool.Proxy(yadda())
|
obj = tpool.Proxy(yadda())
|
||||||
while n < 10:
|
while n < 10:
|
||||||
|
if not (n % 5):
|
||||||
|
stdout.write('.')
|
||||||
|
stdout.flush()
|
||||||
api.sleep(0)
|
api.sleep(0)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
prnt("%s: send (%s,%s)" % (pfx,now,n))
|
prnt("%s: send (%s,%s)" % (pfx,now,n))
|
||||||
|
234
eventlet/util.py
234
eventlet/util.py
@@ -24,9 +24,9 @@ THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import fcntl
|
|
||||||
import socket
|
|
||||||
import select
|
import select
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -63,22 +63,14 @@ def g_log(*args):
|
|||||||
ident = 'greenlet-%d' % (g_id,)
|
ident = 'greenlet-%d' % (g_id,)
|
||||||
print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args)))
|
print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args)))
|
||||||
|
|
||||||
CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)
|
|
||||||
CONNECT_SUCCESS = (0, errno.EISCONN)
|
|
||||||
def socket_connect(descriptor, address):
|
|
||||||
err = descriptor.connect_ex(address)
|
|
||||||
if err in CONNECT_ERR:
|
|
||||||
return None
|
|
||||||
if err not in CONNECT_SUCCESS:
|
|
||||||
raise socket.error(err, errno.errorcode[err])
|
|
||||||
return descriptor
|
|
||||||
|
|
||||||
__original_socket__ = socket.socket
|
__original_socket__ = socket.socket
|
||||||
|
__original_gethostbyname__ = socket.gethostbyname
|
||||||
|
__original_getaddrinfo__ = socket.getaddrinfo
|
||||||
|
__original_fromfd__ = socket.fromfd
|
||||||
|
|
||||||
def tcp_socket():
|
def tcp_socket():
|
||||||
s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM)
|
s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
set_nonblocking(s)
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
@@ -87,9 +79,9 @@ __original_ssl__ = socket.ssl
|
|||||||
|
|
||||||
def wrap_ssl(sock, certificate=None, private_key=None):
|
def wrap_ssl(sock, certificate=None, private_key=None):
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
from eventlet import wrappedfd, util
|
from eventlet import greenio, util
|
||||||
context = SSL.Context(SSL.SSLv23_METHOD)
|
context = SSL.Context(SSL.SSLv23_METHOD)
|
||||||
print certificate, private_key
|
#print certificate, private_key
|
||||||
if certificate is not None:
|
if certificate is not None:
|
||||||
context.use_certificate_file(certificate)
|
context.use_certificate_file(certificate)
|
||||||
if private_key is not None:
|
if private_key is not None:
|
||||||
@@ -99,19 +91,89 @@ def wrap_ssl(sock, certificate=None, private_key=None):
|
|||||||
## TODO only do this on client sockets? how?
|
## TODO only do this on client sockets? how?
|
||||||
connection = SSL.Connection(context, sock)
|
connection = SSL.Connection(context, sock)
|
||||||
connection.set_connect_state()
|
connection.set_connect_state()
|
||||||
return wrappedfd.wrapped_fd(connection)
|
return greenio.GreenSSL(connection)
|
||||||
|
|
||||||
|
|
||||||
def wrap_socket_with_coroutine_socket():
|
socket_already_wrapped = False
|
||||||
|
def wrap_socket_with_coroutine_socket(use_thread_pool=True):
|
||||||
|
global socket_already_wrapped
|
||||||
|
if socket_already_wrapped:
|
||||||
|
return
|
||||||
|
|
||||||
def new_socket(*args, **kw):
|
def new_socket(*args, **kw):
|
||||||
from eventlet import wrappedfd
|
from eventlet import greenio
|
||||||
s = __original_socket__(*args, **kw)
|
return greenio.GreenSocket(__original_socket__(*args, **kw))
|
||||||
set_nonblocking(s)
|
|
||||||
return wrappedfd.wrapped_fd(s)
|
|
||||||
socket.socket = new_socket
|
socket.socket = new_socket
|
||||||
|
|
||||||
socket.ssl = wrap_ssl
|
socket.ssl = wrap_ssl
|
||||||
|
|
||||||
|
if use_thread_pool:
|
||||||
|
from eventlet import tpool
|
||||||
|
def new_gethostbyname(*args, **kw):
|
||||||
|
return tpool.execute(
|
||||||
|
__original_gethostbyname__, *args, **kw)
|
||||||
|
socket.gethostbyname = new_gethostbyname
|
||||||
|
|
||||||
|
def new_getaddrinfo(*args, **kw):
|
||||||
|
return tpool.execute(
|
||||||
|
__original_getaddrinfo__, *args, **kw)
|
||||||
|
socket.getaddrinfo = new_getaddrinfo
|
||||||
|
|
||||||
|
def new_fromfd(*args, **kw):
|
||||||
|
from eventlet import greenio
|
||||||
|
return greenio.GreenSocket(__original_fromfd__(*args, **kw))
|
||||||
|
socket.fromfd = new_fromfd
|
||||||
|
|
||||||
|
socket_already_wrapped = True
|
||||||
|
|
||||||
|
|
||||||
|
__original_fdopen__ = os.fdopen
|
||||||
|
__original_read__ = os.read
|
||||||
|
__original_write__ = os.write
|
||||||
|
__original_waitpid__ = os.waitpid
|
||||||
|
__original_fork__ = os.fork
|
||||||
|
## TODO wrappings for popen functions? not really needed since Process object exists?
|
||||||
|
|
||||||
|
|
||||||
|
pipes_already_wrapped = False
|
||||||
|
def wrap_pipes_with_coroutine_pipes():
|
||||||
|
from eventlet import processes ## Make sure the signal handler is installed
|
||||||
|
global pipes_already_wrapped
|
||||||
|
if pipes_already_wrapped:
|
||||||
|
return
|
||||||
|
def new_fdopen(*args, **kw):
|
||||||
|
from eventlet import greenio
|
||||||
|
return greenio.GreenPipe(__original_fdopen__(*args, **kw))
|
||||||
|
def new_read(fd, *args, **kw):
|
||||||
|
from eventlet import api
|
||||||
|
api.trampoline(fd, read=True)
|
||||||
|
return __original_read__(fd, *args, **kw)
|
||||||
|
def new_write(fd, *args, **kw):
|
||||||
|
from eventlet import api
|
||||||
|
api.trampoline(fd, write=True)
|
||||||
|
return __original_write__(fd, *args, **kw)
|
||||||
|
def new_fork(*args, **kwargs):
|
||||||
|
pid = __original_fork__()
|
||||||
|
if pid:
|
||||||
|
processes._add_child_pid(pid)
|
||||||
|
return pid
|
||||||
|
def new_waitpid(pid, options):
|
||||||
|
from eventlet import processes
|
||||||
|
evt = processes.CHILD_EVENTS.get(pid)
|
||||||
|
if not evt:
|
||||||
|
return 0, 0
|
||||||
|
if options == os.WNOHANG:
|
||||||
|
if evt.ready():
|
||||||
|
return pid, evt.wait()
|
||||||
|
return 0, 0
|
||||||
|
elif options:
|
||||||
|
return __original_waitpid__(pid, result)
|
||||||
|
return pid, evt.wait()
|
||||||
|
os.fdopen = new_fdopen
|
||||||
|
os.read = new_read
|
||||||
|
os.write = new_write
|
||||||
|
os.fork = new_fork
|
||||||
|
os.waitpid = new_waitpid
|
||||||
|
|
||||||
__original_select__ = select.select
|
__original_select__ = select.select
|
||||||
|
|
||||||
@@ -150,86 +212,54 @@ def wrap_select_with_coroutine_select():
|
|||||||
select.select = fake_select
|
select.select = fake_select
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import threading
|
||||||
|
__original_threadlocal__ = threading.local
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_threading_local_with_coro_local():
|
||||||
|
"""monkey patch threading.local with something that is
|
||||||
|
greenlet aware. Since greenlets cannot cross threads,
|
||||||
|
so this should be semantically identical to threadlocal.local
|
||||||
|
"""
|
||||||
|
from eventlet import api
|
||||||
|
def get_ident():
|
||||||
|
return id(api.getcurrent())
|
||||||
|
|
||||||
|
class local(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.__dict__['__objs'] = {}
|
||||||
|
|
||||||
|
def __getattr__(self, attr, g=get_ident):
|
||||||
|
try:
|
||||||
|
return self.__dict__['__objs'][g()][attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
"No variable %s defined for the thread %s"
|
||||||
|
% (attr, g()))
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value, g=get_ident):
|
||||||
|
self.__dict__['__objs'].setdefault(g(), {})[attr] = value
|
||||||
|
|
||||||
|
def __delattr__(self, attr, g=get_ident):
|
||||||
|
try:
|
||||||
|
del self.__dict__['__objs'][g()][attr]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(
|
||||||
|
"No variable %s defined for thread %s"
|
||||||
|
% (attr, g()))
|
||||||
|
|
||||||
|
threading.local = local
|
||||||
|
|
||||||
|
|
||||||
def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50):
|
def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50):
|
||||||
set_reuse_addr(descriptor)
|
set_reuse_addr(descriptor)
|
||||||
descriptor.bind(addr)
|
descriptor.bind(addr)
|
||||||
descriptor.listen(backlog)
|
descriptor.listen(backlog)
|
||||||
return descriptor
|
return descriptor
|
||||||
|
|
||||||
def socket_accept(descriptor):
|
|
||||||
try:
|
|
||||||
return descriptor.accept()
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EWOULDBLOCK:
|
|
||||||
return None
|
|
||||||
raise
|
|
||||||
|
|
||||||
def socket_send(descriptor, data):
|
|
||||||
try:
|
|
||||||
return descriptor.send(data)
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EWOULDBLOCK:
|
|
||||||
return 0
|
|
||||||
raise
|
|
||||||
except SSL.WantWriteError:
|
|
||||||
return 0
|
|
||||||
except SSL.WantReadError:
|
|
||||||
return 0
|
|
||||||
# trap this error
|
|
||||||
except SSL.SysCallError, e:
|
|
||||||
(ssl_errno, ssl_errstr) = e
|
|
||||||
if ssl_errno == -1 or ssl_errno > 0:
|
|
||||||
raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET])
|
|
||||||
raise
|
|
||||||
|
|
||||||
# winsock sometimes throws ENOTCONN
|
|
||||||
SOCKET_CLOSED = (errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN)
|
|
||||||
def socket_recv(descriptor, buflen):
|
|
||||||
try:
|
|
||||||
return descriptor.recv(buflen)
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EWOULDBLOCK:
|
|
||||||
return None
|
|
||||||
if e[0] in SOCKET_CLOSED:
|
|
||||||
return ''
|
|
||||||
raise
|
|
||||||
except SSL.WantReadError:
|
|
||||||
return None
|
|
||||||
except SSL.ZeroReturnError:
|
|
||||||
return ''
|
|
||||||
except SSL.SysCallError, e:
|
|
||||||
(ssl_errno, ssl_errstr) = e
|
|
||||||
if ssl_errno == -1 or ssl_errno > 0:
|
|
||||||
raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET])
|
|
||||||
raise
|
|
||||||
|
|
||||||
def file_recv(fd, buflen):
|
|
||||||
try:
|
|
||||||
return fd.read(buflen)
|
|
||||||
except IOError, e:
|
|
||||||
if e[0] == errno.EAGAIN:
|
|
||||||
return None
|
|
||||||
return ''
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EPIPE:
|
|
||||||
return ''
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def file_send(fd, data):
|
|
||||||
try:
|
|
||||||
fd.write(data)
|
|
||||||
fd.flush()
|
|
||||||
return len(data)
|
|
||||||
except IOError, e:
|
|
||||||
if e[0] == errno.EAGAIN:
|
|
||||||
return 0
|
|
||||||
except ValueError, e:
|
|
||||||
written = 0
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EPIPE:
|
|
||||||
written = 0
|
|
||||||
|
|
||||||
|
|
||||||
def set_reuse_addr(descriptor):
|
def set_reuse_addr(descriptor):
|
||||||
try:
|
try:
|
||||||
@@ -241,17 +271,3 @@ def set_reuse_addr(descriptor):
|
|||||||
except socket.error:
|
except socket.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_nonblocking(descriptor):
|
|
||||||
if hasattr(descriptor, 'setblocking'):
|
|
||||||
# socket
|
|
||||||
descriptor.setblocking(0)
|
|
||||||
else:
|
|
||||||
# fd
|
|
||||||
if hasattr(descriptor, 'fileno'):
|
|
||||||
fd = descriptor.fileno()
|
|
||||||
else:
|
|
||||||
fd = descriptor
|
|
||||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
|
||||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
||||||
return descriptor
|
|
||||||
|
|
||||||
|
@@ -1,285 +0,0 @@
|
|||||||
"""\
|
|
||||||
@file wrappedfd.py
|
|
||||||
@author Bob Ippolito
|
|
||||||
|
|
||||||
Copyright (c) 2005-2006, Bob Ippolito
|
|
||||||
Copyright (c) 2007, Linden Research, Inc.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
from eventlet.api import trampoline, get_hub
|
|
||||||
from eventlet import util
|
|
||||||
|
|
||||||
BUFFER_SIZE = 4096
|
|
||||||
|
|
||||||
import socket, errno
|
|
||||||
|
|
||||||
|
|
||||||
def higher_order_recv(recv_func):
|
|
||||||
def recv(self, buflen):
|
|
||||||
buf = self.recvbuffer
|
|
||||||
if buf:
|
|
||||||
chunk, self.recvbuffer = buf[:buflen], buf[buflen:]
|
|
||||||
return chunk
|
|
||||||
fd = self.fd
|
|
||||||
bytes = recv_func(fd, buflen)
|
|
||||||
while bytes is None:
|
|
||||||
try:
|
|
||||||
trampoline(fd, read=True)
|
|
||||||
except socket.error, e:
|
|
||||||
if e[0] == errno.EPIPE:
|
|
||||||
bytes = ''
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
bytes = recv_func(fd, buflen)
|
|
||||||
self.recvcount += len(bytes)
|
|
||||||
return bytes
|
|
||||||
return recv
|
|
||||||
|
|
||||||
|
|
||||||
def higher_order_send(send_func):
|
|
||||||
def send(self, data):
|
|
||||||
count = send_func(self.fd, data)
|
|
||||||
if not count:
|
|
||||||
return 0
|
|
||||||
self.sendcount += count
|
|
||||||
return count
|
|
||||||
return send
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RefCount(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._count = 1
|
|
||||||
|
|
||||||
def increment(self):
|
|
||||||
self._count += 1
|
|
||||||
|
|
||||||
def decrement(self):
|
|
||||||
self._count -= 1
|
|
||||||
assert self._count >= 0
|
|
||||||
|
|
||||||
def is_referenced(self):
|
|
||||||
return self._count > 0
|
|
||||||
|
|
||||||
class wrapped_fd(object):
|
|
||||||
newlines = '\r\n'
|
|
||||||
mode = 'wb+'
|
|
||||||
is_secure = False
|
|
||||||
|
|
||||||
def __init__(self, fd, refcount = None):
|
|
||||||
self._closed = False
|
|
||||||
self.fd = fd
|
|
||||||
self._fileno = fd.fileno()
|
|
||||||
self.recvbuffer = ''
|
|
||||||
self.recvcount = 0
|
|
||||||
self.sendcount = 0
|
|
||||||
self._refcount = refcount
|
|
||||||
if refcount is None:
|
|
||||||
self._refcount = RefCount()
|
|
||||||
|
|
||||||
def getpeername(self, *args, **kw):
|
|
||||||
fn = self.getpeername = self.fd.getpeername
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def getsockname(self, *args, **kw):
|
|
||||||
fn = self.getsockname = self.fd.getsockname
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def listen(self, *args, **kw):
|
|
||||||
fn = self.listen = self.fd.listen
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def bind(self, *args, **kw):
|
|
||||||
fn = self.bind = self.fd.bind
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def getsockopt(self, *args, **kw):
|
|
||||||
fn = self.getsockopt = self.fd.getsockopt
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def setsockopt(self, *args, **kw):
|
|
||||||
fn = self.setsockopt = self.fd.setsockopt
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def connect_ex(self, *args, **kw):
|
|
||||||
fn = self.connect_ex = self.fd.connect_ex
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def fileno(self, *args, **kw):
|
|
||||||
fn = self.fileno = self.fd.fileno
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def setblocking(self, *args, **kw):
|
|
||||||
fn = self.setblocking = self.fd.setblocking
|
|
||||||
return fn(*args, **kw)
|
|
||||||
|
|
||||||
def close(self, *args, **kw):
|
|
||||||
if self._closed:
|
|
||||||
return
|
|
||||||
self._refcount.decrement()
|
|
||||||
if self._refcount.is_referenced():
|
|
||||||
return
|
|
||||||
self._closed = True
|
|
||||||
fn = self.close = self.fd.close
|
|
||||||
try:
|
|
||||||
res = fn(*args, **kw)
|
|
||||||
finally:
|
|
||||||
# This will raise socket.error(32, 'Broken pipe') if there's
|
|
||||||
# a caller waiting on trampoline (e.g. server on .accept())
|
|
||||||
get_hub().exc_descriptor(self._fileno)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def accept(self):
|
|
||||||
fd = self.fd
|
|
||||||
while True:
|
|
||||||
res = util.socket_accept(fd)
|
|
||||||
if res is not None:
|
|
||||||
client, addr = res
|
|
||||||
util.set_nonblocking(client)
|
|
||||||
return type(self)(client), addr
|
|
||||||
trampoline(fd, read=True, write=True)
|
|
||||||
|
|
||||||
def connect(self, address):
|
|
||||||
fd = self.fd
|
|
||||||
connect = util.socket_connect
|
|
||||||
while not connect(fd, address):
|
|
||||||
trampoline(fd, read=True, write=True)
|
|
||||||
|
|
||||||
recv = higher_order_recv(util.socket_recv)
|
|
||||||
|
|
||||||
def recvfrom(self, *args):
|
|
||||||
trampoline(self.fd, read=True)
|
|
||||||
return self.fd.recvfrom(*args)
|
|
||||||
|
|
||||||
send = higher_order_send(util.socket_send)
|
|
||||||
|
|
||||||
def sendto(self, *args):
|
|
||||||
trampoline(self.fd, write=True)
|
|
||||||
return self.fd.sendto(*args)
|
|
||||||
|
|
||||||
def sendall(self, data):
|
|
||||||
fd = self.fd
|
|
||||||
tail = self.send(data)
|
|
||||||
while tail < len(data):
|
|
||||||
trampoline(self.fd, write=True)
|
|
||||||
tail += self.send(data[tail:])
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
return self.sendall(data)
|
|
||||||
|
|
||||||
def readuntil(self, terminator, size=None):
|
|
||||||
buf, self.recvbuffer = self.recvbuffer, ''
|
|
||||||
checked = 0
|
|
||||||
if size is None:
|
|
||||||
while True:
|
|
||||||
found = buf.find(terminator, checked)
|
|
||||||
if found != -1:
|
|
||||||
found += len(terminator)
|
|
||||||
chunk, self.recvbuffer = buf[:found], buf[found:]
|
|
||||||
return chunk
|
|
||||||
checked = max(0, len(buf) - (len(terminator) - 1))
|
|
||||||
d = self.recv(BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
buf += d
|
|
||||||
return buf
|
|
||||||
while len(buf) < size:
|
|
||||||
found = buf.find(terminator, checked)
|
|
||||||
if found != -1:
|
|
||||||
found += len(terminator)
|
|
||||||
chunk, self.recvbuffer = buf[:found], buf[found:]
|
|
||||||
return chunk
|
|
||||||
checked = len(buf)
|
|
||||||
d = self.recv(BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
buf += d
|
|
||||||
chunk, self.recvbuffer = buf[:size], buf[size:]
|
|
||||||
return chunk
|
|
||||||
|
|
||||||
def readline(self, size=None):
|
|
||||||
return self.readuntil(self.newlines, size=size)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self.xreadlines()
|
|
||||||
|
|
||||||
def readlines(self, size=None):
|
|
||||||
return list(self.xreadlines(size=size))
|
|
||||||
|
|
||||||
def xreadlines(self, size=None):
|
|
||||||
if size is None:
|
|
||||||
while True:
|
|
||||||
line = self.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
yield line
|
|
||||||
else:
|
|
||||||
while size > 0:
|
|
||||||
line = self.readline(size)
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
yield line
|
|
||||||
size -= len(line)
|
|
||||||
|
|
||||||
def writelines(self, lines):
|
|
||||||
for line in lines:
|
|
||||||
self.write(line)
|
|
||||||
|
|
||||||
def read(self, size=None):
|
|
||||||
if size is not None and not isinstance(size, (int, long)):
|
|
||||||
raise TypeError('Expecting an int or long for size, got %s: %s' % (type(size), repr(size)))
|
|
||||||
buf, self.recvbuffer = self.recvbuffer, ''
|
|
||||||
lst = [buf]
|
|
||||||
if size is None:
|
|
||||||
while True:
|
|
||||||
d = self.recv(BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
lst.append(d)
|
|
||||||
else:
|
|
||||||
buflen = len(buf)
|
|
||||||
while buflen < size:
|
|
||||||
d = self.recv(BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
buflen += len(d)
|
|
||||||
lst.append(d)
|
|
||||||
else:
|
|
||||||
d = lst[-1]
|
|
||||||
overbite = buflen - size
|
|
||||||
if overbite:
|
|
||||||
lst[-1], self.recvbuffer = d[:-overbite], d[-overbite:]
|
|
||||||
else:
|
|
||||||
lst[-1], self.recvbuffer = d, ''
|
|
||||||
return ''.join(lst)
|
|
||||||
|
|
||||||
def makefile(self, *args, **kw):
|
|
||||||
self._refcount.increment()
|
|
||||||
return type(self)(self.fd, refcount = self._refcount)
|
|
||||||
|
|
||||||
|
|
||||||
class wrapped_file(wrapped_fd):
|
|
||||||
recv = higher_order_recv(util.file_recv)
|
|
||||||
|
|
||||||
send = higher_order_send(util.file_send)
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
fn = self.flush = self.fd.flush
|
|
||||||
return fn()
|
|
227
eventlet/wsgi.py
227
eventlet/wsgi.py
@@ -23,8 +23,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
import urllib
|
import urllib
|
||||||
import socket
|
import socket
|
||||||
import cStringIO
|
import cStringIO
|
||||||
@@ -33,17 +36,84 @@ import BaseHTTPServer
|
|||||||
|
|
||||||
from eventlet import api
|
from eventlet import api
|
||||||
from eventlet.httpdate import format_date_time
|
from eventlet.httpdate import format_date_time
|
||||||
|
from eventlet import coros
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1'
|
||||||
|
|
||||||
|
|
||||||
|
class Input(object):
|
||||||
|
def __init__(self, rfile, content_length, wfile=None, wfile_line=None):
|
||||||
|
self.rfile = rfile
|
||||||
|
if content_length is not None:
|
||||||
|
content_length = int(content_length)
|
||||||
|
self.content_length = content_length
|
||||||
|
|
||||||
|
self.wfile = wfile
|
||||||
|
self.wfile_line = wfile_line
|
||||||
|
|
||||||
|
self.position = 0
|
||||||
|
|
||||||
|
def _do_read(self, reader, length=None):
|
||||||
|
if self.wfile is not None:
|
||||||
|
## 100 Continue
|
||||||
|
self.wfile.write(self.wfile_line)
|
||||||
|
self.wfile = None
|
||||||
|
self.wfile_line = None
|
||||||
|
|
||||||
|
if length is None and self.content_length is not None:
|
||||||
|
length = self.content_length - self.position
|
||||||
|
if length and length > self.content_length - self.position:
|
||||||
|
length = self.content_length - self.position
|
||||||
|
if not length:
|
||||||
|
return ''
|
||||||
|
read = reader(length)
|
||||||
|
self.position += len(read)
|
||||||
|
return read
|
||||||
|
|
||||||
|
def read(self, length=None):
|
||||||
|
return self._do_read(self.rfile.read, length)
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
return self._do_read(self.rfile.readline)
|
||||||
|
|
||||||
|
def readlines(self, hint=None):
|
||||||
|
return self._do_read(self.rfile.readlines, hint)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.read())
|
||||||
|
|
||||||
|
|
||||||
|
MAX_REQUEST_LINE = 8192
|
||||||
|
|
||||||
|
|
||||||
class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
protocol_version = 'HTTP/1.1'
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
self.server.log_message("%s - - [%s] %s" % (
|
self.server.log_message("(%s) %s - - [%s] %s" % (
|
||||||
|
self.server.pid,
|
||||||
self.address_string(),
|
self.address_string(),
|
||||||
self.log_date_time_string(),
|
self.log_date_time_string(),
|
||||||
format % args))
|
format % args))
|
||||||
|
|
||||||
def handle_one_request(self):
|
def handle_one_request(self):
|
||||||
self.raw_requestline = self.rfile.readline()
|
if self.server.max_http_version:
|
||||||
|
self.protocol_version = self.server.max_http_version
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE)
|
||||||
|
if len(self.raw_requestline) == MAX_REQUEST_LINE:
|
||||||
|
self.wfile.write(
|
||||||
|
"HTTP/1.0 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n")
|
||||||
|
self.close_connection = 1
|
||||||
|
return
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] != errno.EBADF:
|
||||||
|
raise
|
||||||
|
self.raw_requestline = ''
|
||||||
|
|
||||||
if not self.raw_requestline:
|
if not self.raw_requestline:
|
||||||
self.close_connection = 1
|
self.close_connection = 1
|
||||||
@@ -53,6 +123,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.environ = self.get_environ()
|
self.environ = self.get_environ()
|
||||||
|
self.application = self.server.app
|
||||||
|
try:
|
||||||
|
self.server.outstanding_requests += 1
|
||||||
try:
|
try:
|
||||||
self.handle_one_response()
|
self.handle_one_response()
|
||||||
except socket.error, e:
|
except socket.error, e:
|
||||||
@@ -61,8 +134,11 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
self.server.outstanding_requests -= 1
|
||||||
|
|
||||||
def handle_one_response(self):
|
def handle_one_response(self):
|
||||||
|
start = time.time()
|
||||||
headers_set = []
|
headers_set = []
|
||||||
headers_sent = []
|
headers_sent = []
|
||||||
# set of lowercase header names that were sent
|
# set of lowercase header names that were sent
|
||||||
@@ -70,8 +146,13 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
wfile = self.wfile
|
wfile = self.wfile
|
||||||
num_blocks = None
|
num_blocks = None
|
||||||
|
result = None
|
||||||
|
use_chunked = False
|
||||||
|
length = [0]
|
||||||
|
status_code = [200]
|
||||||
|
|
||||||
def write(data, _write=wfile.write):
|
def write(data, _write=wfile.write):
|
||||||
|
towrite = []
|
||||||
if not headers_set:
|
if not headers_set:
|
||||||
raise AssertionError("write() before start_response()")
|
raise AssertionError("write() before start_response()")
|
||||||
elif not headers_sent:
|
elif not headers_sent:
|
||||||
@@ -79,18 +160,34 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
headers_sent.append(1)
|
headers_sent.append(1)
|
||||||
for k, v in response_headers:
|
for k, v in response_headers:
|
||||||
header_dict[k.lower()] = k
|
header_dict[k.lower()] = k
|
||||||
_write('HTTP/1.0 %s\r\n' % status)
|
towrite.append('%s %s\r\n' % (self.protocol_version, status))
|
||||||
|
for header in response_headers:
|
||||||
|
towrite.append('%s: %s\r\n' % header)
|
||||||
|
|
||||||
# send Date header?
|
# send Date header?
|
||||||
if 'date' not in header_dict:
|
if 'date' not in header_dict:
|
||||||
_write('Date: %s\r\n' % (format_date_time(time.time()),))
|
towrite.append('Date: %s\r\n' % (format_date_time(time.time()),))
|
||||||
if 'content-length' not in header_dict and num_blocks == 1:
|
if num_blocks is not None:
|
||||||
_write('Content-Length: %s\r\n' % (len(data),))
|
if 'content-length' not in header_dict:
|
||||||
for header in response_headers:
|
towrite.append('Content-Length: %s\r\n' % (len(''.join(result)),))
|
||||||
_write('%s: %s\r\n' % header)
|
elif use_chunked:
|
||||||
_write('\r\n')
|
towrite.append('Transfer-Encoding: chunked\r\n')
|
||||||
_write(data)
|
else:
|
||||||
|
towrite.append('Connection: close\r\n')
|
||||||
|
self.close_connection = 1
|
||||||
|
towrite.append('\r\n')
|
||||||
|
|
||||||
def start_request(status, response_headers, exc_info=None):
|
if use_chunked:
|
||||||
|
## Write the chunked encoding
|
||||||
|
towrite.append("%x\r\n%s\r\n" % (len(data), data))
|
||||||
|
else:
|
||||||
|
towrite.append(data)
|
||||||
|
joined = ''.join(towrite)
|
||||||
|
length[0] = length[0] + len(joined)
|
||||||
|
_write(joined)
|
||||||
|
|
||||||
|
def start_response(status, response_headers, exc_info=None):
|
||||||
|
status_code[0] = status.split()[0]
|
||||||
if exc_info:
|
if exc_info:
|
||||||
try:
|
try:
|
||||||
if headers_sent:
|
if headers_sent:
|
||||||
@@ -99,27 +196,61 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
finally:
|
finally:
|
||||||
# Avoid dangling circular ref
|
# Avoid dangling circular ref
|
||||||
exc_info = None
|
exc_info = None
|
||||||
elif headers_set:
|
|
||||||
raise AssertionError("Headers already set!")
|
|
||||||
|
|
||||||
headers_set[:] = [status, response_headers]
|
headers_set[:] = [status, response_headers]
|
||||||
return write
|
return write
|
||||||
|
|
||||||
result = self.server.app(self.environ, start_request)
|
try:
|
||||||
|
result = self.application(self.environ, start_response)
|
||||||
|
except Exception, e:
|
||||||
|
exc = ''.join(traceback.format_exception(*sys.exc_info()))
|
||||||
|
print exc
|
||||||
|
if not headers_set:
|
||||||
|
start_response("500 Internal Server Error", [('Content-type', 'text/plain')])
|
||||||
|
write(exc)
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
num_blocks = len(result)
|
num_blocks = len(result)
|
||||||
except (TypeError, AttributeError, NotImplementedError):
|
except (TypeError, AttributeError, NotImplementedError):
|
||||||
pass
|
if self.request_version == 'HTTP/1.1':
|
||||||
|
use_chunked = True
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
towrite = []
|
||||||
try:
|
try:
|
||||||
for data in result:
|
for data in result:
|
||||||
if data:
|
if data:
|
||||||
write(data)
|
towrite.append(data)
|
||||||
|
if use_chunked and sum(map(len, towrite)) > 4096:
|
||||||
|
write(''.join(towrite))
|
||||||
|
del towrite[:]
|
||||||
|
except Exception, e:
|
||||||
|
exc = traceback.format_exc()
|
||||||
|
print exc
|
||||||
|
if not headers_set:
|
||||||
|
start_response("500 Internal Server Error", [('Content-type', 'text/plain')])
|
||||||
|
write(exc)
|
||||||
|
return
|
||||||
|
|
||||||
|
if towrite:
|
||||||
|
write(''.join(towrite))
|
||||||
if not headers_sent:
|
if not headers_sent:
|
||||||
write('')
|
write('')
|
||||||
|
if use_chunked:
|
||||||
|
wfile.write('0\r\n\r\n')
|
||||||
|
except Exception, e:
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
if hasattr(result, 'close'):
|
if hasattr(result, 'close'):
|
||||||
result.close()
|
result.close()
|
||||||
|
if self.environ['eventlet.input'].position < self.environ.get('CONTENT_LENGTH', 0):
|
||||||
|
## Read and discard body
|
||||||
|
self.environ['eventlet.input'].read()
|
||||||
|
finish = time.time()
|
||||||
|
self.log_message(
|
||||||
|
'"%s" %s %s %.6f\n' % (
|
||||||
|
self.requestline, status_code[0], length[0], finish - start))
|
||||||
|
|
||||||
def get_environ(self):
|
def get_environ(self):
|
||||||
env = self.server.get_environ()
|
env = self.server.get_environ()
|
||||||
@@ -161,31 +292,40 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
env[envk] = v
|
env[envk] = v
|
||||||
|
|
||||||
|
if env.get('HTTP_EXPECT') == '100-continue':
|
||||||
|
wfile = self.wfile
|
||||||
|
wfile_line = 'HTTP/1.1 100 Continue\r\n\r\n'
|
||||||
|
else:
|
||||||
|
wfile = None
|
||||||
|
wfile_line = None
|
||||||
|
env['wsgi.input'] = env['eventlet.input'] = Input(
|
||||||
|
self.rfile, length, wfile=wfile, wfile_line=wfile_line)
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
# Override SocketServer.StreamRequestHandler.finish because
|
BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
|
||||||
# we only need to call close on the socket, not the makefile'd things
|
self.connection.close()
|
||||||
|
|
||||||
self.request.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Server(BaseHTTPServer.HTTPServer):
|
class Server(BaseHTTPServer.HTTPServer):
|
||||||
def __init__(self, socket, address, app, log, environ=None):
|
def __init__(self, socket, address, app, log=None, environ=None, max_http_version=None, protocol=HttpProtocol):
|
||||||
|
self.outstanding_requests = 0
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.address = address
|
self.address = address
|
||||||
if log:
|
if log:
|
||||||
self.log = log
|
self.log = log
|
||||||
log.write = log.info
|
|
||||||
else:
|
else:
|
||||||
self.log = sys.stderr
|
self.log = sys.stderr
|
||||||
self.app = app
|
self.app = app
|
||||||
self.environ = environ
|
self.environ = environ
|
||||||
|
self.max_http_version = max_http_version
|
||||||
|
self.protocol = protocol
|
||||||
|
self.pid = os.getpid()
|
||||||
|
|
||||||
def get_environ(self):
|
def get_environ(self):
|
||||||
socket = self.socket
|
socket = self.socket
|
||||||
d = {
|
d = {
|
||||||
'wsgi.input': socket,
|
|
||||||
'wsgi.errors': sys.stderr,
|
'wsgi.errors': sys.stderr,
|
||||||
'wsgi.version': (1, 0),
|
'wsgi.version': (1, 0),
|
||||||
'wsgi.multithread': True,
|
'wsgi.multithread': True,
|
||||||
@@ -198,22 +338,45 @@ class Server(BaseHTTPServer.HTTPServer):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def process_request(self, (socket, address)):
|
def process_request(self, (socket, address)):
|
||||||
proto = HttpProtocol(socket, address, self)
|
proto = self.protocol(socket, address, self)
|
||||||
|
proto.handle()
|
||||||
|
|
||||||
def log_message(self, message):
|
def log_message(self, message):
|
||||||
self.log.write(message + '\n')
|
self.log.write(message + '\n')
|
||||||
|
|
||||||
|
|
||||||
def server(socket, site, log=None, environ=None):
|
def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION, protocol=HttpProtocol, server_event=None):
|
||||||
serv = Server(socket, socket.getsockname(), site, log, environ=None)
|
serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version, protocol=protocol)
|
||||||
|
if server_event is not None:
|
||||||
|
server_event.send(serv)
|
||||||
|
if max_size is None:
|
||||||
|
max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
|
||||||
|
pool = coros.CoroutinePool(max_size=max_size)
|
||||||
try:
|
try:
|
||||||
print "wsgi starting up on", socket.getsockname()
|
host, port = sock.getsockname()
|
||||||
|
port = ':%s' % (port, )
|
||||||
|
if sock.is_secure:
|
||||||
|
scheme = 'https'
|
||||||
|
if port == ':443':
|
||||||
|
port = ''
|
||||||
|
else:
|
||||||
|
scheme = 'http'
|
||||||
|
if port == ':80':
|
||||||
|
port = ''
|
||||||
|
|
||||||
|
print "(%s) wsgi starting up on %s://%s%s/" % (os.getpid(), scheme, host, port)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
api.spawn(serv.process_request, socket.accept())
|
client_socket = sock.accept()
|
||||||
|
pool.execute_async(serv.process_request, client_socket)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
api.get_hub().remove_descriptor(socket.fileno())
|
api.get_hub().remove_descriptor(sock.fileno())
|
||||||
print "wsgi exiting"
|
print "wsgi exiting"
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
socket.close()
|
try:
|
||||||
|
sock.close()
|
||||||
|
except socket.error, e:
|
||||||
|
if e[0] != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
|
||||||
|
271
eventlet/wsgi_test.py
Normal file
271
eventlet/wsgi_test.py
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
"""\
|
||||||
|
@file httpd_test.py
|
||||||
|
@author Donovan Preston
|
||||||
|
|
||||||
|
Copyright (c) 2007, Linden Research, Inc.
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
|
||||||
|
from eventlet import api
|
||||||
|
from eventlet import wsgi
|
||||||
|
from eventlet import processes
|
||||||
|
from eventlet import util
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
|
||||||
|
util.wrap_socket_with_coroutine_socket()
|
||||||
|
|
||||||
|
|
||||||
|
from eventlet import tests
|
||||||
|
|
||||||
|
|
||||||
|
def hello_world(env, start_response):
|
||||||
|
if env['PATH_INFO'] == 'notexist':
|
||||||
|
start_response('404 Not Found', [('Content-type', 'text/plain')])
|
||||||
|
return ["not found"]
|
||||||
|
|
||||||
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||||
|
return ["hello world"]
|
||||||
|
|
||||||
|
|
||||||
|
def chunked_app(env, start_response):
|
||||||
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||||
|
yield "this"
|
||||||
|
yield "is"
|
||||||
|
yield "chunked"
|
||||||
|
|
||||||
|
|
||||||
|
def big_chunks(env, start_response):
|
||||||
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||||
|
line = 'a' * 8192
|
||||||
|
for x in range(10):
|
||||||
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
class Site(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.application = hello_world
|
||||||
|
|
||||||
|
def __call__(self, env, start_response):
|
||||||
|
return self.application(env, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
CONTENT_LENGTH = 'content-length'
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Date: foo
|
||||||
|
Content-length: 11
|
||||||
|
|
||||||
|
hello world
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ConnectionClosed(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def read_http(sock):
|
||||||
|
fd = sock.makefile()
|
||||||
|
response_line = fd.readline()
|
||||||
|
if not response_line:
|
||||||
|
raise ConnectionClosed
|
||||||
|
raw_headers = fd.readuntil('\r\n\r\n').strip()
|
||||||
|
#print "R", response_line, raw_headers
|
||||||
|
headers = dict()
|
||||||
|
for x in raw_headers.split('\r\n'):
|
||||||
|
#print "X", x
|
||||||
|
key, value = x.split(': ', 1)
|
||||||
|
headers[key.lower()] = value
|
||||||
|
|
||||||
|
if CONTENT_LENGTH in headers:
|
||||||
|
num = int(headers[CONTENT_LENGTH])
|
||||||
|
body = fd.read(num)
|
||||||
|
#print body
|
||||||
|
else:
|
||||||
|
body = None
|
||||||
|
|
||||||
|
return response_line, headers, body
|
||||||
|
|
||||||
|
|
||||||
|
class TestHttpd(tests.TestCase):
|
||||||
|
mode = 'static'
|
||||||
|
def setUp(self):
|
||||||
|
self.logfile = StringIO()
|
||||||
|
self.site = Site()
|
||||||
|
self.killer = api.spawn(
|
||||||
|
wsgi.server,
|
||||||
|
api.tcp_listener(('0.0.0.0', 12346)), self.site, max_size=128, log=self.logfile)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
api.kill(self.killer)
|
||||||
|
|
||||||
|
def test_001_server(self):
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n')
|
||||||
|
result = fd.read()
|
||||||
|
fd.close()
|
||||||
|
## The server responds with the maximum version it supports
|
||||||
|
self.assert_(result.startswith('HTTP'), result)
|
||||||
|
self.assert_(result.endswith('hello world'))
|
||||||
|
|
||||||
|
def test_002_keepalive(self):
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
read_http(sock)
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
read_http(sock)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def test_003_passing_non_int_to_read(self):
|
||||||
|
# This should go in greenio_test
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
cancel = api.exc_after(1, RuntimeError)
|
||||||
|
self.assertRaises(TypeError, fd.read, "This shouldn't work")
|
||||||
|
cancel.cancel()
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def test_004_close_keepalive(self):
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
read_http(sock)
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
||||||
|
read_http(sock)
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
self.assertRaises(ConnectionClosed, read_http, sock)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def skip_test_005_run_apachebench(self):
|
||||||
|
url = 'http://localhost:12346/'
|
||||||
|
# ab is apachebench
|
||||||
|
out = processes.Process(tests.find_command('ab'),
|
||||||
|
['-c','64','-n','1024', '-k', url])
|
||||||
|
print out.read()
|
||||||
|
|
||||||
|
def test_006_reject_long_urls(self):
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
path_parts = []
|
||||||
|
for ii in range(3000):
|
||||||
|
path_parts.append('path')
|
||||||
|
path = '/'.join(path_parts)
|
||||||
|
request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write(request)
|
||||||
|
result = fd.readline()
|
||||||
|
status = result.split(' ')[1]
|
||||||
|
self.assertEqual(status, '414')
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def test_007_get_arg(self):
|
||||||
|
# define a new handler that does a get_arg as well as a read_body
|
||||||
|
def new_app(env, start_response):
|
||||||
|
body = env['wsgi.input'].read()
|
||||||
|
a = cgi.parse_qs(body).get('a', [1])[0]
|
||||||
|
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||||
|
return ['a is %s, body is %s' % (a, body)]
|
||||||
|
self.site.application = new_app
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
request = '\r\n'.join((
|
||||||
|
'POST / HTTP/1.0',
|
||||||
|
'Host: localhost',
|
||||||
|
'Content-Length: 3',
|
||||||
|
'',
|
||||||
|
'a=a'))
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write(request)
|
||||||
|
|
||||||
|
# send some junk after the actual request
|
||||||
|
fd.write('01234567890123456789')
|
||||||
|
reqline, headers, body = read_http(sock)
|
||||||
|
self.assertEqual(body, 'a is a, body is a=a')
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def test_008_correctresponse(self):
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
response_line_200,_,_ = read_http(sock)
|
||||||
|
fd.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
response_line_404,_,_ = read_http(sock)
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
|
||||||
|
response_line_test,_,_ = read_http(sock)
|
||||||
|
self.assertEqual(response_line_200,response_line_test)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def test_009_chunked_response(self):
|
||||||
|
self.site.application = chunked_app
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
||||||
|
self.assert_('Transfer-Encoding: chunked' in fd.read())
|
||||||
|
|
||||||
|
def test_010_no_chunked_http_1_0(self):
|
||||||
|
self.site.application = chunked_app
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
||||||
|
self.assert_('Transfer-Encoding: chunked' not in fd.read())
|
||||||
|
|
||||||
|
def test_011_multiple_chunks(self):
|
||||||
|
self.site.application = big_chunks
|
||||||
|
sock = api.connect_tcp(
|
||||||
|
('127.0.0.1', 12346))
|
||||||
|
|
||||||
|
fd = sock.makefile()
|
||||||
|
fd.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n')
|
||||||
|
headers = fd.readuntil('\r\n\r\n')
|
||||||
|
self.assert_('Transfer-Encoding: chunked' in headers)
|
||||||
|
chunks = 0
|
||||||
|
chunklen = int(fd.readline(), 16)
|
||||||
|
while chunklen:
|
||||||
|
chunks += 1
|
||||||
|
chunk = fd.read(chunklen)
|
||||||
|
fd.readline()
|
||||||
|
chunklen = int(fd.readline(), 16)
|
||||||
|
self.assert_(chunks > 1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
tests.main()
|
@@ -1,3 +1,4 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
"""\
|
"""\
|
||||||
@file echoserver.py
|
@file echoserver.py
|
||||||
|
|
||||||
@@ -32,21 +33,22 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
from eventlet import api
|
from eventlet import api
|
||||||
|
|
||||||
def handle_socket(client):
|
def handle_socket(reader, writer):
|
||||||
print "client connected"
|
print "client connected"
|
||||||
while True:
|
while True:
|
||||||
# pass through every non-eof line
|
# pass through every non-eof line
|
||||||
x = client.readline()
|
x = reader.readline()
|
||||||
if not x: break
|
if not x: break
|
||||||
client.write(x)
|
writer.write(x)
|
||||||
print "echoed", x
|
print "echoed", x
|
||||||
print "client disconnected"
|
print "client disconnected"
|
||||||
|
|
||||||
# server socket listening on port 6000
|
print "server socket listening on port 6000"
|
||||||
server = api.tcp_listener(('0.0.0.0', 6000))
|
server = api.tcp_listener(('0.0.0.0', 6000))
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
new_sock, address = server.accept()
|
new_sock, address = server.accept()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
# handle every new connection with a new coroutine
|
# handle every new connection with a new coroutine
|
||||||
api.spawn(handle_socket, new_sock)
|
api.spawn(handle_socket, new_sock.makefile('r'), new_sock.makefile('w'))
|
||||||
|
|
||||||
server.close()
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
"""\
|
"""\
|
||||||
@file webcrawler.py
|
@file webcrawler.py
|
||||||
|
|
||||||
|
20
examples/wsgi.py
Normal file
20
examples/wsgi.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"""This is a simple example of running a wsgi application with eventlet.
|
||||||
|
For a more fully-featured server which supports multiple processes,
|
||||||
|
multiple threads, and graceful code reloading, see:
|
||||||
|
|
||||||
|
http://pypi.python.org/pypi/Spawning/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from eventlet import api, wsgi
|
||||||
|
|
||||||
|
|
||||||
|
def hello_world(env, start_response):
|
||||||
|
if env['PATH_INFO'] != '/':
|
||||||
|
start_response('404 Not Found', [('Content-Type', 'text/plain')])
|
||||||
|
return ['Not Found\r\n']
|
||||||
|
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||||
|
return ['Hello, World!\r\n']
|
||||||
|
|
||||||
|
|
||||||
|
wsgi.server(api.tcp_listener(('', 8080)), hello_world)
|
||||||
|
|
3
makedoc
Executable file
3
makedoc
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
dir="$(dirname "$0")"
|
||||||
|
epydoc -o "$dir/html" --graph classtree --docformat=restructuredtext "$dir/eventlet/" || exit $?
|
||||||
|
open "$dir/html/index.html"
|
12
setup.py
12
setup.py
@@ -1,16 +1,18 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from setuptools import setup
|
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='eventlet',
|
name='eventlet',
|
||||||
version='0.2',
|
version='0.7pre',
|
||||||
description='Coroutine-based networking library',
|
description='Coroutine-based networking library',
|
||||||
author='Linden Lab',
|
author='Linden Lab',
|
||||||
author_email='eventletdev@lists.secondlife.com',
|
author_email='eventletdev@lists.secondlife.com',
|
||||||
url='http://wiki.secondlife.com/wiki/Eventlet',
|
url='http://wiki.secondlife.com/wiki/Eventlet',
|
||||||
packages=['eventlet'],
|
packages=find_packages(),
|
||||||
install_requires=['greenlet'],
|
install_requires=['greenlet', 'pyOpenSSL'],
|
||||||
long_description="""
|
long_description="""
|
||||||
Eventlet is a networking library written in Python. It achieves
|
Eventlet is a networking library written in Python. It achieves
|
||||||
high scalability by using non-blocking io while at the same time
|
high scalability by using non-blocking io while at the same time
|
||||||
@@ -20,6 +22,8 @@ setup(
|
|||||||
classifiers=[
|
classifiers=[
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
|
"Operating System :: MacOS :: MacOS X",
|
||||||
|
"Operating System :: POSIX",
|
||||||
"Topic :: Internet",
|
"Topic :: Internet",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
|
Reference in New Issue
Block a user