This commit is contained in:
rdw
2008-07-21 17:45:49 -07:00
52 changed files with 3073 additions and 1445 deletions

5
.hgignore Normal file
View File

@@ -0,0 +1,5 @@
syntax: glob
*.pyc
dist
eventlet.egg-info
build

26
NEWS Normal file
View 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
View File

@@ -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 ==

View File

@@ -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:
raise ImportError("Unable to find an implementation of greenlet.") try:
import support.stacklesss
support.stacklesss.emulate()
greenlet = sys.modules['greenlet']
except ImportError, e:
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,20 +411,29 @@ 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:
os.path.join The name uses a module-like syntax, eg::
or
mulib.mu.Resource os.path.join
or::
mulib.mu.Resource
""" """
toimport = name toimport = name
obj = None obj = None

View File

@@ -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()

View File

@@ -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
View 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
View 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()))

View File

@@ -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,13 +282,13 @@ 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
# do is try to keep the pool from leaking items # do is try to keep the pool from leaking items
self.put(self.create()) self.put(self.create())
def _safe_apply(self, evt, func, args, kw): def _safe_apply(self, evt, func, args, kw):
""" Private method that runs the function, catches exceptions, and """ Private method that runs the function, catches exceptions, and
passes back the return value in the event.""" passes back the return value in the event."""
@@ -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,7 +316,14 @@ 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."""

View File

@@ -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):
@@ -206,14 +207,29 @@ class TestCoroutinePool(tests.TestCase):
t.cancel() t.cancel()
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):
def received(self, evt): def received(self, evt):
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):
@@ -277,7 +293,6 @@ class TestActor(tests.TestCase):
for evt in waiters: for evt in waiters:
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 = []
@@ -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
View 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
View 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()

View File

@@ -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
View 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

View File

@@ -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,8 +565,9 @@ 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):
connection = connect(params.url, params.use_proxy) if connection is None:
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)
params.response = connection.getresponse() params.response = connection.getresponse()
@@ -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,
loader=self.loader, dumper=self.dumper, ok=None, aux=None, connection=None):
use_proxy=use_proxy, ok=ok, aux=aux)) return self.request_(
_Params(
url, 'HEAD', headers=headers,
loader=self.loader, dumper=self.dumper,
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_(
loader=self.loader, dumper=self.dumper, _Params(
ok=ok, aux=aux)) url, 'PUT', body=data, headers=headers,
loader=self.loader, dumper=self.dumper,
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_(
headers=headers, loader=self.loader, _Params(
dumper=self.dumper, ok=ok, aux=aux)) url, 'POST', body=data,
headers=headers, loader=self.loader,
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]

View File

@@ -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())

View File

@@ -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
@@ -103,10 +104,11 @@ class TestHttpd(tests.TestCase):
def test_001_server(self): def test_001_server(self):
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'))
@@ -114,35 +116,38 @@ class TestHttpd(tests.TestCase):
def test_002_keepalive(self): def test_002_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\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__':

View File

@@ -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.
timer.cancel() try:
print 'Runloop cancelling left-over timer %s' % timer # this might be None due to weirdness with weakrefs
del self.timers_by_greenlet[greenlet] timer.cancel()
except TypeError:
pass
if _g_debug and not quiet:
print 'Hub cancelling left-over timer %s' % timer
try:
del self.timers_by_greenlet[greenlet]
except KeyError:
pass

136
eventlet/hubs/libev.py Normal file
View 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
View 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
View 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
View 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())

View 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
View 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())

View File

@@ -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())

View File

@@ -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())

View File

@@ -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):

View File

@@ -100,8 +100,12 @@ 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:
result = proc.read() try:
self.assertEquals(result, 'hello\n') result = proc.read()
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()

View File

@@ -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()

View File

@@ -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())

View 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
View 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/')

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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
View 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
View 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-----

View File

@@ -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
@@ -56,28 +56,32 @@ class Timer(object):
secs, cb, args, kw) secs, cb, args, kw)
if _g_debug and hasattr(self, 'traceback'): if _g_debug and hasattr(self, 'traceback'):
retval += '\n' + self.traceback.getvalue() retval += '\n' + self.traceback.getvalue()
return retval return retval
def copy(self): def copy(self):
cb, args, kw = self.tpl cb, args, kw = self.tpl
return self.__class__(self.seconds, cb, *args, **kw) return self.__class__(self.seconds, cb, *args, **kw)
def schedule(self): def schedule(self):
"""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
cb(*args, **kw) try:
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
been called, has no effect. been called, has no effect.
""" """
self.cancelled = True self.cancelled = True
self.called = True self.called = True
get_hub().timer_canceled(self)

View File

@@ -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()

View File

@@ -22,7 +22,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
""" """
import threading try:
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']

View File

@@ -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()

View File

@@ -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))

View File

@@ -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,85 +212,53 @@ 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):
@@ -240,18 +270,4 @@ 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

View File

@@ -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()

View File

@@ -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,16 +123,22 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
return return
self.environ = self.get_environ() self.environ = self.get_environ()
self.application = self.server.app
try: try:
self.handle_one_response() self.server.outstanding_requests += 1
except socket.error, e: try:
# Broken pipe, connection reset by peer self.handle_one_response()
if e[0] in (32, 54): except socket.error, e:
pass # Broken pipe, connection reset by peer
else: if e[0] in (32, 54):
raise pass
else:
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')
def start_request(status, response_headers, exc_info=None): self.close_connection = 1
towrite.append('\r\n')
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:
for data in result: try:
if data: towrite = []
write(data) try:
if not headers_sent: for data in result:
write('') if 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:
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
View 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()

View File

@@ -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:
new_sock, address = server.accept() try:
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()

View File

@@ -1,3 +1,4 @@
#! /usr/bin/env python
"""\ """\
@file webcrawler.py @file webcrawler.py

20
examples/wsgi.py Normal file
View 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
View File

@@ -0,0 +1,3 @@
dir="$(dirname "$0")"
epydoc -o "$dir/html" --graph classtree --docformat=restructuredtext "$dir/eventlet/" || exit $?
open "$dir/html/index.html"

View File

@@ -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",