From aec9f79fc2bae16e0ba27e848343ae19824061a8 Mon Sep 17 00:00:00 2001 From: rdw Date: Sun, 16 Mar 2008 21:45:23 -0500 Subject: [PATCH 001/118] Initial implementation of libeventhub. Works for some basic socket stuff but it's got problems. --- .svn/all-wcprops | 17 + .svn/entries | 58 ++ .svn/format | 1 + .svn/text-base/README.svn-base | 66 ++ .svn/text-base/setup.py.svn-base | 28 + README | 66 ++ eventlet/.svn/all-wcprops | 245 +++++++ eventlet/.svn/dir-prop-base | 9 + eventlet/.svn/entries | 541 ++++++++++++++ eventlet/.svn/format | 1 + eventlet/.svn/prop-base/__init__.py.svn-base | 5 + eventlet/.svn/prop-base/api.py.svn-base | 5 + eventlet/.svn/prop-base/api_test.py.svn-base | 5 + eventlet/.svn/prop-base/backdoor.py.svn-base | 5 + eventlet/.svn/prop-base/channel.py.svn-base | 5 + eventlet/.svn/prop-base/coros.py.svn-base | 5 + .../.svn/prop-base/coros_test.py.svn-base | 5 + eventlet/.svn/prop-base/greenlib.py.svn-base | 5 + eventlet/.svn/prop-base/httpc.py.svn-base | 5 + eventlet/.svn/prop-base/httpd.py.svn-base | 5 + .../.svn/prop-base/httpd_test.py.svn-base | 5 + eventlet/.svn/prop-base/httpdate.py.svn-base | 5 + eventlet/.svn/prop-base/jsonhttp.py.svn-base | 5 + eventlet/.svn/prop-base/kqueuehub.py.svn-base | 5 + eventlet/.svn/prop-base/logutil.py.svn-base | 5 + eventlet/.svn/prop-base/pollhub.py.svn-base | 5 + eventlet/.svn/prop-base/pools.py.svn-base | 5 + .../.svn/prop-base/pools_test.py.svn-base | 5 + eventlet/.svn/prop-base/processes.py.svn-base | 5 + .../.svn/prop-base/processes_test.py.svn-base | 5 + .../.svn/prop-base/pylibsupport.py.svn-base | 5 + eventlet/.svn/prop-base/runloop.py.svn-base | 5 + .../.svn/prop-base/runloop_test.py.svn-base | 5 + eventlet/.svn/prop-base/selecthub.py.svn-base | 5 + .../prop-base/stacklesssupport.py.svn-base | 5 + eventlet/.svn/prop-base/tests.py.svn-base | 5 + eventlet/.svn/prop-base/timer.py.svn-base | 5 + .../.svn/prop-base/timer_test.py.svn-base | 5 + eventlet/.svn/prop-base/tls.py.svn-base | 5 + .../.svn/prop-base/twistedsupport.py.svn-base | 5 + eventlet/.svn/prop-base/util.py.svn-base | 5 + eventlet/.svn/prop-base/wrappedfd.py.svn-base | 5 + eventlet/.svn/prop-base/wsgi.py.svn-base | 5 + eventlet/.svn/text-base/__init__.py.svn-base | 24 + eventlet/.svn/text-base/api.py.svn-base | 309 ++++++++ eventlet/.svn/text-base/api_test.py.svn-base | 164 +++++ eventlet/.svn/text-base/backdoor.py.svn-base | 85 +++ eventlet/.svn/text-base/channel.py.svn-base | 98 +++ eventlet/.svn/text-base/coros.py.svn-base | 457 ++++++++++++ .../.svn/text-base/coros_test.py.svn-base | 300 ++++++++ eventlet/.svn/text-base/db_pool.py.svn-base | 220 ++++++ .../.svn/text-base/db_pool_test.py.svn-base | 309 ++++++++ eventlet/.svn/text-base/greenlib.py.svn-base | 331 +++++++++ eventlet/.svn/text-base/httpc.py.svn-base | 597 +++++++++++++++ .../.svn/text-base/httpc_test.py.svn-base | 396 ++++++++++ eventlet/.svn/text-base/httpd.py.svn-base | 584 +++++++++++++++ .../.svn/text-base/httpd_test.py.svn-base | 207 ++++++ eventlet/.svn/text-base/httpdate.py.svn-base | 39 + eventlet/.svn/text-base/jsonhttp.py.svn-base | 40 + eventlet/.svn/text-base/kqueuehub.py.svn-base | 219 ++++++ eventlet/.svn/text-base/logutil.py.svn-base | 112 +++ eventlet/.svn/text-base/pollhub.py.svn-base | 189 +++++ eventlet/.svn/text-base/pools.py.svn-base | 184 +++++ .../.svn/text-base/pools_test.py.svn-base | 179 +++++ eventlet/.svn/text-base/processes.py.svn-base | 141 ++++ .../.svn/text-base/processes_test.py.svn-base | 134 ++++ .../.svn/text-base/pylibsupport.py.svn-base | 42 ++ eventlet/.svn/text-base/runloop.py.svn-base | 228 ++++++ .../.svn/text-base/runloop_test.py.svn-base | 157 ++++ eventlet/.svn/text-base/saranwrap.py.svn-base | 685 ++++++++++++++++++ .../.svn/text-base/saranwrap_test.py.svn-base | 316 ++++++++ eventlet/.svn/text-base/selecthub.py.svn-base | 173 +++++ .../text-base/stacklesssupport.py.svn-base | 110 +++ eventlet/.svn/text-base/tests.py.svn-base | 89 +++ eventlet/.svn/text-base/timer.py.svn-base | 83 +++ .../.svn/text-base/timer_test.py.svn-base | 66 ++ eventlet/.svn/text-base/tls.py.svn-base | 57 ++ eventlet/.svn/text-base/tpool.py.svn-base | 123 ++++ .../.svn/text-base/tpool_test.py.svn-base | 70 ++ .../.svn/text-base/twistedsupport.py.svn-base | 134 ++++ eventlet/.svn/text-base/util.py.svn-base | 214 ++++++ eventlet/.svn/text-base/wrappedfd.py.svn-base | 284 ++++++++ eventlet/.svn/text-base/wsgi.py.svn-base | 219 ++++++ eventlet/.svn/tmp/tempfile.2.tmp | 584 +++++++++++++++ eventlet/__init__.py | 24 + eventlet/__init__.pyc | Bin 0 -> 1247 bytes eventlet/api.py | 315 ++++++++ eventlet/api.pyc | Bin 0 -> 11907 bytes eventlet/api_test.py | 164 +++++ eventlet/api_test.pyc | Bin 0 -> 7015 bytes eventlet/backdoor.py | 85 +++ eventlet/bench.py | 40 + eventlet/channel.py | 98 +++ eventlet/channel.pyc | Bin 0 -> 3970 bytes eventlet/coros.py | 457 ++++++++++++ eventlet/coros.pyc | Bin 0 -> 17398 bytes eventlet/coros_test.py | 300 ++++++++ eventlet/coros_test.pyc | Bin 0 -> 13991 bytes eventlet/db_pool.py | 220 ++++++ eventlet/db_pool.pyc | Bin 0 -> 15281 bytes eventlet/db_pool_test.py | 309 ++++++++ eventlet/db_pool_test.pyc | Bin 0 -> 12508 bytes eventlet/greenlib.py | 331 +++++++++ eventlet/greenlib.pyc | Bin 0 -> 11433 bytes eventlet/httpc.py | 597 +++++++++++++++ eventlet/httpc.pyc | Bin 0 -> 25004 bytes eventlet/httpc_test.py | 396 ++++++++++ eventlet/httpc_test.pyc | Bin 0 -> 21095 bytes eventlet/httpd.py | 584 +++++++++++++++ eventlet/httpd.pyc | Bin 0 -> 21726 bytes eventlet/httpd_test.py | 207 ++++++ eventlet/httpd_test.pyc | Bin 0 -> 8482 bytes eventlet/httpdate.py | 39 + eventlet/httpdate.pyc | Bin 0 -> 1991 bytes eventlet/jsonhttp.py | 40 + eventlet/jsonhttp.pyc | Bin 0 -> 1857 bytes eventlet/kqueuehub.py | 219 ++++++ eventlet/kqueuehub.pyc | Bin 0 -> 6809 bytes eventlet/libeventhub.py | 121 ++++ eventlet/libeventhub.pyc | Bin 0 -> 5117 bytes eventlet/logutil.py | 112 +++ eventlet/logutil.pyc | Bin 0 -> 5419 bytes eventlet/pollhub.py | 189 +++++ eventlet/pools.py | 184 +++++ eventlet/pools.pyc | Bin 0 -> 7640 bytes eventlet/pools_test.py | 179 +++++ eventlet/pools_test.pyc | Bin 0 -> 7575 bytes eventlet/processes.py | 141 ++++ eventlet/processes.pyc | Bin 0 -> 6497 bytes eventlet/processes_test.py | 134 ++++ eventlet/processes_test.pyc | Bin 0 -> 5813 bytes eventlet/pylibsupport.py | 42 ++ eventlet/runloop.py | 228 ++++++ eventlet/runloop.pyc | Bin 0 -> 8842 bytes eventlet/runloop_test.py | 157 ++++ eventlet/runloop_test.pyc | Bin 0 -> 6357 bytes eventlet/saranwrap.py | 685 ++++++++++++++++++ eventlet/saranwrap.pyc | Bin 0 -> 26715 bytes eventlet/saranwrap_test.py | 316 ++++++++ eventlet/saranwrap_test.pyc | Bin 0 -> 14211 bytes eventlet/selecthub.py | 173 +++++ eventlet/selecthub.pyc | Bin 0 -> 5935 bytes eventlet/stacklesssupport.py | 110 +++ eventlet/tests.py | 89 +++ eventlet/tests.pyc | Bin 0 -> 3394 bytes eventlet/timer.py | 83 +++ eventlet/timer.pyc | Bin 0 -> 4055 bytes eventlet/timer_test.py | 66 ++ eventlet/timer_test.pyc | Bin 0 -> 3787 bytes eventlet/tls.py | 57 ++ eventlet/tls.pyc | Bin 0 -> 2581 bytes eventlet/tpool.py | 123 ++++ eventlet/tpool.pyc | Bin 0 -> 5364 bytes eventlet/tpool_test.py | 70 ++ eventlet/tpool_test.pyc | Bin 0 -> 3212 bytes eventlet/twistedsupport.py | 134 ++++ eventlet/util.py | 214 ++++++ eventlet/util.pyc | Bin 0 -> 8313 bytes eventlet/wrappedfd.py | 288 ++++++++ eventlet/wrappedfd.pyc | Bin 0 -> 11895 bytes eventlet/wsgi.py | 219 ++++++ examples/.svn/all-wcprops | 17 + examples/.svn/entries | 54 ++ examples/.svn/format | 1 + .../.svn/prop-base/echoserver.py.svn-base | 5 + .../.svn/prop-base/webcrawler.py.svn-base | 5 + .../.svn/text-base/echoserver.py.svn-base | 52 ++ .../.svn/text-base/webcrawler.py.svn-base | 55 ++ examples/echoserver.py | 52 ++ examples/webcrawler.py | 55 ++ setup.py | 28 + 171 files changed, 19012 insertions(+) create mode 100644 .svn/all-wcprops create mode 100644 .svn/entries create mode 100644 .svn/format create mode 100644 .svn/text-base/README.svn-base create mode 100644 .svn/text-base/setup.py.svn-base create mode 100644 README create mode 100644 eventlet/.svn/all-wcprops create mode 100644 eventlet/.svn/dir-prop-base create mode 100644 eventlet/.svn/entries create mode 100644 eventlet/.svn/format create mode 100644 eventlet/.svn/prop-base/__init__.py.svn-base create mode 100644 eventlet/.svn/prop-base/api.py.svn-base create mode 100644 eventlet/.svn/prop-base/api_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/backdoor.py.svn-base create mode 100644 eventlet/.svn/prop-base/channel.py.svn-base create mode 100644 eventlet/.svn/prop-base/coros.py.svn-base create mode 100644 eventlet/.svn/prop-base/coros_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/greenlib.py.svn-base create mode 100644 eventlet/.svn/prop-base/httpc.py.svn-base create mode 100644 eventlet/.svn/prop-base/httpd.py.svn-base create mode 100644 eventlet/.svn/prop-base/httpd_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/httpdate.py.svn-base create mode 100644 eventlet/.svn/prop-base/jsonhttp.py.svn-base create mode 100644 eventlet/.svn/prop-base/kqueuehub.py.svn-base create mode 100644 eventlet/.svn/prop-base/logutil.py.svn-base create mode 100644 eventlet/.svn/prop-base/pollhub.py.svn-base create mode 100644 eventlet/.svn/prop-base/pools.py.svn-base create mode 100644 eventlet/.svn/prop-base/pools_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/processes.py.svn-base create mode 100644 eventlet/.svn/prop-base/processes_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/pylibsupport.py.svn-base create mode 100644 eventlet/.svn/prop-base/runloop.py.svn-base create mode 100644 eventlet/.svn/prop-base/runloop_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/selecthub.py.svn-base create mode 100644 eventlet/.svn/prop-base/stacklesssupport.py.svn-base create mode 100644 eventlet/.svn/prop-base/tests.py.svn-base create mode 100644 eventlet/.svn/prop-base/timer.py.svn-base create mode 100644 eventlet/.svn/prop-base/timer_test.py.svn-base create mode 100644 eventlet/.svn/prop-base/tls.py.svn-base create mode 100644 eventlet/.svn/prop-base/twistedsupport.py.svn-base create mode 100644 eventlet/.svn/prop-base/util.py.svn-base create mode 100644 eventlet/.svn/prop-base/wrappedfd.py.svn-base create mode 100644 eventlet/.svn/prop-base/wsgi.py.svn-base create mode 100644 eventlet/.svn/text-base/__init__.py.svn-base create mode 100644 eventlet/.svn/text-base/api.py.svn-base create mode 100644 eventlet/.svn/text-base/api_test.py.svn-base create mode 100644 eventlet/.svn/text-base/backdoor.py.svn-base create mode 100644 eventlet/.svn/text-base/channel.py.svn-base create mode 100644 eventlet/.svn/text-base/coros.py.svn-base create mode 100644 eventlet/.svn/text-base/coros_test.py.svn-base create mode 100644 eventlet/.svn/text-base/db_pool.py.svn-base create mode 100644 eventlet/.svn/text-base/db_pool_test.py.svn-base create mode 100644 eventlet/.svn/text-base/greenlib.py.svn-base create mode 100644 eventlet/.svn/text-base/httpc.py.svn-base create mode 100644 eventlet/.svn/text-base/httpc_test.py.svn-base create mode 100644 eventlet/.svn/text-base/httpd.py.svn-base create mode 100644 eventlet/.svn/text-base/httpd_test.py.svn-base create mode 100644 eventlet/.svn/text-base/httpdate.py.svn-base create mode 100644 eventlet/.svn/text-base/jsonhttp.py.svn-base create mode 100644 eventlet/.svn/text-base/kqueuehub.py.svn-base create mode 100644 eventlet/.svn/text-base/logutil.py.svn-base create mode 100644 eventlet/.svn/text-base/pollhub.py.svn-base create mode 100644 eventlet/.svn/text-base/pools.py.svn-base create mode 100644 eventlet/.svn/text-base/pools_test.py.svn-base create mode 100644 eventlet/.svn/text-base/processes.py.svn-base create mode 100644 eventlet/.svn/text-base/processes_test.py.svn-base create mode 100644 eventlet/.svn/text-base/pylibsupport.py.svn-base create mode 100644 eventlet/.svn/text-base/runloop.py.svn-base create mode 100644 eventlet/.svn/text-base/runloop_test.py.svn-base create mode 100644 eventlet/.svn/text-base/saranwrap.py.svn-base create mode 100644 eventlet/.svn/text-base/saranwrap_test.py.svn-base create mode 100644 eventlet/.svn/text-base/selecthub.py.svn-base create mode 100644 eventlet/.svn/text-base/stacklesssupport.py.svn-base create mode 100644 eventlet/.svn/text-base/tests.py.svn-base create mode 100644 eventlet/.svn/text-base/timer.py.svn-base create mode 100644 eventlet/.svn/text-base/timer_test.py.svn-base create mode 100644 eventlet/.svn/text-base/tls.py.svn-base create mode 100644 eventlet/.svn/text-base/tpool.py.svn-base create mode 100644 eventlet/.svn/text-base/tpool_test.py.svn-base create mode 100644 eventlet/.svn/text-base/twistedsupport.py.svn-base create mode 100644 eventlet/.svn/text-base/util.py.svn-base create mode 100644 eventlet/.svn/text-base/wrappedfd.py.svn-base create mode 100644 eventlet/.svn/text-base/wsgi.py.svn-base create mode 100644 eventlet/.svn/tmp/tempfile.2.tmp create mode 100644 eventlet/__init__.py create mode 100644 eventlet/__init__.pyc create mode 100644 eventlet/api.py create mode 100644 eventlet/api.pyc create mode 100644 eventlet/api_test.py create mode 100644 eventlet/api_test.pyc create mode 100644 eventlet/backdoor.py create mode 100644 eventlet/bench.py create mode 100644 eventlet/channel.py create mode 100644 eventlet/channel.pyc create mode 100644 eventlet/coros.py create mode 100644 eventlet/coros.pyc create mode 100644 eventlet/coros_test.py create mode 100644 eventlet/coros_test.pyc create mode 100644 eventlet/db_pool.py create mode 100644 eventlet/db_pool.pyc create mode 100644 eventlet/db_pool_test.py create mode 100644 eventlet/db_pool_test.pyc create mode 100644 eventlet/greenlib.py create mode 100644 eventlet/greenlib.pyc create mode 100644 eventlet/httpc.py create mode 100644 eventlet/httpc.pyc create mode 100644 eventlet/httpc_test.py create mode 100644 eventlet/httpc_test.pyc create mode 100644 eventlet/httpd.py create mode 100644 eventlet/httpd.pyc create mode 100644 eventlet/httpd_test.py create mode 100644 eventlet/httpd_test.pyc create mode 100644 eventlet/httpdate.py create mode 100644 eventlet/httpdate.pyc create mode 100644 eventlet/jsonhttp.py create mode 100644 eventlet/jsonhttp.pyc create mode 100644 eventlet/kqueuehub.py create mode 100644 eventlet/kqueuehub.pyc create mode 100644 eventlet/libeventhub.py create mode 100644 eventlet/libeventhub.pyc create mode 100644 eventlet/logutil.py create mode 100644 eventlet/logutil.pyc create mode 100644 eventlet/pollhub.py create mode 100644 eventlet/pools.py create mode 100644 eventlet/pools.pyc create mode 100644 eventlet/pools_test.py create mode 100644 eventlet/pools_test.pyc create mode 100644 eventlet/processes.py create mode 100644 eventlet/processes.pyc create mode 100644 eventlet/processes_test.py create mode 100644 eventlet/processes_test.pyc create mode 100644 eventlet/pylibsupport.py create mode 100644 eventlet/runloop.py create mode 100644 eventlet/runloop.pyc create mode 100644 eventlet/runloop_test.py create mode 100644 eventlet/runloop_test.pyc create mode 100644 eventlet/saranwrap.py create mode 100644 eventlet/saranwrap.pyc create mode 100644 eventlet/saranwrap_test.py create mode 100644 eventlet/saranwrap_test.pyc create mode 100644 eventlet/selecthub.py create mode 100644 eventlet/selecthub.pyc create mode 100644 eventlet/stacklesssupport.py create mode 100644 eventlet/tests.py create mode 100644 eventlet/tests.pyc create mode 100644 eventlet/timer.py create mode 100644 eventlet/timer.pyc create mode 100644 eventlet/timer_test.py create mode 100644 eventlet/timer_test.pyc create mode 100644 eventlet/tls.py create mode 100644 eventlet/tls.pyc create mode 100644 eventlet/tpool.py create mode 100644 eventlet/tpool.pyc create mode 100644 eventlet/tpool_test.py create mode 100644 eventlet/tpool_test.pyc create mode 100644 eventlet/twistedsupport.py create mode 100644 eventlet/util.py create mode 100644 eventlet/util.pyc create mode 100644 eventlet/wrappedfd.py create mode 100644 eventlet/wrappedfd.pyc create mode 100644 eventlet/wsgi.py create mode 100644 examples/.svn/all-wcprops create mode 100644 examples/.svn/entries create mode 100644 examples/.svn/format create mode 100644 examples/.svn/prop-base/echoserver.py.svn-base create mode 100644 examples/.svn/prop-base/webcrawler.py.svn-base create mode 100644 examples/.svn/text-base/echoserver.py.svn-base create mode 100644 examples/.svn/text-base/webcrawler.py.svn-base create mode 100644 examples/echoserver.py create mode 100644 examples/webcrawler.py create mode 100644 setup.py diff --git a/.svn/all-wcprops b/.svn/all-wcprops new file mode 100644 index 0000000..57eb68b --- /dev/null +++ b/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 32 +/svn/eventlet/!svn/ver/100/trunk +END +setup.py +K 25 +svn:wc:ra_dav:version-url +V 40 +/svn/eventlet/!svn/ver/83/trunk/setup.py +END +README +K 25 +svn:wc:ra_dav:version-url +V 38 +/svn/eventlet/!svn/ver/83/trunk/README +END diff --git a/.svn/entries b/.svn/entries new file mode 100644 index 0000000..f8865c0 --- /dev/null +++ b/.svn/entries @@ -0,0 +1,58 @@ +8 + +dir +100 +https://svn.secondlife.com/svn/eventlet/trunk +https://svn.secondlife.com/svn/eventlet + + + +2008-03-05T09:26:30.084594Z +100 +which.linden + + +svn:special svn:externals svn:needs-lock + + + + + + + + + + + +cde37729-5338-0410-b9e2-c56166a61366 + +eventlet +dir + +setup.py +file + + + + +2008-02-20T19:57:15.000000Z +82a68910db8376655db41f89b3829524 +2008-01-28T22:21:28.111792Z +83 +which.linden + +README +file + + + + +2008-02-20T19:57:15.000000Z +8d9257b4ae3863579afebab7b38fa8ea +2008-01-28T22:21:28.111792Z +83 +which.linden + +examples +dir + diff --git a/.svn/format b/.svn/format new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/.svn/format @@ -0,0 +1 @@ +8 diff --git a/.svn/text-base/README.svn-base b/.svn/text-base/README.svn-base new file mode 100644 index 0000000..18094ae --- /dev/null +++ b/.svn/text-base/README.svn-base @@ -0,0 +1,66 @@ += eventlet = + +Eventlet is a networking library written in Python. It achieves high +scalability by using non-blocking io while at the same time retaining +high programmer usability by using coroutines to make the non-blocking +io operations appear blocking at the source code level. + +The wiki at http://wiki.secondlife.com/wiki/Eventlet is likely to be a +more current source of information than this README. Questions, +patches, and general discussion go to the eventlet mailing list: +https://lists.secondlife.com/cgi-bin/mailman/listinfo/eventletdev + +== requirements === + +Eventlet runs on Python version 2.3 or greater, with the following dependenceis: +* [http://cheeseshop.python.org/pypi/greenlet +* (if running python versions < 2.4) collections.py from the 2.4 distribution or later + +== limitations == + +* Sorely lacking in documentation +* Not enough test coverage -- the goal is 100%, but we are not there yet. +* Eventlet does not currently run on stackless using tasklets, though +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 == + +% python +>>> from eventlet import api +>>> help(api) + +Also, look at the examples in the examples directory. + +== eventlet history == + +eventlet began life as Donovan Preston was talking to Bob Ippolito +about coroutine-based non-blocking networking frameworks in +Python. Most non-blocking frameworks require you to run the "main +loop" in order to perform all network operations, but Donovan wondered +if a library written using a trampolining style could get away with +transparently running the main loop any time i/o was required, +stopping the main loop once no more i/o was scheduled. Bob spent a few +days during PyCon 2005 writing a proof-of-concept. He named it +eventlet, after the coroutine implementation it used, +[[greenlet]]. Donovan began using eventlet as a light-weight network +library for his spare-time project Pavel, and also began writing some +unittests. + +* http://svn.red-bean.com/bob/eventlet/trunk/ +* http://soundfarmer.com/Pavel/trunk/ + +When Donovan started at Linden Lab in May of 2006, he added eventlet +as an svn external in the indra/lib/python directory, to be a +dependency of the yet-to-be-named [[backbone]] project (at the time, +it was named restserv). However, including eventlet as an svn external +meant that any time the externally hosted project had hosting issues, +Linden developers were not able to perform svn updates. Thus, the +eventlet source was imported into the linden source tree at the same +location, and became a fork. + +Bob Ippolito has ceased working on eventlet and has stated his desire +for Linden to take its fork forward to the open source world as "the" +eventlet. diff --git a/.svn/text-base/setup.py.svn-base b/.svn/text-base/setup.py.svn-base new file mode 100644 index 0000000..dd5999c --- /dev/null +++ b/.svn/text-base/setup.py.svn-base @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + name='eventlet', + version='0.2', + description='Coroutine-based networking library', + author='Linden Lab', + author_email='eventletdev@lists.secondlife.com', + url='http://wiki.secondlife.com/wiki/Eventlet', + packages=['eventlet'], + install_requires=['greenlet'], + long_description=""" + Eventlet is a networking library written in Python. It achieves + high scalability by using non-blocking io while at the same time + retaining high programmer usability by using coroutines to make + the non-blocking io operations appear blocking at the source code + level.""", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", + "Intended Audience :: Developers", + "Development Status :: 4 - Beta"] + ) + diff --git a/README b/README new file mode 100644 index 0000000..18094ae --- /dev/null +++ b/README @@ -0,0 +1,66 @@ += eventlet = + +Eventlet is a networking library written in Python. It achieves high +scalability by using non-blocking io while at the same time retaining +high programmer usability by using coroutines to make the non-blocking +io operations appear blocking at the source code level. + +The wiki at http://wiki.secondlife.com/wiki/Eventlet is likely to be a +more current source of information than this README. Questions, +patches, and general discussion go to the eventlet mailing list: +https://lists.secondlife.com/cgi-bin/mailman/listinfo/eventletdev + +== requirements === + +Eventlet runs on Python version 2.3 or greater, with the following dependenceis: +* [http://cheeseshop.python.org/pypi/greenlet +* (if running python versions < 2.4) collections.py from the 2.4 distribution or later + +== limitations == + +* Sorely lacking in documentation +* Not enough test coverage -- the goal is 100%, but we are not there yet. +* Eventlet does not currently run on stackless using tasklets, though +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 == + +% python +>>> from eventlet import api +>>> help(api) + +Also, look at the examples in the examples directory. + +== eventlet history == + +eventlet began life as Donovan Preston was talking to Bob Ippolito +about coroutine-based non-blocking networking frameworks in +Python. Most non-blocking frameworks require you to run the "main +loop" in order to perform all network operations, but Donovan wondered +if a library written using a trampolining style could get away with +transparently running the main loop any time i/o was required, +stopping the main loop once no more i/o was scheduled. Bob spent a few +days during PyCon 2005 writing a proof-of-concept. He named it +eventlet, after the coroutine implementation it used, +[[greenlet]]. Donovan began using eventlet as a light-weight network +library for his spare-time project Pavel, and also began writing some +unittests. + +* http://svn.red-bean.com/bob/eventlet/trunk/ +* http://soundfarmer.com/Pavel/trunk/ + +When Donovan started at Linden Lab in May of 2006, he added eventlet +as an svn external in the indra/lib/python directory, to be a +dependency of the yet-to-be-named [[backbone]] project (at the time, +it was named restserv). However, including eventlet as an svn external +meant that any time the externally hosted project had hosting issues, +Linden developers were not able to perform svn updates. Thus, the +eventlet source was imported into the linden source tree at the same +location, and became a fork. + +Bob Ippolito has ceased working on eventlet and has stated his desire +for Linden to take its fork forward to the open source world as "the" +eventlet. diff --git a/eventlet/.svn/all-wcprops b/eventlet/.svn/all-wcprops new file mode 100644 index 0000000..bb8cae0 --- /dev/null +++ b/eventlet/.svn/all-wcprops @@ -0,0 +1,245 @@ +K 25 +svn:wc:ra_dav:version-url +V 41 +/svn/eventlet/!svn/ver/100/trunk/eventlet +END +stacklesssupport.py +K 25 +svn:wc:ra_dav:version-url +V 59 +/svn/eventlet/!svn/ver/3/trunk/eventlet/stacklesssupport.py +END +pools_test.py +K 25 +svn:wc:ra_dav:version-url +V 53 +/svn/eventlet/!svn/ver/3/trunk/eventlet/pools_test.py +END +pools.py +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/eventlet/!svn/ver/101/trunk/eventlet/pools.py +END +tpool_test.py +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/eventlet/!svn/ver/86/trunk/eventlet/tpool_test.py +END +saranwrap_test.py +K 25 +svn:wc:ra_dav:version-url +V 58 +/svn/eventlet/!svn/ver/80/trunk/eventlet/saranwrap_test.py +END +runloop_test.py +K 25 +svn:wc:ra_dav:version-url +V 56 +/svn/eventlet/!svn/ver/68/trunk/eventlet/runloop_test.py +END +saranwrap.py +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/eventlet/!svn/ver/100/trunk/eventlet/saranwrap.py +END +runloop.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/91/trunk/eventlet/runloop.py +END +kqueuehub.py +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/eventlet/!svn/ver/3/trunk/eventlet/kqueuehub.py +END +__init__.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/3/trunk/eventlet/__init__.py +END +tests.py +K 25 +svn:wc:ra_dav:version-url +V 49 +/svn/eventlet/!svn/ver/88/trunk/eventlet/tests.py +END +tls.py +K 25 +svn:wc:ra_dav:version-url +V 46 +/svn/eventlet/!svn/ver/3/trunk/eventlet/tls.py +END +pollhub.py +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/eventlet/!svn/ver/3/trunk/eventlet/pollhub.py +END +api.py +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/eventlet/!svn/ver/37/trunk/eventlet/api.py +END +wsgi.py +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/eventlet/!svn/ver/3/trunk/eventlet/wsgi.py +END +pylibsupport.py +K 25 +svn:wc:ra_dav:version-url +V 55 +/svn/eventlet/!svn/ver/3/trunk/eventlet/pylibsupport.py +END +wrappedfd.py +K 25 +svn:wc:ra_dav:version-url +V 53 +/svn/eventlet/!svn/ver/87/trunk/eventlet/wrappedfd.py +END +util.py +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/eventlet/!svn/ver/3/trunk/eventlet/util.py +END +selecthub.py +K 25 +svn:wc:ra_dav:version-url +V 53 +/svn/eventlet/!svn/ver/16/trunk/eventlet/selecthub.py +END +timer_test.py +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/eventlet/!svn/ver/68/trunk/eventlet/timer_test.py +END +timer.py +K 25 +svn:wc:ra_dav:version-url +V 49 +/svn/eventlet/!svn/ver/92/trunk/eventlet/timer.py +END +twistedsupport.py +K 25 +svn:wc:ra_dav:version-url +V 57 +/svn/eventlet/!svn/ver/3/trunk/eventlet/twistedsupport.py +END +httpc_test.py +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/eventlet/!svn/ver/70/trunk/eventlet/httpc_test.py +END +httpc.py +K 25 +svn:wc:ra_dav:version-url +V 49 +/svn/eventlet/!svn/ver/95/trunk/eventlet/httpc.py +END +httpd_test.py +K 25 +svn:wc:ra_dav:version-url +V 54 +/svn/eventlet/!svn/ver/97/trunk/eventlet/httpd_test.py +END +coros_test.py +K 25 +svn:wc:ra_dav:version-url +V 55 +/svn/eventlet/!svn/ver/101/trunk/eventlet/coros_test.py +END +tpool.py +K 25 +svn:wc:ra_dav:version-url +V 49 +/svn/eventlet/!svn/ver/93/trunk/eventlet/tpool.py +END +processes_test.py +K 25 +svn:wc:ra_dav:version-url +V 57 +/svn/eventlet/!svn/ver/3/trunk/eventlet/processes_test.py +END +processes.py +K 25 +svn:wc:ra_dav:version-url +V 53 +/svn/eventlet/!svn/ver/16/trunk/eventlet/processes.py +END +api_test.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/3/trunk/eventlet/api_test.py +END +channel.py +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/eventlet/!svn/ver/3/trunk/eventlet/channel.py +END +jsonhttp.py +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/eventlet/!svn/ver/75/trunk/eventlet/jsonhttp.py +END +db_pool_test.py +K 25 +svn:wc:ra_dav:version-url +V 56 +/svn/eventlet/!svn/ver/85/trunk/eventlet/db_pool_test.py +END +httpdate.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/3/trunk/eventlet/httpdate.py +END +db_pool.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/93/trunk/eventlet/db_pool.py +END +logutil.py +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/eventlet/!svn/ver/3/trunk/eventlet/logutil.py +END +httpd.py +K 25 +svn:wc:ra_dav:version-url +V 49 +/svn/eventlet/!svn/ver/97/trunk/eventlet/httpd.py +END +backdoor.py +K 25 +svn:wc:ra_dav:version-url +V 51 +/svn/eventlet/!svn/ver/3/trunk/eventlet/backdoor.py +END +coros.py +K 25 +svn:wc:ra_dav:version-url +V 50 +/svn/eventlet/!svn/ver/101/trunk/eventlet/coros.py +END +greenlib.py +K 25 +svn:wc:ra_dav:version-url +V 52 +/svn/eventlet/!svn/ver/96/trunk/eventlet/greenlib.py +END diff --git a/eventlet/.svn/dir-prop-base b/eventlet/.svn/dir-prop-base new file mode 100644 index 0000000..96ae2a7 --- /dev/null +++ b/eventlet/.svn/dir-prop-base @@ -0,0 +1,9 @@ +K 10 +svn:ignore +V 16 +*.pyc +*~ + +*.tmp + +END diff --git a/eventlet/.svn/entries b/eventlet/.svn/entries new file mode 100644 index 0000000..c9b2174 --- /dev/null +++ b/eventlet/.svn/entries @@ -0,0 +1,541 @@ +8 + +dir +100 +https://svn.secondlife.com/svn/eventlet/trunk/eventlet +https://svn.secondlife.com/svn/eventlet + + + +2008-03-05T09:26:30.084594Z +100 +which.linden +has-props + +svn:special svn:externals svn:needs-lock + + + + + + + + + + + +cde37729-5338-0410-b9e2-c56166a61366 + +stacklesssupport.py +file + + + + +2008-02-20T19:57:15.000000Z +6c51017e724b0fadba816ed73de7648f +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +pools_test.py +file + + + + +2008-02-20T19:57:15.000000Z +3835ff82a7af6edc7c09d276c67cd5db +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +pools.py +file +101 + + + +2008-03-08T01:16:18.000000Z +3734d5d23e28b08aaf0ec7df1f97b164 +2008-03-11T02:26:32.072967Z +101 +which.linden +has-props + +tpool_test.py +file + + + + +2008-02-20T19:57:15.000000Z +c5cb15c24b12acc0bcc14694a74de48b +2008-02-05T01:39:10.353080Z +86 +which.linden + +saranwrap_test.py +file + + + + +2008-02-20T19:57:15.000000Z +f99f25b424a2fc6780dfa0ab6a69eba4 +2008-01-24T00:14:38.439330Z +80 +which.linden + +runloop_test.py +file + + + + +2008-02-20T19:57:15.000000Z +e724370d70719da2834273b0bd5e84fc +2007-12-13T19:39:23.000239Z +68 +donovan.linden +has-props + +saranwrap.py +file + + + + +2008-03-05T09:22:18.000000Z +9b5f20eb03a7d45981c5d32e944008e4 +2008-03-05T09:26:30.084594Z +100 +which.linden + +runloop.py +file + + + + +2008-02-20T19:57:15.000000Z +0313cf6b7844ced258f7f6918d923435 +2008-02-19T06:10:56.251879Z +91 +which.linden +has-props + +kqueuehub.py +file + + + + +2008-02-20T19:57:15.000000Z +c68edf1618f20648e93e0a5a9b841b42 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +__init__.py +file + + + + +2008-02-20T19:57:15.000000Z +d2fd03ba7a2f3c38b210f598e9e0f52e +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +tests.py +file + + + + +2008-02-20T19:57:15.000000Z +7e2f8dcedb9825b705c2267a74dc9a56 +2008-02-14T03:13:16.377716Z +88 +which.linden +has-props + +tls.py +file + + + + +2008-02-20T19:57:15.000000Z +dc5cb5394ea3a29319f7302e51a7807c +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +pollhub.py +file + + + + +2008-02-20T19:57:15.000000Z +a6ac6144487b6d5f5df84262f5079955 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +api.py +file + + + + +2008-02-20T19:57:15.000000Z +dcff6139f8203bc7a1827207e06eb423 +2007-10-27T07:49:23.267939Z +37 +donovan.linden +has-props + +wsgi.py +file + + + + +2008-02-20T19:57:15.000000Z +7ce7f2d3c85243ec3ecaf1049aedbae4 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +pylibsupport.py +file + + + + +2008-02-20T19:57:15.000000Z +cf6f3b08989af055c8746292c5c8fc91 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +wrappedfd.py +file + + + + +2008-02-20T19:57:15.000000Z +5a1e9d228852226773e6c4ea1c17de6f +2008-02-13T20:04:45.776541Z +87 +nat.linden +has-props + +util.py +file + + + + +2008-02-20T19:57:15.000000Z +2dc700683fa2156f0b5c04cc0549da11 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +selecthub.py +file + + + + +2008-02-20T19:57:15.000000Z +86e451fd018d86d8b015f34d4f20fd3b +2007-10-08T07:12:35.632195Z +16 +which.linden +has-props + +timer_test.py +file + + + + +2008-02-20T19:57:15.000000Z +57ad2ed0e3cdc5b0825c05d7419ac133 +2007-12-13T19:39:23.000239Z +68 +donovan.linden +has-props + +timer.py +file + + + + +2008-02-20T19:57:15.000000Z +ebe5204bb1c220e903962b6abe0b24b4 +2008-02-19T06:15:19.940493Z +92 +which.linden +has-props + +twistedsupport.py +file + + + + +2008-02-20T19:57:15.000000Z +19b766b248cd310896dde84bedba6d3e +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +httpc_test.py +file + + + + +2008-02-20T19:57:15.000000Z +5ee0387bc46dd1f8cdff0d7059aa9bf6 +2007-12-14T00:34:51.703611Z +70 +sardonyx.linden + +httpc.py +file + + + + +2008-03-03T02:05:45.000000Z +76f48ad7feb782f4c1f0807acfecd688 +2008-02-29T22:22:47.916198Z +95 +donovan.linden +has-props + +httpd_test.py +file + + + + +2008-02-20T20:28:52.000000Z +147ad0bb4398ebf9427a1fc2b7843763 +2008-03-03T02:08:25.755808Z +97 +which.linden +has-props + +coros_test.py +file +101 + + + +2008-03-08T09:13:27.000000Z +7a922fe4bea9a193c5dee62ce86aa64c +2008-03-11T02:26:32.072967Z +101 +which.linden +has-props + +tpool.py +file + + + + +2008-02-20T19:57:15.000000Z +954ac5ec927128a5ccfe20c486c73db4 +2008-02-19T23:24:32.369211Z +93 +seeping.blister + +processes_test.py +file + + + + +2008-02-20T19:57:15.000000Z +42cd89f0b207638ed21c18db0d53b6f6 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +processes.py +file + + + + +2008-02-20T19:57:15.000000Z +4871f77ae4a77479cb4c16eff8b00897 +2007-10-08T07:12:35.632195Z +16 +which.linden +has-props + +api_test.py +file + + + + +2008-02-20T19:57:15.000000Z +fc0dd790c0de74912408bc5cc1c6087f +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +channel.py +file + + + + +2008-02-20T19:57:15.000000Z +6ef49c57d8fe793f488775fb7c7d2013 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +jsonhttp.py +file + + + + +2008-02-20T19:57:15.000000Z +a2f7e287f8df9cc66a34a5f701aa3edb +2008-01-10T00:10:39.631442Z +75 +which.linden +has-props + +db_pool_test.py +file + + + + +2008-02-20T19:57:15.000000Z +f9dcfdeb653bde2fc4bde41ee5e2d1d0 +2008-02-05T01:34:59.036551Z +85 +which.linden + +httpdate.py +file + + + + +2008-02-20T19:57:15.000000Z +9fb5c627d69fcdf9d952d38f8510f252 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +db_pool.py +file + + + + +2008-02-20T19:57:15.000000Z +e0fa71cdef20bba5cd3902a3093ca7da +2008-02-19T23:24:32.369211Z +93 +seeping.blister + +logutil.py +file + + + + +2008-02-20T19:57:15.000000Z +06f2ae2b2973cb742a8ae707fd504214 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +httpd.py +file + + + + +2008-03-03T02:05:45.000000Z +c072047f7b212b0407940805031dda86 +2008-03-03T02:08:25.755808Z +97 +which.linden +has-props + +backdoor.py +file + + + + +2008-02-20T19:57:15.000000Z +e06e68e073115125d5d209b75302e224 +2007-08-23T23:51:23.835123Z +1 +which.linden +has-props + +coros.py +file +101 + + + +2008-03-10T22:11:13.000000Z +0c7c37dbe89ebcb51958528b006903aa +2008-03-11T02:26:32.072967Z +101 +which.linden +has-props + +greenlib.py +file + + + + +2008-03-03T02:05:45.000000Z +996debf62c9f5dacc2dee99ed2568a25 +2008-03-02T23:59:20.899444Z +96 +which.linden +has-props + diff --git a/eventlet/.svn/format b/eventlet/.svn/format new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/eventlet/.svn/format @@ -0,0 +1 @@ +8 diff --git a/eventlet/.svn/prop-base/__init__.py.svn-base b/eventlet/.svn/prop-base/__init__.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/__init__.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/api.py.svn-base b/eventlet/.svn/prop-base/api.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/api.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/api_test.py.svn-base b/eventlet/.svn/prop-base/api_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/api_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/backdoor.py.svn-base b/eventlet/.svn/prop-base/backdoor.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/backdoor.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/channel.py.svn-base b/eventlet/.svn/prop-base/channel.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/channel.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/coros.py.svn-base b/eventlet/.svn/prop-base/coros.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/coros.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/coros_test.py.svn-base b/eventlet/.svn/prop-base/coros_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/coros_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/greenlib.py.svn-base b/eventlet/.svn/prop-base/greenlib.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/greenlib.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/httpc.py.svn-base b/eventlet/.svn/prop-base/httpc.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/httpc.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/httpd.py.svn-base b/eventlet/.svn/prop-base/httpd.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/httpd.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/httpd_test.py.svn-base b/eventlet/.svn/prop-base/httpd_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/httpd_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/httpdate.py.svn-base b/eventlet/.svn/prop-base/httpdate.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/httpdate.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/jsonhttp.py.svn-base b/eventlet/.svn/prop-base/jsonhttp.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/jsonhttp.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/kqueuehub.py.svn-base b/eventlet/.svn/prop-base/kqueuehub.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/kqueuehub.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/logutil.py.svn-base b/eventlet/.svn/prop-base/logutil.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/logutil.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/pollhub.py.svn-base b/eventlet/.svn/prop-base/pollhub.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/pollhub.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/pools.py.svn-base b/eventlet/.svn/prop-base/pools.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/pools.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/pools_test.py.svn-base b/eventlet/.svn/prop-base/pools_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/pools_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/processes.py.svn-base b/eventlet/.svn/prop-base/processes.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/processes.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/processes_test.py.svn-base b/eventlet/.svn/prop-base/processes_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/processes_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/pylibsupport.py.svn-base b/eventlet/.svn/prop-base/pylibsupport.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/pylibsupport.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/runloop.py.svn-base b/eventlet/.svn/prop-base/runloop.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/runloop.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/runloop_test.py.svn-base b/eventlet/.svn/prop-base/runloop_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/runloop_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/selecthub.py.svn-base b/eventlet/.svn/prop-base/selecthub.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/selecthub.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/stacklesssupport.py.svn-base b/eventlet/.svn/prop-base/stacklesssupport.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/stacklesssupport.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/tests.py.svn-base b/eventlet/.svn/prop-base/tests.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/tests.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/timer.py.svn-base b/eventlet/.svn/prop-base/timer.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/timer.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/timer_test.py.svn-base b/eventlet/.svn/prop-base/timer_test.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/timer_test.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/tls.py.svn-base b/eventlet/.svn/prop-base/tls.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/tls.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/twistedsupport.py.svn-base b/eventlet/.svn/prop-base/twistedsupport.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/twistedsupport.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/util.py.svn-base b/eventlet/.svn/prop-base/util.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/util.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/wrappedfd.py.svn-base b/eventlet/.svn/prop-base/wrappedfd.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/wrappedfd.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/prop-base/wsgi.py.svn-base b/eventlet/.svn/prop-base/wsgi.py.svn-base new file mode 100644 index 0000000..bdbd305 --- /dev/null +++ b/eventlet/.svn/prop-base/wsgi.py.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:eol-style +V 6 +native +END diff --git a/eventlet/.svn/text-base/__init__.py.svn-base b/eventlet/.svn/text-base/__init__.py.svn-base new file mode 100644 index 0000000..5021bea --- /dev/null +++ b/eventlet/.svn/text-base/__init__.py.svn-base @@ -0,0 +1,24 @@ +"""\ +@file __init__.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. +""" + +# This text exists only for the purpose of not making a complete +# mockery of the above copyright header. diff --git a/eventlet/.svn/text-base/api.py.svn-base b/eventlet/.svn/text-base/api.py.svn-base new file mode 100644 index 0000000..e9877fa --- /dev/null +++ b/eventlet/.svn/text-base/api.py.svn-base @@ -0,0 +1,309 @@ +"""\ +@file api.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 socket +import string +import linecache +import inspect +import traceback + +try: + import greenlet +except ImportError: + try: + import pylibsupport + pylibsupport.emulate() + greenlet = sys.modules['greenlet'] + except ImportError: + try: + import stacklesssupport + stacklesssupport.emulate() + greenlet = sys.modules['greenlet'] + except ImportError: + raise ImportError("Unable to find an implementation of greenlet.") + +from eventlet import greenlib, tls + +__all__ = [ + 'use_hub', 'get_hub', 'sleep', 'spawn', 'kill', + 'call_after', 'exc_after', 'trampoline', 'tcp_listener', 'tcp_server', +] + + +class TimeoutError(Exception): + pass + +_threadlocal = tls.local() + +def tcp_listener(address): + """ + Listen on the given (ip, port) address with a TCP socket. + Returns a socket object which one should call accept() on to + accept a connection on the newly bound socket. + + Generally, the returned socket will be passed to tcp_server, + which accepts connections forever and spawns greenlets for + each incoming connection. + """ + from eventlet import wrappedfd, util + socket = wrappedfd.wrapped_fd(util.tcp_socket()) + util.socket_bind_and_listen(socket, address) + return socket + +def ssl_listener(address, certificate, private_key): + """Listen on the given (ip, port) address with a TCP socket that + can do SSL. + + Returns a socket object which one should call accept() on to + accept a connection on the newly bound socket. + + Generally, the returned socket will be passed to tcp_server, + which accepts connections forever and spawns greenlets for + each incoming connection. + """ + from eventlet import util + socket = util.wrap_ssl(util.tcp_socket(), certificate, private_key) + util.socket_bind_and_listen(socket, address) + socket.is_secure = True + return socket + +def connect_tcp(address): + """ + Create a TCP connection to address (host, port) and return the socket. + """ + from eventlet import wrappedfd, util + desc = wrappedfd.wrapped_fd(util.tcp_socket()) + desc.connect(address) + return desc + +def tcp_server(listensocket, server, *args, **kw): + """ + Given a socket, accept connections forever, spawning greenlets + and executing "server" for each new incoming connection. + When listensocket is closed, the tcp_server greenlet will end. + + listensocket: + The socket to accept connections from. + + server: + The callable to call when a new connection is made. + + *args: + The arguments to pass to the call to server. + + **kw: + The keyword arguments to pass to the call to server. + """ + try: + try: + while True: + spawn(server, listensocket.accept(), *args, **kw) + except socket.error, e: + # Broken pipe means it was shutdown + if e[0] != 32: + raise + finally: + listensocket.close() + +def trampoline(fd, read=None, write=None, timeout=None): + t = None + hub = get_hub() + self = greenlet.getcurrent() + fileno = getattr(fd, 'fileno', lambda: fd)() + def _do_close(fn): + hub.remove_descriptor(fn) + greenlib.switch(self, exc=socket.error(32, 'Broken pipe')) + def _do_timeout(fn): + hub.remove_descriptor(fn) + greenlib.switch(self, exc=TimeoutError()) + def cb(_fileno): + if t is not None: + t.cancel() + hub.remove_descriptor(fileno) + greenlib.switch(self, fd) + if timeout is not None: + t = hub.schedule_call(timeout, _do_timeout) + hub.add_descriptor(fileno, read and cb, write and cb, _do_close) + return hub.switch() + +def _spawn_startup(cb, args, kw, cancel=None): + try: + greenlib.switch(greenlet.getcurrent().parent) + cancel = None + finally: + if cancel is not None: + cancel() + return cb(*args, **kw) + +def _spawn(g): + g.parent = greenlet.getcurrent() + greenlib.switch(g) + + +def spawn(cb, *args, **kw): + # killable + t = None + g = greenlib.tracked_greenlet() + t = get_hub().schedule_call(0, _spawn, g) + greenlib.switch(g, (_spawn_startup, cb, args, kw, t.cancel)) + return g + +kill = greenlib.kill + +def call_after(seconds, cb, *args, **kw): + # cancellable + def startup(): + g = greenlib.tracked_greenlet() + greenlib.switch(g, (_spawn_startup, cb, args, kw)) + greenlib.switch(g) + return get_hub().schedule_call(seconds, startup) + + +def exc_after(seconds, exc): + return call_after(seconds, switch, getcurrent(), None, exc) + + +def get_default_hub(): + try: + import eventlet.kqueuehub + except ImportError: + pass + else: + return eventlet.kqueuehub + import select + if hasattr(select, 'poll'): + import eventlet.pollhub + return eventlet.pollhub + else: + import eventlet.selecthub + return eventlet.selecthub + +def use_hub(mod=None): + if mod is None: + mod = get_default_hub() + if hasattr(_threadlocal, 'hub'): + del _threadlocal.hub + if hasattr(mod, 'Hub'): + _threadlocal.Hub = mod.Hub + else: + _threadlocal.Hub = mod + +def get_hub(): + try: + hub = _threadlocal.hub + except AttributeError: + try: + _threadlocal.Hub + except AttributeError: + use_hub() + hub = _threadlocal.hub = _threadlocal.Hub() + return hub + + +def sleep(timeout=0): + hub = get_hub() + hub.schedule_call(timeout, greenlib.switch, greenlet.getcurrent()) + hub.switch() + + +switch = greenlib.switch +getcurrent = greenlet.getcurrent +GreenletExit = greenlet.GreenletExit + + +class Spew(object): + def __init__(self, trace_names=None): + self.trace_names = trace_names + + def __call__(self, frame, event, arg): + if event == 'line': + lineno = frame.f_lineno + if '__file__' in frame.f_globals: + filename = frame.f_globals['__file__'] + if (filename.endswith('.pyc') or + filename.endswith('.pyo')): + filename = filename[:-1] + name = frame.f_globals['__name__'] + line = linecache.getline(filename, lineno) + else: + name = '[unknown]' + try: + src = inspect.getsourcelines(frame) + line = src[lineno] + except IOError: + line = 'Unknown code named [%s]. VM instruction #%d' % ( + frame.f_code.co_name, frame.f_lasti) + if self.trace_names is None or name in self.trace_names: + print '%s:%s: %s' % (name, lineno, line.rstrip()) + details = '\t' + tokens = line.translate( + string.maketrans(' ,.()', '\0' * 5)).split('\0') + for tok in tokens: + if tok in frame.f_globals: + details += '%s=%r ' % (tok, frame.f_globals[tok]) + if tok in frame.f_locals: + details += '%s=%r ' % (tok, frame.f_locals[tok]) + if details.strip(): + print details + return self + + +def spew(trace_names=None): + sys.settrace(Spew(trace_names)) + + +def unspew(): + sys.settrace(None) + + +def named(name): + """Return an object given its name. The name uses a module-like +syntax, eg: + os.path.join + or + mulib.mu.Resource + """ + toimport = name + obj = None + while toimport: + try: + obj = __import__(toimport) + break + except ImportError, err: + # print 'Import error on %s: %s' % (toimport, err) # debugging spam + toimport = '.'.join(toimport.split('.')[:-1]) + if obj is None: + raise ImportError('%s could not be imported' % (name, )) + for seg in name.split('.')[1:]: + try: + obj = getattr(obj, seg) + except AttributeError: + dirobj = dir(obj) + dirobj.sort() + raise AttributeError('attribute %r missing from %r (%r) %r' % ( + seg, obj, dirobj, name)) + return obj + diff --git a/eventlet/.svn/text-base/api_test.py.svn-base b/eventlet/.svn/text-base/api_test.py.svn-base new file mode 100644 index 0000000..b2c371a --- /dev/null +++ b/eventlet/.svn/text-base/api_test.py.svn-base @@ -0,0 +1,164 @@ +"""\ +@file api_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. +""" + +from eventlet import tests +from eventlet import api, wrappedfd, util +import socket + + +def check_hub(): + # Clear through the descriptor queue + api.sleep(0) + api.sleep(0) + assert not api.get_hub().descriptors, repr(api.get_hub().descriptors) + # Stop the runloop + api.get_hub().runloop.abort() + api.sleep(0) + assert not api.get_hub().runloop.running + + +class TestApi(tests.TestCase): + mode = 'static' + def test_tcp_listener(self): + socket = api.tcp_listener(('0.0.0.0', 0)) + assert socket.getsockname()[0] == '0.0.0.0' + socket.close() + + check_hub() + + def dont_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): + try: + conn, addr = listenfd.accept() + conn.write('hello\n') + conn.close() + finally: + listenfd.close() + + server = api.tcp_listener(('0.0.0.0', 0)) + api.spawn(accept_once, server) + + client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) + assert client.readline() == 'hello\n' + + assert client.read() == '' + client.close() + + check_hub() + + def test_server(self): + server = api.tcp_listener(('0.0.0.0', 0)) + bound_port = server.getsockname()[1] + connected = [] + + def accept_twice((conn, addr)): + print 'connected' + connected.append(True) + conn.close() + if len(connected) == 2: + server.close() + + api.call_after(0, api.connect_tcp, ('127.0.0.1', bound_port)) + api.call_after(0, api.connect_tcp, ('127.0.0.1', bound_port)) + api.tcp_server(server, accept_twice) + + assert len(connected) == 2 + + check_hub() + + def dont_test_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)) + bound_port = server.getsockname()[1] + + try: + desc = wrappedfd.wrapped_fd(util.tcp_socket()) + api.trampoline(desc, read=True, write=True, timeout=0.1) + except api.TimeoutError: + pass # test passed + else: + assert False, "Didn't timeout" + + check_hub() + + def test_timeout_cancel(self): + server = api.tcp_listener(('0.0.0.0', 0)) + bound_port = server.getsockname()[1] + + def client_connected((conn, addr)): + conn.close() + + def go(): + client = util.tcp_socket() + + desc = wrappedfd.wrapped_fd(client) + desc.connect(('127.0.0.1', bound_port)) + try: + api.trampoline(desc, read=True, write=True, timeout=0.1) + except api.TimeoutError: + assert False, "Timed out" + + server.close() + client.close() + + api.call_after(0, go) + + api.tcp_server(server, client_connected) + + check_hub() + + def dont_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) + assert isinstance(api.get_hub(), Foo), api.get_hub() + + api.use_hub(api.get_default_hub()) + + check_hub() + + def test_named(self): + named_foo = api.named('api_test.Foo') + self.assertEquals( + named_foo.__name__, + "Foo") + + def test_naming_missing_class(self): + self.assertRaises( + ImportError, api.named, 'this_name_should_hopefully_not_exist.Foo') + + +class Foo(object): + pass + + +if __name__ == '__main__': + tests.main() + diff --git a/eventlet/.svn/text-base/backdoor.py.svn-base b/eventlet/.svn/text-base/backdoor.py.svn-base new file mode 100644 index 0000000..8f792dd --- /dev/null +++ b/eventlet/.svn/text-base/backdoor.py.svn-base @@ -0,0 +1,85 @@ +"""\ +@file backdoor.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 +from code import InteractiveConsole +from eventlet import greenlib + +try: + sys.ps1 +except AttributeError: + sys.ps1 = '>>> ' +try: + sys.ps2 +except AttributeError: + sys.ps2 = '... ' + +class SocketConsole(greenlib.GreenletContext): + def __init__(self, desc): + # mangle the socket + self.desc = desc + readline = desc.readline + self.old = {} + self.fixups = { + 'softspace': 0, + 'isatty': lambda: True, + 'flush': lambda: None, + 'readline': lambda *a: readline(*a).replace('\r\n', '\n'), + } + for key, value in self.fixups.iteritems(): + if hasattr(desc, key): + self.old[key] = getattr(desc, key) + setattr(desc, key, value) + + def finalize(self): + # restore the state of the socket + for key in self.fixups: + try: + value = self.old[key] + except KeyError: + delattr(self.desc, key) + else: + setattr(self.desc, key, value) + self.fixups.clear() + self.old.clear() + self.desc = None + + def swap_in(self): + self.saved = sys.stdin, sys.stderr, sys.stdout + sys.stdin = sys.stdout = sys.stderr = self.desc + + def swap_out(self): + sys.stdin, sys.stderr, sys.stdout = self.saved + +def backdoor((conn, addr), locals=None): + host, port = addr + print "backdoor to %s:%s" % (host, port) + ctx = SocketConsole(conn) + ctx.register() + try: + console = InteractiveConsole(locals) + console.interact() + finally: + ctx.unregister() diff --git a/eventlet/.svn/text-base/channel.py.svn-base b/eventlet/.svn/text-base/channel.py.svn-base new file mode 100644 index 0000000..a24799a --- /dev/null +++ b/eventlet/.svn/text-base/channel.py.svn-base @@ -0,0 +1,98 @@ +"""\ +@file channel.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 collections + +from eventlet import api, greenlib + +import greenlet + +__all__ = ['channel'] + +class channel(object): + """A channel is a control flow primitive for co-routines. It is a + "thread-like" queue for controlling flow between two (or more) co-routines. + The state model is: + + * If one co-routine calls send(), it is unscheduled until another + co-routine calls receive(). + * If one co-rounte calls receive(), it is unscheduled until another + co-routine calls send(). + * Once a paired send()/receive() have been called, both co-routeines + are rescheduled. + + This is similar to: http://stackless.com/wiki/Channels + """ + balance = 0 + + def _tasklet_loop(self): + deque = self.deque = collections.deque() + hub = api.get_hub() + switch = greenlib.switch + direction, caller, args = switch() + try: + while True: + if direction == -1: + # waiting to receive + if self.balance > 0: + sender, args = deque.popleft() + hub.schedule_call(0, switch, sender) + hub.schedule_call(0, switch, caller, *args) + else: + deque.append(caller) + else: + # waiting to send + if self.balance < 0: + receiver = deque.popleft() + hub.schedule_call(0, switch, receiver, *args) + hub.schedule_call(0, switch, caller) + else: + deque.append((caller, args)) + self.balance += direction + direction, caller, args = hub.switch() + finally: + deque.clear() + del self.deque + self.balance = 0 + + def _send_tasklet(self, *args): + try: + t = self._tasklet + except AttributeError: + t = self._tasklet = greenlib.tracked_greenlet() + greenlib.switch(t, (self._tasklet_loop,)) + if args: + return greenlib.switch(t, (1, greenlet.getcurrent(), args)) + else: + return greenlib.switch(t, (-1, greenlet.getcurrent(), args)) + + def receive(self): + return self._send_tasklet() + + def send(self, value): + return self._send_tasklet(value) + + def send_exception(self, exc): + return self._send_tasklet(None, exc) diff --git a/eventlet/.svn/text-base/coros.py.svn-base b/eventlet/.svn/text-base/coros.py.svn-base new file mode 100644 index 0000000..c53f807 --- /dev/null +++ b/eventlet/.svn/text-base/coros.py.svn-base @@ -0,0 +1,457 @@ +"""\ +@file coros.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 collections +import sys +import time +import traceback + + +from eventlet import api +from eventlet import channel +from eventlet import pools +from eventlet import greenlib + + +try: + set +except NameError: # python 2.3 compatibility + from sets import Set as set + + +class Cancelled(RuntimeError): + pass + + +NOT_USED = object() + + +class event(object): + """An abstraction where an arbitrary number of coroutines + can wait for one event from another. + + Events differ from channels in two ways: + 1) calling send() does not unschedule the current coroutine + 2) send() can only be called once; use reset() to prepare the event for + another send() + They are ideal for communicating return values between coroutines. + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def baz(b): + ... evt.send(b + 1) + ... + >>> _ = api.spawn(baz, 3) + >>> evt.wait() + 4 + """ + _result = None + def __init__(self): + self.reset() + + def reset(self): + """ Reset this event so it can be used to send again. + Can only be called after send has been called. + + >>> from eventlet import coros + >>> evt = coros.event() + >>> evt.send(1) + >>> evt.reset() + >>> evt.send(2) + >>> evt.wait() + 2 + + Calling reset multiple times in a row is an error. + + >>> evt.reset() + >>> evt.reset() + Traceback (most recent call last): + ... + AssertionError: Trying to re-reset() a fresh event. + + """ + assert self._result is not NOT_USED, 'Trying to re-reset() a fresh event.' + self.epoch = time.time() + self._result = NOT_USED + self._waiters = {} + + def wait(self): + """Wait until another coroutine calls send. + Returns the value the other coroutine passed to + send. + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def wait_on(): + ... retval = evt.wait() + ... print "waited for", retval + >>> _ = api.spawn(wait_on) + >>> evt.send('result') + >>> api.sleep(0) + waited for result + + Returns immediately if the event has already + occured. + + >>> evt.wait() + 'result' + """ + if self._result is NOT_USED: + self._waiters[api.getcurrent()] = True + return api.get_hub().switch() + if self._exc is not None: + raise self._exc + return self._result + + def cancel(self, waiter): + """Raise an exception into a coroutine which called + wait() an this event instead of returning a value + from wait. Sends the eventlet.coros.Cancelled + exception + + waiter: The greenlet (greenlet.getcurrent()) of the + coroutine to cancel + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def wait_on(): + ... try: + ... print "received " + evt.wait() + ... except coros.Cancelled, c: + ... print "Cancelled" + ... + >>> waiter = api.spawn(wait_on) + + The cancel call works on coroutines that are in the wait() call. + + >>> api.sleep(0) # enter the wait() + >>> evt.cancel(waiter) + >>> api.sleep(0) # receive the exception + Cancelled + + The cancel is invisible to coroutines that call wait() after cancel() + is called. This is different from send()'s behavior, where the result + is passed to any waiter regardless of the ordering of the calls. + + >>> waiter = api.spawn(wait_on) + >>> api.sleep(0) + + Cancels have no effect on the ability to send() to the event. + + >>> evt.send('stuff') + >>> api.sleep(0) + received stuff + """ + if waiter in self._waiters: + del self._waiters[waiter] + api.get_hub().schedule_call( + 0, greenlib.switch, waiter, None, Cancelled()) + + def send(self, result=None, exc=None): + """Makes arrangements for the waiters to be woken with the + result and then returns immediately to the parent. + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def waiter(): + ... print 'about to wait' + ... result = evt.wait() + ... print 'waited for', result + >>> _ = api.spawn(waiter) + >>> api.sleep(0) + about to wait + >>> evt.send('a') + >>> api.sleep(0) + waited for a + + It is an error to call send() multiple times on the same event. + + >>> evt.send('whoops') + Traceback (most recent call last): + ... + AssertionError: Trying to re-send() an already-triggered event. + + Use reset() between send()s to reuse an event object. + """ + assert self._result is NOT_USED, 'Trying to re-send() an already-triggered event.' + self._result = result + self._exc = exc + hub = api.get_hub() + for waiter in self._waiters: + hub.schedule_call(0, greenlib.switch, waiter, self._result) + + +def execute(func, *args, **kw): + """ Executes an operation asynchronously in a new coroutine, returning + an event to retrieve the return value. + + This has the same api as the CoroutinePool.execute method; the only + difference is that this one creates a new coroutine instead of drawing + from a pool. + + >>> from eventlet import coros + >>> evt = coros.execute(lambda a: ('foo', a), 1) + >>> evt.wait() + ('foo', 1) + """ + evt = event() + def _really_execute(): + evt.send(func(*args, **kw)) + api.spawn(_really_execute) + return evt + + +class CoroutinePool(pools.Pool): + """ Like a thread pool, but with coroutines. + + Coroutine pools are useful for splitting up tasks or globally controlling + concurrency. You don't retrieve the coroutines directly with get() -- + instead use the execute() and execute_async() methods to run code. + + >>> from eventlet import coros, api + >>> p = coros.CoroutinePool(max_size=2) + >>> def foo(a): + ... print "foo", a + ... + >>> evt = p.execute(foo, 1) + >>> evt.wait() + foo 1 + + Once the pool is exhausted, calling an execute forces a yield. + + >>> p.execute_async(foo, 2) + >>> p.execute_async(foo, 3) + >>> p.free() + 0 + >>> p.execute_async(foo, 4) + foo 2 + foo 3 + + >>> api.sleep(0) + foo 4 + """ + + def __init__(self, min_size=0, max_size=4): + self._greenlets = set() + super(CoroutinePool, self).__init__(min_size, max_size) + + def _main_loop(self, sender): + """ Private, infinite loop run by a pooled coroutine. """ + while True: + recvd = sender.wait() + sender.reset() + (evt, func, args, kw) = recvd + self._safe_apply(evt, func, args, kw) + api.get_hub().runloop.cancel_timers(api.getcurrent()) + self.put(sender) + + def _safe_apply(self, evt, func, args, kw): + """ Private method that runs the function, catches exceptions, and + passes back the return value in the event.""" + try: + result = func(*args, **kw) + if evt is not None: + evt.send(result) + except api.GreenletExit, e: + # we're printing this out to see if it ever happens + # in practice + print "GreenletExit raised in coroutine pool", e + if evt is not None: + evt.send(e) # sent as a return value, not an exception + except KeyboardInterrupt: + raise # allow program to exit + except Exception, e: + traceback.print_exc() + if evt is not None: + evt.send(exc=e) + + def _execute(self, evt, func, args, kw): + """ Private implementation of the execute methods. + """ + # if reentering an empty pool, don't try to wait on a coroutine freeing + # itself -- instead, just execute in the current coroutine + if self.free() == 0 and api.getcurrent() in self._greenlets: + self._safe_apply(evt, func, args, kw) + else: + sender = self.get() + sender.send((evt, func, args, kw)) + + def create(self): + """Private implementation of eventlet.pools.Pool + interface. Creates an event and spawns the + _main_loop coroutine, passing the event. + The event is used to send a callable into the + new coroutine, to be executed. + """ + sender = event() + self._greenlets.add(api.spawn(self._main_loop, sender)) + return sender + + def execute(self, func, *args, **kw): + """Execute func in one of the coroutines maintained + by the pool, when one is free. + + Immediately returns an eventlet.coros.event object which + func's result will be sent to when it is available. + + >>> from eventlet import coros + >>> p = coros.CoroutinePool() + >>> evt = p.execute(lambda a: ('foo', a), 1) + >>> evt.wait() + ('foo', 1) + """ + receiver = event() + self._execute(receiver, func, args, kw) + return receiver + + def execute_async(self, func, *args, **kw): + """Execute func in one of the coroutines maintained + by the pool, when one is free. + + No return value is provided. + >>> from eventlet import coros, api + >>> p = coros.CoroutinePool() + >>> def foo(a): + ... print "foo", a + ... + >>> p.execute_async(foo, 1) + >>> api.sleep(0) + foo 1 + """ + self._execute(None, func, args, kw) + + +class pipe(object): + """ Implementation of pipe using events. Not tested! Not used, either.""" + def __init__(self): + self._event = event() + self._buffer = '' + + def send(self, txt): + self._buffer += txt + evt, self._event = self._event, event() + evt.send() + + def recv(self, num=16384): + if not self._buffer: + self._event.wait() + if num >= len(self._buffer): + buf, self._buffer = self._buffer, '' + else: + buf, self._buffer = self._buffer[:num], self._buffer[num:] + return buf + + +class Actor(object): + """ A free-running coroutine that accepts and processes messages. + + Kind of the equivalent of an Erlang process, really. It processes + a queue of messages in the order that they were sent. You must + subclass this and implement your own version of receive(). + + The actor's reference count will never drop to zero while the + coroutine exists; if you lose all references to the actor object + it will never be freed. + """ + def __init__(self, concurrency = 1): + """ Constructs an Actor, kicking off a new coroutine to process the messages. + + The concurrency argument specifies how many messages the actor will try + to process concurrently. If it is 1, the actor will process messages + serially. + """ + self._mailbox = collections.deque() + self._event = event() + self._killer = api.spawn(self.run_forever) + self._pool = CoroutinePool(min_size=0, max_size=concurrency) + + def run_forever(self): + """ Loops forever, continually checking the mailbox. """ + while True: + if not self._mailbox: + self._event.wait() + self._event = event() + else: + # leave the message in the mailbox until after it's + # been processed so the event doesn't get triggered + # while in the received method + self._pool.execute_async( + self.received, self._mailbox[0]) + self._mailbox.popleft() + + def cast(self, message): + """ Send a message to the actor. + + If the actor is busy, the message will be enqueued for later + consumption. There is no return value. + + >>> a = Actor() + >>> a.received = lambda msg: msg + >>> a.cast("hello") + """ + self._mailbox.append(message) + # if this is the only message, the coro could be waiting + if len(self._mailbox) == 1: + self._event.send() + + def received(self, message): + """ Called to process each incoming message. + + The default implementation just raises an exception, so + replace it with something useful! + + >>> class Greeter(Actor): + ... def received(self, (message, evt) ): + ... print "received", message + ... if evt: evt.send() + ... + >>> a = Greeter() + + This example uses events to synchronize between the actor and the main + coroutine in a predictable manner, but this kinda defeats the point of + the Actor, so don't do it in a real application. + + >>> evt = event() + >>> a.cast( ("message 1", evt) ) + >>> evt.wait() # force it to run at this exact moment + received message 1 + >>> evt.reset() + >>> a.cast( ("message 2", None) ) + >>> a.cast( ("message 3", evt) ) + >>> evt.wait() + received message 2 + received message 3 + + >>> api.kill(a._killer) # test cleanup + """ + raise NotImplementedError() + + +def _test(): + print "Running doctests. There will be no further output if they succeed." + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() diff --git a/eventlet/.svn/text-base/coros_test.py.svn-base b/eventlet/.svn/text-base/coros_test.py.svn-base new file mode 100644 index 0000000..a72bc7a --- /dev/null +++ b/eventlet/.svn/text-base/coros_test.py.svn-base @@ -0,0 +1,300 @@ +"""\ +@file coros_test.py +@author Donovan Preston, Ryan Williams + +Copyright (c) 2000-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 timer +from eventlet import coros, api + +class TestEvent(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, RuntimeError()) + + def tearDown(self): + self._cancel_timeout.cancel() + + def test_waiting_for_event(self): + evt = coros.event() + value = 'some stuff' + def send_to_event(): + evt.send(value) + api.spawn(send_to_event) + self.assertEqual(evt.wait(), value) + + def test_multiple_waiters(self): + evt = coros.event() + value = 'some stuff' + results = [] + def wait_on_event(i_am_done): + evt.wait() + results.append(True) + i_am_done.send() + + waiters = [] + count = 5 + for i in range(count): + waiters.append(coros.event()) + api.spawn(wait_on_event, waiters[-1]) + evt.send() + + for w in waiters: + w.wait() + + self.assertEqual(len(results), count) + + def test_cancel(self): + evt = coros.event() + # close over the current coro so we can cancel it explicitly + current = api.getcurrent() + def cancel_event(): + evt.cancel(current) + api.spawn(cancel_event) + + self.assertRaises(coros.Cancelled, evt.wait) + + def test_reset(self): + evt = coros.event() + + # calling reset before send should throw + self.assertRaises(AssertionError, evt.reset) + + value = 'some stuff' + def send_to_event(): + evt.send(value) + api.spawn(send_to_event) + self.assertEqual(evt.wait(), value) + + # now try it again, and we should get the same exact value, + # and we shouldn't be allowed to resend without resetting + value2 = 'second stuff' + self.assertRaises(AssertionError, evt.send, value2) + self.assertEqual(evt.wait(), value) + + # reset and everything should be happy + evt.reset() + def send_to_event2(): + evt.send(value2) + api.spawn(send_to_event2) + self.assertEqual(evt.wait(), value2) + + def test_double_exception(self): + evt = coros.event() + # send an exception through the event + evt.send(exc=RuntimeError()) + self.assertRaises(RuntimeError, evt.wait) + evt.reset() + # shouldn't see the RuntimeError again + api.exc_after(0.001, api.TimeoutError) + self.assertRaises(api.TimeoutError, evt.wait) + +class TestCoroutinePool(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, api.TimeoutError) + + def tearDown(self): + self._cancel_timeout.cancel() + + def test_execute_async(self): + done = coros.event() + def some_work(): + done.send() + pool = coros.CoroutinePool(0, 2) + pool.execute_async(some_work) + done.wait() + + def test_execute(self): + value = 'return value' + def some_work(): + return value + pool = coros.CoroutinePool(0, 2) + worker = pool.execute(some_work) + self.assertEqual(value, worker.wait()) + + def test_multiple_coros(self): + evt = coros.event() + results = [] + def producer(): + results.append('prod') + evt.send() + + def consumer(): + results.append('cons1') + evt.wait() + results.append('cons2') + + pool = coros.CoroutinePool(0, 2) + done = pool.execute(consumer) + pool.execute_async(producer) + done.wait() + self.assertEquals(['cons1', 'prod', 'cons2'], results) + + def test_timer_cancel(self): + def some_work(): + t = timer.Timer(5, lambda: None) + t.schedule() + return t + pool = coros.CoroutinePool(0, 2) + worker = pool.execute(some_work) + t = worker.wait() + api.sleep(0) + self.assertEquals(t.cancelled, True) + + def test_reentrant(self): + pool = coros.CoroutinePool(0,1) + def reenter(): + waiter = pool.execute(lambda a: a, 'reenter') + self.assertEqual('reenter', waiter.wait()) + + outer_waiter = pool.execute(reenter) + outer_waiter.wait() + + evt = coros.event() + def reenter_async(): + pool.execute_async(lambda a: a, 'reenter') + evt.send('done') + + pool.execute_async(reenter_async) + evt.wait() + + +class IncrActor(coros.Actor): + def received(self, evt): + self.value = getattr(self, 'value', 0) + 1 + if evt: evt.send() + +class TestActor(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, api.TimeoutError()) + self.actor = IncrActor() + + def tearDown(self): + self._cancel_timeout.cancel() + api.kill(self.actor._killer) + + def test_cast(self): + evt = coros.event() + self.actor.cast(evt) + evt.wait() + evt.reset() + self.assertEqual(self.actor.value, 1) + self.actor.cast(evt) + evt.wait() + self.assertEqual(self.actor.value, 2) + + def test_cast_multi_1(self): + # make sure that both messages make it in there + evt = coros.event() + evt1 = coros.event() + self.actor.cast(evt) + self.actor.cast(evt1) + evt.wait() + evt1.wait() + self.assertEqual(self.actor.value, 2) + + def test_cast_multi_2(self): + # the actor goes through a slightly different code path if it + # is forced to enter its event loop prior to any cast()s + api.sleep(0) + self.test_cast_multi_1() + + def test_sleeping_during_received(self): + # ensure that even if the received method cooperatively + # yields, eventually all messages are delivered + msgs = [] + waiters = [] + def received( (message, evt) ): + api.sleep(0) + msgs.append(message) + evt.send() + self.actor.received = received + + waiters.append(coros.event()) + self.actor.cast( (1, waiters[-1])) + api.sleep(0) + waiters.append(coros.event()) + self.actor.cast( (2, waiters[-1]) ) + waiters.append(coros.event()) + self.actor.cast( (3, waiters[-1]) ) + api.sleep(0) + waiters.append(coros.event()) + self.actor.cast( (4, waiters[-1]) ) + waiters.append(coros.event()) + self.actor.cast( (5, waiters[-1]) ) + for evt in waiters: + evt.wait() + self.assertEqual(msgs, [1,2,3,4,5]) + + + def test_raising_received(self): + msgs = [] + def received( (message, evt) ): + evt.send() + if message == 'fail': + raise RuntimeError() + else: + msgs.append(message) + + self.actor.received = received + + evt = coros.event() + self.actor.cast( ('fail', evt) ) + evt.wait() + evt.reset() + self.actor.cast( ('should_appear', evt) ) + evt.wait() + self.assertEqual(['should_appear'], msgs) + + def test_multiple(self): + self.actor = IncrActor(concurrency=2) + total = [0] + def received( (func, ev, value) ): + func() + total[0] += value + ev.send() + self.actor.received = received + + def onemoment(): + api.sleep(0.1) + + evt = coros.event() + evt1 = coros.event() + + self.actor.cast( (onemoment, evt, 1) ) + self.actor.cast( (lambda: None, evt1, 2) ) + + evt1.wait() + self.assertEqual(total[0], 2) + # both coroutines should have been used + self.assertEqual(self.actor._pool.current_size, 2) + self.assertEqual(self.actor._pool.free(), 1) + evt.wait() + self.assertEqual(total[0], 3) + self.assertEqual(self.actor._pool.free(), 2) + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/.svn/text-base/db_pool.py.svn-base b/eventlet/.svn/text-base/db_pool.py.svn-base new file mode 100644 index 0000000..3c91793 --- /dev/null +++ b/eventlet/.svn/text-base/db_pool.py.svn-base @@ -0,0 +1,220 @@ +"""\ +@file db_pool.py +@brief Uses saranwrap to implement a pool of nonblocking database connections to a db server. + +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 os, sys + +from eventlet.pools import Pool +from eventlet.processes import DeadProcess +from eventlet import saranwrap + +class DatabaseConnector(object): + """\ +@brief This is an object which will maintain a collection of database +connection pools keyed on host,databasename""" + def __init__(self, module, credentials, min_size = 0, max_size = 4, conn_pool=None, *args, **kwargs): + """\ + @brief constructor + @param min_size the minimum size of a child pool. + @param max_size the maximum size of a child pool.""" + assert(module) + self._conn_pool_class = conn_pool + if self._conn_pool_class is None: + self._conn_pool_class = ConnectionPool + self._module = module + self._min_size = min_size + self._max_size = max_size + self._args = args + self._kwargs = kwargs + self._credentials = credentials # this is a map of hostname to username/password + self._databases = {} + + def credentials_for(self, host): + if host in self._credentials: + return self._credentials[host] + else: + return self._credentials.get('default', None) + + def get(self, host, dbname): + key = (host, dbname) + if key not in self._databases: + new_kwargs = self._kwargs.copy() + new_kwargs['db'] = dbname + new_kwargs['host'] = host + new_kwargs.update(self.credentials_for(host)) + dbpool = self._conn_pool_class(self._module, min_size=self._min_size, max_size=self._max_size, + *self._args, **new_kwargs) + self._databases[key] = dbpool + + return self._databases[key] + +class BaseConnectionPool(Pool): + # *TODO: we need to expire and close connections if they've been + # idle for a while, so that system-wide connection count doesn't + # monotonically increase forever + def __init__(self, db_module, min_size = 0, max_size = 4, *args, **kwargs): + assert(db_module) + self._db_module = db_module + self._args = args + self._kwargs = kwargs + super(BaseConnectionPool, self).__init__(min_size, max_size) + + def get(self): + # wrap the connection for easier use + conn = super(BaseConnectionPool, self).get() + return PooledConnectionWrapper(conn, self) + + def put(self, conn): + # rollback any uncommitted changes, so that the next client + # has a clean slate. This also pokes the connection to see if + # it's dead or None + try: + conn.rollback() + except AttributeError, e: + # this means it's already been destroyed, so we don't need to print anything + conn = None + except: + # we don't care what the exception was, we just know the + # connection is dead + print "WARNING: connection.rollback raised: %s" % (sys.exc_info()[1]) + conn = None + + # unwrap the connection for storage + if isinstance(conn, GenericConnectionWrapper): + if conn: + base = conn._base + conn._destroy() + conn = base + else: + conn = None + + if conn is not None: + super(BaseConnectionPool, self).put(conn) + else: + self.current_size -= 1 + + +class SaranwrappedConnectionPool(BaseConnectionPool): + """A pool which gives out saranwrapped database connections from a pool + """ + def create(self): + return saranwrap.wrap(self._db_module).connect(*self._args, **self._kwargs) + +class TpooledConnectionPool(BaseConnectionPool): + """A pool which gives out tpool.Proxy-based database connections from a pool. + """ + def create(self): + from eventlet import tpool + try: + # *FIX: this is a huge hack that will probably only work for MySQLdb + autowrap = (self._db_module.cursors.DictCursor,) + except: + autowrap = () + return tpool.Proxy(self._db_module.connect(*self._args, **self._kwargs), + autowrap=autowrap) + +class RawConnectionPool(BaseConnectionPool): + """A pool which gives out plain database connections from a pool. + """ + def create(self): + return self._db_module.connect(*self._args, **self._kwargs) + +# default connection pool is the tpool one +ConnectionPool = TpooledConnectionPool + + +class GenericConnectionWrapper(object): + def __init__(self, baseconn): + self._base = baseconn + def __enter__(self): return self._base.__enter__() + def __exit__(self, exc, value, tb): return self._base.__exit__(exc, value, tb) + def __repr__(self): return self._base.__repr__() + def affected_rows(self): return self._base.affected_rows() + def autocommit(self,*args, **kwargs): return self._base.autocommit(*args, **kwargs) + def begin(self): return self._base.begin() + def change_user(self,*args, **kwargs): return self._base.change_user(*args, **kwargs) + def character_set_name(self,*args, **kwargs): return self._base.character_set_name(*args, **kwargs) + def close(self,*args, **kwargs): return self._base.close(*args, **kwargs) + def commit(self,*args, **kwargs): return self._base.commit(*args, **kwargs) + def cursor(self, cursorclass=None, **kwargs): return self._base.cursor(cursorclass, **kwargs) + def dump_debug_info(self,*args, **kwargs): return self._base.dump_debug_info(*args, **kwargs) + def errno(self,*args, **kwargs): return self._base.errno(*args, **kwargs) + def error(self,*args, **kwargs): return self._base.error(*args, **kwargs) + def errorhandler(self, conn, curs, errcls, errval): return self._base.errorhandler(conn, curs, errcls, errval) + def literal(self, o): return self._base.literal(o) + def set_character_set(self, charset): return self._base.set_character_set(charset) + def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) + def show_warnings(self): return self._base.show_warnings() + def warning_count(self): return self._base.warning_count() + def literal(self, o): return self._base.literal(o) + def ping(self,*args, **kwargs): return self._base.ping(*args, **kwargs) + def query(self,*args, **kwargs): return self._base.query(*args, **kwargs) + def rollback(self,*args, **kwargs): return self._base.rollback(*args, **kwargs) + def select_db(self,*args, **kwargs): return self._base.select_db(*args, **kwargs) + def set_server_option(self,*args, **kwargs): return self._base.set_server_option(*args, **kwargs) + def set_character_set(self, charset): return self._base.set_character_set(charset) + def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) + def server_capabilities(self,*args, **kwargs): return self._base.server_capabilities(*args, **kwargs) + def show_warnings(self): return self._base.show_warnings() + def shutdown(self,*args, **kwargs): return self._base.shutdown(*args, **kwargs) + def sqlstate(self,*args, **kwargs): return self._base.sqlstate(*args, **kwargs) + def stat(self,*args, **kwargs): return self._base.stat(*args, **kwargs) + def store_result(self,*args, **kwargs): return self._base.store_result(*args, **kwargs) + def string_literal(self,*args, **kwargs): return self._base.string_literal(*args, **kwargs) + def thread_id(self,*args, **kwargs): return self._base.thread_id(*args, **kwargs) + def use_result(self,*args, **kwargs): return self._base.use_result(*args, **kwargs) + def warning_count(self): return self._base.warning_count() + + +class PooledConnectionWrapper(GenericConnectionWrapper): + """ A connection wrapper where: + - the close method returns the connection to the pool instead of closing it directly + - you can do if conn: + - returns itself to the pool if it gets garbage collected + """ + def __init__(self, baseconn, pool): + super(PooledConnectionWrapper, self).__init__(baseconn) + self._pool = pool + + def __nonzero__(self): + return (hasattr(self, '_base') and bool(self._base)) + + def _destroy(self): + self._pool = None + try: + del self._base + except AttributeError: + pass + + def close(self): + """ Return the connection to the pool, and remove the + reference to it so that you can't use it again through this + wrapper object. + """ + if self and self._pool: + self._pool.put(self) + self._destroy() + + def __del__(self): + self.close() diff --git a/eventlet/.svn/text-base/db_pool_test.py.svn-base b/eventlet/.svn/text-base/db_pool_test.py.svn-base new file mode 100644 index 0000000..df1a043 --- /dev/null +++ b/eventlet/.svn/text-base/db_pool_test.py.svn-base @@ -0,0 +1,309 @@ +#!/usr/bin/python +# @file test_mysql_pool.py +# @brief Test cases for mysql_pool +# +# 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 os.path + +from eventlet import api, coros, tests +from eventlet import db_pool + +class DBTester(object): + def setUp(self): + self.create_db() + self.connection = None + connection = self._dbmodule.connect(**self._auth) + cursor = connection.cursor() + cursor.execute("""CREATE TABLE gargleblatz + ( + a INTEGER + ) ENGINE = InnoDB;""") + connection.commit() + cursor.close() + + def tearDown(self): + if self.connection is not None: + self.connection.close() + self.drop_db() + + def set_up_test_table(self, connection = None): + if connection is None: + if self.connection is None: + self.connection = self._dbmodule.connect(**self._auth) + connection = self.connection + + cursor = connection.cursor() + cursor.execute("""CREATE TEMPORARY TABLE test_table + ( + row_id INTEGER PRIMARY KEY AUTO_INCREMENT, + value_int INTEGER, + value_float FLOAT, + value_string VARCHAR(200), + value_uuid CHAR(36), + value_binary BLOB, + value_binary_string VARCHAR(200) BINARY, + value_enum ENUM('Y','N'), + created TIMESTAMP + ) ENGINE = InnoDB;""") + connection.commit() + cursor.close() + +class TestDBConnectionPool(DBTester): + def setUp(self): + super(TestDBConnectionPool, self).setUp() + self.pool = self.create_pool() + self.connection = self.pool.get() + + def tearDown(self): + self.pool.put(self.connection) + super(TestDBConnectionPool, self).tearDown() + + def assert_cursor_works(self, cursor): + cursor.execute("show full processlist") + rows = cursor.fetchall() + self.assert_(rows) + + def test_connecting(self): + self.assert_(self.connection is not None) + + def test_create_cursor(self): + cursor = self.connection.cursor() + cursor.close() + + def test_run_query(self): + cursor = self.connection.cursor() + self.assert_cursor_works(cursor) + cursor.close() + + def test_run_bad_query(self): + cursor = self.connection.cursor() + try: + cursor.execute("garbage blah blah") + self.assert_(False) + except AssertionError: + raise + except Exception, e: + pass + cursor.close() + + def test_put_none(self): + # the pool is of size 1, and its only connection is out + self.assert_(self.pool.free() == 0) + self.pool.put(None) + # ha ha we fooled it into thinking that we had a dead process + self.assert_(self.pool.free() == 1) + conn2 = self.pool.get() + self.assert_(conn2 is not None) + self.assert_(conn2.cursor) + del conn2 + + def test_close_does_a_put(self): + self.assert_(self.pool.free() == 0) + self.connection.close() + self.assert_(self.pool.free() == 1) + self.assertRaises(AttributeError, self.connection.cursor) + + def test_deletion_does_a_put(self): + self.assert_(self.pool.free() == 0) + self.connection = None + self.assert_(self.pool.free() == 1) + + def test_put_doesnt_double_wrap(self): + self.pool.put(self.connection) + conn = self.pool.get() + self.assert_(not isinstance(conn._base, db_pool.PooledConnectionWrapper)) + + def test_bool(self): + self.assert_(self.connection) + self.connection.close() + self.assert_(not self.connection) + + def fill_test_table(self, conn): + curs = conn.cursor() + for i in range(1000): + curs.execute('insert into test_table (value_int) values (%s)' % i) + conn.commit() + + def test_returns_immediately(self): + self.pool = self.create_pool() + conn = self.pool.get() + self.set_up_test_table(conn) + self.fill_test_table(conn) + curs = conn.cursor() + results = [] + SHORT_QUERY = "select * from test_table" + evt = coros.event() + def a_query(): + self.assert_cursor_works(curs) + curs.execute(SHORT_QUERY) + results.append(2) + evt.send() + evt2 = coros.event() + api.spawn(a_query) + results.append(1) + self.assertEqual([1], results) + evt.wait() + self.assertEqual([1, 2], results) + + def test_connection_is_clean_after_put(self): + self.pool = self.create_pool() + conn = self.pool.get() + self.set_up_test_table(conn) + curs = conn.cursor() + for i in range(10): + curs.execute('insert into test_table (value_int) values (%s)' % i) + # do not commit :-) + self.pool.put(conn) + del conn + conn2 = self.pool.get() + curs2 = conn2.cursor() + for i in range(10): + curs2.execute('insert into test_table (value_int) values (%s)' % i) + conn2.commit() + rows = curs2.execute("select * from test_table") + # we should have only inserted them once + self.assertEqual(10, rows) + + def test_visibility_from_other_connections(self): + # *FIX: use some non-indra-specific table for testing (can't use a temp table) + self.pool = self.create_pool(3) + conn = self.pool.get() + conn2 = self.pool.get() + curs = conn.cursor() + try: + curs2 = conn2.cursor() + rows2 = curs2.execute("insert into gargleblatz (a) values (%s)" % (314159)) + self.assertEqual(rows2, 1) + conn2.commit() + selection_query = "select * from gargleblatz" + rows2 = curs2.execute(selection_query) + self.assertEqual(rows2, 1) + del curs2 + del conn2 + # create a new connection, it should see the addition + conn3 = self.pool.get() + curs3 = conn3.cursor() + rows3 = curs3.execute(selection_query) + self.assertEqual(rows3, 1) + # now, does the already-open connection see it? + rows = curs.execute(selection_query) + self.assertEqual(rows, 1) + finally: + # clean up my litter + curs.execute("delete from gargleblatz where a=314159") + conn.commit() + + + def test_two_simultaneous_connections(self): + self.pool = self.create_pool(2) + conn = self.pool.get() + self.set_up_test_table(conn) + self.fill_test_table(conn) + curs = conn.cursor() + conn2 = self.pool.get() + self.set_up_test_table(conn2) + self.fill_test_table(conn2) + curs2 = conn2.cursor() + results = [] + LONG_QUERY = "select * from test_table" + SHORT_QUERY = "select * from test_table where row_id <= 20" + + evt = coros.event() + def long_running_query(): + self.assert_cursor_works(curs) + curs.execute(LONG_QUERY) + results.append(1) + evt.send() + evt2 = coros.event() + def short_running_query(): + self.assert_cursor_works(curs2) + curs2.execute(SHORT_QUERY) + results.append(2) + evt2.send() + + api.spawn(long_running_query) + api.spawn(short_running_query) + evt.wait() + evt2.wait() + #print "results %s" % results + results.sort() + self.assertEqual([1, 2], results) + + +class TestTpoolConnectionPool(TestDBConnectionPool): + def create_pool(self, max_items = 1): + return db_pool.TpooledConnectionPool(self._dbmodule, 0, max_items, **self._auth) + + +class TestSaranwrapConnectionPool(TestDBConnectionPool): + def create_pool(self, max_items = 1): + return db_pool.SaranwrappedConnectionPool(self._dbmodule, 0, max_items, **self._auth) + +class TestMysqlConnectionPool(object): + def setUp(self): + import MySQLdb + self._dbmodule = MySQLdb + try: + import simplejson + import os.path + auth_utf8 = simplejson.load(open(os.path.join(os.path.dirname(__file__), 'auth.json'))) + # have to convert unicode objects to str objects because mysqldb is dum + self._auth = dict([(str(k), str(v)) + for k, v in auth_utf8.items()]) + except (IOError, ImportError), e: + self._auth = {'host': 'localhost','user': 'root','passwd': '','db': 'persist0'} + super(TestMysqlConnectionPool, self).setUp() + + def create_db(self): + auth = self._auth.copy() + try: + self.drop_db() + except Exception: + pass + dbname = auth.pop('db') + db = self._dbmodule.connect(**auth).cursor() + db.execute("create database "+dbname) + db.close() + del db + + def drop_db(self): + db = self._dbmodule.connect(**self._auth).cursor() + db.execute("drop database "+self._auth['db']) + db.close() + del db + +class TestMysqlTpool(TestMysqlConnectionPool, TestTpoolConnectionPool, tests.TestCase): + pass + +class TestMysqlSaranwrap(TestMysqlConnectionPool, TestSaranwrapConnectionPool, tests.TestCase): + pass + + +if __name__ == '__main__': + try: + import MySQLdb + except ImportError: + print "Unable to import MySQLdb, skipping db_pool_test." + else: + tests.main() +else: + import MySQLdb diff --git a/eventlet/.svn/text-base/greenlib.py.svn-base b/eventlet/.svn/text-base/greenlib.py.svn-base new file mode 100644 index 0000000..1dbf01e --- /dev/null +++ b/eventlet/.svn/text-base/greenlib.py.svn-base @@ -0,0 +1,331 @@ +"""\ +@file greenlib.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 itertools + +import greenlet + +from eventlet import tls + +__all__ = [ + 'switch', 'kill', 'tracked_greenlets', + 'greenlet_id', 'greenlet_dict', 'GreenletContext', + 'tracked_greenlet', +] + +try: + reversed +except NameError: + def reversed(something): + for x in something[::-1]: + yield x + +_threadlocal = tls.local() + +def tracked_greenlet(): + """ + Returns a greenlet that has a greenlet-local dictionary and can be + used with GreenletContext and enumerated with tracked_greenlets + """ + return greenlet.greenlet(greenlet_body) + +class GreenletContextManager(object): + """ + Per-thread manager for GreenletContext. Created lazily on registration + """ + def __new__(cls, *args, **kw): + dct = greenlet_dict() + self = dct.get('greenlet_context', None) + if self is not None: + return self + self = super(GreenletContextManager, cls).__new__(cls, *args, **kw) + dct['greenlet_context'] = self + self.contexts = [] + return self + + def add_context(self, ctx): + fn = getattr(ctx, '_swap_in', None) + if fn is not None: + fn() + self.contexts.append(ctx) + + def remove_context(self, ctx): + try: + idx = self.contexts.index(ctx) + except ValueError: + return + else: + del self.contexts[idx] + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + fn = getattr(ctx, '_finalize', None) + if fn is not None: + fn() + + def swap_in(self): + for ctx in self.contexts: + fn = getattr(ctx, '_swap_in', None) + if fn is not None: + fn() + + def swap_out(self): + for ctx in reversed(self.contexts): + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + + def finalize(self): + for ctx in reversed(self.contexts): + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + fn = getattr(ctx, '_finalize', None) + if fn is not None: + fn() + del self.contexts[:] + try: + del greenlet_dict()['greenlet_context'] + except KeyError: + pass + +class GreenletContext(object): + """ + A context manager to be triggered when a specific tracked greenlet is + swapped in, swapped out, or finalized. + + To use, subclass and override the swap_in, swap_out, and/or finalize + methods, for example:: + + import greenlib + from greenlib import greenlet_id, tracked_greenlet, switch + + class NotifyContext(greenlib.GreenletContext): + + def swap_in(self): + print "swap_in" + + def swap_out(self): + print "swap_out" + + def finalize(self): + print "finalize" + + def another_greenlet(): + print "another_greenlet" + + def notify_demo(): + print "starting" + NotifyContext().register() + switch(tracked_greenlet(), (another_greenlet,)) + print "finishing" + # we could have kept the NotifyContext object + # to unregister it here but finalization of all + # contexts is implicit when the greenlet returns + + t = tracked_greenlet() + switch(t, (notify_demo,)) + + The output should be: + + starting + swap_in + swap_out + another_greenlet + swap_in + finishing + swap_out + finalize + + """ + _balance = 0 + + def _swap_in(self): + if self._balance != 0: + raise RuntimeError("balance != 0: %r" % (self._balance,)) + self._balance = self._balance + 1 + fn = getattr(self, 'swap_in', None) + if fn is not None: + fn() + + def _swap_out(self): + if self._balance != 1: + raise RuntimeError("balance != 1: %r" % (self._balance,)) + self._balance = self._balance - 1 + fn = getattr(self, 'swap_out', None) + if fn is not None: + fn() + + def register(self): + GreenletContextManager().add_context(self) + + def unregister(self): + GreenletContextManager().remove_context(self) + + def _finalize(self): + fn = getattr(self, 'finalize', None) + if fn is not None: + fn() + + +def kill(g): + """ + Kill the given greenlet if it is alive by sending it a GreenletExit. + + Note that of any other exception is raised, it will pass-through! + """ + if not g: + return + kill_exc = greenlet.GreenletExit() + try: + try: + g.parent = greenlet.getcurrent() + except ValueError: + pass + try: + switch(g, exc=kill_exc) + except SwitchingToDeadGreenlet: + pass + except greenlet.GreenletExit, e: + if e is not kill_exc: + raise + +def tracked_greenlets(): + """ + Return a list of greenlets tracked in this thread. Tracked greenlets + use greenlet_body() to ensure that they have greenlet-local storage. + """ + try: + return _threadlocal.greenlets.keys() + except AttributeError: + return [] + +def greenlet_id(): + """ + Get the id of the current tracked greenlet, returns None if the + greenlet is not tracked. + """ + try: + d = greenlet_dict() + except RuntimeError: + return None + return d['greenlet_id'] + +def greenlet_dict(): + """ + Return the greenlet local storage for this greenlet. Raises RuntimeError + if this greenlet is not tracked. + """ + self = greenlet.getcurrent() + try: + return _threadlocal.greenlets[self] + except (AttributeError, KeyError): + raise RuntimeError("greenlet %r is not tracked" % (self,)) + +def _greenlet_context(dct=None): + if dct is None: + try: + dct = greenlet_dict() + except RuntimeError: + return None + return dct.get('greenlet_context', None) + +def _greenlet_context_call(name, dct=None): + ctx = _greenlet_context(dct) + fn = getattr(ctx, name, None) + if fn is not None: + fn() + +def greenlet_body(value, exc): + """ + Track the current greenlet during the execution of the given callback, + normally you would use tracked_greenlet() to get a greenlet that uses this. + + Greenlets using this body must be greenlib.switch()'ed to + """ + if exc is not None: + if isinstance(exc, tuple): + raise exc[0], exc[1], exc[2] + raise exc + cb, args = value[0], value[1:] + try: + greenlets = _threadlocal.greenlets + except AttributeError: + greenlets = _threadlocal.greenlets = {} + else: + if greenlet.getcurrent() in greenlets: + raise RuntimeError("greenlet_body can not be called recursively!") + try: + greenlet_id = _threadlocal.next_greenlet_id.next() + except AttributeError: + greenlet_id = 1 + _threadlocal.next_greenlet_id = itertools.count(2) + greenlets[greenlet.getcurrent()] = {'greenlet_id': greenlet_id} + try: + return cb(*args) + finally: + _greenlet_context_call('finalize') + greenlets.pop(greenlet.getcurrent(), None) + + +class SwitchingToDeadGreenlet(RuntimeError): + pass + + +def switch(other=None, value=None, exc=None): + """ + Switch to another greenlet, passing value or exception + """ + self = greenlet.getcurrent() + if other is None: + other = self.parent + if other is None: + other = self + if not (other or hasattr(other, 'run')): + raise SwitchingToDeadGreenlet("Switching to dead greenlet %r %r %r" % (other, value, exc)) + _greenlet_context_call('swap_out') + sys.exc_clear() # don't pass along exceptions to the other coroutine + try: + rval = other.switch(value, exc) + if not rval or not other: + res, exc = rval, None + else: + res, exc = rval + except: + res, exc = None, sys.exc_info() + _greenlet_context_call('swap_in') + # *NOTE: we don't restore exc_info, so don't switch inside an + # exception handler and then call sys.exc_info() or use bare + # raise. Instead, explicitly save off the exception before + # switching. We need an extension that allows us to restore the + # exception state at this point because vanilla Python doesn't + # allow that. + if isinstance(exc, tuple): + typ, exc, tb = exc + raise typ, exc, tb + elif exc is not None: + raise exc + + return res diff --git a/eventlet/.svn/text-base/httpc.py.svn-base b/eventlet/.svn/text-base/httpc.py.svn-base new file mode 100644 index 0000000..63c6caf --- /dev/null +++ b/eventlet/.svn/text-base/httpc.py.svn-base @@ -0,0 +1,597 @@ +"""\ +@file httpc.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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 datetime +import httplib +import os.path +import os +import time +import urlparse + + +_old_HTTPConnection = httplib.HTTPConnection +_old_HTTPSConnection = httplib.HTTPSConnection + + +HTTP_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT' +to_http_time = lambda t: time.strftime(HTTP_TIME_FORMAT, time.gmtime(t)) + +try: + + from mx import DateTime + def from_http_time(t, defaultdate=None): + return int(DateTime.Parser.DateTimeFromString( + t, defaultdate=defaultdate).gmticks()) + +except ImportError: + + import calendar + parse_formats = (HTTP_TIME_FORMAT, # RFC 1123 + '%A, %d-%b-%y %H:%M:%S GMT', # RFC 850 + '%a %b %d %H:%M:%S %Y') # asctime + def from_http_time(t, defaultdate=None): + for parser in parse_formats: + try: + return calendar.timegm(time.strptime(t, parser)) + except ValueError: + continue + return defaultdate + + +def host_and_port_from_url(url): + """@brief Simple function to get host and port from an http url. + @return Returns host, port and port may be None. + """ + host = None + port = None + parsed_url = urlparse.urlparse(url) + try: + host, port = parsed_url[1].split(':') + except ValueError: + host = parsed_url[1].split(':') + return host, port + + +def better_putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): + self.method = method + self.path = url + try: + # Python 2.4 and above + self.old_putrequest(method, url, skip_host, skip_accept_encoding) + except TypeError: + # Python 2.3 and below + self.old_putrequest(method, url, skip_host) + + +class HttpClient(httplib.HTTPConnection): + """A subclass of httplib.HTTPConnection that provides a better + putrequest that records the method and path on the request object. + """ + def __init__(self, host, port=None, strict=None): + _old_HTTPConnection.__init__(self, host, port, strict) + + old_putrequest = httplib.HTTPConnection.putrequest + putrequest = better_putrequest + +class HttpsClient(httplib.HTTPSConnection): + """A subclass of httplib.HTTPSConnection that provides a better + putrequest that records the method and path on the request object. + """ + old_putrequest = httplib.HTTPSConnection.putrequest + putrequest = better_putrequest + + +def wrap_httplib_with_httpc(): + """Replace httplib's implementations of these classes with our enhanced ones. + + Needed to work around code that uses httplib directly.""" + httplib.HTTP._connection_class = httplib.HTTPConnection = HttpClient + httplib.HTTPS._connection_class = httplib.HTTPSConnection = HttpsClient + + + +class FileScheme(object): + """Retarded scheme to local file wrapper.""" + host = '' + port = '' + reason = '' + + def __init__(self, location): + pass + + def request(self, method, fullpath, body='', headers=None): + self.status = 200 + self.msg = '' + self.path = fullpath.split('?')[0] + self.method = method = method.lower() + assert method in ('get', 'put', 'delete') + if method == 'delete': + try: + os.remove(self.path) + except OSError: + pass # don't complain if already deleted + elif method == 'put': + try: + f = file(self.path, 'w') + f.write(body) + f.close() + except IOError, e: + self.status = 500 + self.raise_connection_error() + elif method == 'get': + if not os.path.exists(self.path): + self.status = 404 + self.raise_connection_error(NotFound) + + def connect(self): + pass + + def getresponse(self): + return self + + def getheader(self, header): + if header == 'content-length': + try: + return os.path.getsize(self.path) + except OSError: + return 0 + + def read(self, howmuch=None): + if self.method == 'get': + try: + fl = file(self.path, 'r') + if howmuch is None: + return fl.read() + else: + return fl.read(howmuch) + except IOError: + self.status = 500 + self.raise_connection_error() + return '' + + def raise_connection_error(self, klass=None): + if klass is None: + klass=ConnectionError + raise klass(_Params('file://' + self.path, self.method)) + + def close(self): + """We're challenged here, and read the whole file rather than + integrating with this lib. file object already out of scope at this + point""" + pass + +class _Params(object): + def __init__(self, url, method, body='', headers=None, dumper=None, + loader=None, use_proxy=False, ok=(), aux=None): + ''' + @param connection The connection (as returned by make_connection) to use for the request. + @param method HTTP method + @param url Full url to make request on. + @param body HTTP body, if necessary for the method. Can be any object, assuming an appropriate dumper is also provided. + @param headers Dict of header name to header value + @param dumper Method that formats the body as a string. + @param loader Method that converts the response body into an object. + @param use_proxy Set to True if the connection is to a proxy. + @param ok Set of valid response statuses. If the returned status is not in this list, an exception is thrown. + ''' + self.instance = None + self.url = url + self.path = url + self.method = method + self.body = body + if headers is None: + self.headers = {} + else: + self.headers = headers + self.dumper = dumper + self.loader = loader + self.use_proxy = use_proxy + self.ok = ok or (200, 201, 204) + self.orig_body = body + self.aux = aux + + +class _LocalParams(_Params): + def __init__(self, params, **kwargs): + self._delegate = params + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + def __getattr__(self, key): + return getattr(self._delegate, key) + + +class ConnectionError(Exception): + """Detailed exception class for reporting on http connection problems. + + There are lots of subclasses so you can use closely-specified + exception clauses.""" + def __init__(self, params): + self.params = params + Exception.__init__(self) + + def location(self): + return self.params.response.msg.dict.get('location') + + def expired(self): + # 14.21 Expires + # + # HTTP/1.1 clients and caches MUST treat other invalid date + # formats, especially including the value "0", as in the past + # (i.e., "already expired"). + expires = from_http_time( + self.params.response_headers.get('expires', '0'), + defaultdate=DateTime.Epoch) + return time.time() > expires + + def __repr__(self): + response = self.params.response + return "%s(url=%r, method=%r, status=%r, reason=%r, body=%r)" % ( + self.__class__.__name__, self.params.url, self.params.method, + response.status, response.reason, self.params.body) + + __str__ = __repr__ + + +class UnparseableResponse(ConnectionError): + """Raised when a loader cannot parse the response from the server.""" + def __init__(self, content_type, response, url): + self.content_type = content_type + self.response = response + self.url = url + Exception.__init__(self) + + def __repr__(self): + return "Could not parse the data at the URL %r of content-type %r\nData:\n%r)" % ( + self.url, self.content_type, self.response) + + __str__ = __repr__ + + +class Accepted(ConnectionError): + """ 202 Accepted """ + pass + + +class Retriable(ConnectionError): + def retry_method(self): + return self.params.method + + def retry_url(self): + return self.location() or self.url() + + def retry_(self): + params = _LocalParams(self.params, + url=self.retry_url(), + method=self.retry_method()) + return self.params.instance.request_(params) + + def retry(self): + return self.retry_()[-1] + + +class MovedPermanently(Retriable): + """ 301 Moved Permanently """ + pass + + +class Found(Retriable): + """ 302 Found """ + + pass + + +class SeeOther(Retriable): + """ 303 See Other """ + + def retry_method(self): + return 'GET' + + +class NotModified(ConnectionError): + """ 304 Not Modified """ + pass + + +class TemporaryRedirect(Retriable): + """ 307 Temporary Redirect """ + pass + + +class BadRequest(ConnectionError): + """ 400 Bad Request """ + pass + + +class Forbidden(ConnectionError): + """ 403 Forbidden """ + pass + + +class NotFound(ConnectionError): + """ 404 Not Found """ + pass + + +class Gone(ConnectionError): + """ 410 Gone """ + pass + + +class ServiceUnavailable(Retriable): + """ 503 Service Unavailable """ + def url(self): + return self.params._delegate.url + + +class GatewayTimeout(Retriable): + """ 504 Gateway Timeout """ + def url(self): + return self.params._delegate.url + + +class InternalServerError(ConnectionError): + """ 500 Internal Server Error """ + def __repr__(self): + try: + import simplejson + traceback = simplejson.loads(self.params.response_body) + except: + try: + from indra.base import llsd + traceback = llsd.parse(self.params.response_body) + except: + traceback = self.params.response_body + if isinstance(traceback, dict): + body = traceback + traceback = "Traceback (most recent call last):\n" + for frame in body['stack-trace']: + traceback += ' File "%s", line %s, in %s\n' % ( + frame['filename'], frame['lineno'], frame['method']) + for line in frame['code']: + if line['lineno'] == frame['lineno']: + traceback += ' %s' % (line['line'].lstrip(), ) + break + traceback += body['description'] + return "The server raised an exception from our request:\n%s %s\n%s %s\n%s" % ( + self.params.method, self.params.url, self.params.response.status, self.params.response.reason, traceback) + __str__ = __repr__ + + + +status_to_error_map = { + 202: Accepted, + 301: MovedPermanently, + 302: Found, + 303: SeeOther, + 304: NotModified, + 307: TemporaryRedirect, + 400: BadRequest, + 403: Forbidden, + 404: NotFound, + 410: Gone, + 500: InternalServerError, + 503: ServiceUnavailable, + 504: GatewayTimeout, +} + +scheme_to_factory_map = { + 'http': HttpClient, + 'https': HttpsClient, + 'file': FileScheme, +} + + +def make_connection(scheme, location, use_proxy): + """ Create a connection object to a host:port. + + @param scheme Protocol, scheme, whatever you want to call it. http, file, https are currently supported. + @param location Hostname and port number, formatted as host:port or http://host:port if you're so inclined. + @param use_proxy Connect to a proxy instead of the actual location. Uses environment variables to decide where the proxy actually lives. + """ + if use_proxy: + if "http_proxy" in os.environ: + location = os.environ["http_proxy"] + elif "ALL_PROXY" in os.environ: + location = os.environ["ALL_PROXY"] + else: + location = "localhost:3128" #default to local squid + + # run a little heuristic to see if location is an url, and if so parse out the hostpart + if location.startswith('http'): + _scheme, location, path, parameters, query, fragment = urlparse.urlparse(location) + + result = scheme_to_factory_map[scheme](location) + result.connect() + return result + + +def connect(url, use_proxy=False): + """ Create a connection object to the host specified in a url. Convenience function for make_connection.""" + scheme, location = urlparse.urlparse(url)[:2] + return make_connection(scheme, location, use_proxy) + + +def make_safe_loader(loader): + if not callable(loader): + return loader + def safe_loader(what): + try: + return loader(what) + except Exception: + import traceback + traceback.print_exc() + return None + return safe_loader + + +class HttpSuite(object): + def __init__(self, dumper, loader, fallback_content_type): + self.dumper = dumper + self.loader = loader + self.fallback_content_type = fallback_content_type + + def request_(self, params): + '''Make an http request to a url, for internal use mostly.''' + + params = _LocalParams(params, instance=self) + + (scheme, location, path, parameters, query, + fragment) = urlparse.urlparse(params.url) + + if params.use_proxy: + if scheme == 'file': + params.use_proxy = False + else: + params.headers['host'] = location + + if not params.use_proxy: + params.path = path + if query: + params.path += '?' + query + + params.orig_body = params.body + + if params.method in ('PUT', 'POST'): + if self.dumper is not None: + params.body = self.dumper(params.body) + # don't set content-length header because httplib does it + # for us in _send_request + else: + params.body = '' + + params.response, params.response_body = self._get_response_body(params) + response, body = params.response, params.response_body + + if self.loader is not None: + try: + body = make_safe_loader(self.loader(body)) + except KeyboardInterrupt: + raise + except Exception, e: + raise UnparseableResponse(self.loader, body, params.url) + + return response.status, response.msg, body + + def _check_status(self, params): + response = params.response + if response.status not in params.ok: + klass = status_to_error_map.get(response.status, ConnectionError) + raise klass(params) + + def _get_response_body(self, params): + connection = connect(params.url, params.use_proxy) + connection.request(params.method, params.path, params.body, + params.headers) + params.response = connection.getresponse() + params.response_body = params.response.read() + connection.close() + self._check_status(params) + + return params.response, params.response_body + + def request(self, params): + return self.request_(params)[-1] + + def head_(self, url, headers=None, use_proxy=False, ok=None, aux=None): + return self.request_(_Params(url, 'HEAD', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux)) + + def head(self, *args, **kwargs): + return self.head_(*args, **kwargs)[-1] + + def get_(self, url, headers=None, use_proxy=False, ok=None, aux=None): + if headers is None: + headers = {} + headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0' + return self.request_(_Params(url, 'GET', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux)) + + def get(self, *args, **kwargs): + return self.get_(*args, **kwargs)[-1] + + def put_(self, url, data, headers=None, content_type=None, ok=None, + aux=None): + if headers is None: + headers = {} + if 'content-type' not in headers: + if content_type is None: + headers['content-type'] = self.fallback_content_type + else: + headers['content-type'] = content_type + headers['accept'] = headers['content-type']+';q=1,*/*;q=0' + return self.request_(_Params(url, 'PUT', body=data, headers=headers, + loader=self.loader, dumper=self.dumper, + ok=ok, aux=aux)) + + def put(self, *args, **kwargs): + return self.put_(*args, **kwargs)[-1] + + def delete_(self, url, ok=None, aux=None): + return self.request_(_Params(url, 'DELETE', loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux)) + + def delete(self, *args, **kwargs): + return self.delete_(*args, **kwargs)[-1] + + def post_(self, url, data='', headers=None, content_type=None, ok=None, + aux=None): + if headers is None: + headers = {} + if 'content-type' not in headers: + if content_type is None: + headers['content-type'] = self.fallback_content_type + else: + headers['content-type'] = content_type + headers['accept'] = headers['content-type']+';q=1,*/*;q=0' + return self.request_(_Params(url, 'POST', body=data, + headers=headers, loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux)) + + def post(self, *args, **kwargs): + return self.post_(*args, **kwargs)[-1] + + +def make_suite(dumper, loader, fallback_content_type): + """ Return a tuple of methods for making http requests with automatic bidirectional formatting with a particular content-type.""" + suite = HttpSuite(dumper, loader, fallback_content_type) + return suite.get, suite.put, suite.delete, suite.post + + +suite = HttpSuite(str, None, 'text/plain') +delete = suite.delete +delete_ = suite.delete_ +get = suite.get +get_ = suite.get_ +head = suite.head +head_ = suite.head_ +post = suite.post +post_ = suite.post_ +put = suite.put +put_ = suite.put_ +request = suite.request +request_ = suite.request_ diff --git a/eventlet/.svn/text-base/httpc_test.py.svn-base b/eventlet/.svn/text-base/httpc_test.py.svn-base new file mode 100644 index 0000000..b0e14f0 --- /dev/null +++ b/eventlet/.svn/text-base/httpc_test.py.svn-base @@ -0,0 +1,396 @@ +"""\ +@file httpc_test.py +@author Bryan O'Sullivan + +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 import api +from eventlet import httpc +from eventlet import httpd +from eventlet import processes +from eventlet import util +import time +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + + +util.wrap_socket_with_coroutine_socket() + + +from eventlet import tests + + +class Site(object): + def __init__(self): + self.stuff = {'hello': 'hello world'} + + def adapt(self, obj, req): + req.write(str(obj)) + + def handle_request(self, req): + return getattr(self, 'handle_%s' % req.method().lower())(req) + + +class BasicSite(Site): + def handle_get(self, req): + req.set_header('x-get', 'hello') + resp = StringIO() + path = req.path().lstrip('/') + try: + resp.write(self.stuff[path]) + except KeyError: + req.response(404, body='Not found') + return + for k,v in req.get_query_pairs(): + resp.write(k + '=' + v + '\n') + req.write(resp.getvalue()) + + def handle_head(self, req): + req.set_header('x-head', 'hello') + path = req.path().lstrip('/') + try: + req.write('') + except KeyError: + req.response(404, body='Not found') + + def handle_put(self, req): + req.set_header('x-put', 'hello') + path = req.path().lstrip('/') + if not path: + req.response(400, body='') + return + if path in self.stuff: + req.response(204) + else: + req.response(201) + self.stuff[path] = req.read_body() + req.write('') + + def handle_delete(self, req): + req.set_header('x-delete', 'hello') + path = req.path().lstrip('/') + if not path: + req.response(400, body='') + return + try: + del self.stuff[path] + req.response(204) + except KeyError: + req.response(404) + req.write('') + + def handle_post(self, req): + req.set_header('x-post', 'hello') + req.write(req.read_body()) + + +class TestBase(object): + site_class = BasicSite + + def base_url(self): + return 'http://localhost:31337/' + + def setUp(self): + self.logfile = StringIO() + self.victim = api.spawn(httpd.server, + api.tcp_listener(('0.0.0.0', 31337)), + self.site_class(), + log=self.logfile, + max_size=128) + + def tearDown(self): + api.kill(self.victim) + + +class TestHttpc(TestBase, tests.TestCase): + def test_get_bad_uri(self): + self.assertRaises(httpc.NotFound, + lambda: httpc.get(self.base_url() + 'b0gu5')) + + def test_get(self): + response = httpc.get(self.base_url() + 'hello') + self.assertEquals(response, 'hello world') + + def test_get_(self): + status, msg, body = httpc.get_(self.base_url() + 'hello') + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-get'], 'hello') + self.assertEquals(body, 'hello world') + + def test_get_query(self): + response = httpc.get(self.base_url() + 'hello?foo=bar&foo=quux') + self.assertEquals(response, 'hello worldfoo=bar\nfoo=quux\n') + + def test_head_(self): + status, msg, body = httpc.head_(self.base_url() + 'hello') + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-head'], 'hello') + self.assertEquals(body, '') + + def test_head(self): + self.assertEquals(httpc.head(self.base_url() + 'hello'), '') + + def test_post_(self): + data = 'qunge' + status, msg, body = httpc.post_(self.base_url() + '', data=data) + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-post'], 'hello') + self.assertEquals(body, data) + + def test_post(self): + data = 'qunge' + self.assertEquals(httpc.post(self.base_url() + '', data=data), + data) + + def test_put_bad_uri(self): + self.assertRaises( + httpc.BadRequest, + lambda: httpc.put(self.base_url() + '', data='')) + + def test_put_empty(self): + httpc.put(self.base_url() + 'empty', data='') + self.assertEquals(httpc.get(self.base_url() + 'empty'), '') + + def test_put_nonempty(self): + data = 'nonempty' + httpc.put(self.base_url() + 'nonempty', data=data) + self.assertEquals(httpc.get(self.base_url() + 'nonempty'), data) + + def test_put_01_create(self): + data = 'goodbye world' + status, msg, body = httpc.put_(self.base_url() + 'goodbye', + data=data) + self.assertEquals(status, 201) + self.assertEquals(msg.dict['x-put'], 'hello') + self.assertEquals(body, '') + self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) + + def test_put_02_modify(self): + self.test_put_01_create() + data = 'i really mean goodbye' + status = httpc.put_(self.base_url() + 'goodbye', data=data)[0] + self.assertEquals(status, 204) + self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) + + def test_delete_(self): + httpc.put(self.base_url() + 'killme', data='killme') + status, msg, body = httpc.delete_(self.base_url() + 'killme') + self.assertEquals(status, 204) + self.assertRaises( + httpc.NotFound, + lambda: httpc.get(self.base_url() + 'killme')) + + def test_delete(self): + httpc.put(self.base_url() + 'killme', data='killme') + self.assertEquals(httpc.delete(self.base_url() + 'killme'), '') + self.assertRaises( + httpc.NotFound, + lambda: httpc.get(self.base_url() + 'killme')) + + def test_delete_bad_uri(self): + self.assertRaises( + httpc.NotFound, + lambda: httpc.delete(self.base_url() + 'b0gu5')) + + +class RedirectSite(BasicSite): + response_code = 301 + + def handle_request(self, req): + if req.path().startswith('/redirect/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/redirect/', '/')) + req.response(self.response_code, headers={'location': url}, + body='') + return + return Site.handle_request(self, req) + +class Site301(RedirectSite): + pass + + +class Site302(BasicSite): + def handle_request(self, req): + if req.path().startswith('/expired/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/expired/', '/')) + headers = {'location': url, 'expires': '0'} + req.response(302, headers=headers, body='') + return + if req.path().startswith('/expires/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/expires/', '/')) + expires = time.time() + (100 * 24 * 60 * 60) + headers = {'location': url, 'expires': httpc.to_http_time(expires)} + req.response(302, headers=headers, body='') + return + return Site.handle_request(self, req) + + +class Site303(RedirectSite): + response_code = 303 + + +class Site307(RedirectSite): + response_code = 307 + + +class TestHttpc301(TestBase, tests.TestCase): + site_class = Site301 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_get(self): + try: + httpc.get(self.base_url() + 'hello') + self.assert_(False) + except httpc.MovedPermanently, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + def test_post(self): + data = 'qunge' + try: + response = httpc.post(self.base_url() + '', data=data) + self.assert_(False) + except httpc.MovedPermanently, err: + response = err.retry() + self.assertEquals(response, data) + + +class TestHttpc302(TestBase, tests.TestCase): + site_class = Site302 + + def test_get_expired(self): + try: + httpc.get(self.base_url() + 'expired/hello') + self.assert_(False) + except httpc.Found, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + def test_get_expires(self): + try: + httpc.get(self.base_url() + 'expires/hello') + self.assert_(False) + except httpc.Found, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + +class TestHttpc303(TestBase, tests.TestCase): + site_class = Site303 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_post(self): + data = 'hello world' + try: + response = httpc.post(self.base_url() + 'hello', data=data) + self.assert_(False) + except httpc.SeeOther, err: + response = err.retry() + self.assertEquals(response, data) + + +class TestHttpc307(TestBase, tests.TestCase): + site_class = Site307 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_post(self): + data = 'hello world' + try: + response = httpc.post(self.base_url() + 'hello', data=data) + self.assert_(False) + except httpc.TemporaryRedirect, err: + response = err.retry() + self.assertEquals(response, data) + + +class Site500(BasicSite): + def handle_request(self, req): + req.response(500, body="screw you world") + return + + +class Site500(BasicSite): + def handle_request(self, req): + req.response(500, body="screw you world") + return + + +class TestHttpc500(TestBase, tests.TestCase): + site_class = Site500 + + def base_url(self): + return 'http://localhost:31337/' + + def test_get(self): + data = 'screw you world' + try: + response = httpc.get(self.base_url()) + self.fail() + except httpc.InternalServerError, e: + self.assertEquals(e.params.response_body, data) + self.assert_(str(e).count(data)) + self.assert_(repr(e).count(data)) + + +class Site504(BasicSite): + def handle_request(self, req): + req.response(504, body="screw you world") + + +class TestHttpc504(TestBase, tests.TestCase): + site_class = Site504 + + def base_url(self): + return 'http://localhost:31337/' + + def test_post(self): + # Simply ensure that a 504 status code results in a + # GatewayTimeout. Don't bother retrying. + data = 'hello world' + self.assertRaises(httpc.GatewayTimeout, + lambda: httpc.post(self.base_url(), data=data)) + + +class TestHttpTime(tests.TestCase): + rfc1123_time = 'Sun, 06 Nov 1994 08:49:37 GMT' + rfc850_time = 'Sunday, 06-Nov-94 08:49:37 GMT' + asctime_time = 'Sun Nov 6 08:49:37 1994' + secs_since_epoch = 784111777 + def test_to_http_time(self): + self.assertEqual(self.rfc1123_time, httpc.to_http_time(self.secs_since_epoch)) + + def test_from_http_time(self): + for formatted in (self.rfc1123_time, self.rfc850_time, self.asctime_time): + ticks = httpc.from_http_time(formatted, 0) + self.assertEqual(ticks, self.secs_since_epoch) + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/.svn/text-base/httpd.py.svn-base b/eventlet/.svn/text-base/httpd.py.svn-base new file mode 100644 index 0000000..7d73756 --- /dev/null +++ b/eventlet/.svn/text-base/httpd.py.svn-base @@ -0,0 +1,584 @@ +"""\ +@file httpd.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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 +import errno +import socket +import sys +import time +import urllib +import socket +import traceback +import BaseHTTPServer + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from eventlet import api +from eventlet import coros + + +DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' + +USE_ACCESS_LOG = True + + +CONNECTION_CLOSED = (errno.EPIPE, errno.ECONNRESET) + + +class ErrorResponse(Exception): + _responses = BaseHTTPServer.BaseHTTPRequestHandler.responses + + def __init__(self, code, reason_phrase=None, headers=None, body=None): + Exception.__init__(self, reason_phrase) + self.code = code + if reason_phrase is None: + self.reason = self._responses[code][0] + else: + self.reason = reason_phrase + self.headers = headers + if body is None: + self.body = self._responses[code][1] + else: + self.body = body + + +class Request(object): + _method = None + _path = None + _responsecode = 200 + _reason_phrase = None + _request_started = False + _chunked = False + _producer_adapters = {} + depth = 0 + def __init__(self, protocol, method, path, headers): + self.context = {} + self.request_start_time = time.time() + self.site = protocol.server.site + self.protocol = protocol + self._method = method + if '?' in path: + self._path, self._query = path.split('?', 1) + self._query = self._query.replace('&', '&') + else: + self._path, self._query = path, None + self._incoming_headers = headers + self._outgoing_headers = dict() + + def response(self, code, reason_phrase=None, headers=None, body=None): + """Change the response code. This will not be sent until some + data is written; last call to this method wins. Default is + 200 if this is not called. + """ + self._responsecode = code + self._reason_phrase = reason_phrase + self.protocol.set_response_code(self, code, reason_phrase) + if headers is not None: + try: + headers = headers.iteritems() + except AttributeError: + pass + for key, value in headers: + self.set_header(key, value) + if body is not None: + self.write(body) + + def is_okay(self): + return 200 <= self._responsecode <= 299 + + def full_url(self): + path = self.path() + query = self.query() + if query: + path = path + '?' + query + + via = self.get_header('via', '') + if via.strip(): + next_part = iter(via.split()).next + + received_protocol = next_part() + received_by = next_part() + if received_by.endswith(','): + received_by = received_by[:-1] + else: + comment = '' + while not comment.endswith(','): + try: + comment += next_part() + except StopIteration: + comment += ',' + break + comment = comment[:-1] + else: + received_by = self.get_header('host') + + return '%s://%s%s' % (self.request_protocol(), received_by, path) + + def begin_response(self, length="-"): + """Begin the response, and return the initial response text + """ + self._request_started = True + request_time = time.time() - self.request_start_time + + code = self._responsecode + proto = self.protocol + + if USE_ACCESS_LOG: + proto.server.write_access_log_line( + proto.client_address[0], + time.strftime("%d/%b/%Y %H:%M:%S"), + proto.requestline, + code, + length, + request_time) + + if self._reason_phrase is not None: + message = self._reason_phrase.split("\n")[0] + elif code in proto.responses: + message = proto.responses[code][0] + else: + message = '' + if proto.request_version == 'HTTP/0.9': + return [] + + response_lines = proto.generate_status_line() + + if not self._outgoing_headers.has_key('connection'): + con = self.get_header('connection') + if con is None and proto.request_version == 'HTTP/1.0': + con = 'close' + if con is not None: + self.set_header('connection', con) + + for key, value in self._outgoing_headers.items(): + key = '-'.join([x.capitalize() for x in key.split('-')]) + response_lines.append("%s: %s" % (key, value)) + + response_lines.append("") + return response_lines + + def write(self, obj): + """Writes an arbitrary object to the response, using + the sitemap's adapt method to convert it to bytes. + """ + if isinstance(obj, str): + self._write_bytes(obj) + elif isinstance(obj, unicode): + # use utf8 encoding for now, *TODO support charset negotiation + # Content-Type: text/html; charset=utf-8 + ctype = self._outgoing_headers.get('content-type', 'text/html') + ctype = ctype + '; charset=utf-8' + self._outgoing_headers['content-type'] = ctype + self._write_bytes(obj.encode('utf8')) + else: + self.site.adapt(obj, self) + + def _write_bytes(self, data): + """Write all the data of the response. + Can be called just once. + """ + if self._request_started: + print "Request has already written a response:" + traceback.print_stack() + return + + self._outgoing_headers['content-length'] = len(data) + + response_lines = self.begin_response(len(data)) + response_lines.append(data) + self.protocol.wfile.write("\r\n".join(response_lines)) + if hasattr(self.protocol.wfile, 'flush'): + self.protocol.wfile.flush() + + def method(self): + return self._method + + def path(self): + return self._path + + def path_segments(self): + return [urllib.unquote_plus(x) for x in self._path.split('/')[1:]] + + def query(self): + return self._query + + def uri(self): + if self._query: + return '%s?%s' % ( + self._path, self._query) + return self._path + + def get_headers(self): + return self._incoming_headers + + def get_header(self, header_name, default=None): + return self.get_headers().get(header_name.lower(), default) + + def get_query_pairs(self): + if not hasattr(self, '_split_query'): + if self._query is None: + self._split_query = () + else: + spl = self._query.split('&') + spl = [x.split('=', 1) for x in spl if x] + self._split_query = [] + for query in spl: + if len(query) == 1: + key = query[0] + value = '' + else: + key, value = query + self._split_query.append((urllib.unquote_plus(key), urllib.unquote_plus(value))) + + return self._split_query + + def get_queries_generator(self, name): + """Generate all query parameters matching the given name. + """ + for key, value in self.get_query_pairs(): + if key == name or not name: + yield value + + get_queries = lambda self, name: list(self.get_queries_generator) + + def get_query(self, name, default=None): + try: + return self.get_queries_generator(name).next() + except StopIteration: + return default + + def get_arg_list(self, name): + return self.get_field_storage().getlist(name) + + def get_arg(self, name, default=None): + return self.get_field_storage().getfirst(name, default) + + def get_field_storage(self): + if not hasattr(self, '_field_storage'): + if self.method() == 'GET': + data = '' + if self._query: + data = self._query + else: + data = self.read_body() + fl = StringIO(data) + ## Allow our resource to provide the FieldStorage instance for + ## customization purposes. + headers = self.get_headers() + environ = dict( + REQUEST_METHOD='POST', + QUERY_STRING=self._query or '') + + self._field_storage = cgi.FieldStorage(fl, headers, environ=environ) + + return self._field_storage + + def set_header(self, key, value): + if key.lower() == 'connection' and value.lower() == 'close': + self.protocol.close_connection = 1 + self._outgoing_headers[key.lower()] = value + __setitem__ = set_header + + def get_outgoing_header(self, key): + return self._outgoing_headers[key.lower()] + + def has_outgoing_header(self, key): + return self._outgoing_headers.has_key(key.lower()) + + def socket(self): + return self.protocol.socket + + def error(self, response=None, body=None, log_traceback=True): + if log_traceback: + traceback.print_exc(file=self.log) + if response is None: + response = 500 + if body is None: + typ, val, tb = sys.exc_info() + body = dict(type=str(typ), error=True, reason=str(val)) + self.response(response) + if(type(body) is str and not self.response_written()): + self.write(body) + return + try: + produce(body, self) + except Exception, e: + traceback.print_exc(file=self.log) + if not self.response_written(): + self.write('Internal Server Error') + + def not_found(self): + self.error(404, 'Not Found\n', log_traceback=False) + + def raw_body(self): + if not hasattr(self, '_cached_body'): + self.read_body() + return self._cached_body + + def read_body(self): + """ Returns the string body that was read off the request, or + the empty string if there was no request body. + + Requires a content-length header. Caches the body so multiple + calls to read_body() are free. + """ + if not hasattr(self, '_cached_body'): + length = self.get_header('content-length') + if length: + length = int(length) + if length: + self._cached_body = self.protocol.rfile.read(length) + else: + self._cached_body = '' + return self._cached_body + + def parsed_body(self): + """ Returns the parsed version of the body, using the + content-type header to select from the parsers on the site + object. + + If no parser is found, returns the string body from + read_body(). Caches the parsed body so multiple calls to + parsed_body() are free. + """ + if not hasattr(self, '_cached_parsed_body'): + body = self.read_body() + if hasattr(self.site, 'parsers'): + parser = self.site.parsers.get( + self.get_header('content-type')) + if parser is not None: + body = parser(body) + self._cached_parsed_body = body + return self._cached_parsed_body + + def override_body(self, body): + if not hasattr(self, '_cached_parsed_body'): + self.read_body() ## Read and discard body + self._cached_parsed_body = body + + def response_written(self): + ## TODO change badly named variable + return self._request_started + + def request_version(self): + return self.protocol.request_version + + def request_protocol(self): + if self.protocol.socket.is_secure: + return "https" + return "http" + + def server_address(self): + return self.protocol.server.address + + def __repr__(self): + return "" % ( + getattr(self, '_method'), getattr(self, '_path')) + +DEFAULT_TIMEOUT = 300 + +# This value was chosen because apache 2 has a default limit of 8190. +# I believe that slightly smaller number is because apache does not +# count the \r\n. +MAX_REQUEST_LINE = 8192 + +class Timeout(RuntimeError): + pass + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + def __init__(self, request, client_address, server): + self.socket = self.request = self.rfile = self.wfile = request + self.client_address = client_address + self.server = server + self.set_response_code(None, 200, None) + self.protocol_version = server.max_http_version + + def set_response_code(self, request, code, message): + self._code = code + if message is not None: + self._message = message.split("\n")[0] + elif code in self.responses: + self._message = self.responses[code][0] + else: + self._message = '' + + def generate_status_line(self): + return [ + "%s %d %s" % ( + self.protocol_version, self._code, self._message)] + + def write_bad_request(self, status, reason): + self.set_response_code(self, status, reason) + self.wfile.write(''.join(self.generate_status_line())) + self.wfile.write('\r\nServer: %s\r\n' % self.version_string()) + self.wfile.write('Date: %s\r\n' % self.date_time_string()) + self.wfile.write('Content-Length: 0\r\n\r\n') + + def handle(self): + self.close_connection = 0 + + timeout = DEFAULT_TIMEOUT + while not self.close_connection: + if timeout == 0: + break + cancel = api.exc_after(timeout, Timeout) + try: + self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) + if self.raw_requestline is not None: + if len(self.raw_requestline) == MAX_REQUEST_LINE: + # Someone sent a request line which is too + # large. Be helpful and tell them. + self.write_bad_request(414, 'Request-URI Too Long') + self.close_connection = True + continue + except socket.error, e: + if e[0] in CONNECTION_CLOSED: + self.close_connection = True + cancel.cancel() + continue + except Timeout: + self.close_connection = True + continue + except Exception, e: + try: + if e[0][0][0].startswith('SSL'): + print "SSL Error:", e[0][0] + self.close_connection = True + cancel.cancel() + continue + except Exception, f: + print "Exception in ssl test:",f + pass + raise e + cancel.cancel() + + if not self.raw_requestline or not self.parse_request(): + self.close_connection = True + continue + + self.set_response_code(None, 200, None) + request = Request(self, self.command, self.path, self.headers) + request.set_header('Server', self.version_string()) + request.set_header('Date', self.date_time_string()) + try: + timeout = int(request.get_header('keep-alive', timeout)) + except TypeError, ValueError: + pass + + try: + try: + try: + self.server.site.handle_request(request) + except ErrorResponse, err: + request.response(code=err.code, + reason_phrase=err.reason, + headers=err.headers, + body=err.body) + finally: + # clean up any timers that might have been left around by the handling code + api.get_hub().runloop.cancel_timers(api.getcurrent()) + + # throw an exception if it failed to write a body + if not request.response_written(): + raise NotImplementedError("Handler failed to write response to request: %s" % request) + + if not hasattr(self, '_cached_body'): + try: + request.read_body() ## read & discard body + except: + pass + + except socket.error, e: + # Broken pipe, connection reset by peer + if e[0] in CONNECTION_CLOSED: + #print "Remote host closed connection before response could be sent" + pass + else: + raise + except Exception, e: + self.server.log_message("Exception caught in HttpRequest.handle():\n") + self.server.log_exception(*sys.exc_info()) + if not request.response_written(): + request.response(500) + request.write('Internal Server Error') + self.socket.close() + raise e # can't do a plain raise since exc_info might have been cleared + self.socket.close() + + +class Server(BaseHTTPServer.HTTPServer): + def __init__(self, socket, address, site, log, max_http_version=DEFAULT_MAX_HTTP_VERSION): + self.socket = socket + self.address = address + self.site = site + self.max_http_version = max_http_version + if log: + self.log = log + if hasattr(log, 'info'): + log.write = log.info + else: + self.log = self + + def write(self, something): + sys.stdout.write('%s' % (something, )); sys.stdout.flush() + + def log_message(self, message): + self.log.write(message) + + def log_exception(self, type, value, tb): + self.log.write(''.join(traceback.format_exception(type, value, tb))) + + def write_access_log_line(self, *args): + """Write a line to the access.log. Arguments: + client_address, date_time, requestline, code, size, request_time + """ + self.log.write( + '%s - - [%s] "%s" %s %s %.6f\n' % args) + + +def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): + pool = coros.CoroutinePool(max_size=max_size) + if serv is None: + serv = Server(sock, sock.getsockname(), site, log, max_http_version=max_http_version) + try: + serv.log.write("httpd starting up on %s\n" % (sock.getsockname(), )) + while True: + try: + new_sock, address = sock.accept() + proto = HttpProtocol(new_sock, address, serv) + pool.execute_async(proto.handle) + api.sleep(0) # sleep to allow other coros to run + except KeyboardInterrupt: + api.get_hub().remove_descriptor(sock.fileno()) + serv.log.write("httpd exiting\n") + break + finally: + try: + sock.close() + except socket.error: + pass diff --git a/eventlet/.svn/text-base/httpd_test.py.svn-base b/eventlet/.svn/text-base/httpd_test.py.svn-base new file mode 100644 index 0000000..e7e0fbc --- /dev/null +++ b/eventlet/.svn/text-base/httpd_test.py.svn-base @@ -0,0 +1,207 @@ +"""\ +@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. +""" + + +from eventlet import api +from eventlet import httpd +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 + + +class Site(object): + def handle_request(self, req): + path = req.path_segments() + if len(path) > 0 and path[0] == "notexist": + req.response(404, body='not found') + return + req.write('hello world') + + def adapt(self, obj, req): + req.write(str(obj)) + + +CONTENT_LENGTH = 'content-length' + + +""" +HTTP/1.1 200 OK +Date: foo +Content-length: 11 + +hello world +""" + +class ConnectionClosed(Exception): + pass + + +def read_http(sock): + response_line = sock.readline() + if not response_line: + raise ConnectionClosed + raw_headers = sock.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 = sock.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( + httpd.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)) + + sock.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = sock.read() + sock.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)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.close() + + def test_003_passing_non_int_to_read(self): + # This should go in test_wrappedfd + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + cancel = api.exc_after(1, RuntimeError) + self.assertRaises(TypeError, sock.read, "This shouldn't work") + cancel.cancel() + sock.close() + + def test_004_close_keepalive(self): + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + self.assertRaises(ConnectionClosed, read_http, sock) + sock.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 + sock.write(request) + result = sock.readline() + status = result.split(' ')[1] + self.assertEqual(status, '414') + sock.close() + + def test_007_get_arg(self): + # define a new handler that does a get_arg as well as a read_body + def new_handle_request(req): + a = req.get_arg('a') + body = req.read_body() + req.write('a is %s, body is %s' % (a, body)) + self.site.handle_request = new_handle_request + + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + request = '\r\n'.join(( + 'POST /%s HTTP/1.0', + 'Host: localhost', + 'Content-Length: 3', + '', + 'a=a')) + sock.write(request) + + # send some junk after the actual request + sock.write('01234567890123456789') + reqline, headers, body = read_http(sock) + self.assertEqual(body, 'a is a, body is a=a') + sock.close() + + def test_008_correctresponse(self): + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + response_line_200,_,_ = read_http(sock) + sock.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') + response_line_404,_,_ = read_http(sock) + sock.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) + sock.close() + + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/.svn/text-base/httpdate.py.svn-base b/eventlet/.svn/text-base/httpdate.py.svn-base new file mode 100644 index 0000000..e1f81f3 --- /dev/null +++ b/eventlet/.svn/text-base/httpdate.py.svn-base @@ -0,0 +1,39 @@ +"""\ +@file httpdate.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 time + +__all__ = ['format_date_time'] + +# Weekday and month names for HTTP date/time formatting; always English! +_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +_monthname = [None, # Dummy so we can use 1-based month numbers + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + +def format_date_time(timestamp): + year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp) + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], day, _monthname[month], year, hh, mm, ss + ) diff --git a/eventlet/.svn/text-base/jsonhttp.py.svn-base b/eventlet/.svn/text-base/jsonhttp.py.svn-base new file mode 100644 index 0000000..2fc0e54 --- /dev/null +++ b/eventlet/.svn/text-base/jsonhttp.py.svn-base @@ -0,0 +1,40 @@ +"""\ +@file jsonhttp.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. +""" + +from eventlet import httpc + +import simplejson + + +def safe_load(what): + if not what: + return None + return simplejson.loads(what) + + +suite = httpc.HttpSuite(simplejson.dumps, safe_load, 'application/json') +head, get, put, delete, post = ( + suite.head, suite.get, suite.put, suite.delete, suite.post) +head_, get_, put_, delete_, post_ = ( + suite.head_, suite.get_, suite.put_, suite.delete_, suite.post_) diff --git a/eventlet/.svn/text-base/kqueuehub.py.svn-base b/eventlet/.svn/text-base/kqueuehub.py.svn-base new file mode 100644 index 0000000..595e7b9 --- /dev/null +++ b/eventlet/.svn/text-base/kqueuehub.py.svn-base @@ -0,0 +1,219 @@ +"""\ +@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()) diff --git a/eventlet/.svn/text-base/logutil.py.svn-base b/eventlet/.svn/text-base/logutil.py.svn-base new file mode 100644 index 0000000..0885042 --- /dev/null +++ b/eventlet/.svn/text-base/logutil.py.svn-base @@ -0,0 +1,112 @@ +"""\ +@file logutil.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 syslog +import logging + + +def file_logger(filename): + """Create a logger. This sucks, the logging module sucks, but + it'll do for now. + """ + handler = logging.FileHandler(filename) + formatter = logging.Formatter() + handler.setFormatter(formatter) + log = logging.getLogger(filename) + log.addHandler(handler) + log.setLevel(logging.DEBUG) + return log, handler + + +def stream_logger(stream): + """Create a logger. This sucks.""" + handler = logging.StreamHandler(stream) + formatter = logging.Formatter() + handler.setFormatter(formatter) + log = logging.getLogger() + log.addHandler(handler) + log.setLevel(logging.DEBUG) + return log, handler + + +class LineLogger(object): + towrite = '' + def __init__(self, emit=None): + if emit is not None: + self.emit = emit + + def write(self, stuff): + self.towrite += stuff + if '\n' in self.towrite: + self.flush() + + def flush(self): + try: + newline = self.towrite.index('\n') + except ValueError: + newline = len(self.towrite) + while True: + self.emit(self.towrite[:newline]) + self.towrite = self.towrite[newline+1:] + try: + newline = self.towrite.index('\n') + except ValueError: + break + + def close(self): + pass + + def emit(self, *args): + pass + + +class SysLogger(LineLogger): + """A file-like object which writes to syslog. Can be inserted + as sys.stdin and sys.stderr to have logging output redirected + to syslog. + """ + def __init__(self, priority): + self.priority = priority + + def emit(self, line): + syslog.syslog(self.priority, line) + + +class TeeLogger(LineLogger): + def __init__(self, one, two): + self.one, self.two = one, two + + def emit(self, line): + self.one.emit(line) + self.two.emit(line) + + +class FileLogger(LineLogger): + def __init__(self, file): + self.file = file + + def emit(self, line): + self.file.write(line + '\n') + self.file.flush() + diff --git a/eventlet/.svn/text-base/pollhub.py.svn-base b/eventlet/.svn/text-base/pollhub.py.svn-base new file mode 100644 index 0000000..0540837 --- /dev/null +++ b/eventlet/.svn/text-base/pollhub.py.svn-base @@ -0,0 +1,189 @@ +"""\ +@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()) diff --git a/eventlet/.svn/text-base/pools.py.svn-base b/eventlet/.svn/text-base/pools.py.svn-base new file mode 100644 index 0000000..5e89dc4 --- /dev/null +++ b/eventlet/.svn/text-base/pools.py.svn-base @@ -0,0 +1,184 @@ +"""\ +@file pools.py +@author Donovan Preston, Aaron Brashears + +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 collections +import os +import socket + +from eventlet import api +from eventlet import channel +from eventlet import httpc + +class FanFailed(RuntimeError): + pass + + +class SomeFailed(FanFailed): + pass + + +class AllFailed(FanFailed): + pass + + +class Pool(object): + """ + When using the pool, if you do a get, you should ALWAYS do a put. + The pattern is: + + thing = self.pool.get() + try: + # do stuff + finally: + self.pool.put(thing) + """ + def __init__(self, min_size=0, max_size=4): + self.min_size = min_size + self.max_size = max_size + self.current_size = 0 + self.channel = channel.channel() + self.free_items = collections.deque() + for x in range(min_size): + self.current_size += 1 + self.free_items.append(self.create()) + + def get(self): + """Return an item from the pool, when one is available + """ + if self.free_items: + return self.free_items.popleft() + if self.current_size < self.max_size: + self.current_size += 1 + return self.create() + return self.channel.receive() + + def put(self, item): + """Put an item back into the pool, when done + """ + if self.current_size > self.max_size: + self.current_size -= 1 + return + + if self.channel.balance < 0: + self.channel.send(item) + else: + self.free_items.append(item) + + def resize(self, new_size): + """Resize the pool + """ + self.max_size = new_size + + def free(self): + """Return the number of free items in the pool. + """ + return len(self.free_items) + self.max_size - self.current_size + + def waiting(self): + """Return the number of routines waiting for a pool item. + """ + if self.channel.balance < 0: + return -self.channel.balance + return 0 + + def create(self): + """Generate a new pool item + """ + raise NotImplementedError("Implement in subclass") + + def fan(self, block, input_list): + chan = channel.channel() + results = [] + exceptional_results = 0 + for index, input_item in enumerate(input_list): + pool_item = self.get() + + ## Fan out + api.spawn( + self._invoke, block, pool_item, input_item, index, chan) + + ## Fan back in + for i in range(len(input_list)): + ## Wait for all guys to send to the queue + index, value = chan.receive() + if isinstance(value, Exception): + exceptional_results += 1 + results.append((index, value)) + + results.sort() + results = [value for index, value in results] + + if exceptional_results: + if exceptional_results == len(results): + raise AllFailed(results) + raise SomeFailed(results) + return results + + def _invoke(self, block, pool_item, input_item, index, chan): + try: + result = block(pool_item, input_item) + except Exception, e: + self.put(pool_item) + chan.send((index, e)) + return + self.put(pool_item) + chan.send((index, result)) + + +class Token(object): + pass + + +class TokenPool(Pool): + """A pool which gives out tokens, an object indicating that + the person who holds the token has a right to consume some + limited resource. + """ + def create(self): + return Token() + + +class ConnectionPool(Pool): + """A Pool which can limit the number of concurrent http operations + being made to a given server. + + *NOTE: *TODO: + + This does NOT currently keep sockets open. It discards the created + http object when it is put back in the pool. This is because we do + not yet have a combination of http clients and servers which can work + together to do HTTP keepalive sockets without errors. + """ + def __init__(self, proto, netloc, use_proxy, min_size=0, max_size=4): + self.proto = proto + self.netloc = netloc + self.use_proxy = use_proxy + Pool.__init__(self, min_size, max_size) + + def create(self): + return httpc.make_connection(self.proto, self.netloc, self.use_proxy) + + def put(self, item): + ## Discard item, create a new connection for the pool + Pool.put(self, self.create()) diff --git a/eventlet/.svn/text-base/pools_test.py.svn-base b/eventlet/.svn/text-base/pools_test.py.svn-base new file mode 100644 index 0000000..e604bc1 --- /dev/null +++ b/eventlet/.svn/text-base/pools_test.py.svn-base @@ -0,0 +1,179 @@ +"""\ +@file test_pools.py +@author Donovan Preston, Aaron Brashears + +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 unittest + +from eventlet import api +from eventlet import channel +from eventlet import pools + + +class IntPool(pools.Pool): + def create(self): + self.current_integer = getattr(self, 'current_integer', 0) + 1 + return self.current_integer + + +class TestIntPool(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(min_size=0, max_size=4) + + def test_integers(self): + # Do not actually use this pattern in your code. The pool will be + # exhausted, and unrestoreable. + # If you do a get, you should ALWAYS do a put, probably like this: + # try: + # thing = self.pool.get() + # # do stuff + # finally: + # self.pool.put(thing) + + # with self.pool.some_api_name() as thing: + # # do stuff + self.assertEquals(self.pool.get(), 1) + self.assertEquals(self.pool.get(), 2) + self.assertEquals(self.pool.get(), 3) + self.assertEquals(self.pool.get(), 4) + + def test_free(self): + self.assertEquals(self.pool.free(), 4) + gotten = self.pool.get() + self.assertEquals(self.pool.free(), 3) + self.pool.put(gotten) + self.assertEquals(self.pool.free(), 4) + + def test_exhaustion(self): + waiter = channel.channel() + def consumer(): + gotten = None + try: + gotten = self.pool.get() + finally: + waiter.send(gotten) + + api.spawn(consumer) + + one, two, three, four = ( + self.pool.get(), self.pool.get(), self.pool.get(), self.pool.get()) + self.assertEquals(self.pool.free(), 0) + + # Let consumer run; nothing will be in the pool, so he will wait + api.sleep(0) + + # Wake consumer + self.pool.put(one) + + # wait for the consumer + self.assertEquals(waiter.receive(), one) + + def test_blocks_on_pool(self): + waiter = channel.channel() + def greedy(): + self.pool.get() + self.pool.get() + self.pool.get() + self.pool.get() + # No one should be waiting yet. + self.assertEquals(self.pool.waiting(), 0) + # The call to the next get will unschedule this routine. + self.pool.get() + # So this send should never be called. + waiter.send('Failed!') + + killable = api.spawn(greedy) + + # no one should be waiting yet. + self.assertEquals(self.pool.waiting(), 0) + + ## Wait for greedy + api.sleep(0) + + ## Greedy should be blocking on the last get + self.assertEquals(self.pool.waiting(), 1) + + ## Send will never be called, so balance should be 0. + self.assertEquals(waiter.balance, 0) + + api.kill(killable) + + +class TestAbstract(unittest.TestCase): + mode = 'static' + def test_abstract(self): + ## Going for 100% coverage here + ## A Pool cannot be used without overriding create() + pool = pools.Pool() + self.assertRaises(NotImplementedError, pool.get) + + +class TestIntPool2(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(min_size=3, max_size=3) + + def test_something(self): + self.assertEquals(len(self.pool.free_items), 3) + ## Cover the clause in get where we get from the free list instead of creating + ## an item on get + gotten = self.pool.get() + self.assertEquals(gotten, 1) + + +ALWAYS = RuntimeError('I always fail') +SOMETIMES = RuntimeError('I fail half the time') + + +class TestFan(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(max_size=2) + + def test_with_list(self): + list_of_input = ['agent-one', 'agent-two', 'agent-three'] + + def my_callable(pool_item, next_thing): + ## Do some "blocking" (yielding) thing + api.sleep(0.01) + return next_thing + + output = self.pool.fan(my_callable, list_of_input) + self.assertEquals(list_of_input, output) + + def test_all_fail(self): + def my_failure(pool_item, next_thing): + raise ALWAYS + self.assertRaises(pools.AllFailed, self.pool.fan, my_failure, range(4)) + + def test_some_fail(self): + def my_failing_callable(pool_item, next_thing): + if next_thing % 2: + raise SOMETIMES + return next_thing + self.assertRaises(pools.SomeFailed, self.pool.fan, my_failing_callable, range(4)) + + +if __name__ == '__main__': + unittest.main() + diff --git a/eventlet/.svn/text-base/processes.py.svn-base b/eventlet/.svn/text-base/processes.py.svn-base new file mode 100644 index 0000000..c51d1e7 --- /dev/null +++ b/eventlet/.svn/text-base/processes.py.svn-base @@ -0,0 +1,141 @@ +"""\ +@file processes.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. +""" + + +import os +import popen2 +import signal + + +from eventlet import util, pools +from eventlet import wrappedfd + +class DeadProcess(RuntimeError): + pass + + +class Process(object): + process_number = 0 + def __init__(self, command, args, dead_callback=lambda:None): + self.process_number = self.process_number + 1 + Process.process_number = self.process_number + self.command = command + self.args = args + self._dead_callback = dead_callback + self.run() + + def run(self): + self.dead = False + self.started = False + self.popen4 = None + + ## We use popen4 so that read() will read from either stdout or stderr + self.popen4 = popen2.Popen4([self.command] + self.args) + child_stdout_stderr = self.popen4.fromchild + child_stdin = self.popen4.tochild + util.set_nonblocking(child_stdout_stderr) + util.set_nonblocking(child_stdin) + self.child_stdout_stderr = wrappedfd.wrapped_file(child_stdout_stderr) + 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.newlines = '\n' + + self.sendall = self.child_stdin.write + self.send = self.child_stdin.write + self.recv = self.child_stdout_stderr.read + self.readline = self.child_stdout_stderr.readline + + def dead_callback(self): + self.dead = True + if self._dead_callback: + self._dead_callback() + + def makefile(self, mode, *arg): + if mode.startswith('r'): + return self.child_stdout_stderr + if mode.startswith('w'): + return self.child_stdin + raise RuntimeError("Unknown mode", mode) + + def read(self, amount=None): + result = self.child_stdout_stderr.read(amount) + if result == '': + # This process is dead. + self.dead_callback() + raise DeadProcess + return result + + def write(self, stuff): + written = self.child_stdin.send(stuff) + try: + self.child_stdin.flush() + except ValueError, e: + ## File was closed + assert str(e) == 'I/O operation on closed file' + if written == 0: + self.dead_callback() + raise DeadProcess + + def flush(self): + self.child_stdin.flush() + + def close(self): + self.child_stdout_stderr.close() + self.child_stdin.close() + self.dead_callback() + + def close_stdin(self): + self.child_stdin.close() + + def kill(self, sig=None): + if sig == None: + sig = signal.SIGTERM + os.kill(self.popen4.pid, sig) + + def getpid(self): + return self.popen4.pid + + +class ProcessPool(pools.Pool): + def __init__(self, command, args=None, min_size=0, max_size=4): + """@param command the command to run + """ + self.command = command + if args is None: + args = [] + self.args = args + pools.Pool.__init__(self, min_size, max_size) + + def create(self): + """Generate a process + """ + def dead_callback(): + self.current_size -= 1 + return Process(self.command, self.args, dead_callback) + + def put(self, item): + if not item.dead: + if item.popen4.poll() != -1: + item.dead_callback() + else: + pools.Pool.put(self, item) diff --git a/eventlet/.svn/text-base/processes_test.py.svn-base b/eventlet/.svn/text-base/processes_test.py.svn-base new file mode 100644 index 0000000..2cbab7d --- /dev/null +++ b/eventlet/.svn/text-base/processes_test.py.svn-base @@ -0,0 +1,134 @@ +"""\ +@file processes_test.py +@author Donovan Preston, Aaron Brashears + +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 +from eventlet import channel +from eventlet import processes + +class TestEchoPool(tests.TestCase): + mode = 'static' + def setUp(self): + self.pool = processes.ProcessPool('echo', ["hello"]) + + def test_echo(self): + result = None + + proc = self.pool.get() + try: + result = proc.read() + finally: + self.pool.put(proc) + self.assertEquals(result, 'hello\n') + + def test_read_eof(self): + proc = self.pool.get() + try: + proc.read() + self.assertRaises(processes.DeadProcess, proc.read) + finally: + self.pool.put(proc) + + +class TestCatPool(tests.TestCase): + mode = 'static' + def setUp(self): + self.pool = processes.ProcessPool('cat') + + def test_cat(self): + result = None + + proc = self.pool.get() + try: + proc.write('goodbye') + proc.close_stdin() + result = proc.read() + finally: + self.pool.put(proc) + + self.assertEquals(result, 'goodbye') + + def test_write_to_dead(self): + result = None + + proc = self.pool.get() + try: + proc.write('goodbye') + proc.close_stdin() + result = proc.read() + self.assertRaises(processes.DeadProcess, proc.write, 'foo') + finally: + self.pool.put(proc) + + def test_close(self): + result = None + + proc = self.pool.get() + try: + proc.write('hello') + proc.close() + self.assertRaises(processes.DeadProcess, proc.write, 'goodbye') + finally: + self.pool.put(proc) + + +class TestDyingProcessesLeavePool(tests.TestCase): + mode = 'static' + def setUp(self): + self.pool = processes.ProcessPool('echo', ['hello'], max_size=1) + + def test_dead_process_not_inserted_into_pool(self): + proc = self.pool.get() + try: + result = proc.read() + self.assertEquals(result, 'hello\n') + finally: + self.pool.put(proc) + proc2 = self.pool.get() + self.assert_(proc is not proc2) + + +class TestProcessLivesForever(tests.TestCase): + mode = 'static' + def setUp(self): + self.pool = processes.ProcessPool('yes', max_size=1) + + def test_reading_twice_from_same_process(self): + proc = self.pool.get() + try: + result = proc.read(2) + self.assertEquals(result, 'y\n') + finally: + self.pool.put(proc) + + proc2 = self.pool.get() + self.assert_(proc is proc2) + try: + result = proc2.read(2) + self.assertEquals(result, 'y\n') + finally: + self.pool.put(proc2) + + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/.svn/text-base/pylibsupport.py.svn-base b/eventlet/.svn/text-base/pylibsupport.py.svn-base new file mode 100644 index 0000000..1378e7a --- /dev/null +++ b/eventlet/.svn/text-base/pylibsupport.py.svn-base @@ -0,0 +1,42 @@ +"""\ +@file pylibsupport.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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. +""" + + +from py.magic 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 + + + diff --git a/eventlet/.svn/text-base/runloop.py.svn-base b/eventlet/.svn/text-base/runloop.py.svn-base new file mode 100644 index 0000000..3fde1a4 --- /dev/null +++ b/eventlet/.svn/text-base/runloop.py.svn-base @@ -0,0 +1,228 @@ +"""\ +@file runloop.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. +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 time +import bisect +import sys +import traceback + +import greenlet + +from eventlet.timer import Timer + + +class RunLoop(object): + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + def __init__(self, wait=None, clock=None): + if clock is None: + clock = self.default_clock() + self.clock = clock + if wait is None: + wait = self.default_wait + self.wait = wait + self.stopping = False + self.running = False + self.timers = [] + self.timers_by_greenlet = {} + self.next_timers = [] + self.observers = {} + self.observer_modes = { + 'entry': [], + 'before_timers': [], + 'before_waiting': [], + 'after_waiting': [], + 'exit': [], + } + + def default_wait(self, time): + return None + + def default_clock(self): + return time.time + + def default_sleep(self): + return 60.0 + + def sleep_until(self): + t = self.timers + if not t: + return None + return t[0][0] + + def run(self): + """Run the runloop until abort is called. + """ + if self.running: + raise RuntimeError("Already running!") + try: + self.running = True + self.stopping = False + self.fire_observers('entry') + while not self.stopping: + self.prepare_timers() + self.fire_observers('before_timers') + self.fire_timers(self.clock()) + self.prepare_timers() + wakeup_when = self.sleep_until() + if wakeup_when is None: + sleep_time = self.default_sleep() + else: + sleep_time = wakeup_when - self.clock() + if sleep_time > 0: + self.fire_observers('before_waiting') + self.wait(sleep_time) + self.fire_observers('after_waiting') + else: + self.wait(0) + else: + del self.timers[:] + del self.next_timers[:] + self.fire_observers('exit') + finally: + self.running = False + self.stopping = False + + def abort(self): + """Stop the runloop. If run is executing, it will exit after completing + the next runloop iteration. + """ + if self.running: + self.stopping = True + + def add_observer(self, observer, *modes): + """Add an event observer to this runloop with the given modes. + Valid modes are: + entry: The runloop is being entered. + before_timers: Before the expired timers for this iteration are executed. + before_waiting: Before waiting for the calculated wait_time + where nothing will happen. + after_waiting: After waiting, immediately before starting the top of the + runloop again. + exit: The runloop is exiting. + + If no mode is passed or mode is all, the observer will be fired for every + event type. + """ + if not modes or modes == ('all',): + modes = tuple(self.observer_modes) + self.observers[observer] = modes + for mode in modes: + self.observer_modes[mode].append(observer) + + def remove_observer(self, observer): + """Remove a previously registered observer from all event types. + """ + for mode in self.observers.pop(observer, ()): + self.observer_modes[mode].remove(observer) + + def squelch_observer_exception(self, observer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Removing observer: %r" % (observer,) + self.remove_observer(observer) + + def fire_observers(self, activity): + for observer in self.observer_modes[activity]: + try: + observer(self, activity) + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_observer_exception(observer, sys.exc_info()) + + def squelch_timer_exception(self, timer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Timer raised: %r" % (timer,) + + def _add_absolute_timer(self, when, info): + # the 0 placeholder makes it easy to bisect_right using (now, 1) + self.next_timers.append((when, 0, info)) + + def add_timer(self, timer): + scheduled_time = self.clock() + timer.seconds + self._add_absolute_timer(scheduled_time, timer) + current_greenlet = greenlet.getcurrent() + 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 + + + def prepare_timers(self): + ins = bisect.insort_right + t = self.timers + for item in self.next_timers: + ins(t, item) + del self.next_timers[:] + + def schedule_call(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = Timer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def fire_timers(self, when): + t = self.timers + last = bisect.bisect_right(t, (when, 1)) + i = 0 + for i in xrange(last): + timer = t[i][2] + try: + try: + timer() + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_timer_exception(timer, sys.exc_info()) + finally: + try: + del self.timers_by_greenlet[timer.greenlet][timer] + except KeyError: + pass + del t[:last] + + def cancel_timers(self, greenlet): + if greenlet not in self.timers_by_greenlet: + return + for timer in self.timers_by_greenlet[greenlet]: + if not timer.cancelled and timer.seconds: + ## If timer.seconds is 0, this isn't a timer, it's + ## actually eventlet's silly way of specifying whether + ## a coroutine is "ready to run" or not. + timer.cancel() + print 'Runloop cancelling left-over timer %s' % timer + del self.timers_by_greenlet[greenlet] + diff --git a/eventlet/.svn/text-base/runloop_test.py.svn-base b/eventlet/.svn/text-base/runloop_test.py.svn-base new file mode 100644 index 0000000..4827c32 --- /dev/null +++ b/eventlet/.svn/text-base/runloop_test.py.svn-base @@ -0,0 +1,157 @@ +"""\ +@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() + diff --git a/eventlet/.svn/text-base/saranwrap.py.svn-base b/eventlet/.svn/text-base/saranwrap.py.svn-base new file mode 100644 index 0000000..43e5fe3 --- /dev/null +++ b/eventlet/.svn/text-base/saranwrap.py.svn-base @@ -0,0 +1,685 @@ +"""\ +@file saranwrap.py +@author Phoenix +@date 2007-07-13 +@brief A simple, pickle based rpc mechanism which reflects python +objects and callables. + +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. + +This file provides classes and exceptions used for simple python level +remote procedure calls. This is achieved by intercepting the basic +getattr and setattr calls in a client proxy, which commnicates those +down to the server which will dispatch them to objects in it's process +space. + +The basic protocol to get and set attributes is for the client proxy +to issue the command: + +getattr $id $name +setattr $id $name $value + +getitem $id $item +setitem $id $item $value +eq $id $rhs +del $id + +When the get returns a callable, the client proxy will provide a +callable proxy which will invoke a remote procedure call. The command +issued from the callable proxy to server is: + +call $id $name $args $kwargs + +If the client supplies an id of None, then the get/set/call is applied +to the object(s) exported from the server. + +The server will parse the get/set/call, take the action indicated, and +return back to the caller one of: + +value $val +callable +object $id +exception $excp + +To handle object expiration, the proxy will instruct the rpc server to +discard objects which are no longer in use. This is handled by +catching proxy deletion and sending the command: + +del $id + +The server will handle this by removing clearing it's own internal +references. This does not mean that the object will necessarily be +cleaned from the server, but no artificial references will remain +after successfully completing. On completion, the server will return +one of: + +value None +exception $excp + +The server also accepts a special command for debugging purposes: + +status + +Which will be intercepted by the server to write back: + +status {...} + +The wire protocol is to pickle the Request class in this file. The +request class is basically an action and a map of parameters' +""" + +import os +from cPickle import dumps, loads +import struct +import sys + +try: + set = set + frozenset = frozenset +except NameError: + from sets import Set as set, ImmutableSet as frozenset + +from eventlet.processes import Process, DeadProcess +from eventlet import api, pools + +# debugging hooks +_g_debug_mode = False +if _g_debug_mode: + import traceback + import tempfile + +def pythonpath_sync(): + """ +@brief apply the current sys.path to the environment variable PYTHONPATH, so that child processes have the same paths as the caller does. +""" + pypath = os.pathsep.join(sys.path) + os.environ['PYTHONPATH'] = pypath + +def wrap(obj, dead_callback = None): + """ +@brief wrap in object in another process through a saranwrap proxy +@param object The object to wrap. +@param dead_callback A callable to invoke if the process exits.""" + + if type(obj).__name__ == 'module': + return wrap_module(obj.__name__, dead_callback) + pythonpath_sync() + if _g_debug_mode: + p = Process('python', [__file__, '--child', '--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')], dead_callback) + else: + p = Process('python', [__file__, '--child'], dead_callback) + prox = Proxy(ChildProcess(p, p)) + prox.obj = obj + return prox.obj + +def wrap_module(fqname, dead_callback = None): + """ +@brief wrap a module in another process through a saranwrap proxy +@param fqname The fully qualified name of the module. +@param dead_callback A callable to invoke if the process exits.""" + pythonpath_sync() + global _g_debug_mode + if _g_debug_mode: + p = Process('python', [__file__, '--module', fqname, '--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')], dead_callback) + else: + p = Process('python', [__file__, '--module', fqname,], dead_callback) + prox = Proxy(ChildProcess(p,p)) + return prox + +def status(proxy): + """ +@brief get the status from the server through a proxy +@param proxy a saranwrap.Proxy object connected to a server.""" + return proxy.__local_dict['_cp'].make_request(Request('status', {})) + +class BadResponse(Exception): + """"This exception is raised by an saranwrap client when it could + parse but cannot understand the response from the server.""" + pass + +class BadRequest(Exception): + """"This exception is raised by a saranwrap server when it could parse + but cannot understand the response from the server.""" + pass + +class UnrecoverableError(Exception): + pass + +class Request(object): + "@brief A wrapper class for proxy requests to the server." + def __init__(self, action, param): + self._action = action + self._param = param + def __str__(self): + return "Request `"+self._action+"` "+str(self._param) + def __getitem__(self, name): + return self._param[name] + def get(self, name, default = None): + try: + return self[name] + except KeyError: + return default + def action(self): + return self._action + +def _read_lp_hunk(stream): + len_bytes = stream.read(4) + length = struct.unpack('I', len_bytes)[0] + body = stream.read(length) + return body + +def _read_response(id, attribute, input, cp): + """@brief local helper method to read respones from the rpc server.""" + try: + str = _read_lp_hunk(input) + _prnt(`str`) + response = loads(str) + except (AttributeError, DeadProcess), e: + raise UnrecoverableError(e) + _prnt("response: %s" % response) + if response[0] == 'value': + return response[1] + elif response[0] == 'callable': + return CallableProxy(id, attribute, cp) + elif response[0] == 'object': + return ObjectProxy(cp, response[1]) + elif response[0] == 'exception': + exp = response[1] + raise exp + else: + raise BadResponse(response[0]) + +def _write_lp_hunk(stream, hunk): + write_length = struct.pack('I', len(hunk)) + stream.write(write_length + hunk) + if hasattr(stream, 'flush'): + stream.flush() + +def _write_request(param, output): + _prnt("request: %s" % param) + str = dumps(param) + _write_lp_hunk(output, str) + +def _is_local(attribute): + "Return true if the attribute should be handled locally" +# return attribute in ('_in', '_out', '_id', '__getattribute__', '__setattr__', '__dict__') + # good enough for now. :) + if '__local_dict' in attribute: + return True + return False + +def _prnt(message): + global _g_debug_mode + if _g_debug_mode: + print message + +_g_logfile = None +def _log(message): + global _g_logfile + if _g_logfile: + _g_logfile.write(str(os.getpid()) + ' ' + message) + _g_logfile.write('\n') + _g_logfile.flush() + +def _unmunge_attr_name(name): + """ Sometimes attribute names come in with classname prepended, not sure why. + This function removes said classname, because we're huge hackers and we didn't + find out what the true right thing to do is. *TODO: find out. """ + if(name.startswith('_Proxy')): + name = name[len('_Proxy'):] + if(name.startswith('_ObjectProxy')): + name = name[len('_ObjectProxy'):] + return name + +class ChildProcess(object): + """\ + This class wraps a remote python process, presumably available + in an instance of an Server. + """ + def __init__(self, instr, outstr, dead_list = None): + """ + @param instr a file-like object which supports read(). + @param outstr a file-like object which supports write() and flush(). + @param dead_list a list of ids of remote objects that are dead + """ + # default dead_list inside the function because all objects in method + # argument lists are init-ed only once globally + _prnt("ChildProcess::__init__") + if dead_list is None: + dead_list = set() + self._dead_list = dead_list + self._in = instr + self._out = outstr + self._lock = pools.TokenPool(max_size=1) + + def make_request(self, request, attribute=None): + _id = request.get('id') + + t = self._lock.get() + try: + _write_request(request, self._out) + retval = _read_response(_id, attribute, self._in, self) + finally: + self._lock.put(t) + + return retval + + +class Proxy(object): + """\ +@class Proxy +@brief This is the class you will typically use as a client to a child +process. + +Simply instantiate one around a file-like interface and start +calling methods on the thing that is exported. The dir() builtin is +not supported, so you have to know what has been exported. +""" + def __init__(self, cp): + """@param A ChildProcess instance that wraps the i/o to the child process. + """ + #_prnt("Proxy::__init__") + self.__local_dict = dict( + _cp = cp, + _id = None) + + def __getattribute__(self, attribute): + #_prnt("Proxy::__getattr__: %s" % attribute) + if _is_local(attribute): + # call base class getattribute so we actually get the local variable + attribute = _unmunge_attr_name(attribute) + return super(Proxy, self).__getattribute__(attribute) + elif attribute in ('__deepcopy__', '__copy__'): + # redirect copy function calls to our own versions instead of + # to the proxied object + return super(Proxy, self).__getattribute__('__deepcopy__') + else: + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + + _dead_list = my_cp._dead_list + for dead_object in _dead_list.copy(): + request = Request('del', {'id':dead_object}) + + my_cp.make_request(request) + _dead_list.remove(dead_object) + + # Pass all public attributes across to find out if it is + # callable or a simple attribute. + request = Request('getattr', {'id':my_id, 'attribute':attribute}) + return my_cp.make_request(request, attribute=attribute) + + def __setattr__(self, attribute, value): + #_prnt("Proxy::__setattr__: %s" % attribute) + if _is_local(attribute): + # It must be local to this actual object, so we have to apply + # it to the dict in a roundabout way + attribute = _unmunge_attr_name(attribute) + super(Proxy, self).__getattribute__('__dict__')[attribute]=value + else: + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + # Pass the set attribute across + request = Request('setattr', {'id':my_id, 'attribute':attribute, 'value':value}) + return my_cp.make_request(request, attribute=attribute) + +class ObjectProxy(Proxy): + """\ +@class ObjectProxy +@brief This class wraps a remote object in the Server + +This class will be created during normal operation, and users should +not need to deal with this class directly.""" + + def __init__(self, cp, _id): + """\ +@param cp A ChildProcess object that wraps the i/o of a child process. +@param _id an identifier for the remote object. humans do not provide this. +""" + Proxy.__init__(self, cp) + self.__local_dict['_id'] = _id + #_prnt("ObjectProxy::__init__ %s" % _id) + + def __del__(self): + my_id = self.__local_dict['_id'] + #_prnt("ObjectProxy::__del__ %s" % my_id) + self.__local_dict['_cp']._dead_list.add(my_id) + + def __getitem__(self, key): + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('getitem', {'id':my_id, 'key':key}) + return my_cp.make_request(request, attribute=key) + + def __setitem__(self, key, value): + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('setitem', {'id':my_id, 'key':key, 'value':value}) + return my_cp.make_request(request, attribute=key) + + def __eq__(self, rhs): + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('eq', {'id':my_id, 'rhs':rhs.__local_dict['_id']}) + return my_cp.make_request(request) + + def __repr__(self): + # apparently repr(obj) skips the whole getattribute thing and just calls __repr__ + # directly. Therefore we just pass it through the normal call pipeline, and + # tack on a little header so that you can tell it's an object proxy. + val = self.__repr__() + return "saran:%s" % val + + def __str__(self): + # see description for __repr__, because str(obj) works the same. We don't + # tack anything on to the return value here because str values are used as data. + return self.__str__() + + def __nonzero__(self): + # bool(obj) is another method that skips __getattribute__. There's no good way to just pass + # the method on, so we use a special message. + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('nonzero', {'id':my_id}) + return my_cp.make_request(request) + + def __len__(self): + # see description for __repr__, len(obj) is the same. + return self.__len__() + + def __deepcopy__(self, memo=None): + """Copies the entire external object and returns its + value. Will only work if the remote object is pickleable.""" + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('copy', {'id':my_id}) + return my_cp.make_request(request) + + # since the remote object is being serialized whole anyway, + # there's no semantic difference between copy and deepcopy + __copy__ = __deepcopy__ + + +def proxied_type(self): + if type(self) is not ObjectProxy: + return type(self) + + my_cp = self.__local_dict['_cp'] + my_id = self.__local_dict['_id'] + request = Request('type', {'id':my_id}) + return my_cp.make_request(request) + +class CallableProxy(object): + """\ +@class CallableProxy +@brief This class wraps a remote function in the Server + +This class will be created by an Proxy during normal operation, +and users should not need to deal with this class directly.""" + + def __init__(self, object_id, name, cp): + #_prnt("CallableProxy::__init__: %s, %s" % (object_id, name)) + self._object_id = object_id + self._name = name + self._cp = cp + + def __call__(self, *args, **kwargs): + #_prnt("CallableProxy::__call__: %s, %s" % (args, kwargs)) + + # Pass the call across. We never build a callable without + # having already checked if the method starts with '_' so we + # can safely pass this one to the remote object. + #_prnt("calling %s %s" % (self._object_id, self._name) + request = Request('call', {'id':self._object_id, 'name':self._name, 'args':args, 'kwargs':kwargs}) + return self._cp.make_request(request, attribute=self._name) + +class Server(object): + def __init__(self, input, output, export): + """\ +@param input a file-like object which supports read(). +@param output a file-like object which supports write() and flush(). +@param export an object, function, or map which is exported to clients +when the id is None.""" + #_log("Server::__init__") + self._in = input + self._out = output + self._export = export + self._next_id = 1 + self._objects = {} + + def handle_status(self, obj, req): + return { + 'object_count':len(self._objects), + 'next_id':self._next_id, + 'pid':os.getpid()} + + def handle_getattr(self, obj, req): + try: + return getattr(obj, req['attribute']) + except AttributeError, e: + if hasattr(obj, "__getitem__"): + return obj[req['attribute']] + else: + raise e + #_log('getattr: %s' % str(response)) + + def handle_setattr(self, obj, req): + try: + return setattr(obj, req['attribute'], req['value']) + except AttributeError, e: + if hasattr(obj, "__setitem__"): + return obj.__setitem__(req['attribute'], req['value']) + else: + raise e + + def handle_getitem(self, obj, req): + return obj[req['key']] + + def handle_setitem(self, obj, req): + obj[req['key']] = req['value'] + return None # *TODO figure out what the actual return value of __setitem__ should be + + def handle_eq(self, obj, req): + #_log("__eq__ %s %s" % (obj, req)) + rhs = None + try: + rhs = self._objects[req['rhs']] + except KeyError, e: + return False + return (obj == rhs) + + def handle_call(self, obj, req): + #_log("calling %s " % (req['name'])) + try: + fn = getattr(obj, req['name']) + except AttributeError, e: + if hasattr(obj, "__setitem__"): + fn = obj[req['name']] + else: + raise e + + return fn(*req['args'],**req['kwargs']) + + def handle_del(self, obj, req): + id = req['id'] + _log("del %s from %s" % (id, self._objects)) + + # *TODO what does __del__ actually return? + del self._objects[id] + return None + + def handle_type(self, obj, req): + return type(obj) + + def handle_nonzero(self, obj, req): + return bool(obj) + + def handle_copy(self, obj, req): + return obj + + def loop(self): + """@brief Loop forever and respond to all requests.""" + _log("Server::loop") + while True: + try: + try: + str = _read_lp_hunk(self._in) + except EOFError: + sys.exit(0) # normal exit + request = loads(str) + _log("request: %s (%s)" % (request, self._objects)) + req = request + id = None + obj = None + try: + id = req['id'] + if id: + id = int(id) + obj = self._objects[id] + #_log("id, object: %d %s" % (id, obj)) + except Exception, e: + #_log("Exception %s" % str(e)) + pass + if obj is None or id is None: + id = None + obj = self._export + #_log("found object %s" % str(obj)) + + # Handle the request via a method with a special name on the server + handler_name = 'handle_%s' % request.action() + + try: + handler = getattr(self, handler_name) + except AttributeError: + raise BadRequest, request.action() + + response = handler(obj, request) + + # figure out what to do with the response, and respond + # apprpriately. + if request.action() in ['status', 'type', 'copy']: + # have to handle these specially since we want to + # pickle up the actual value and not return a proxy + self.respond(['value', response]) + elif callable(response): + #_log("callable %s" % response) + self.respond(['callable']) + elif self.is_value(response): + self.respond(['value', response]) + else: + self._objects[self._next_id] = response + #_log("objects: %s" % str(self._objects)) + self.respond(['object', self._next_id]) + self._next_id += 1 + except SystemExit, e: + raise e + except Exception, e: + self.write_exception(e) + except: + self.write_exception(sys.exc_info()[0]) + + def is_value(self, value): + """\ +@brief Test if value should be serialized as a simple dataset. +@param value The value to test. +@return Returns true if value is a simple serializeable set of data. +""" + return type(value) in (str,unicode,int,float,long,bool,type(None)) + + def respond(self, body): + _log("responding with: %s" % body) + #_log("objects: %s" % self._objects) + s = dumps(body) + _log(`s`) + str = _write_lp_hunk(self._out, s) + + def write_exception(self, e): + """@brief Helper method to respond with an exception.""" + #_log("exception: %s" % sys.exc_info()[0]) + # TODO: serialize traceback using generalization of code from mulib.htmlexception + self.respond(['exception', e]) + global _g_debug_mode + if _g_debug_mode: + _log("traceback: %s" % traceback.format_tb(sys.exc_info()[2])) + + +# test function used for testing that final except clause +def raise_a_weird_error(): + raise "oh noes you can raise a string" + +# test function used for testing return of unpicklable exceptions +def raise_an_unpicklable_error(): + class Unpicklable(Exception): + pass + raise Unpicklable() + +# test function used for testing return of picklable exceptions +def raise_standard_error(): + raise FloatingPointError() + +# test function to make sure print doesn't break the wrapper +def print_string(str): + print str + +# test function to make sure printing on stdout doesn't break the +# wrapper +def err_string(str): + print >>sys.stderr, str + +def main(): + import optparse + parser = optparse.OptionParser( + usage="usage: %prog [options]", + description="Simple saranwrap.Server wrapper") + parser.add_option( + '-c', '--child', default=False, action='store_true', + help='Wrap an object serialized via setattr.') + parser.add_option( + '-m', '--module', type='string', dest='module', default=None, + help='a module to load and export.') + parser.add_option( + '-l', '--logfile', type='string', dest='logfile', default=None, + help='file to log to.') + options, args = parser.parse_args() + global _g_logfile + if options.logfile: + _g_logfile = open(options.logfile, 'a') + if options.module: + export = api.named(options.module) + server = Server(sys.stdin, sys.stdout, export) + elif options.child: + server = Server(sys.stdin, sys.stdout, {}) + + # *HACK: some modules may emit on stderr, which breaks everything. + class NullSTDOut(object): + def write(a, b): + pass + sys.stderr = NullSTDOut() + sys.stdout = NullSTDOut() + + # Loop until EOF + server.loop() + if _g_logfile: + _g_logfile.close() + + +if __name__ == "__main__": + main() diff --git a/eventlet/.svn/text-base/saranwrap_test.py.svn-base b/eventlet/.svn/text-base/saranwrap_test.py.svn-base new file mode 100644 index 0000000..df0878d --- /dev/null +++ b/eventlet/.svn/text-base/saranwrap_test.py.svn-base @@ -0,0 +1,316 @@ +#!/usr/bin/python +# @file test_saranwrap.py +# @brief Test cases for saranwrap. +# +# 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 import saranwrap, coros + +import os +import sys +import tempfile +import time +import unittest +import uuid + +# random test stuff +def list_maker(): + return [0,1,2] + +one = 1 +two = 2 +three = 3 + +class TestSaranwrap(unittest.TestCase): + def assert_server_exists(self, prox): + self.assert_(saranwrap.status(prox)) + prox.foo = 0 + self.assertEqual(0, prox.foo) + + def test_wrap_tuple(self): + my_tuple = (1, 2) + prox = saranwrap.wrap(my_tuple) + self.assertEqual(prox[0], 1) + self.assertEqual(prox[1], 2) + self.assertEqual(len(my_tuple), 2) + + def test_wrap_string(self): + my_object = "whatever" + prox = saranwrap.wrap(my_object) + self.assertEqual(str(my_object), str(prox)) + self.assertEqual(len(my_object), len(prox)) + self.assertEqual(my_object.join(['a', 'b']), prox.join(['a', 'b'])) + + def test_wrap_uniterable(self): + # here we're treating the exception as just a normal class + prox = saranwrap.wrap(FloatingPointError()) + def index(): + prox[0] + def key(): + prox['a'] + + self.assertRaises(IndexError, index) + self.assertRaises(TypeError, key) + + def test_wrap_dict(self): + my_object = {'a':1} + prox = saranwrap.wrap(my_object) + self.assertEqual('a', prox.keys()[0]) + self.assertEqual(1, prox['a']) + self.assertEqual(str(my_object), str(prox)) + self.assertEqual('saran:' + repr(my_object), repr(prox)) + self.assertEqual('saran:' + `my_object`, `prox`) + + def test_wrap_module_class(self): + prox = saranwrap.wrap(uuid) + self.assertEqual(saranwrap.Proxy, type(prox)) + id = prox.uuid4() + self.assertEqual(id.get_version(), uuid.uuid4().get_version()) + self.assert_(repr(prox.uuid4)) + + def test_wrap_eq(self): + prox = saranwrap.wrap(uuid) + id1 = prox.uuid4() + id2 = prox.UUID(str(id1)) + self.assertEqual(id1, id2) + id3 = prox.uuid4() + self.assert_(id1 != id3) + + def test_wrap_nonzero(self): + prox = saranwrap.wrap(uuid) + id1 = prox.uuid4() + self.assert_(bool(id1)) + prox2 = saranwrap.wrap([1, 2, 3]) + self.assert_(bool(prox2)) + + def test_multiple_wraps(self): + prox1 = saranwrap.wrap(uuid) + prox2 = saranwrap.wrap(uuid) + x1 = prox1.uuid4() + x2 = prox1.uuid4() + del x2 + x3 = prox2.uuid4() + + def test_dict_passthru(self): + prox = saranwrap.wrap(uuid) + x = prox.uuid4() + self.assertEqual(type(x.__dict__), saranwrap.ObjectProxy) + # try it all on one line just for the sake of it + self.assertEqual(type(saranwrap.wrap(uuid).uuid4().__dict__), saranwrap.ObjectProxy) + + def test_is_value(self): + server = saranwrap.Server(None, None, None) + self.assert_(server.is_value(None)) + + def test_wrap_getitem(self): + prox = saranwrap.wrap([0,1,2]) + self.assertEqual(prox[0], 0) + + def test_wrap_setitem(self): + prox = saranwrap.wrap([0,1,2]) + prox[1] = 2 + self.assertEqual(prox[1], 2) + + def test_raising_exceptions(self): + prox = saranwrap.wrap(uuid) + def nofunc(): + prox.never_name_a_function_like_this() + self.assertRaises(AttributeError, nofunc) + + def test_raising_weird_exceptions(self): + # the recursion is killing me! + prox = saranwrap.wrap(saranwrap) + try: + prox.raise_a_weird_error() + self.assert_(False) + except: + import sys + ex = sys.exc_info()[0] + self.assertEqual(ex, "oh noes you can raise a string") + self.assert_server_exists(prox) + + def test_unpicklable_server_exception(self): + prox = saranwrap.wrap(saranwrap) + def unpickle(): + prox.raise_an_unpicklable_error() + + self.assertRaises(saranwrap.UnrecoverableError, unpickle) + + # It's basically dead + #self.assert_server_exists(prox) + + def test_pickleable_server_exception(self): + prox = saranwrap.wrap(saranwrap) + def fperror(): + prox.raise_standard_error() + + self.assertRaises(FloatingPointError, fperror) + self.assert_server_exists(prox) + + def test_print_does_not_break_wrapper(self): + prox = saranwrap.wrap(saranwrap) + prox.print_string('hello') + self.assert_server_exists(prox) + + def test_stderr_does_not_break_wrapper(self): + prox = saranwrap.wrap(saranwrap) + prox.err_string('goodbye') + self.assert_server_exists(prox) + + def assertLessThan(self, a, b): + self.assert_(a < b, "%s is not less than %s" % (a, b)) + + def test_status(self): + prox = saranwrap.wrap(time) + a = prox.gmtime(0) + status = saranwrap.status(prox) + self.assertEqual(status['object_count'], 1) + self.assertEqual(status['next_id'], 2) + self.assert_(status['pid']) # can't guess what it will be + # status of an object should be the same as the module + self.assertEqual(saranwrap.status(a), status) + # create a new one then immediately delete it + prox.gmtime(1) + is_id = prox.ctime(1) # sync up deletes + status = saranwrap.status(prox) + self.assertEqual(status['object_count'], 1) + self.assertEqual(status['next_id'], 3) + prox2 = saranwrap.wrap(uuid) + self.assert_(status['pid'] != saranwrap.status(prox2)['pid']) + + def test_del(self): + prox = saranwrap.wrap(time) + delme = prox.gmtime(0) + status_before = saranwrap.status(prox) + #print status_before['objects'] + del delme + # need to do an access that doesn't create an object + # in order to sync up the deleted objects + prox.ctime(1) + status_after = saranwrap.status(prox) + #print status_after['objects'] + self.assertLessThan(status_after['object_count'], status_before['object_count']) + + def test_variable_and_keyword_arguments_with_function_calls(self): + import optparse + prox = saranwrap.wrap(optparse) + parser = prox.OptionParser() + z = parser.add_option('-n', action='store', type='string', dest='n') + opts,args = parser.parse_args(["-nfoo"]) + self.assertEqual(opts.n, 'foo') + + def test_original_proxy_going_out_of_scope(self): + def make_uuid(): + prox = saranwrap.wrap(uuid) + # after this function returns, prox should fall out of scope + return prox.uuid4() + tid = make_uuid() + self.assertEqual(tid.get_version(), uuid.uuid4().get_version()) + def make_list(): + from eventlet import saranwrap_test + prox = saranwrap.wrap(saranwrap_test.list_maker) + # after this function returns, prox should fall out of scope + return prox() + proxl = make_list() + self.assertEqual(proxl[2], 2) + + def test_status_of_none(self): + try: + saranwrap.status(None) + self.assert_(False) + except AttributeError, e: + pass + + def test_not_inheriting_pythonpath(self): + # construct a fake module in the temp directory + temp_dir = tempfile.mkdtemp("saranwrap_test") + fp = open(os.path.join(temp_dir, "jitar_hero.py"), "w") + fp.write("""import os, sys +pypath = os.environ['PYTHONPATH'] +sys_path = sys.path""") + fp.close() + + # this should fail because we haven't stuck the temp_dir in our path yet + prox = saranwrap.wrap_module('jitar_hero') + import cPickle + try: + prox.pypath + self.fail() + except cPickle.UnpicklingError: + pass + + # now try to saranwrap it + sys.path.append(temp_dir) + try: + import jitar_hero + prox = saranwrap.wrap(jitar_hero) + self.assert_(prox.pypath.count(temp_dir)) + self.assert_(prox.sys_path.count(temp_dir)) + finally: + import shutil + shutil.rmtree(temp_dir) + sys.path.remove(temp_dir) + + def test_contention(self): + from eventlet import saranwrap_test + prox = saranwrap.wrap(saranwrap_test) + + pool = coros.CoroutinePool(max_size=4) + waiters = [] + waiters.append(pool.execute(lambda: self.assertEquals(prox.one, 1))) + waiters.append(pool.execute(lambda: self.assertEquals(prox.two, 2))) + waiters.append(pool.execute(lambda: self.assertEquals(prox.three, 3))) + for waiter in waiters: + waiter.wait() + + def test_copy(self): + import copy + compound_object = {'a':[1,2,3]} + prox = saranwrap.wrap(compound_object) + def make_assertions(copied): + self.assert_(isinstance(copied, dict)) + self.assert_(isinstance(copied['a'], list)) + self.assertEquals(copied, compound_object) + self.assertNotEqual(id(compound_object), id(copied)) + + make_assertions(copy.copy(prox)) + make_assertions(copy.deepcopy(prox)) + + def test_list_of_functions(self): + return # this test is known to fail, we can implement it sometime in the future if we wish + from eventlet import saranwrap_test + prox = saranwrap.wrap([saranwrap_test.list_maker]) + self.assertEquals(list_maker(), prox[0]()) + + def test_detection_of_server_crash(self): + # make the server crash here + pass + + def test_equality_with_local_object(self): + # we'll implement this if there's a use case for it + pass + + def test_non_blocking(self): + # here we test whether it's nonblocking + pass + +if __name__ == '__main__': + unittest.main() diff --git a/eventlet/.svn/text-base/selecthub.py.svn-base b/eventlet/.svn/text-base/selecthub.py.svn-base new file mode 100644 index 0000000..60e2bb6 --- /dev/null +++ b/eventlet/.svn/text-base/selecthub.py.svn-base @@ -0,0 +1,173 @@ +"""\ +@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.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 = select.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()) diff --git a/eventlet/.svn/text-base/stacklesssupport.py.svn-base b/eventlet/.svn/text-base/stacklesssupport.py.svn-base new file mode 100644 index 0000000..8e97555 --- /dev/null +++ b/eventlet/.svn/text-base/stacklesssupport.py.svn-base @@ -0,0 +1,110 @@ +"""\ +@file __init__.py +@brief Support for using stackless python. Broken and riddled with +print statements at the moment. Please fix it! + +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 types + +import stackless +import traceback + + +caller = None + + +coro_args = {} + + +tasklet_to_greenlet = {} + + +def getcurrent(): + return tasklet_to_greenlet[stackless.getcurrent()] + + +class FirstSwitch(object): + def __init__(self, gr): + self.gr = gr + + def __call__(self, *args, **kw): + print "first call", args, kw + gr = self.gr + del gr.switch + run, gr.run = gr.run, None + t = stackless.tasklet(run) + gr.t = t + tasklet_to_greenlet[t] = gr + t.setup(*args, **kw) + result = t.run() + + +class greenlet(object): + def __init__(self, run=None, parent=None): + self.dead = False + if parent is None: + parent = getcurrent() + + self.parent = parent + if run is not None: + self.run = run + + self.switch = FirstSwitch(self) + + def switch(self, *args): + print "switch", args + global caller + caller = stackless.getcurrent() + coro_args[self] = args + self.t.insert() + stackless.schedule() + if caller is not self.t: + caller.remove() + rval = coro_args[self] + return rval + + def run(self): + pass + + def __bool__(self): + return self.run is None and not self.dead + + +class GreenletExit(Exception): + pass + + +def emulate(): + module = types.ModuleType('greenlet') + sys.modules['greenlet'] = module + module.greenlet = greenlet + module.getcurrent = getcurrent + module.GreenletExit = GreenletExit + + caller = t = stackless.getcurrent() + tasklet_to_greenlet[t] = None + main_coro = greenlet() + tasklet_to_greenlet[t] = main_coro + main_coro.t = t + del main_coro.switch ## It's already running + coro_args[main_coro] = None diff --git a/eventlet/.svn/text-base/tests.py.svn-base b/eventlet/.svn/text-base/tests.py.svn-base new file mode 100644 index 0000000..41968c2 --- /dev/null +++ b/eventlet/.svn/text-base/tests.py.svn-base @@ -0,0 +1,89 @@ +"""\ +@file tests.py +@author Donovan Preston +@brief Indirection layer for tests in case we want to fix unittest. + +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 errno +import os +import sys +import unittest, doctest + + +TestCase = unittest.TestCase + + +name = getattr(sys.modules['__main__'], '__name__', None) +main = unittest.main + +# listing of files containing doctests +doc_test_files = ['coros'] + +def find_command(command): + for dir in os.getenv('PATH', '/usr/bin:/usr/sbin').split(os.pathsep): + p = os.path.join(dir, command) + if os.access(p, os.X_OK): + return p + raise IOError(errno.ENOENT, 'Command not found: %r' % command) + +def run_all_tests(test_files = doc_test_files): + """ Runs all the unit tests, returning immediately after the + first failed test. + + Returns true if the tests all succeeded. This method is really much longer + than it ought to be. + """ + eventlet_dir = os.path.realpath(os.path.dirname(__file__)) + if eventlet_dir not in sys.path: + sys.path.append(eventlet_dir) + + # add all _test files as a policy + import glob + test_files += [os.path.splitext(os.path.basename(x))[0] + for x in glob.glob(os.path.join(eventlet_dir, "*_test.py"))] + test_files.sort() + + for test_file in test_files: + print "-=", test_file, "=-" + try: + test_module = __import__(test_file) + except ImportError: + print "Unable to import %s, skipping" % test_file + continue + + if test_file.endswith('_test'): + # gawd, unittest, why you make it so difficult to just run some tests! + suite = unittest.findTestCases(test_module) + result = unittest.TextTestRunner().run(suite) + if not result.wasSuccessful(): + return False + else: + failures, tests = doctest.testmod(test_module) + if failures: + return False + else: + print "OK" + + return True + +if __name__ == '__main__': + run_all_tests() diff --git a/eventlet/.svn/text-base/timer.py.svn-base b/eventlet/.svn/text-base/timer.py.svn-base new file mode 100644 index 0000000..a91390a --- /dev/null +++ b/eventlet/.svn/text-base/timer.py.svn-base @@ -0,0 +1,83 @@ +"""\ +@file timer.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 get_hub + +""" If true, captures a stack trace for each timer when constructed. This is +useful for debugging leaking timers, to find out where the timer was set up. """ +_g_debug = False + +class Timer(object): + __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback'] + def __init__(self, seconds, cb, *args, **kw): + """Create a timer. + seconds: The minimum number of seconds to wait before calling + cb: The callback to call when the timer has expired + *args: The arguments to pass to cb + **kw: The keyword arguments to pass to cb + + This timer will not be run unless it is scheduled in a runloop by + calling timer.schedule() or runloop.add_timer(timer). + """ + self.cancelled = False + self.seconds = seconds + self.tpl = cb, args, kw + self.called = False + if _g_debug: + import traceback, cStringIO + self.traceback = cStringIO.StringIO() + traceback.print_stack(file=self.traceback) + + def __repr__(self): + secs = getattr(self, 'seconds', None) + cb, args, kw = getattr(self, 'tpl', (None, None, None)) + retval = "Timer(%s, %s, *%s, **%s)" % ( + secs, cb, args, kw) + if _g_debug and hasattr(self, 'traceback'): + retval += '\n' + self.traceback.getvalue() + return retval + + def copy(self): + cb, args, kw = self.tpl + return self.__class__(self.seconds, cb, *args, **kw) + + def schedule(self): + """Schedule this timer to run in the current runloop. + """ + self.called = False + self.scheduled_time = get_hub().runloop.add_timer(self) + return self + + def __call__(self): + if not self.called: + self.called = True + cb, args, kw = self.tpl + cb(*args, **kw) + + def cancel(self): + """Prevent this timer from being called. If the timer has already + been called, has no effect. + """ + self.cancelled = True + self.called = True diff --git a/eventlet/.svn/text-base/timer_test.py.svn-base b/eventlet/.svn/text-base/timer_test.py.svn-base new file mode 100644 index 0000000..496a884 --- /dev/null +++ b/eventlet/.svn/text-base/timer_test.py.svn-base @@ -0,0 +1,66 @@ +"""\ +@file timer_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 unittest + +from eventlet import api, runloop, tests, timer + +class TestTimer(tests.TestCase): + mode = 'static' + def test_copy(self): + t = timer.Timer(0, lambda: None) + t2 = t.copy() + assert t.seconds == t2.seconds + assert t.tpl == t2.tpl + assert t.called == t2.called + + def test_cancel(self): + r = runloop.RunLoop() + called = [] + t = timer.Timer(0, lambda: called.append(True)) + t.cancel() + r.add_timer(t) + r.add_observer(lambda r, activity: r.abort(), 'after_waiting') + r.run() + assert not called + assert not r.running + + def test_schedule(self): + hub = api.get_hub() + r = hub.runloop + # clean up the runloop, preventing side effects from previous tests + # on this thread + if r.running: + r.abort() + api.sleep(0) + called = [] + t = timer.Timer(0, lambda: (called.append(True), hub.runloop.abort())) + t.schedule() + r.default_sleep = lambda: 0.0 + r.run() + assert called + assert not r.running + +if __name__ == '__main__': + unittest.main() diff --git a/eventlet/.svn/text-base/tls.py.svn-base b/eventlet/.svn/text-base/tls.py.svn-base new file mode 100644 index 0000000..3cb3921 --- /dev/null +++ b/eventlet/.svn/text-base/tls.py.svn-base @@ -0,0 +1,57 @@ +"""\ +@file tls.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 threading +import weakref + +__all__ = ['local'] + +class _local(object): + """ + Crappy Python 2.3 compatible implementation of thread-local storage + """ + + __slots__ = ('__thread_dict__',) + + def __init__(self): + object.__setattr__(self, '__thread_dict__', weakref.WeakKeyDictionary()) + + def __getattr__(self, attr): + try: + return self.__thread_dict__[threading.currentThread()][attr] + except KeyError: + raise AttributeError(attr) + + def __setattr__(self, attr, value): + t = threading.currentThread() + try: + d = self.__thread_dict__[t] + except KeyError: + d = self.__thread_dict__[t] = {} + d[attr] = value + +try: + local = threading.local +except AttributeError: + local = _local diff --git a/eventlet/.svn/text-base/tpool.py.svn-base b/eventlet/.svn/text-base/tpool.py.svn-base new file mode 100644 index 0000000..6ca505d --- /dev/null +++ b/eventlet/.svn/text-base/tpool.py.svn-base @@ -0,0 +1,123 @@ +"""\ +@file tpool.py + +Copyright (c) 2007, Linden Research, Inc. +Copyright (c) 2007, IBM Corp. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os, socket, time, threading +import Queue + +from sys import stdout +from Queue import Empty, Queue + +from eventlet import api, coros, httpc, httpd, util, wrappedfd +from eventlet.api import trampoline, get_hub + +_rpipe, _wpipe = os.pipe() +_rfile = os.fdopen(_rpipe,"r",0) +_wrap_rfile = wrappedfd.wrapped_file(_rfile) +util.set_nonblocking(_rfile) + +def _signal_t2e(): + nwritten = os.write(_wpipe,' ') + +_reqq = Queue(maxsize=-1) +_rspq = Queue(maxsize=-1) + +def tpool_trampoline(): + global _reqq, _rspq + while(True): + _c = _wrap_rfile.recv(1) + assert(_c != "") + while not _rspq.empty(): + try: + (e,rv) = _rspq.get(block=False) + e.send(rv) + except Empty: + pass + +def esend(meth,*args, **kwargs): + global _reqq, _rspq + e = coros.event() + _reqq.put((e,meth,args,kwargs)) + return e + +def tworker(): + global _reqq, _rspq + while(True): + (e,meth,args,kwargs) = _reqq.get() + rv = None + try: + rv = meth(*args,**kwargs) + except Exception,exn: + import sys, traceback + (a,b,tb) = sys.exc_info() + rv = (exn,a,b,tb) + _rspq.put((e,rv)) + _signal_t2e() + +def erecv(e): + rv = e.wait() + if isinstance(rv,tuple) and len(rv) == 4 and isinstance(rv[0],Exception): + import sys, traceback + (e,a,b,tb) = rv + traceback.print_exception(Exception,e,tb) + traceback.print_stack() + raise e + return rv + +def erpc(meth,*args, **kwargs): + e = esend(meth,*args,**kwargs) + rv = erecv(e) + return rv + +class Proxy(object): + """ 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. + A key restriction is that the object's methods cannot call into eventlets, since the + eventlet dispatcher runs on a different native thread. This is for running native-threaded + code only. """ + def __init__(self, obj,autowrap=()): + self._obj = obj + self._autowrap = autowrap + + def __getattr__(self,attr_name): + f = getattr(self._obj,attr_name) + if not callable(f): + return f + def doit(*args, **kwargs): + if kwargs.pop('nonblocking',False): + rv = f(*args, **kwargs) + else: + rv = erpc(f,*args,**kwargs) + if type(rv) in self._autowrap: + return Proxy(rv) + else: + return rv + return doit + +_nthreads = 20 +_threads = {} +def setup(): + global _threads + for i in range(0,_nthreads): + _threads[i] = threading.Thread(target=tworker) + _threads[i].setDaemon(True) + _threads[i].start() + + api.spawn(tpool_trampoline) + +setup() diff --git a/eventlet/.svn/text-base/tpool_test.py.svn-base b/eventlet/.svn/text-base/tpool_test.py.svn-base new file mode 100644 index 0000000..19442bc --- /dev/null +++ b/eventlet/.svn/text-base/tpool_test.py.svn-base @@ -0,0 +1,70 @@ +"""\ +@file tpool_test.py + +Copyright (c) 2007, Linden Research, Inc. +Copyright (c) 2007, IBM Corp. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os, socket, time, threading +from eventlet import coros, api, tpool, tests + +from eventlet.tpool import erpc +from sys import stdout + +import random +r = random.WichmannHill() + +_g_debug = False + +def prnt(msg): + if _g_debug: + print msg + +class yadda(object): + def __init__(self): + pass + + def foo(self,when,n=None): + assert(n is not None) + prnt("foo: %s, %s" % (when,n)) + time.sleep(r.random()) + return n + +def sender_loop(pfx): + n = 0 + obj = tpool.Proxy(yadda()) + while n < 10: + api.sleep(0) + now = time.time() + prnt("%s: send (%s,%s)" % (pfx,now,n)) + rv = obj.foo(now,n=n) + prnt("%s: recv %s" % (pfx, rv)) + assert(n == rv) + api.sleep(0) + n += 1 + + +class TestTpool(tests.TestCase): + def test1(self): + pool = coros.CoroutinePool(max_size=10) + waiters = [] + for i in range(0,9): + waiters.append(pool.execute(sender_loop,i)) + for waiter in waiters: + waiter.wait() + + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/.svn/text-base/twistedsupport.py.svn-base b/eventlet/.svn/text-base/twistedsupport.py.svn-base new file mode 100644 index 0000000..5bbbc8b --- /dev/null +++ b/eventlet/.svn/text-base/twistedsupport.py.svn-base @@ -0,0 +1,134 @@ +"""\ +@file twistedsupport.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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 sys +import traceback + +from eventlet import api +from eventlet import timer + +from twisted.internet import posixbase +from twisted.internet.interfaces import IReactorFDSet + +try: + from zope.interface import implements + _working = True +except ImportError: + _working = False + def implements(*args, **kw): + pass + + +class TwistedTimer(object): + def __init__(self, timer): + self.timer = timer + + def cancel(self): + self.timer.cancel() + + def getTime(self): + return self.timer.scheduled_time + + def delay(self, seconds): + hub = api.get_hub() + new_time = hub.clock() - self.timer_scheduled_time + seconds + self.timer.cancel() + cb, args, kw = self.timer.tpl + self.timer = hub.schedule_call(new_time, cb, *args, **kw) + + def reset(self, new_time): + self.timer.cancel() + cb, args, kw = self.timer.tpl + self.timer = api.get_hub().schedule_call(new_time, cb, *args, **kw) + + def active(self): + return not self.timer.called + + +class EventletReactor(posixbase.PosixReactorBase): + implements(IReactorFDSet) + + def __init__(self, *args, **kw): + self._readers = {} + self._writers = {} + posixbase.PosixReactorBase.__init__(self, *args, **kw) + + def callLater(self, func, *args, **kw): + return TwistedTimer(api.call_after(func, *args, **kw)) + + def run(self): + self.running = True + self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None) + ## schedule a call way in the future, and cancel it in stop? + api.get_hub().runloop.run() + + def stop(self): + self._stopper.cancel() + posixbase.PosixReactorBase.stop(self) + api.get_hub().remove_descriptor(self._readers.keys()[0]) + api.get_hub().runloop.abort() + + def addReader(self, reader): + fileno = reader.fileno() + self._readers[fileno] = reader + api.get_hub().add_descriptor(fileno, read=self._got_read) + + def _got_read(self, fileno): + self._readers[fileno].doRead() + + def addWriter(self, writer): + fileno = writer.fileno() + self._writers[fileno] = writer + api.get_hub().add_descriptor(fileno, write=self._got_write) + + def _got_write(self, fileno): + self._writers[fileno].doWrite() + + def removeReader(self, reader): + fileno = reader.fileno() + if fileno in self._readers: + self._readers.pop(fileno) + api.get_hub().remove_descriptor(fileno) + + def removeWriter(self, writer): + fileno = writer.fileno() + if fileno in self._writers: + self._writers.pop(fileno) + api.get_hub().remove_descriptor(fileno) + + def removeAll(self): + return self._removeAll(self._readers.values(), self._writers.values()) + + +def emulate(): + if not _working: + raise RuntimeError, "Can't use twistedsupport because zope.interface is not installed." + reactor = EventletReactor() + from twisted.internet.main import installReactor + installReactor(reactor) + + +__all__ = ['emulate'] + diff --git a/eventlet/.svn/text-base/util.py.svn-base b/eventlet/.svn/text-base/util.py.svn-base new file mode 100644 index 0000000..657716c --- /dev/null +++ b/eventlet/.svn/text-base/util.py.svn-base @@ -0,0 +1,214 @@ +"""\ +@file util.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 os +import fcntl +import socket +import errno +from errno import EWOULDBLOCK, EAGAIN + +try: + from OpenSSL import SSL +except ImportError: + class SSL(object): + class WantWriteError(object): + pass + + class WantReadError(object): + pass + + class ZeroReturnError(object): + pass + + class SysCallError(object): + pass + + +def g_log(*args): + import sys + import greenlet + from eventlet.greenlib import greenlet_id + g_id = greenlet_id() + if g_id is None: + if greenlet.getcurrent().parent is None: + ident = 'greenlet-main' + else: + g_id = id(greenlet.getcurrent()) + if g_id < 0: + g_id += 1 + ((sys.maxint + 1) << 1) + ident = '%08X' % (g_id,) + else: + ident = 'greenlet-%d' % (g_id,) + print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args))) + +CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, 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 + +def tcp_socket(): + s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) + set_nonblocking(s) + return s + + +__original_ssl__ = socket.ssl + + +def wrap_ssl(sock, certificate=None, private_key=None): + from OpenSSL import SSL + from eventlet import wrappedfd, util + context = SSL.Context(SSL.SSLv23_METHOD) + print certificate, private_key + if certificate is not None: + context.use_certificate_file(certificate) + if private_key is not None: + context.use_privatekey_file(private_key) + context.set_verify(SSL.VERIFY_NONE, lambda *x: True) + + ## TODO only do this on client sockets? how? + connection = SSL.Connection(context, sock) + connection.set_connect_state() + return wrappedfd.wrapped_fd(connection) + + +def wrap_socket_with_coroutine_socket(): + def new_socket(*args, **kw): + from eventlet import wrappedfd + s = __original_socket__(*args, **kw) + set_nonblocking(s) + return wrappedfd.wrapped_fd(s) + socket.socket = new_socket + + socket.ssl = wrap_ssl + + +def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): + set_reuse_addr(descriptor) + descriptor.bind(addr) + descriptor.listen(backlog) + return descriptor + +def socket_accept(descriptor): + try: + return descriptor.accept() + except socket.error, e: + if e[0] == EWOULDBLOCK: + return None + raise + +def socket_send(descriptor, data): + try: + return descriptor.send(data) + except socket.error, e: + if e[0] == EWOULDBLOCK: + return 0 + raise + except SSL.WantWriteError: + return 0 + except SSL.WantReadError: + return 0 + + +# 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] == EWOULDBLOCK: + return None + if e[0] in SOCKET_CLOSED: + return '' + raise + except SSL.WantReadError: + return None + except SSL.ZeroReturnError: + return '' + except SSL.SysCallError, e: + if e[0] == -1 or e[0] > 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] == 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_reuse_addr(descriptor): + try: + descriptor.setsockopt( + socket.SOL_SOCKET, + socket.SO_REUSEADDR, + descriptor.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1, + ) + except socket.error: + 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 + diff --git a/eventlet/.svn/text-base/wrappedfd.py.svn-base b/eventlet/.svn/text-base/wrappedfd.py.svn-base new file mode 100644 index 0000000..21265fb --- /dev/null +++ b/eventlet/.svn/text-base/wrappedfd.py.svn-base @@ -0,0 +1,284 @@ +"""\ +@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): + self.fd.flush() diff --git a/eventlet/.svn/text-base/wsgi.py.svn-base b/eventlet/.svn/text-base/wsgi.py.svn-base new file mode 100644 index 0000000..2fa765a --- /dev/null +++ b/eventlet/.svn/text-base/wsgi.py.svn-base @@ -0,0 +1,219 @@ +"""\ +@file wsgi.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 time +import urllib +import socket +import cStringIO +import SocketServer +import BaseHTTPServer + +from eventlet import api +from eventlet.httpdate import format_date_time + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + + def log_message(self, format, *args): + self.server.log_message("%s - - [%s] %s" % ( + self.address_string(), + self.log_date_time_string(), + format % args)) + + def handle_one_request(self): + self.raw_requestline = self.rfile.readline() + + if not self.raw_requestline: + self.close_connection = 1 + return + + if not self.parse_request(): + return + + self.environ = self.get_environ() + try: + self.handle_one_response() + except socket.error, e: + # Broken pipe, connection reset by peer + if e[0] in (32, 54): + pass + else: + raise + + def handle_one_response(self): + headers_set = [] + headers_sent = [] + # set of lowercase header names that were sent + header_dict = {} + + wfile = self.wfile + num_blocks = None + + def write(data, _write=wfile.write): + if not headers_set: + raise AssertionError("write() before start_response()") + elif not headers_sent: + status, response_headers = headers_set + headers_sent.append(1) + for k, v in response_headers: + header_dict[k.lower()] = k + _write('HTTP/1.0 %s\r\n' % status) + # send Date header? + if 'date' not in header_dict: + _write('Date: %s\r\n' % (format_date_time(time.time()),)) + if 'content-length' not in header_dict and num_blocks == 1: + _write('Content-Length: %s\r\n' % (len(data),)) + for header in response_headers: + _write('%s: %s\r\n' % header) + _write('\r\n') + _write(data) + + def start_request(status, response_headers, exc_info=None): + if exc_info: + try: + if headers_sent: + # Re-raise original exception if headers sent + raise exc_info[0], exc_info[1], exc_info[2] + finally: + # Avoid dangling circular ref + exc_info = None + elif headers_set: + raise AssertionError("Headers already set!") + + headers_set[:] = [status, response_headers] + return write + + result = self.server.app(self.environ, start_request) + try: + num_blocks = len(result) + except (TypeError, AttributeError, NotImplementedError): + pass + + try: + for data in result: + if data: + write(data) + if not headers_sent: + write('') + finally: + if hasattr(result, 'close'): + result.close() + + def get_environ(self): + env = self.server.get_environ() + env['REQUEST_METHOD'] = self.command + env['SCRIPT_NAME'] = '' + + if '?' in self.path: + path, query = self.path.split('?', 1) + else: + path, query = self.path, '' + env['PATH_INFO'] = urllib.unquote(path) + env['QUERY_STRING'] = query + + if self.headers.typeheader is None: + env['CONTENT_TYPE'] = self.headers.type + else: + env['CONTENT_TYPE'] = self.headers.typeheader + + length = self.headers.getheader('content-length') + if length: + env['CONTENT_LENGTH'] = length + env['SERVER_PROTOCOL'] = 'HTTP/1.0' + + host, port = self.request.getsockname() + env['SERVER_NAME'] = host + env['SERVER_PORT'] = str(port) + env['REMOTE_ADDR'] = self.client_address[0] + env['GATEWAY_INTERFACE'] = 'CGI/1.1' + + for h in self.headers.headers: + k, v = h.split(':', 1) + k = k.replace('-', '_').upper() + v = v.strip() + if k in env: + continue + envk = 'HTTP_' + k + if envk in env: + env[envk] += ',' + v + else: + env[envk] = v + + return env + + def finish(self): + # Override SocketServer.StreamRequestHandler.finish because + # we only need to call close on the socket, not the makefile'd things + + self.request.close() + + +class Server(BaseHTTPServer.HTTPServer): + def __init__(self, socket, address, app, log, environ=None): + self.socket = socket + self.address = address + if log: + self.log = log + log.write = log.info + else: + self.log = sys.stderr + self.app = app + self.environ = environ + + def get_environ(self): + socket = self.socket + d = { + 'wsgi.input': socket, + 'wsgi.errors': sys.stderr, + 'wsgi.version': (1, 0), + 'wsgi.multithread': True, + 'wsgi.multiprocess': False, + 'wsgi.run_once': False, + 'wsgi.url_scheme': 'http', + } + if self.environ is not None: + d.update(self.environ) + return d + + def process_request(self, (socket, address)): + proto = HttpProtocol(socket, address, self) + + def log_message(self, message): + self.log.write(message + '\n') + + +def server(socket, site, log=None, environ=None): + serv = Server(socket, socket.getsockname(), site, log, environ=None) + try: + print "wsgi starting up on", socket.getsockname() + while True: + try: + api.spawn(serv.process_request, socket.accept()) + except KeyboardInterrupt: + api.get_hub().remove_descriptor(socket.fileno()) + print "wsgi exiting" + break + finally: + socket.close() diff --git a/eventlet/.svn/tmp/tempfile.2.tmp b/eventlet/.svn/tmp/tempfile.2.tmp new file mode 100644 index 0000000..7d73756 --- /dev/null +++ b/eventlet/.svn/tmp/tempfile.2.tmp @@ -0,0 +1,584 @@ +"""\ +@file httpd.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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 +import errno +import socket +import sys +import time +import urllib +import socket +import traceback +import BaseHTTPServer + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from eventlet import api +from eventlet import coros + + +DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' + +USE_ACCESS_LOG = True + + +CONNECTION_CLOSED = (errno.EPIPE, errno.ECONNRESET) + + +class ErrorResponse(Exception): + _responses = BaseHTTPServer.BaseHTTPRequestHandler.responses + + def __init__(self, code, reason_phrase=None, headers=None, body=None): + Exception.__init__(self, reason_phrase) + self.code = code + if reason_phrase is None: + self.reason = self._responses[code][0] + else: + self.reason = reason_phrase + self.headers = headers + if body is None: + self.body = self._responses[code][1] + else: + self.body = body + + +class Request(object): + _method = None + _path = None + _responsecode = 200 + _reason_phrase = None + _request_started = False + _chunked = False + _producer_adapters = {} + depth = 0 + def __init__(self, protocol, method, path, headers): + self.context = {} + self.request_start_time = time.time() + self.site = protocol.server.site + self.protocol = protocol + self._method = method + if '?' in path: + self._path, self._query = path.split('?', 1) + self._query = self._query.replace('&', '&') + else: + self._path, self._query = path, None + self._incoming_headers = headers + self._outgoing_headers = dict() + + def response(self, code, reason_phrase=None, headers=None, body=None): + """Change the response code. This will not be sent until some + data is written; last call to this method wins. Default is + 200 if this is not called. + """ + self._responsecode = code + self._reason_phrase = reason_phrase + self.protocol.set_response_code(self, code, reason_phrase) + if headers is not None: + try: + headers = headers.iteritems() + except AttributeError: + pass + for key, value in headers: + self.set_header(key, value) + if body is not None: + self.write(body) + + def is_okay(self): + return 200 <= self._responsecode <= 299 + + def full_url(self): + path = self.path() + query = self.query() + if query: + path = path + '?' + query + + via = self.get_header('via', '') + if via.strip(): + next_part = iter(via.split()).next + + received_protocol = next_part() + received_by = next_part() + if received_by.endswith(','): + received_by = received_by[:-1] + else: + comment = '' + while not comment.endswith(','): + try: + comment += next_part() + except StopIteration: + comment += ',' + break + comment = comment[:-1] + else: + received_by = self.get_header('host') + + return '%s://%s%s' % (self.request_protocol(), received_by, path) + + def begin_response(self, length="-"): + """Begin the response, and return the initial response text + """ + self._request_started = True + request_time = time.time() - self.request_start_time + + code = self._responsecode + proto = self.protocol + + if USE_ACCESS_LOG: + proto.server.write_access_log_line( + proto.client_address[0], + time.strftime("%d/%b/%Y %H:%M:%S"), + proto.requestline, + code, + length, + request_time) + + if self._reason_phrase is not None: + message = self._reason_phrase.split("\n")[0] + elif code in proto.responses: + message = proto.responses[code][0] + else: + message = '' + if proto.request_version == 'HTTP/0.9': + return [] + + response_lines = proto.generate_status_line() + + if not self._outgoing_headers.has_key('connection'): + con = self.get_header('connection') + if con is None and proto.request_version == 'HTTP/1.0': + con = 'close' + if con is not None: + self.set_header('connection', con) + + for key, value in self._outgoing_headers.items(): + key = '-'.join([x.capitalize() for x in key.split('-')]) + response_lines.append("%s: %s" % (key, value)) + + response_lines.append("") + return response_lines + + def write(self, obj): + """Writes an arbitrary object to the response, using + the sitemap's adapt method to convert it to bytes. + """ + if isinstance(obj, str): + self._write_bytes(obj) + elif isinstance(obj, unicode): + # use utf8 encoding for now, *TODO support charset negotiation + # Content-Type: text/html; charset=utf-8 + ctype = self._outgoing_headers.get('content-type', 'text/html') + ctype = ctype + '; charset=utf-8' + self._outgoing_headers['content-type'] = ctype + self._write_bytes(obj.encode('utf8')) + else: + self.site.adapt(obj, self) + + def _write_bytes(self, data): + """Write all the data of the response. + Can be called just once. + """ + if self._request_started: + print "Request has already written a response:" + traceback.print_stack() + return + + self._outgoing_headers['content-length'] = len(data) + + response_lines = self.begin_response(len(data)) + response_lines.append(data) + self.protocol.wfile.write("\r\n".join(response_lines)) + if hasattr(self.protocol.wfile, 'flush'): + self.protocol.wfile.flush() + + def method(self): + return self._method + + def path(self): + return self._path + + def path_segments(self): + return [urllib.unquote_plus(x) for x in self._path.split('/')[1:]] + + def query(self): + return self._query + + def uri(self): + if self._query: + return '%s?%s' % ( + self._path, self._query) + return self._path + + def get_headers(self): + return self._incoming_headers + + def get_header(self, header_name, default=None): + return self.get_headers().get(header_name.lower(), default) + + def get_query_pairs(self): + if not hasattr(self, '_split_query'): + if self._query is None: + self._split_query = () + else: + spl = self._query.split('&') + spl = [x.split('=', 1) for x in spl if x] + self._split_query = [] + for query in spl: + if len(query) == 1: + key = query[0] + value = '' + else: + key, value = query + self._split_query.append((urllib.unquote_plus(key), urllib.unquote_plus(value))) + + return self._split_query + + def get_queries_generator(self, name): + """Generate all query parameters matching the given name. + """ + for key, value in self.get_query_pairs(): + if key == name or not name: + yield value + + get_queries = lambda self, name: list(self.get_queries_generator) + + def get_query(self, name, default=None): + try: + return self.get_queries_generator(name).next() + except StopIteration: + return default + + def get_arg_list(self, name): + return self.get_field_storage().getlist(name) + + def get_arg(self, name, default=None): + return self.get_field_storage().getfirst(name, default) + + def get_field_storage(self): + if not hasattr(self, '_field_storage'): + if self.method() == 'GET': + data = '' + if self._query: + data = self._query + else: + data = self.read_body() + fl = StringIO(data) + ## Allow our resource to provide the FieldStorage instance for + ## customization purposes. + headers = self.get_headers() + environ = dict( + REQUEST_METHOD='POST', + QUERY_STRING=self._query or '') + + self._field_storage = cgi.FieldStorage(fl, headers, environ=environ) + + return self._field_storage + + def set_header(self, key, value): + if key.lower() == 'connection' and value.lower() == 'close': + self.protocol.close_connection = 1 + self._outgoing_headers[key.lower()] = value + __setitem__ = set_header + + def get_outgoing_header(self, key): + return self._outgoing_headers[key.lower()] + + def has_outgoing_header(self, key): + return self._outgoing_headers.has_key(key.lower()) + + def socket(self): + return self.protocol.socket + + def error(self, response=None, body=None, log_traceback=True): + if log_traceback: + traceback.print_exc(file=self.log) + if response is None: + response = 500 + if body is None: + typ, val, tb = sys.exc_info() + body = dict(type=str(typ), error=True, reason=str(val)) + self.response(response) + if(type(body) is str and not self.response_written()): + self.write(body) + return + try: + produce(body, self) + except Exception, e: + traceback.print_exc(file=self.log) + if not self.response_written(): + self.write('Internal Server Error') + + def not_found(self): + self.error(404, 'Not Found\n', log_traceback=False) + + def raw_body(self): + if not hasattr(self, '_cached_body'): + self.read_body() + return self._cached_body + + def read_body(self): + """ Returns the string body that was read off the request, or + the empty string if there was no request body. + + Requires a content-length header. Caches the body so multiple + calls to read_body() are free. + """ + if not hasattr(self, '_cached_body'): + length = self.get_header('content-length') + if length: + length = int(length) + if length: + self._cached_body = self.protocol.rfile.read(length) + else: + self._cached_body = '' + return self._cached_body + + def parsed_body(self): + """ Returns the parsed version of the body, using the + content-type header to select from the parsers on the site + object. + + If no parser is found, returns the string body from + read_body(). Caches the parsed body so multiple calls to + parsed_body() are free. + """ + if not hasattr(self, '_cached_parsed_body'): + body = self.read_body() + if hasattr(self.site, 'parsers'): + parser = self.site.parsers.get( + self.get_header('content-type')) + if parser is not None: + body = parser(body) + self._cached_parsed_body = body + return self._cached_parsed_body + + def override_body(self, body): + if not hasattr(self, '_cached_parsed_body'): + self.read_body() ## Read and discard body + self._cached_parsed_body = body + + def response_written(self): + ## TODO change badly named variable + return self._request_started + + def request_version(self): + return self.protocol.request_version + + def request_protocol(self): + if self.protocol.socket.is_secure: + return "https" + return "http" + + def server_address(self): + return self.protocol.server.address + + def __repr__(self): + return "" % ( + getattr(self, '_method'), getattr(self, '_path')) + +DEFAULT_TIMEOUT = 300 + +# This value was chosen because apache 2 has a default limit of 8190. +# I believe that slightly smaller number is because apache does not +# count the \r\n. +MAX_REQUEST_LINE = 8192 + +class Timeout(RuntimeError): + pass + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + def __init__(self, request, client_address, server): + self.socket = self.request = self.rfile = self.wfile = request + self.client_address = client_address + self.server = server + self.set_response_code(None, 200, None) + self.protocol_version = server.max_http_version + + def set_response_code(self, request, code, message): + self._code = code + if message is not None: + self._message = message.split("\n")[0] + elif code in self.responses: + self._message = self.responses[code][0] + else: + self._message = '' + + def generate_status_line(self): + return [ + "%s %d %s" % ( + self.protocol_version, self._code, self._message)] + + def write_bad_request(self, status, reason): + self.set_response_code(self, status, reason) + self.wfile.write(''.join(self.generate_status_line())) + self.wfile.write('\r\nServer: %s\r\n' % self.version_string()) + self.wfile.write('Date: %s\r\n' % self.date_time_string()) + self.wfile.write('Content-Length: 0\r\n\r\n') + + def handle(self): + self.close_connection = 0 + + timeout = DEFAULT_TIMEOUT + while not self.close_connection: + if timeout == 0: + break + cancel = api.exc_after(timeout, Timeout) + try: + self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) + if self.raw_requestline is not None: + if len(self.raw_requestline) == MAX_REQUEST_LINE: + # Someone sent a request line which is too + # large. Be helpful and tell them. + self.write_bad_request(414, 'Request-URI Too Long') + self.close_connection = True + continue + except socket.error, e: + if e[0] in CONNECTION_CLOSED: + self.close_connection = True + cancel.cancel() + continue + except Timeout: + self.close_connection = True + continue + except Exception, e: + try: + if e[0][0][0].startswith('SSL'): + print "SSL Error:", e[0][0] + self.close_connection = True + cancel.cancel() + continue + except Exception, f: + print "Exception in ssl test:",f + pass + raise e + cancel.cancel() + + if not self.raw_requestline or not self.parse_request(): + self.close_connection = True + continue + + self.set_response_code(None, 200, None) + request = Request(self, self.command, self.path, self.headers) + request.set_header('Server', self.version_string()) + request.set_header('Date', self.date_time_string()) + try: + timeout = int(request.get_header('keep-alive', timeout)) + except TypeError, ValueError: + pass + + try: + try: + try: + self.server.site.handle_request(request) + except ErrorResponse, err: + request.response(code=err.code, + reason_phrase=err.reason, + headers=err.headers, + body=err.body) + finally: + # clean up any timers that might have been left around by the handling code + api.get_hub().runloop.cancel_timers(api.getcurrent()) + + # throw an exception if it failed to write a body + if not request.response_written(): + raise NotImplementedError("Handler failed to write response to request: %s" % request) + + if not hasattr(self, '_cached_body'): + try: + request.read_body() ## read & discard body + except: + pass + + except socket.error, e: + # Broken pipe, connection reset by peer + if e[0] in CONNECTION_CLOSED: + #print "Remote host closed connection before response could be sent" + pass + else: + raise + except Exception, e: + self.server.log_message("Exception caught in HttpRequest.handle():\n") + self.server.log_exception(*sys.exc_info()) + if not request.response_written(): + request.response(500) + request.write('Internal Server Error') + self.socket.close() + raise e # can't do a plain raise since exc_info might have been cleared + self.socket.close() + + +class Server(BaseHTTPServer.HTTPServer): + def __init__(self, socket, address, site, log, max_http_version=DEFAULT_MAX_HTTP_VERSION): + self.socket = socket + self.address = address + self.site = site + self.max_http_version = max_http_version + if log: + self.log = log + if hasattr(log, 'info'): + log.write = log.info + else: + self.log = self + + def write(self, something): + sys.stdout.write('%s' % (something, )); sys.stdout.flush() + + def log_message(self, message): + self.log.write(message) + + def log_exception(self, type, value, tb): + self.log.write(''.join(traceback.format_exception(type, value, tb))) + + def write_access_log_line(self, *args): + """Write a line to the access.log. Arguments: + client_address, date_time, requestline, code, size, request_time + """ + self.log.write( + '%s - - [%s] "%s" %s %s %.6f\n' % args) + + +def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): + pool = coros.CoroutinePool(max_size=max_size) + if serv is None: + serv = Server(sock, sock.getsockname(), site, log, max_http_version=max_http_version) + try: + serv.log.write("httpd starting up on %s\n" % (sock.getsockname(), )) + while True: + try: + new_sock, address = sock.accept() + proto = HttpProtocol(new_sock, address, serv) + pool.execute_async(proto.handle) + api.sleep(0) # sleep to allow other coros to run + except KeyboardInterrupt: + api.get_hub().remove_descriptor(sock.fileno()) + serv.log.write("httpd exiting\n") + break + finally: + try: + sock.close() + except socket.error: + pass diff --git a/eventlet/__init__.py b/eventlet/__init__.py new file mode 100644 index 0000000..5021bea --- /dev/null +++ b/eventlet/__init__.py @@ -0,0 +1,24 @@ +"""\ +@file __init__.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. +""" + +# This text exists only for the purpose of not making a complete +# mockery of the above copyright header. diff --git a/eventlet/__init__.pyc b/eventlet/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc4aeba0f37cd1fbeccc2674509670a0ef27480e GIT binary patch literal 1247 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 b8vlbAvAkTZYn4S@CKjsoQx``Uz#{(xi0KF; literal 0 HcmV?d00001 diff --git a/eventlet/backdoor.py b/eventlet/backdoor.py new file mode 100644 index 0000000..8f792dd --- /dev/null +++ b/eventlet/backdoor.py @@ -0,0 +1,85 @@ +"""\ +@file backdoor.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 +from code import InteractiveConsole +from eventlet import greenlib + +try: + sys.ps1 +except AttributeError: + sys.ps1 = '>>> ' +try: + sys.ps2 +except AttributeError: + sys.ps2 = '... ' + +class SocketConsole(greenlib.GreenletContext): + def __init__(self, desc): + # mangle the socket + self.desc = desc + readline = desc.readline + self.old = {} + self.fixups = { + 'softspace': 0, + 'isatty': lambda: True, + 'flush': lambda: None, + 'readline': lambda *a: readline(*a).replace('\r\n', '\n'), + } + for key, value in self.fixups.iteritems(): + if hasattr(desc, key): + self.old[key] = getattr(desc, key) + setattr(desc, key, value) + + def finalize(self): + # restore the state of the socket + for key in self.fixups: + try: + value = self.old[key] + except KeyError: + delattr(self.desc, key) + else: + setattr(self.desc, key, value) + self.fixups.clear() + self.old.clear() + self.desc = None + + def swap_in(self): + self.saved = sys.stdin, sys.stderr, sys.stdout + sys.stdin = sys.stdout = sys.stderr = self.desc + + def swap_out(self): + sys.stdin, sys.stderr, sys.stdout = self.saved + +def backdoor((conn, addr), locals=None): + host, port = addr + print "backdoor to %s:%s" % (host, port) + ctx = SocketConsole(conn) + ctx.register() + try: + console = InteractiveConsole(locals) + console.interact() + finally: + ctx.unregister() diff --git a/eventlet/bench.py b/eventlet/bench.py new file mode 100644 index 0000000..081b10a --- /dev/null +++ b/eventlet/bench.py @@ -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 \ No newline at end of file diff --git a/eventlet/channel.py b/eventlet/channel.py new file mode 100644 index 0000000..a24799a --- /dev/null +++ b/eventlet/channel.py @@ -0,0 +1,98 @@ +"""\ +@file channel.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 collections + +from eventlet import api, greenlib + +import greenlet + +__all__ = ['channel'] + +class channel(object): + """A channel is a control flow primitive for co-routines. It is a + "thread-like" queue for controlling flow between two (or more) co-routines. + The state model is: + + * If one co-routine calls send(), it is unscheduled until another + co-routine calls receive(). + * If one co-rounte calls receive(), it is unscheduled until another + co-routine calls send(). + * Once a paired send()/receive() have been called, both co-routeines + are rescheduled. + + This is similar to: http://stackless.com/wiki/Channels + """ + balance = 0 + + def _tasklet_loop(self): + deque = self.deque = collections.deque() + hub = api.get_hub() + switch = greenlib.switch + direction, caller, args = switch() + try: + while True: + if direction == -1: + # waiting to receive + if self.balance > 0: + sender, args = deque.popleft() + hub.schedule_call(0, switch, sender) + hub.schedule_call(0, switch, caller, *args) + else: + deque.append(caller) + else: + # waiting to send + if self.balance < 0: + receiver = deque.popleft() + hub.schedule_call(0, switch, receiver, *args) + hub.schedule_call(0, switch, caller) + else: + deque.append((caller, args)) + self.balance += direction + direction, caller, args = hub.switch() + finally: + deque.clear() + del self.deque + self.balance = 0 + + def _send_tasklet(self, *args): + try: + t = self._tasklet + except AttributeError: + t = self._tasklet = greenlib.tracked_greenlet() + greenlib.switch(t, (self._tasklet_loop,)) + if args: + return greenlib.switch(t, (1, greenlet.getcurrent(), args)) + else: + return greenlib.switch(t, (-1, greenlet.getcurrent(), args)) + + def receive(self): + return self._send_tasklet() + + def send(self, value): + return self._send_tasklet(value) + + def send_exception(self, exc): + return self._send_tasklet(None, exc) diff --git a/eventlet/channel.pyc b/eventlet/channel.pyc new file mode 100644 index 0000000000000000000000000000000000000000..287e2ee24166903afefa61a31ea0bd3e1ca1e2e4 GIT binary patch literal 3970 zcmb_fPjB1E6(3TvY%}pT+bp(4Qv_3Q4@#j}!2;VwgGC!!qHUtGBvA4?_HL`C$dNTM z%~3d`cwN}16g~9|^mFvuFV)_9YJYDiId-zAg#~jseDmhL-}^h`fBt8~d-S(|?IvvT ztKt0uk3PhZGIos7VX4bb9d?Wf{wnO$Wm$zCS6EhM$5rMmI9Ay43QMc(1gtfhh*dVh zh86Z##@;e^bi78A26a-R-&CP#A~V60X`JUG+nQZ?FXFkGs)BE;F%M@ml}V#Kucc-e zg*=%WUQeFz&!0W};wgsTHg2XOUh6k@cRnDe+0 zU`~0gwMt|Rc|29g{7mE~Hl&XX*1T?}f^QBMJ2szKC8>xrPv(#&_;QJ#OZa1McpM8Es@%r%!?c<;S_MB3NEu6 zUx~yJg^*8FmZ@_xEm3(Y$zJ{3^M+W8$LbA|+XldbD5JVq($hH0_*g6) z2E8)(h_o~Z*3HM-An-EI_)Ha6-}`26LH}OB2fdx)p&tc29PobB`#Nj~ZNBLb@V(jK zhv9IqcQEAOi2UyGi1&85-#y}g47=?H4_@}8U@+jl$P4%ToiG3`?6x`w?XbJcx3RC= z8}d%L9}XdO*yE&dAu0^8Z-?&(QELy9{q3+54v!k%PB`q6%$;7uectz@Vc0t8_z~|P zME%|%fciGXcEj#Y1TDdS&>e0;D`-6U8XtVH=XW}!%JUC^J|cX))$1Qc;qKm$@AW$E z0L1M8$o%b2P^yAat&Sh=H+b9M_jd_h#CwnudBj$t7ql7#+~I@n|1|Kx0H>!{|~= zL?=XFLz6=@M0=!(ZG<#IBeebRc$K9!yR^olL7i0S$AI83+aI+eYALxXRK_Q$0zNCK ztR=F3fkxUS3i0HYU@MQR4OL+T^$d|g({oF=!*6&v;VP#}T;kygiV7W2ZjJ)WP^k(fUT`ahf4IL8iGZ>7CtE+H2+et+=bG=wt16)`_4IP;BYYOi0$|Iv z=Cu|+jS+z{`Hk$PBVr6gmiPq)WMLAue{|}l-Dn&Xo*^9%bV4ASSzMsiJ?B$nX3v{V zcsV)6iJ-RN|WuCi2u}uc}P_ z0pAXjYb5YnmtC#W-b<)hCk}|cTxD-7V7f_KVV5=bcTitHaO`%EU9PaV4s-H9-IULm z`rQrrS8KN=Xr<< zN2_G=cPM2T1Q)KdmslLsD{ykxwh0@6`h9$xdl(WNPdKgUR5C00P6eth!i3qTaU#rU zIv+7ju|`6hQKr=FbBNLn674*6?mO$wPo0Y6 zIUDW+%pN+wa6hg*0R2oN6=Irv>Ceih3UNcaNruTmD!wiUXEzIBm1cwQp->N93HSq|hs{DRy;K45Kvg262{ z=QmkUP#?0~$Q6pXO_(~tw!U4u!h8gZZx(l~peR&PCddQgEYJW%Ix5c|VQd_uPnI&4 zWaEK?>`LZEfe6?HjVNiNkBQah+ug;GI^Yt0AM!OJWr*E|^AY0q$XRthb8VbRF+D9j znlP3@f+GiYqIc~WJ;Jn{5#~DqN(bm7d%wX{(`n9}gkH>> from eventlet import coros, api + >>> evt = coros.event() + >>> def baz(b): + ... evt.send(b + 1) + ... + >>> _ = api.spawn(baz, 3) + >>> evt.wait() + 4 + """ + _result = None + def __init__(self): + self.reset() + + def reset(self): + """ Reset this event so it can be used to send again. + Can only be called after send has been called. + + >>> from eventlet import coros + >>> evt = coros.event() + >>> evt.send(1) + >>> evt.reset() + >>> evt.send(2) + >>> evt.wait() + 2 + + Calling reset multiple times in a row is an error. + + >>> evt.reset() + >>> evt.reset() + Traceback (most recent call last): + ... + AssertionError: Trying to re-reset() a fresh event. + + """ + assert self._result is not NOT_USED, 'Trying to re-reset() a fresh event.' + self.epoch = time.time() + self._result = NOT_USED + self._waiters = {} + + def wait(self): + """Wait until another coroutine calls send. + Returns the value the other coroutine passed to + send. + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def wait_on(): + ... retval = evt.wait() + ... print "waited for", retval + >>> _ = api.spawn(wait_on) + >>> evt.send('result') + >>> api.sleep(0) + waited for result + + Returns immediately if the event has already + occured. + + >>> evt.wait() + 'result' + """ + if self._result is NOT_USED: + self._waiters[api.getcurrent()] = True + return api.get_hub().switch() + if self._exc is not None: + raise self._exc + return self._result + + def cancel(self, waiter): + """Raise an exception into a coroutine which called + wait() an this event instead of returning a value + from wait. Sends the eventlet.coros.Cancelled + exception + + waiter: The greenlet (greenlet.getcurrent()) of the + coroutine to cancel + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def wait_on(): + ... try: + ... print "received " + evt.wait() + ... except coros.Cancelled, c: + ... print "Cancelled" + ... + >>> waiter = api.spawn(wait_on) + + The cancel call works on coroutines that are in the wait() call. + + >>> api.sleep(0) # enter the wait() + >>> evt.cancel(waiter) + >>> api.sleep(0) # receive the exception + Cancelled + + The cancel is invisible to coroutines that call wait() after cancel() + is called. This is different from send()'s behavior, where the result + is passed to any waiter regardless of the ordering of the calls. + + >>> waiter = api.spawn(wait_on) + >>> api.sleep(0) + + Cancels have no effect on the ability to send() to the event. + + >>> evt.send('stuff') + >>> api.sleep(0) + received stuff + """ + if waiter in self._waiters: + del self._waiters[waiter] + api.get_hub().schedule_call( + 0, greenlib.switch, waiter, None, Cancelled()) + + def send(self, result=None, exc=None): + """Makes arrangements for the waiters to be woken with the + result and then returns immediately to the parent. + + >>> from eventlet import coros, api + >>> evt = coros.event() + >>> def waiter(): + ... print 'about to wait' + ... result = evt.wait() + ... print 'waited for', result + >>> _ = api.spawn(waiter) + >>> api.sleep(0) + about to wait + >>> evt.send('a') + >>> api.sleep(0) + waited for a + + It is an error to call send() multiple times on the same event. + + >>> evt.send('whoops') + Traceback (most recent call last): + ... + AssertionError: Trying to re-send() an already-triggered event. + + Use reset() between send()s to reuse an event object. + """ + assert self._result is NOT_USED, 'Trying to re-send() an already-triggered event.' + self._result = result + self._exc = exc + hub = api.get_hub() + for waiter in self._waiters: + hub.schedule_call(0, greenlib.switch, waiter, self._result) + + +def execute(func, *args, **kw): + """ Executes an operation asynchronously in a new coroutine, returning + an event to retrieve the return value. + + This has the same api as the CoroutinePool.execute method; the only + difference is that this one creates a new coroutine instead of drawing + from a pool. + + >>> from eventlet import coros + >>> evt = coros.execute(lambda a: ('foo', a), 1) + >>> evt.wait() + ('foo', 1) + """ + evt = event() + def _really_execute(): + evt.send(func(*args, **kw)) + api.spawn(_really_execute) + return evt + + +class CoroutinePool(pools.Pool): + """ Like a thread pool, but with coroutines. + + Coroutine pools are useful for splitting up tasks or globally controlling + concurrency. You don't retrieve the coroutines directly with get() -- + instead use the execute() and execute_async() methods to run code. + + >>> from eventlet import coros, api + >>> p = coros.CoroutinePool(max_size=2) + >>> def foo(a): + ... print "foo", a + ... + >>> evt = p.execute(foo, 1) + >>> evt.wait() + foo 1 + + Once the pool is exhausted, calling an execute forces a yield. + + >>> p.execute_async(foo, 2) + >>> p.execute_async(foo, 3) + >>> p.free() + 0 + >>> p.execute_async(foo, 4) + foo 2 + foo 3 + + >>> api.sleep(0) + foo 4 + """ + + def __init__(self, min_size=0, max_size=4): + self._greenlets = set() + super(CoroutinePool, self).__init__(min_size, max_size) + + def _main_loop(self, sender): + """ Private, infinite loop run by a pooled coroutine. """ + while True: + recvd = sender.wait() + sender.reset() + (evt, func, args, kw) = recvd + self._safe_apply(evt, func, args, kw) + api.get_hub().runloop.cancel_timers(api.getcurrent()) + self.put(sender) + + def _safe_apply(self, evt, func, args, kw): + """ Private method that runs the function, catches exceptions, and + passes back the return value in the event.""" + try: + result = func(*args, **kw) + if evt is not None: + evt.send(result) + except api.GreenletExit, e: + # we're printing this out to see if it ever happens + # in practice + print "GreenletExit raised in coroutine pool", e + if evt is not None: + evt.send(e) # sent as a return value, not an exception + except KeyboardInterrupt: + raise # allow program to exit + except Exception, e: + traceback.print_exc() + if evt is not None: + evt.send(exc=e) + + def _execute(self, evt, func, args, kw): + """ Private implementation of the execute methods. + """ + # if reentering an empty pool, don't try to wait on a coroutine freeing + # itself -- instead, just execute in the current coroutine + if self.free() == 0 and api.getcurrent() in self._greenlets: + self._safe_apply(evt, func, args, kw) + else: + sender = self.get() + sender.send((evt, func, args, kw)) + + def create(self): + """Private implementation of eventlet.pools.Pool + interface. Creates an event and spawns the + _main_loop coroutine, passing the event. + The event is used to send a callable into the + new coroutine, to be executed. + """ + sender = event() + self._greenlets.add(api.spawn(self._main_loop, sender)) + return sender + + def execute(self, func, *args, **kw): + """Execute func in one of the coroutines maintained + by the pool, when one is free. + + Immediately returns an eventlet.coros.event object which + func's result will be sent to when it is available. + + >>> from eventlet import coros + >>> p = coros.CoroutinePool() + >>> evt = p.execute(lambda a: ('foo', a), 1) + >>> evt.wait() + ('foo', 1) + """ + receiver = event() + self._execute(receiver, func, args, kw) + return receiver + + def execute_async(self, func, *args, **kw): + """Execute func in one of the coroutines maintained + by the pool, when one is free. + + No return value is provided. + >>> from eventlet import coros, api + >>> p = coros.CoroutinePool() + >>> def foo(a): + ... print "foo", a + ... + >>> p.execute_async(foo, 1) + >>> api.sleep(0) + foo 1 + """ + self._execute(None, func, args, kw) + + +class pipe(object): + """ Implementation of pipe using events. Not tested! Not used, either.""" + def __init__(self): + self._event = event() + self._buffer = '' + + def send(self, txt): + self._buffer += txt + evt, self._event = self._event, event() + evt.send() + + def recv(self, num=16384): + if not self._buffer: + self._event.wait() + if num >= len(self._buffer): + buf, self._buffer = self._buffer, '' + else: + buf, self._buffer = self._buffer[:num], self._buffer[num:] + return buf + + +class Actor(object): + """ A free-running coroutine that accepts and processes messages. + + Kind of the equivalent of an Erlang process, really. It processes + a queue of messages in the order that they were sent. You must + subclass this and implement your own version of receive(). + + The actor's reference count will never drop to zero while the + coroutine exists; if you lose all references to the actor object + it will never be freed. + """ + def __init__(self, concurrency = 1): + """ Constructs an Actor, kicking off a new coroutine to process the messages. + + The concurrency argument specifies how many messages the actor will try + to process concurrently. If it is 1, the actor will process messages + serially. + """ + self._mailbox = collections.deque() + self._event = event() + self._killer = api.spawn(self.run_forever) + self._pool = CoroutinePool(min_size=0, max_size=concurrency) + + def run_forever(self): + """ Loops forever, continually checking the mailbox. """ + while True: + if not self._mailbox: + self._event.wait() + self._event = event() + else: + # leave the message in the mailbox until after it's + # been processed so the event doesn't get triggered + # while in the received method + self._pool.execute_async( + self.received, self._mailbox[0]) + self._mailbox.popleft() + + def cast(self, message): + """ Send a message to the actor. + + If the actor is busy, the message will be enqueued for later + consumption. There is no return value. + + >>> a = Actor() + >>> a.received = lambda msg: msg + >>> a.cast("hello") + """ + self._mailbox.append(message) + # if this is the only message, the coro could be waiting + if len(self._mailbox) == 1: + self._event.send() + + def received(self, message): + """ Called to process each incoming message. + + The default implementation just raises an exception, so + replace it with something useful! + + >>> class Greeter(Actor): + ... def received(self, (message, evt) ): + ... print "received", message + ... if evt: evt.send() + ... + >>> a = Greeter() + + This example uses events to synchronize between the actor and the main + coroutine in a predictable manner, but this kinda defeats the point of + the Actor, so don't do it in a real application. + + >>> evt = event() + >>> a.cast( ("message 1", evt) ) + >>> evt.wait() # force it to run at this exact moment + received message 1 + >>> evt.reset() + >>> a.cast( ("message 2", None) ) + >>> a.cast( ("message 3", evt) ) + >>> evt.wait() + received message 2 + received message 3 + + >>> api.kill(a._killer) # test cleanup + """ + raise NotImplementedError() + + +def _test(): + print "Running doctests. There will be no further output if they succeed." + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() diff --git a/eventlet/coros.pyc b/eventlet/coros.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a24471c8133493eeab9fd418f411afffb0a9b97 GIT binary patch literal 17398 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 PdVKN1#XFVRGv5CJosZBY literal 0 HcmV?d00001 diff --git a/eventlet/coros_test.py b/eventlet/coros_test.py new file mode 100644 index 0000000..a72bc7a --- /dev/null +++ b/eventlet/coros_test.py @@ -0,0 +1,300 @@ +"""\ +@file coros_test.py +@author Donovan Preston, Ryan Williams + +Copyright (c) 2000-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 timer +from eventlet import coros, api + +class TestEvent(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, RuntimeError()) + + def tearDown(self): + self._cancel_timeout.cancel() + + def test_waiting_for_event(self): + evt = coros.event() + value = 'some stuff' + def send_to_event(): + evt.send(value) + api.spawn(send_to_event) + self.assertEqual(evt.wait(), value) + + def test_multiple_waiters(self): + evt = coros.event() + value = 'some stuff' + results = [] + def wait_on_event(i_am_done): + evt.wait() + results.append(True) + i_am_done.send() + + waiters = [] + count = 5 + for i in range(count): + waiters.append(coros.event()) + api.spawn(wait_on_event, waiters[-1]) + evt.send() + + for w in waiters: + w.wait() + + self.assertEqual(len(results), count) + + def test_cancel(self): + evt = coros.event() + # close over the current coro so we can cancel it explicitly + current = api.getcurrent() + def cancel_event(): + evt.cancel(current) + api.spawn(cancel_event) + + self.assertRaises(coros.Cancelled, evt.wait) + + def test_reset(self): + evt = coros.event() + + # calling reset before send should throw + self.assertRaises(AssertionError, evt.reset) + + value = 'some stuff' + def send_to_event(): + evt.send(value) + api.spawn(send_to_event) + self.assertEqual(evt.wait(), value) + + # now try it again, and we should get the same exact value, + # and we shouldn't be allowed to resend without resetting + value2 = 'second stuff' + self.assertRaises(AssertionError, evt.send, value2) + self.assertEqual(evt.wait(), value) + + # reset and everything should be happy + evt.reset() + def send_to_event2(): + evt.send(value2) + api.spawn(send_to_event2) + self.assertEqual(evt.wait(), value2) + + def test_double_exception(self): + evt = coros.event() + # send an exception through the event + evt.send(exc=RuntimeError()) + self.assertRaises(RuntimeError, evt.wait) + evt.reset() + # shouldn't see the RuntimeError again + api.exc_after(0.001, api.TimeoutError) + self.assertRaises(api.TimeoutError, evt.wait) + +class TestCoroutinePool(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, api.TimeoutError) + + def tearDown(self): + self._cancel_timeout.cancel() + + def test_execute_async(self): + done = coros.event() + def some_work(): + done.send() + pool = coros.CoroutinePool(0, 2) + pool.execute_async(some_work) + done.wait() + + def test_execute(self): + value = 'return value' + def some_work(): + return value + pool = coros.CoroutinePool(0, 2) + worker = pool.execute(some_work) + self.assertEqual(value, worker.wait()) + + def test_multiple_coros(self): + evt = coros.event() + results = [] + def producer(): + results.append('prod') + evt.send() + + def consumer(): + results.append('cons1') + evt.wait() + results.append('cons2') + + pool = coros.CoroutinePool(0, 2) + done = pool.execute(consumer) + pool.execute_async(producer) + done.wait() + self.assertEquals(['cons1', 'prod', 'cons2'], results) + + def test_timer_cancel(self): + def some_work(): + t = timer.Timer(5, lambda: None) + t.schedule() + return t + pool = coros.CoroutinePool(0, 2) + worker = pool.execute(some_work) + t = worker.wait() + api.sleep(0) + self.assertEquals(t.cancelled, True) + + def test_reentrant(self): + pool = coros.CoroutinePool(0,1) + def reenter(): + waiter = pool.execute(lambda a: a, 'reenter') + self.assertEqual('reenter', waiter.wait()) + + outer_waiter = pool.execute(reenter) + outer_waiter.wait() + + evt = coros.event() + def reenter_async(): + pool.execute_async(lambda a: a, 'reenter') + evt.send('done') + + pool.execute_async(reenter_async) + evt.wait() + + +class IncrActor(coros.Actor): + def received(self, evt): + self.value = getattr(self, 'value', 0) + 1 + if evt: evt.send() + +class TestActor(tests.TestCase): + mode = 'static' + def setUp(self): + # raise an exception if we're waiting forever + self._cancel_timeout = api.exc_after(1, api.TimeoutError()) + self.actor = IncrActor() + + def tearDown(self): + self._cancel_timeout.cancel() + api.kill(self.actor._killer) + + def test_cast(self): + evt = coros.event() + self.actor.cast(evt) + evt.wait() + evt.reset() + self.assertEqual(self.actor.value, 1) + self.actor.cast(evt) + evt.wait() + self.assertEqual(self.actor.value, 2) + + def test_cast_multi_1(self): + # make sure that both messages make it in there + evt = coros.event() + evt1 = coros.event() + self.actor.cast(evt) + self.actor.cast(evt1) + evt.wait() + evt1.wait() + self.assertEqual(self.actor.value, 2) + + def test_cast_multi_2(self): + # the actor goes through a slightly different code path if it + # is forced to enter its event loop prior to any cast()s + api.sleep(0) + self.test_cast_multi_1() + + def test_sleeping_during_received(self): + # ensure that even if the received method cooperatively + # yields, eventually all messages are delivered + msgs = [] + waiters = [] + def received( (message, evt) ): + api.sleep(0) + msgs.append(message) + evt.send() + self.actor.received = received + + waiters.append(coros.event()) + self.actor.cast( (1, waiters[-1])) + api.sleep(0) + waiters.append(coros.event()) + self.actor.cast( (2, waiters[-1]) ) + waiters.append(coros.event()) + self.actor.cast( (3, waiters[-1]) ) + api.sleep(0) + waiters.append(coros.event()) + self.actor.cast( (4, waiters[-1]) ) + waiters.append(coros.event()) + self.actor.cast( (5, waiters[-1]) ) + for evt in waiters: + evt.wait() + self.assertEqual(msgs, [1,2,3,4,5]) + + + def test_raising_received(self): + msgs = [] + def received( (message, evt) ): + evt.send() + if message == 'fail': + raise RuntimeError() + else: + msgs.append(message) + + self.actor.received = received + + evt = coros.event() + self.actor.cast( ('fail', evt) ) + evt.wait() + evt.reset() + self.actor.cast( ('should_appear', evt) ) + evt.wait() + self.assertEqual(['should_appear'], msgs) + + def test_multiple(self): + self.actor = IncrActor(concurrency=2) + total = [0] + def received( (func, ev, value) ): + func() + total[0] += value + ev.send() + self.actor.received = received + + def onemoment(): + api.sleep(0.1) + + evt = coros.event() + evt1 = coros.event() + + self.actor.cast( (onemoment, evt, 1) ) + self.actor.cast( (lambda: None, evt1, 2) ) + + evt1.wait() + self.assertEqual(total[0], 2) + # both coroutines should have been used + self.assertEqual(self.actor._pool.current_size, 2) + self.assertEqual(self.actor._pool.free(), 1) + evt.wait() + self.assertEqual(total[0], 3) + self.assertEqual(self.actor._pool.free(), 2) + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/coros_test.pyc b/eventlet/coros_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0566c871ef766e45f0d2245f5c03228944e5a6fd GIT binary patch literal 13991 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 K;`Cy?;QS9Av*WA) literal 0 HcmV?d00001 diff --git a/eventlet/db_pool.py b/eventlet/db_pool.py new file mode 100644 index 0000000..3c91793 --- /dev/null +++ b/eventlet/db_pool.py @@ -0,0 +1,220 @@ +"""\ +@file db_pool.py +@brief Uses saranwrap to implement a pool of nonblocking database connections to a db server. + +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 os, sys + +from eventlet.pools import Pool +from eventlet.processes import DeadProcess +from eventlet import saranwrap + +class DatabaseConnector(object): + """\ +@brief This is an object which will maintain a collection of database +connection pools keyed on host,databasename""" + def __init__(self, module, credentials, min_size = 0, max_size = 4, conn_pool=None, *args, **kwargs): + """\ + @brief constructor + @param min_size the minimum size of a child pool. + @param max_size the maximum size of a child pool.""" + assert(module) + self._conn_pool_class = conn_pool + if self._conn_pool_class is None: + self._conn_pool_class = ConnectionPool + self._module = module + self._min_size = min_size + self._max_size = max_size + self._args = args + self._kwargs = kwargs + self._credentials = credentials # this is a map of hostname to username/password + self._databases = {} + + def credentials_for(self, host): + if host in self._credentials: + return self._credentials[host] + else: + return self._credentials.get('default', None) + + def get(self, host, dbname): + key = (host, dbname) + if key not in self._databases: + new_kwargs = self._kwargs.copy() + new_kwargs['db'] = dbname + new_kwargs['host'] = host + new_kwargs.update(self.credentials_for(host)) + dbpool = self._conn_pool_class(self._module, min_size=self._min_size, max_size=self._max_size, + *self._args, **new_kwargs) + self._databases[key] = dbpool + + return self._databases[key] + +class BaseConnectionPool(Pool): + # *TODO: we need to expire and close connections if they've been + # idle for a while, so that system-wide connection count doesn't + # monotonically increase forever + def __init__(self, db_module, min_size = 0, max_size = 4, *args, **kwargs): + assert(db_module) + self._db_module = db_module + self._args = args + self._kwargs = kwargs + super(BaseConnectionPool, self).__init__(min_size, max_size) + + def get(self): + # wrap the connection for easier use + conn = super(BaseConnectionPool, self).get() + return PooledConnectionWrapper(conn, self) + + def put(self, conn): + # rollback any uncommitted changes, so that the next client + # has a clean slate. This also pokes the connection to see if + # it's dead or None + try: + conn.rollback() + except AttributeError, e: + # this means it's already been destroyed, so we don't need to print anything + conn = None + except: + # we don't care what the exception was, we just know the + # connection is dead + print "WARNING: connection.rollback raised: %s" % (sys.exc_info()[1]) + conn = None + + # unwrap the connection for storage + if isinstance(conn, GenericConnectionWrapper): + if conn: + base = conn._base + conn._destroy() + conn = base + else: + conn = None + + if conn is not None: + super(BaseConnectionPool, self).put(conn) + else: + self.current_size -= 1 + + +class SaranwrappedConnectionPool(BaseConnectionPool): + """A pool which gives out saranwrapped database connections from a pool + """ + def create(self): + return saranwrap.wrap(self._db_module).connect(*self._args, **self._kwargs) + +class TpooledConnectionPool(BaseConnectionPool): + """A pool which gives out tpool.Proxy-based database connections from a pool. + """ + def create(self): + from eventlet import tpool + try: + # *FIX: this is a huge hack that will probably only work for MySQLdb + autowrap = (self._db_module.cursors.DictCursor,) + except: + autowrap = () + return tpool.Proxy(self._db_module.connect(*self._args, **self._kwargs), + autowrap=autowrap) + +class RawConnectionPool(BaseConnectionPool): + """A pool which gives out plain database connections from a pool. + """ + def create(self): + return self._db_module.connect(*self._args, **self._kwargs) + +# default connection pool is the tpool one +ConnectionPool = TpooledConnectionPool + + +class GenericConnectionWrapper(object): + def __init__(self, baseconn): + self._base = baseconn + def __enter__(self): return self._base.__enter__() + def __exit__(self, exc, value, tb): return self._base.__exit__(exc, value, tb) + def __repr__(self): return self._base.__repr__() + def affected_rows(self): return self._base.affected_rows() + def autocommit(self,*args, **kwargs): return self._base.autocommit(*args, **kwargs) + def begin(self): return self._base.begin() + def change_user(self,*args, **kwargs): return self._base.change_user(*args, **kwargs) + def character_set_name(self,*args, **kwargs): return self._base.character_set_name(*args, **kwargs) + def close(self,*args, **kwargs): return self._base.close(*args, **kwargs) + def commit(self,*args, **kwargs): return self._base.commit(*args, **kwargs) + def cursor(self, cursorclass=None, **kwargs): return self._base.cursor(cursorclass, **kwargs) + def dump_debug_info(self,*args, **kwargs): return self._base.dump_debug_info(*args, **kwargs) + def errno(self,*args, **kwargs): return self._base.errno(*args, **kwargs) + def error(self,*args, **kwargs): return self._base.error(*args, **kwargs) + def errorhandler(self, conn, curs, errcls, errval): return self._base.errorhandler(conn, curs, errcls, errval) + def literal(self, o): return self._base.literal(o) + def set_character_set(self, charset): return self._base.set_character_set(charset) + def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) + def show_warnings(self): return self._base.show_warnings() + def warning_count(self): return self._base.warning_count() + def literal(self, o): return self._base.literal(o) + def ping(self,*args, **kwargs): return self._base.ping(*args, **kwargs) + def query(self,*args, **kwargs): return self._base.query(*args, **kwargs) + def rollback(self,*args, **kwargs): return self._base.rollback(*args, **kwargs) + def select_db(self,*args, **kwargs): return self._base.select_db(*args, **kwargs) + def set_server_option(self,*args, **kwargs): return self._base.set_server_option(*args, **kwargs) + def set_character_set(self, charset): return self._base.set_character_set(charset) + def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) + def server_capabilities(self,*args, **kwargs): return self._base.server_capabilities(*args, **kwargs) + def show_warnings(self): return self._base.show_warnings() + def shutdown(self,*args, **kwargs): return self._base.shutdown(*args, **kwargs) + def sqlstate(self,*args, **kwargs): return self._base.sqlstate(*args, **kwargs) + def stat(self,*args, **kwargs): return self._base.stat(*args, **kwargs) + def store_result(self,*args, **kwargs): return self._base.store_result(*args, **kwargs) + def string_literal(self,*args, **kwargs): return self._base.string_literal(*args, **kwargs) + def thread_id(self,*args, **kwargs): return self._base.thread_id(*args, **kwargs) + def use_result(self,*args, **kwargs): return self._base.use_result(*args, **kwargs) + def warning_count(self): return self._base.warning_count() + + +class PooledConnectionWrapper(GenericConnectionWrapper): + """ A connection wrapper where: + - the close method returns the connection to the pool instead of closing it directly + - you can do if conn: + - returns itself to the pool if it gets garbage collected + """ + def __init__(self, baseconn, pool): + super(PooledConnectionWrapper, self).__init__(baseconn) + self._pool = pool + + def __nonzero__(self): + return (hasattr(self, '_base') and bool(self._base)) + + def _destroy(self): + self._pool = None + try: + del self._base + except AttributeError: + pass + + def close(self): + """ Return the connection to the pool, and remove the + reference to it so that you can't use it again through this + wrapper object. + """ + if self and self._pool: + self._pool.put(self) + self._destroy() + + def __del__(self): + self.close() diff --git a/eventlet/db_pool.pyc b/eventlet/db_pool.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57cb0a09336539756ef8051febf29d13ec5b39f9 GIT binary patch literal 15281 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 literal 0 HcmV?d00001 diff --git a/eventlet/greenlib.py b/eventlet/greenlib.py new file mode 100644 index 0000000..1dbf01e --- /dev/null +++ b/eventlet/greenlib.py @@ -0,0 +1,331 @@ +"""\ +@file greenlib.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 itertools + +import greenlet + +from eventlet import tls + +__all__ = [ + 'switch', 'kill', 'tracked_greenlets', + 'greenlet_id', 'greenlet_dict', 'GreenletContext', + 'tracked_greenlet', +] + +try: + reversed +except NameError: + def reversed(something): + for x in something[::-1]: + yield x + +_threadlocal = tls.local() + +def tracked_greenlet(): + """ + Returns a greenlet that has a greenlet-local dictionary and can be + used with GreenletContext and enumerated with tracked_greenlets + """ + return greenlet.greenlet(greenlet_body) + +class GreenletContextManager(object): + """ + Per-thread manager for GreenletContext. Created lazily on registration + """ + def __new__(cls, *args, **kw): + dct = greenlet_dict() + self = dct.get('greenlet_context', None) + if self is not None: + return self + self = super(GreenletContextManager, cls).__new__(cls, *args, **kw) + dct['greenlet_context'] = self + self.contexts = [] + return self + + def add_context(self, ctx): + fn = getattr(ctx, '_swap_in', None) + if fn is not None: + fn() + self.contexts.append(ctx) + + def remove_context(self, ctx): + try: + idx = self.contexts.index(ctx) + except ValueError: + return + else: + del self.contexts[idx] + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + fn = getattr(ctx, '_finalize', None) + if fn is not None: + fn() + + def swap_in(self): + for ctx in self.contexts: + fn = getattr(ctx, '_swap_in', None) + if fn is not None: + fn() + + def swap_out(self): + for ctx in reversed(self.contexts): + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + + def finalize(self): + for ctx in reversed(self.contexts): + fn = getattr(ctx, '_swap_out', None) + if fn is not None: + fn() + fn = getattr(ctx, '_finalize', None) + if fn is not None: + fn() + del self.contexts[:] + try: + del greenlet_dict()['greenlet_context'] + except KeyError: + pass + +class GreenletContext(object): + """ + A context manager to be triggered when a specific tracked greenlet is + swapped in, swapped out, or finalized. + + To use, subclass and override the swap_in, swap_out, and/or finalize + methods, for example:: + + import greenlib + from greenlib import greenlet_id, tracked_greenlet, switch + + class NotifyContext(greenlib.GreenletContext): + + def swap_in(self): + print "swap_in" + + def swap_out(self): + print "swap_out" + + def finalize(self): + print "finalize" + + def another_greenlet(): + print "another_greenlet" + + def notify_demo(): + print "starting" + NotifyContext().register() + switch(tracked_greenlet(), (another_greenlet,)) + print "finishing" + # we could have kept the NotifyContext object + # to unregister it here but finalization of all + # contexts is implicit when the greenlet returns + + t = tracked_greenlet() + switch(t, (notify_demo,)) + + The output should be: + + starting + swap_in + swap_out + another_greenlet + swap_in + finishing + swap_out + finalize + + """ + _balance = 0 + + def _swap_in(self): + if self._balance != 0: + raise RuntimeError("balance != 0: %r" % (self._balance,)) + self._balance = self._balance + 1 + fn = getattr(self, 'swap_in', None) + if fn is not None: + fn() + + def _swap_out(self): + if self._balance != 1: + raise RuntimeError("balance != 1: %r" % (self._balance,)) + self._balance = self._balance - 1 + fn = getattr(self, 'swap_out', None) + if fn is not None: + fn() + + def register(self): + GreenletContextManager().add_context(self) + + def unregister(self): + GreenletContextManager().remove_context(self) + + def _finalize(self): + fn = getattr(self, 'finalize', None) + if fn is not None: + fn() + + +def kill(g): + """ + Kill the given greenlet if it is alive by sending it a GreenletExit. + + Note that of any other exception is raised, it will pass-through! + """ + if not g: + return + kill_exc = greenlet.GreenletExit() + try: + try: + g.parent = greenlet.getcurrent() + except ValueError: + pass + try: + switch(g, exc=kill_exc) + except SwitchingToDeadGreenlet: + pass + except greenlet.GreenletExit, e: + if e is not kill_exc: + raise + +def tracked_greenlets(): + """ + Return a list of greenlets tracked in this thread. Tracked greenlets + use greenlet_body() to ensure that they have greenlet-local storage. + """ + try: + return _threadlocal.greenlets.keys() + except AttributeError: + return [] + +def greenlet_id(): + """ + Get the id of the current tracked greenlet, returns None if the + greenlet is not tracked. + """ + try: + d = greenlet_dict() + except RuntimeError: + return None + return d['greenlet_id'] + +def greenlet_dict(): + """ + Return the greenlet local storage for this greenlet. Raises RuntimeError + if this greenlet is not tracked. + """ + self = greenlet.getcurrent() + try: + return _threadlocal.greenlets[self] + except (AttributeError, KeyError): + raise RuntimeError("greenlet %r is not tracked" % (self,)) + +def _greenlet_context(dct=None): + if dct is None: + try: + dct = greenlet_dict() + except RuntimeError: + return None + return dct.get('greenlet_context', None) + +def _greenlet_context_call(name, dct=None): + ctx = _greenlet_context(dct) + fn = getattr(ctx, name, None) + if fn is not None: + fn() + +def greenlet_body(value, exc): + """ + Track the current greenlet during the execution of the given callback, + normally you would use tracked_greenlet() to get a greenlet that uses this. + + Greenlets using this body must be greenlib.switch()'ed to + """ + if exc is not None: + if isinstance(exc, tuple): + raise exc[0], exc[1], exc[2] + raise exc + cb, args = value[0], value[1:] + try: + greenlets = _threadlocal.greenlets + except AttributeError: + greenlets = _threadlocal.greenlets = {} + else: + if greenlet.getcurrent() in greenlets: + raise RuntimeError("greenlet_body can not be called recursively!") + try: + greenlet_id = _threadlocal.next_greenlet_id.next() + except AttributeError: + greenlet_id = 1 + _threadlocal.next_greenlet_id = itertools.count(2) + greenlets[greenlet.getcurrent()] = {'greenlet_id': greenlet_id} + try: + return cb(*args) + finally: + _greenlet_context_call('finalize') + greenlets.pop(greenlet.getcurrent(), None) + + +class SwitchingToDeadGreenlet(RuntimeError): + pass + + +def switch(other=None, value=None, exc=None): + """ + Switch to another greenlet, passing value or exception + """ + self = greenlet.getcurrent() + if other is None: + other = self.parent + if other is None: + other = self + if not (other or hasattr(other, 'run')): + raise SwitchingToDeadGreenlet("Switching to dead greenlet %r %r %r" % (other, value, exc)) + _greenlet_context_call('swap_out') + sys.exc_clear() # don't pass along exceptions to the other coroutine + try: + rval = other.switch(value, exc) + if not rval or not other: + res, exc = rval, None + else: + res, exc = rval + except: + res, exc = None, sys.exc_info() + _greenlet_context_call('swap_in') + # *NOTE: we don't restore exc_info, so don't switch inside an + # exception handler and then call sys.exc_info() or use bare + # raise. Instead, explicitly save off the exception before + # switching. We need an extension that allows us to restore the + # exception state at this point because vanilla Python doesn't + # allow that. + if isinstance(exc, tuple): + typ, exc, tb = exc + raise typ, exc, tb + elif exc is not None: + raise exc + + return res diff --git a/eventlet/greenlib.pyc b/eventlet/greenlib.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03d7f4f4f43b47e51e9ebe6c6d40d04e52d696bc GIT binary patch literal 11433 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 Sf@`)@ expires + + def __repr__(self): + response = self.params.response + return "%s(url=%r, method=%r, status=%r, reason=%r, body=%r)" % ( + self.__class__.__name__, self.params.url, self.params.method, + response.status, response.reason, self.params.body) + + __str__ = __repr__ + + +class UnparseableResponse(ConnectionError): + """Raised when a loader cannot parse the response from the server.""" + def __init__(self, content_type, response, url): + self.content_type = content_type + self.response = response + self.url = url + Exception.__init__(self) + + def __repr__(self): + return "Could not parse the data at the URL %r of content-type %r\nData:\n%r)" % ( + self.url, self.content_type, self.response) + + __str__ = __repr__ + + +class Accepted(ConnectionError): + """ 202 Accepted """ + pass + + +class Retriable(ConnectionError): + def retry_method(self): + return self.params.method + + def retry_url(self): + return self.location() or self.url() + + def retry_(self): + params = _LocalParams(self.params, + url=self.retry_url(), + method=self.retry_method()) + return self.params.instance.request_(params) + + def retry(self): + return self.retry_()[-1] + + +class MovedPermanently(Retriable): + """ 301 Moved Permanently """ + pass + + +class Found(Retriable): + """ 302 Found """ + + pass + + +class SeeOther(Retriable): + """ 303 See Other """ + + def retry_method(self): + return 'GET' + + +class NotModified(ConnectionError): + """ 304 Not Modified """ + pass + + +class TemporaryRedirect(Retriable): + """ 307 Temporary Redirect """ + pass + + +class BadRequest(ConnectionError): + """ 400 Bad Request """ + pass + + +class Forbidden(ConnectionError): + """ 403 Forbidden """ + pass + + +class NotFound(ConnectionError): + """ 404 Not Found """ + pass + + +class Gone(ConnectionError): + """ 410 Gone """ + pass + + +class ServiceUnavailable(Retriable): + """ 503 Service Unavailable """ + def url(self): + return self.params._delegate.url + + +class GatewayTimeout(Retriable): + """ 504 Gateway Timeout """ + def url(self): + return self.params._delegate.url + + +class InternalServerError(ConnectionError): + """ 500 Internal Server Error """ + def __repr__(self): + try: + import simplejson + traceback = simplejson.loads(self.params.response_body) + except: + try: + from indra.base import llsd + traceback = llsd.parse(self.params.response_body) + except: + traceback = self.params.response_body + if isinstance(traceback, dict): + body = traceback + traceback = "Traceback (most recent call last):\n" + for frame in body['stack-trace']: + traceback += ' File "%s", line %s, in %s\n' % ( + frame['filename'], frame['lineno'], frame['method']) + for line in frame['code']: + if line['lineno'] == frame['lineno']: + traceback += ' %s' % (line['line'].lstrip(), ) + break + traceback += body['description'] + return "The server raised an exception from our request:\n%s %s\n%s %s\n%s" % ( + self.params.method, self.params.url, self.params.response.status, self.params.response.reason, traceback) + __str__ = __repr__ + + + +status_to_error_map = { + 202: Accepted, + 301: MovedPermanently, + 302: Found, + 303: SeeOther, + 304: NotModified, + 307: TemporaryRedirect, + 400: BadRequest, + 403: Forbidden, + 404: NotFound, + 410: Gone, + 500: InternalServerError, + 503: ServiceUnavailable, + 504: GatewayTimeout, +} + +scheme_to_factory_map = { + 'http': HttpClient, + 'https': HttpsClient, + 'file': FileScheme, +} + + +def make_connection(scheme, location, use_proxy): + """ Create a connection object to a host:port. + + @param scheme Protocol, scheme, whatever you want to call it. http, file, https are currently supported. + @param location Hostname and port number, formatted as host:port or http://host:port if you're so inclined. + @param use_proxy Connect to a proxy instead of the actual location. Uses environment variables to decide where the proxy actually lives. + """ + if use_proxy: + if "http_proxy" in os.environ: + location = os.environ["http_proxy"] + elif "ALL_PROXY" in os.environ: + location = os.environ["ALL_PROXY"] + else: + location = "localhost:3128" #default to local squid + + # run a little heuristic to see if location is an url, and if so parse out the hostpart + if location.startswith('http'): + _scheme, location, path, parameters, query, fragment = urlparse.urlparse(location) + + result = scheme_to_factory_map[scheme](location) + result.connect() + return result + + +def connect(url, use_proxy=False): + """ Create a connection object to the host specified in a url. Convenience function for make_connection.""" + scheme, location = urlparse.urlparse(url)[:2] + return make_connection(scheme, location, use_proxy) + + +def make_safe_loader(loader): + if not callable(loader): + return loader + def safe_loader(what): + try: + return loader(what) + except Exception: + import traceback + traceback.print_exc() + return None + return safe_loader + + +class HttpSuite(object): + def __init__(self, dumper, loader, fallback_content_type): + self.dumper = dumper + self.loader = loader + self.fallback_content_type = fallback_content_type + + def request_(self, params): + '''Make an http request to a url, for internal use mostly.''' + + params = _LocalParams(params, instance=self) + + (scheme, location, path, parameters, query, + fragment) = urlparse.urlparse(params.url) + + if params.use_proxy: + if scheme == 'file': + params.use_proxy = False + else: + params.headers['host'] = location + + if not params.use_proxy: + params.path = path + if query: + params.path += '?' + query + + params.orig_body = params.body + + if params.method in ('PUT', 'POST'): + if self.dumper is not None: + params.body = self.dumper(params.body) + # don't set content-length header because httplib does it + # for us in _send_request + else: + params.body = '' + + params.response, params.response_body = self._get_response_body(params) + response, body = params.response, params.response_body + + if self.loader is not None: + try: + body = make_safe_loader(self.loader(body)) + except KeyboardInterrupt: + raise + except Exception, e: + raise UnparseableResponse(self.loader, body, params.url) + + return response.status, response.msg, body + + def _check_status(self, params): + response = params.response + if response.status not in params.ok: + klass = status_to_error_map.get(response.status, ConnectionError) + raise klass(params) + + def _get_response_body(self, params): + connection = connect(params.url, params.use_proxy) + connection.request(params.method, params.path, params.body, + params.headers) + params.response = connection.getresponse() + params.response_body = params.response.read() + connection.close() + self._check_status(params) + + return params.response, params.response_body + + def request(self, params): + return self.request_(params)[-1] + + def head_(self, url, headers=None, use_proxy=False, ok=None, aux=None): + return self.request_(_Params(url, 'HEAD', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux)) + + def head(self, *args, **kwargs): + return self.head_(*args, **kwargs)[-1] + + def get_(self, url, headers=None, use_proxy=False, ok=None, aux=None): + if headers is None: + headers = {} + headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0' + return self.request_(_Params(url, 'GET', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux)) + + def get(self, *args, **kwargs): + return self.get_(*args, **kwargs)[-1] + + def put_(self, url, data, headers=None, content_type=None, ok=None, + aux=None): + if headers is None: + headers = {} + if 'content-type' not in headers: + if content_type is None: + headers['content-type'] = self.fallback_content_type + else: + headers['content-type'] = content_type + headers['accept'] = headers['content-type']+';q=1,*/*;q=0' + return self.request_(_Params(url, 'PUT', body=data, headers=headers, + loader=self.loader, dumper=self.dumper, + ok=ok, aux=aux)) + + def put(self, *args, **kwargs): + return self.put_(*args, **kwargs)[-1] + + def delete_(self, url, ok=None, aux=None): + return self.request_(_Params(url, 'DELETE', loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux)) + + def delete(self, *args, **kwargs): + return self.delete_(*args, **kwargs)[-1] + + def post_(self, url, data='', headers=None, content_type=None, ok=None, + aux=None): + if headers is None: + headers = {} + if 'content-type' not in headers: + if content_type is None: + headers['content-type'] = self.fallback_content_type + else: + headers['content-type'] = content_type + headers['accept'] = headers['content-type']+';q=1,*/*;q=0' + return self.request_(_Params(url, 'POST', body=data, + headers=headers, loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux)) + + def post(self, *args, **kwargs): + return self.post_(*args, **kwargs)[-1] + + +def make_suite(dumper, loader, fallback_content_type): + """ Return a tuple of methods for making http requests with automatic bidirectional formatting with a particular content-type.""" + suite = HttpSuite(dumper, loader, fallback_content_type) + return suite.get, suite.put, suite.delete, suite.post + + +suite = HttpSuite(str, None, 'text/plain') +delete = suite.delete +delete_ = suite.delete_ +get = suite.get +get_ = suite.get_ +head = suite.head +head_ = suite.head_ +post = suite.post +post_ = suite.post_ +put = suite.put +put_ = suite.put_ +request = suite.request +request_ = suite.request_ diff --git a/eventlet/httpc.pyc b/eventlet/httpc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48dda615fb8abb23590b8170c76e41d4b3baea60 GIT binary patch literal 25004 zcmd^{?{geSdf$7oAP9g2e~Tm}>TW~{5=em{B~d4dq9lRcKXFx2u z*ade6BylJDd?)#Q{_QxIIOVEKoK)GBAFjBZi&J*WNiJt6xl8f~ByaK}Z}K8ld2!{c z#IKUi_vzW$12@x1Zh^GmqAqu<;15Uk1%S zx3JH}G_>7Cy>5v{_Y2~vFMF}x9ct$2>V>C^z(V?b1n*8{G7G$TwZ$AMMqnu&*!Dj zyXb{h=}=z!f{R{kwfj5DEp-m3d@Uiy-YMp~sO^3q`!jkZcp=B3A6 zbgEVQN?v;0MX$Dcdo3@0*+r*YrLX6uBQE+vtA#i6(i3jkK{0{rir#c*yW^u|-csgy zGG~-|fy~>=43Rmj%!_2+QRXEw?vnU~3oD>Fi7!kt|h z^<5w2c2ebs$Xrn7X)+h7aMDGW1n2@If5k;pMyjv6=r38}Yc6`fMManM@tk(i6?^=; zi$TH{jL4_mClLG_E`GyR-*gLaTIMac@RnuHxP>#8dD|_#ZJD!f;jCreaSQKQ=3Te& zu4UeH3-4LxoLe|&nK8F8W|{MD;k;$W-NLx~og1E!l=yNt;$o>rleBuE=UxxOB(#?6Em8jESUTCdh`1m&OtX#LbomuV(xETwnL&6vtjP)?FYrCO%F zAZk?BR^obEPSu|Vodm<_avU6;%icJ8%sPqUa;?8wr?og}Jqqqt!OB`12pi36MQw}( z)q172772#d<63p4n)RU94PQw=kgp|-KyZ!(D~+hSq`$EZYjth0R!u-jR8`Z9YbiAo z6;|T9-eMdlnUEx|)%t0n3fwl^9OfwfUz!Z&W-iQM8w)3cskvY_ zocZF^#N5IX6-kYA84=zt#nVP53`I$f+W=%~^^4^8u%49fxiI&IC zPhFmxzdq7`VQPL_ZC;oOgRx+CES#Sjzj}Er3}&x}vomv(bU#6}(^JzILVB6JGC4gz zO0Sd!lV9W}n7cG~`Lep|AG-?lq2LR~XJ)U5Qx`AI2bX3pPfSvHeiF#W&R?GNT`{Qf z%VSel;J~pfV;2Qo7|hU0*sp3Hy5QQSNfoKTG5(LwPt8ng7~?b3^C7n*j53_hU%fUp zH#riFg;R4v$c1p`%1FP^$s4pzBfK{~>01%14MF*gP(=v4%Ai}Y1QU~EmuZXXncgsc zqy5$Y#cw(pL=p{^M}na!7+MU5t_MSx&J0~SGc*@myfR-Y@KhI)%8heb_0+j)!KGbJ znWFocRlndqkyxXK8&=)alIlvF_Uf&)xs>W=4>ygAr)PV;#3sd}ycBFHNm@}4&2Jy?}`rniGgaK0qgROiWEl5>mA zYP=N8RaaKgJeJn#ww!9^xe=$qawAD?G0`%B62SU}8Y0pO)|$1^{(#@PW}LEyp-<=! zssrzg*t*evbEUklC1<)(kNryw&REL1Gt+Gmq31SVG;Z?%O#tU=t(t~=$@!X^G%A=- zQD>GBfHu4KUR0vOkl&?&|tv{_2=G0FO6ZbI^rP~!AF{vBtp{W+UG3Ija(4-m-L+Hzm$m6A) zE~lcvMvW1yrOo*E8fsJ8N74NHYQ_fw)vBY|EQLq73lFJCwUS%a)so>^+;4Z~N+n)R zOL4t|`c%KM1&G${F2-pZH#>m5qW;82uF%K7V%O7!U0pWGm!QD$S`}em(PRBY73aB< zlO!?L0IWo6>;lKYJD*~tcDs0+tM;_4XH4hqE}Sztko+7kj!F5c)KFEW@QBcB)x}XQ zQsa$!9i2@2YPwuTuWL5$R3r4iGL$XAjTzb)_O+UErO}Mg{?OI@tazqiRsw4Yz0}VL zO0;x7&mI*`r7lg(I9KvC3BuG1g`8SMDvYuv9<>Ig`$1nBCj1&gc z3=b>OvTu~*73#;I(C|fC{E#aVOs+6JEXkX}P%p24H`_#Xs-1k3Wb8*0-P~U&)eUQqp%4}xQ*vC%%OvS;5@Egr zp74a8ZaKn7X?2k+(NZsjvs#j~Uy;<9L4d&w-Cidv6}4QJx*e;974^8R2q#ucHA}w1 zJ7K(9D`OsI1ocu9*s_;fqAg=C!K9K5fV*W(293355Z9N>^$O+^YgB^uqor>;jw5fl z-EA~)1?6UA4LcX17<=x=&}2RILIjf%VUX6=N2fiWMOq%aVsv3Ql@u&t<7I-TWI%*c z&_dzGRzsVHP(n@<)sbwL##2f9yUj8}-a}f_nC+%=lSf`R?A~86hP{BTK37?e(UjG_ zewr4edZuIp)r0&6(^}-!MEK&vESl4$M~+I|hS4??%Dv{8L|FI9@AxSh{F%L$G4EpL zeWX7B@jcS)fELNwp?PiZ#MY%@cf4$%eD%ll~nQZ3Z^YttxIV!HZ$(ol*yLc_WDOkXn-4TabKvYyyl7{yqUvDLa; z{WB_8zeVD81<^e96aA&Wk&&-IEGEv@(sH_%Sd%NsjZnH|_^cjkQOBVZH%-&BWo-;$ zV~!THOE9KU*b-}{8L!~tv2_stg{&4+)I`13(A{PgAk}20)<}HCsTp5UT{g>AETs;i z5?dRhsQ4aUE^j?eH_{6dUc;g`uNWL5XGFfVRzn*{jTApDHllUED97apBizulWGU`l zewJ*O3#{c#Az~g~BH8D)zk`MSg#(2F^y~xJP<@u$N4l%9hr9jc4)V96Ic;6WF^^(m zRj@&B5yTmGzeTed`#u^DhSXBksOr5BTLp(a4^SjN(N)}o@o#j%B5Kl=E0Y(v5!)Ax zrCx`34`s@wOc4lO&JCI6=X1+)hjBgdvhg0@go8lF{hk;FST0&<>L z8+TXMD$6ETmbSny4!=mte}~H)LJkfr7K|6W_8M>Q_ZSwV@P)@p;+rad~gP(X&04D~z0j>y4vPY-Ll9<(wxTA@J8wvwqt-V~hCE3-;|O3Azu z(V(H;dT39hFR7%NX`nGW zsgW$A(*MWygY}ii1%d6?Esa%dVXR$|WYQ&ZWQ_7x$)Lo+1k&tG0F}Ihh!SLoSNf>n z5&7!mybad1bD>IcV~b#cX9e3AzEWxX5RkZ3~#@#R3SocfK2 zx~?^(w*{T=GiP_uKE0Q!r9cGM5+Xr6y&IfU9aF>fl^Eo`^dmYKdh^rAZ=XDr{APpLGI|!zHn>juCN5VAwn#e(VG|4C5Gw{au zfyHH?1tym_6pC*$F;ZKpqUhtABO7LnCGWAcR&?OWnXGk(p z%eK{V30djDj?Jqhw$s9<4E`k(y_u69|NHHO?wTnl>fBS2&234R;0@$~-C>@|L|aKr z6~jU+r)kq`F9GivwU{XN_TDYxC2n}L>Xzw&ciPx|tSSCabY^Z$*;4}!znXBNtH?F3 zr`pd*y&`N&4=Py8UQs<80D83s5H3;Bp--ueTk-ni!)ngeQYq`ERQk^}oEcSq)l?VW zKPkcu8zk*sEw^bjK{xI{a<%AfTX>zj3YYzI8k3Hm(^w~gvC50&SdfdB!Q3SWo}5;U4__i)fz}~ zX?WQt2JT0eq4maEP(dhau{9?_ZT&>D8ds`I)i|>Cr7bRjz$9)?9GZ9|4l!2FBT%L{ z6|r?XcOXOtp=>`R{mGUT_EJGjc?qW)?Md78e2D=5E0=dsLe{z2okKmyV-y`agrNah zo+GBm8)-(r)8XraM$_UU?3vg&X{WUHYM6 z{S7iXOM3VL9y)_JAu>Q?SAj?bOerJ@J6oUFEZvmOrbecXY2$mVc%QvgIpsqMWFXld zwI8%tzcuJmrk>bvebj=mGr8KREZd%Xo9{#E%8!qS4j`ldPK#bn^%M(-Tf5~#ia<7` zOpwVZm62zrV=FvElWm%>GU&BTJZdkg>AWf=w6(__Y+04k`G=BWl*f05nu-O}?x|(H zmSI_MlUP>EHko6?eY|Ms3vZM2vfn%H*~1c6I+pUYy8TdzR-6tD>eVg4U@y&-N{EeS zsr2XMGf}@2)BKsjBX@R8e2M_WyGqtcY#vG_R7ds*x!(f7OoaXynzEhT9(K$7y0N2n z^!3L=c*e5PN_qnYw3$cMY7 zqE+t zRH26baw6ZwZ6}eVtm>KhRtfQaHE6vWc+?5P_CD=aL4KcB_qMPm$W5xTuZDpOk3I~x z*dhn}(~bJ}!|G7x`p8L7BNRcIgBCiFaBhctiTAvGJwk3PslzxDW3stk^5XuG#;pEa zn=f+U4pHoZ`-JTtIaUlzZaURt(<6+cw-iz>G9FSEsHYzQQxNxX84hK|CITh% zAc)z8Q$SglHUez`tdf3xL}vLQnCbS4<=KU)&8FaIl#`!4{1S-~bGB1c>R8Q~zz#0j zECZQQ9t)_w!fs(SOYC{Mu2|Uh3N`Jg(Vyos zfs%79)=Nv!h{}W4PQ4OX6G2BCPgLR{Gu%rWisZCOoNZ)mH}bQ?zQe=*YGCg@QFwor zu0}fG?J`!Fi{ly8hvvf|3$2~#hCn4Ib8kcix54JTL}SR+ABG7=&;0qSPGas*DyW>g@V;}SL*@vJ_2Zr!Qkheitt#9Os z4&>+Q_uUNx(U!z{_7#0_;%6VhUX9@NsZ#+j1G@#AK2gwrfeyzvfUX7eLZi7@jX37? z*&#PJJPjM>?>QYDRPm(sm~6wXev1U%BeuF zJyGQT5pED&-t)th z@)NQB6peuG(?Ql!kTv!H7^c~&{R_kNuYi&-ZNT(?N~UlwH0$M>XgqFO48+6eewK=l zR7KdM_WRPJmgE@{kba7Tp}tWx6SW#xvmW*G#c4BUTrbQ4&N@e$ZR7iv%OVXScu~H2 z;_r98Ki4RVEDX}0#T&1`HamM!$8_A#T{!gzPlw;!!y>ro?_c`8q+Yye) zMtQ3mwG|N*ST71Qfx+Fnjw4YzY0pUAz6)IkAMj1vER|pxU-rse#tu zEKE^IVTauhcc-i7Ps#7HLps~!a1jelx2+fxxwTpnCC~F6i#wdcPBFO&$1R%($V7ImChoc2wELd@`zH!tyiYsuG^6VLCbYL(%HbF)0USY|)+*SNKp z%u{8pRg-)>x2dNZPEFh8tL>vN-u-r#C+4*6h>QFidPO>hp;QxKeVALPnRb}v^sN1T3Wx6i@mP4gWJx{f@!E1{ z*RRrv3>G5`b9No7{yWO5Cn&6rDmkg-l#m^7G_-8&>#V~Dm8P`G++-{`|0$t>DL(f%JOh{*~O8BG|`!gP})qY#LrtPZIqS(p&>71dDQ~kY){D~lCR^PiXXKaE@h=! z$*pyynnblCvBm7zTg`PZ#hc|D!b9dtOE{7I_~OV80zDAN^&1d$qDXVMeLK=I3Tuya7=VQCQ_GVGJ+AL*@8Nn5)FZ<|^oZW&cQ%DRa zwt4k!3XMw$f@6P+SOSZivdSjMC7nXm@)k}>Ij+s0N=n$y*&C}~6V2ZE^AAQ~w27Itqp{9Hn)%&id% zRM8Xtdbl(-$@fWe&HU4!w2P7lmGCj8nA1~x>_3O8*ahVb6<>Lx1EH4x-*@6Er>gv?op!aJa>$BU6%yXP!9KxL2QeQm6amO_mSn z;r-LkQ~G1}Pb1Nn=+*~PwFBNvU@kF-QnhrDCErE$d>QagyLR{;ZvE7f*))mSzTlr| z7Bxlw1d2&Ak)DdixsuLNZ{NDa8 zXD6T36UGv)qK}Y=K6*_-I>Bp5VaSBDU45=G*t+*`ASCn>-(mP64YB8iIphoU)=uTA zg4|Xi_6sSN3msS0VE%2TZ&<3$9GwV9u^6G#D#H z3w+D&@OlQjYo^e!Y-Coi;-zF-ac0x@y!_z{`DjBmWls3ntMew~__)lxKdT`b8U7O` ze?T%ltfeu8x3dLBE|2gdB~ZMN9rf{~@9TC^$q^+M@OgloeI`Wla0GYwxLC7uyn*sJ zRngscnh|v?!Mjc?2NY`+u3-<6|t9om)cNM+ABpekkch$$v&=M(Rd#MqZay_ESoXtU3M-I&u5s3$xzxGp0+ahxz?Em>t==Wed-v$iT#> z7B5rB{_hcl(5sbmm(c4i1f9;?`?yr`UD|C^MflG((ygHgi@816K@D=W><>?{cHL z+dvIPzktEzd2dIof$cGHnWEA9G<1Miv1;f)(rm|ZI;6sEJ{!8k4b*a4qwCSoX9X@Avpse{He z6kXyFTR2XW(bR&)&V|Ed3)G=U&_OO+2rTRBw#7oTG;P9SV)F9j{A7Ct=n(Yk>X9NxhcC6XZgS8CK$jxCo0QnpvI5lgRVx*jD44!2?!H%Ddvnhu@}m6?DP?C-THZF*Sl z!j3JY7j^qJCDOi4I}S~Ax79sT*&i$U7fQaX#LS`ZDJL-#ic3P#bEuJo|AwTkH(748 z%2W7{Y3qCNZ*r6jL6F<}juU6Sqp$eXHm+VmA`3eQIJwPv142g!Z0|^Xugk>TD4EnG z9oiD>8;%5T7Rp{?!v|g2Z~KsTyCt2ek+?`?OB38=pFJ!G>6$*B$i{?^Bu?xEF&;sk z5AJj`WFM2_o6ZfsT3M|GL@3!ep*W_hPZZfH-u6*`PRp`!P_3-h%FPX7^rL=TPoww9 zHar`O4Z9lN0}e6Mj1##Y+qmB>+HKpxJ3Q6kxQItxx~_c%3*%1Xd+A9&v{kJSPY9Ib zB<6@^F^=^WPXE;_iL*vBsxQlg=Twg?^O1}wH>BhwNh(syzi8L_2{O9&XDhJBZO6vG z@u||4dv&ls1<-T%1t>jZBKda;$O1`P&=au||l3!FZrR0~CC}t+a@^Im=D)~btzpdnV zl|)KrmHfUEo9^$EGq)4F)#1NYu}%A*DrbE6UFD1yjVnbDp6`rCzNcH`UU77r6fCz{ zdN3!%S^pWocMsEU^0!EOx)iOQ{euzv_>@~;S5Z!#?p-|77It4@Z(rZ8-8%;R2YdVa z4qiC;`GfrjgM+&dZacV_tLNZ$()Pa|N}lcJtw;acQS{GtboU+J+0!?0@N0cN`+h{- F{|ywby_f(1 literal 0 HcmV?d00001 diff --git a/eventlet/httpc_test.py b/eventlet/httpc_test.py new file mode 100644 index 0000000..b0e14f0 --- /dev/null +++ b/eventlet/httpc_test.py @@ -0,0 +1,396 @@ +"""\ +@file httpc_test.py +@author Bryan O'Sullivan + +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 import api +from eventlet import httpc +from eventlet import httpd +from eventlet import processes +from eventlet import util +import time +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + + +util.wrap_socket_with_coroutine_socket() + + +from eventlet import tests + + +class Site(object): + def __init__(self): + self.stuff = {'hello': 'hello world'} + + def adapt(self, obj, req): + req.write(str(obj)) + + def handle_request(self, req): + return getattr(self, 'handle_%s' % req.method().lower())(req) + + +class BasicSite(Site): + def handle_get(self, req): + req.set_header('x-get', 'hello') + resp = StringIO() + path = req.path().lstrip('/') + try: + resp.write(self.stuff[path]) + except KeyError: + req.response(404, body='Not found') + return + for k,v in req.get_query_pairs(): + resp.write(k + '=' + v + '\n') + req.write(resp.getvalue()) + + def handle_head(self, req): + req.set_header('x-head', 'hello') + path = req.path().lstrip('/') + try: + req.write('') + except KeyError: + req.response(404, body='Not found') + + def handle_put(self, req): + req.set_header('x-put', 'hello') + path = req.path().lstrip('/') + if not path: + req.response(400, body='') + return + if path in self.stuff: + req.response(204) + else: + req.response(201) + self.stuff[path] = req.read_body() + req.write('') + + def handle_delete(self, req): + req.set_header('x-delete', 'hello') + path = req.path().lstrip('/') + if not path: + req.response(400, body='') + return + try: + del self.stuff[path] + req.response(204) + except KeyError: + req.response(404) + req.write('') + + def handle_post(self, req): + req.set_header('x-post', 'hello') + req.write(req.read_body()) + + +class TestBase(object): + site_class = BasicSite + + def base_url(self): + return 'http://localhost:31337/' + + def setUp(self): + self.logfile = StringIO() + self.victim = api.spawn(httpd.server, + api.tcp_listener(('0.0.0.0', 31337)), + self.site_class(), + log=self.logfile, + max_size=128) + + def tearDown(self): + api.kill(self.victim) + + +class TestHttpc(TestBase, tests.TestCase): + def test_get_bad_uri(self): + self.assertRaises(httpc.NotFound, + lambda: httpc.get(self.base_url() + 'b0gu5')) + + def test_get(self): + response = httpc.get(self.base_url() + 'hello') + self.assertEquals(response, 'hello world') + + def test_get_(self): + status, msg, body = httpc.get_(self.base_url() + 'hello') + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-get'], 'hello') + self.assertEquals(body, 'hello world') + + def test_get_query(self): + response = httpc.get(self.base_url() + 'hello?foo=bar&foo=quux') + self.assertEquals(response, 'hello worldfoo=bar\nfoo=quux\n') + + def test_head_(self): + status, msg, body = httpc.head_(self.base_url() + 'hello') + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-head'], 'hello') + self.assertEquals(body, '') + + def test_head(self): + self.assertEquals(httpc.head(self.base_url() + 'hello'), '') + + def test_post_(self): + data = 'qunge' + status, msg, body = httpc.post_(self.base_url() + '', data=data) + self.assertEquals(status, 200) + self.assertEquals(msg.dict['x-post'], 'hello') + self.assertEquals(body, data) + + def test_post(self): + data = 'qunge' + self.assertEquals(httpc.post(self.base_url() + '', data=data), + data) + + def test_put_bad_uri(self): + self.assertRaises( + httpc.BadRequest, + lambda: httpc.put(self.base_url() + '', data='')) + + def test_put_empty(self): + httpc.put(self.base_url() + 'empty', data='') + self.assertEquals(httpc.get(self.base_url() + 'empty'), '') + + def test_put_nonempty(self): + data = 'nonempty' + httpc.put(self.base_url() + 'nonempty', data=data) + self.assertEquals(httpc.get(self.base_url() + 'nonempty'), data) + + def test_put_01_create(self): + data = 'goodbye world' + status, msg, body = httpc.put_(self.base_url() + 'goodbye', + data=data) + self.assertEquals(status, 201) + self.assertEquals(msg.dict['x-put'], 'hello') + self.assertEquals(body, '') + self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) + + def test_put_02_modify(self): + self.test_put_01_create() + data = 'i really mean goodbye' + status = httpc.put_(self.base_url() + 'goodbye', data=data)[0] + self.assertEquals(status, 204) + self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) + + def test_delete_(self): + httpc.put(self.base_url() + 'killme', data='killme') + status, msg, body = httpc.delete_(self.base_url() + 'killme') + self.assertEquals(status, 204) + self.assertRaises( + httpc.NotFound, + lambda: httpc.get(self.base_url() + 'killme')) + + def test_delete(self): + httpc.put(self.base_url() + 'killme', data='killme') + self.assertEquals(httpc.delete(self.base_url() + 'killme'), '') + self.assertRaises( + httpc.NotFound, + lambda: httpc.get(self.base_url() + 'killme')) + + def test_delete_bad_uri(self): + self.assertRaises( + httpc.NotFound, + lambda: httpc.delete(self.base_url() + 'b0gu5')) + + +class RedirectSite(BasicSite): + response_code = 301 + + def handle_request(self, req): + if req.path().startswith('/redirect/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/redirect/', '/')) + req.response(self.response_code, headers={'location': url}, + body='') + return + return Site.handle_request(self, req) + +class Site301(RedirectSite): + pass + + +class Site302(BasicSite): + def handle_request(self, req): + if req.path().startswith('/expired/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/expired/', '/')) + headers = {'location': url, 'expires': '0'} + req.response(302, headers=headers, body='') + return + if req.path().startswith('/expires/'): + url = ('http://' + req.get_header('host') + + req.uri().replace('/expires/', '/')) + expires = time.time() + (100 * 24 * 60 * 60) + headers = {'location': url, 'expires': httpc.to_http_time(expires)} + req.response(302, headers=headers, body='') + return + return Site.handle_request(self, req) + + +class Site303(RedirectSite): + response_code = 303 + + +class Site307(RedirectSite): + response_code = 307 + + +class TestHttpc301(TestBase, tests.TestCase): + site_class = Site301 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_get(self): + try: + httpc.get(self.base_url() + 'hello') + self.assert_(False) + except httpc.MovedPermanently, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + def test_post(self): + data = 'qunge' + try: + response = httpc.post(self.base_url() + '', data=data) + self.assert_(False) + except httpc.MovedPermanently, err: + response = err.retry() + self.assertEquals(response, data) + + +class TestHttpc302(TestBase, tests.TestCase): + site_class = Site302 + + def test_get_expired(self): + try: + httpc.get(self.base_url() + 'expired/hello') + self.assert_(False) + except httpc.Found, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + def test_get_expires(self): + try: + httpc.get(self.base_url() + 'expires/hello') + self.assert_(False) + except httpc.Found, err: + response = err.retry() + self.assertEquals(response, 'hello world') + + +class TestHttpc303(TestBase, tests.TestCase): + site_class = Site303 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_post(self): + data = 'hello world' + try: + response = httpc.post(self.base_url() + 'hello', data=data) + self.assert_(False) + except httpc.SeeOther, err: + response = err.retry() + self.assertEquals(response, data) + + +class TestHttpc307(TestBase, tests.TestCase): + site_class = Site307 + + def base_url(self): + return 'http://localhost:31337/redirect/' + + def test_post(self): + data = 'hello world' + try: + response = httpc.post(self.base_url() + 'hello', data=data) + self.assert_(False) + except httpc.TemporaryRedirect, err: + response = err.retry() + self.assertEquals(response, data) + + +class Site500(BasicSite): + def handle_request(self, req): + req.response(500, body="screw you world") + return + + +class Site500(BasicSite): + def handle_request(self, req): + req.response(500, body="screw you world") + return + + +class TestHttpc500(TestBase, tests.TestCase): + site_class = Site500 + + def base_url(self): + return 'http://localhost:31337/' + + def test_get(self): + data = 'screw you world' + try: + response = httpc.get(self.base_url()) + self.fail() + except httpc.InternalServerError, e: + self.assertEquals(e.params.response_body, data) + self.assert_(str(e).count(data)) + self.assert_(repr(e).count(data)) + + +class Site504(BasicSite): + def handle_request(self, req): + req.response(504, body="screw you world") + + +class TestHttpc504(TestBase, tests.TestCase): + site_class = Site504 + + def base_url(self): + return 'http://localhost:31337/' + + def test_post(self): + # Simply ensure that a 504 status code results in a + # GatewayTimeout. Don't bother retrying. + data = 'hello world' + self.assertRaises(httpc.GatewayTimeout, + lambda: httpc.post(self.base_url(), data=data)) + + +class TestHttpTime(tests.TestCase): + rfc1123_time = 'Sun, 06 Nov 1994 08:49:37 GMT' + rfc850_time = 'Sunday, 06-Nov-94 08:49:37 GMT' + asctime_time = 'Sun Nov 6 08:49:37 1994' + secs_since_epoch = 784111777 + def test_to_http_time(self): + self.assertEqual(self.rfc1123_time, httpc.to_http_time(self.secs_since_epoch)) + + def test_from_http_time(self): + for formatted in (self.rfc1123_time, self.rfc850_time, self.asctime_time): + ticks = httpc.from_http_time(formatted, 0) + self.assertEqual(ticks, self.secs_since_epoch) + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/httpc_test.pyc b/eventlet/httpc_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec2d16c495e53590f7a20d8b91f002d9dbb63b3b GIT binary patch literal 21095 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 Uk" % ( + getattr(self, '_method'), getattr(self, '_path')) + +DEFAULT_TIMEOUT = 300 + +# This value was chosen because apache 2 has a default limit of 8190. +# I believe that slightly smaller number is because apache does not +# count the \r\n. +MAX_REQUEST_LINE = 8192 + +class Timeout(RuntimeError): + pass + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + def __init__(self, request, client_address, server): + self.socket = self.request = self.rfile = self.wfile = request + self.client_address = client_address + self.server = server + self.set_response_code(None, 200, None) + self.protocol_version = server.max_http_version + + def set_response_code(self, request, code, message): + self._code = code + if message is not None: + self._message = message.split("\n")[0] + elif code in self.responses: + self._message = self.responses[code][0] + else: + self._message = '' + + def generate_status_line(self): + return [ + "%s %d %s" % ( + self.protocol_version, self._code, self._message)] + + def write_bad_request(self, status, reason): + self.set_response_code(self, status, reason) + self.wfile.write(''.join(self.generate_status_line())) + self.wfile.write('\r\nServer: %s\r\n' % self.version_string()) + self.wfile.write('Date: %s\r\n' % self.date_time_string()) + self.wfile.write('Content-Length: 0\r\n\r\n') + + def handle(self): + self.close_connection = 0 + + timeout = DEFAULT_TIMEOUT + while not self.close_connection: + if timeout == 0: + break + cancel = api.exc_after(timeout, Timeout) + try: + self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) + if self.raw_requestline is not None: + if len(self.raw_requestline) == MAX_REQUEST_LINE: + # Someone sent a request line which is too + # large. Be helpful and tell them. + self.write_bad_request(414, 'Request-URI Too Long') + self.close_connection = True + continue + except socket.error, e: + if e[0] in CONNECTION_CLOSED: + self.close_connection = True + cancel.cancel() + continue + except Timeout: + self.close_connection = True + continue + except Exception, e: + try: + if e[0][0][0].startswith('SSL'): + print "SSL Error:", e[0][0] + self.close_connection = True + cancel.cancel() + continue + except Exception, f: + print "Exception in ssl test:",f + pass + raise e + cancel.cancel() + + if not self.raw_requestline or not self.parse_request(): + self.close_connection = True + continue + + self.set_response_code(None, 200, None) + request = Request(self, self.command, self.path, self.headers) + request.set_header('Server', self.version_string()) + request.set_header('Date', self.date_time_string()) + try: + timeout = int(request.get_header('keep-alive', timeout)) + except TypeError, ValueError: + pass + + try: + try: + try: + self.server.site.handle_request(request) + except ErrorResponse, err: + request.response(code=err.code, + reason_phrase=err.reason, + headers=err.headers, + body=err.body) + finally: + # clean up any timers that might have been left around by the handling code + api.get_hub().runloop.cancel_timers(api.getcurrent()) + + # throw an exception if it failed to write a body + if not request.response_written(): + raise NotImplementedError("Handler failed to write response to request: %s" % request) + + if not hasattr(self, '_cached_body'): + try: + request.read_body() ## read & discard body + except: + pass + + except socket.error, e: + # Broken pipe, connection reset by peer + if e[0] in CONNECTION_CLOSED: + #print "Remote host closed connection before response could be sent" + pass + else: + raise + except Exception, e: + self.server.log_message("Exception caught in HttpRequest.handle():\n") + self.server.log_exception(*sys.exc_info()) + if not request.response_written(): + request.response(500) + request.write('Internal Server Error') + self.socket.close() + raise e # can't do a plain raise since exc_info might have been cleared + self.socket.close() + + +class Server(BaseHTTPServer.HTTPServer): + def __init__(self, socket, address, site, log, max_http_version=DEFAULT_MAX_HTTP_VERSION): + self.socket = socket + self.address = address + self.site = site + self.max_http_version = max_http_version + if log: + self.log = log + if hasattr(log, 'info'): + log.write = log.info + else: + self.log = self + + def write(self, something): + sys.stdout.write('%s' % (something, )); sys.stdout.flush() + + def log_message(self, message): + self.log.write(message) + + def log_exception(self, type, value, tb): + self.log.write(''.join(traceback.format_exception(type, value, tb))) + + def write_access_log_line(self, *args): + """Write a line to the access.log. Arguments: + client_address, date_time, requestline, code, size, request_time + """ + self.log.write( + '%s - - [%s] "%s" %s %s %.6f\n' % args) + + +def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): + pool = coros.CoroutinePool(max_size=max_size) + if serv is None: + serv = Server(sock, sock.getsockname(), site, log, max_http_version=max_http_version) + try: + serv.log.write("httpd starting up on %s\n" % (sock.getsockname(), )) + while True: + try: + new_sock, address = sock.accept() + proto = HttpProtocol(new_sock, address, serv) + pool.execute_async(proto.handle) + api.sleep(0) # sleep to allow other coros to run + except KeyboardInterrupt: + api.get_hub().remove_descriptor(sock.fileno()) + serv.log.write("httpd exiting\n") + break + finally: + try: + sock.close() + except socket.error: + pass diff --git a/eventlet/httpd.pyc b/eventlet/httpd.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a6fb969c926c210132074a62c55bbc6b41e0e66 GIT binary patch literal 21726 zcmcJ1ZEPG#dS3MmKS!iQkrE~CYNcLjspalcly>iKZ%?olUi!IyBQ3p zn`BGF>EU#bB<@KU=On;(;@B~KaK4bs#f}r_JBX3McD@56u;Cy-oF54SBrsqjL4W`W zi~vD?InVP{ch5-L3ml0i*~RMme5>BK-mj|aU;h2k(*OD7x31Mq`g08bzkw%yZ=N@O!69?IWTF$s!4H8ziBeXgSuoBmCia;-Bc?HGHb#vK_3Qnx z**J`Mz;MI_V`k&12?{10S2$s|Fcx$=XoBNr3vCRU$HqJ|=A(^C;}4tQgi=d+>Ph1t zG8?CiKVmjsGXAIuURH_2d5P1;M@K^@d_@^!dBz#zqi|+J4S_QGpU8mu(|El z+&Vxjp=7dyX5wfoxmRlkDD1nnIF9OJ4ehypRPXEt&7_vFKMp!}r<0w)J-3?HIQND+ z@q=2U6gJUX;AUCwy%4j~NnFlGJFK&f88>X!8y%l8WZ8{yH%xnAbeFXq3=G2)H%YE_5ox`N7?Iya!q@aW!Van9w?Vz48g^+JWjYf2jL#szkKje7hE2YvJ zvTB>r-9Tfp4{f5mFld|Dpbe+vO0pi12!)--v z_4o8JXVL%lvb(x`b!~mlE4z!U?uxhk;o?Gh!96#(iuZFf?)u`|_2rvuE=qWFm9>xD z<*V*o&!S8EK1^`K6l+i)k81k8|BK{EP6$nTmBF)?&|fqr6qP%n!5?;J;LYCFRy&$End66 z=3ZZ3S|}s&gEAnS`(UYTyTYL6m*y64UW_bNHKI zTU@Si81u`OH4m>d7^SzCS6yFREzh`f-r_1J zP=ph96NAoRaTm&SOK1z|sXT$c*;4oi_^C`oAedwb!79kVxxKh-Q;JBbwL<&SkC%GX zj^daFudl7GTzY%~Rje7R^sus|z6z?AW4 z%wtH7LF27X7f`g$p7^l)c|7rV5G2Ngj(K3rgMxVksqjEA)dJtq!tEgwokS{1^%>-y zLc^!K#*Z-rU_3aL_kQ4ndUqs!wAav z>p_d_MRQoKf+;4|YSM?SdgKR+RH9~(l#o&d|I$Ro$pBv4K@Ge#8ANm^sQKWmD!v){ z`_r5Ry@By;c`U8zwpFUFopvn_Jhtf_LNJZT2JsgVyL2-~W0%_gy-N!z-;FQD?fRwQ zE?96QNV<_s@_6j7UOpWj+x0jRf}=Ye#h!ai#M+ne#6-t{Gg8o8 zr-0OvrXOaCSicX{1|nAfb*VoHMJgm�N|O)n-WHu-QS$5{Y=@5M}yfypFI~`d1WX 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 v!T4PS13X6i{y9UFLWl literal 0 HcmV?d00001 diff --git a/eventlet/httpd_test.py b/eventlet/httpd_test.py new file mode 100644 index 0000000..e7e0fbc --- /dev/null +++ b/eventlet/httpd_test.py @@ -0,0 +1,207 @@ +"""\ +@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. +""" + + +from eventlet import api +from eventlet import httpd +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 + + +class Site(object): + def handle_request(self, req): + path = req.path_segments() + if len(path) > 0 and path[0] == "notexist": + req.response(404, body='not found') + return + req.write('hello world') + + def adapt(self, obj, req): + req.write(str(obj)) + + +CONTENT_LENGTH = 'content-length' + + +""" +HTTP/1.1 200 OK +Date: foo +Content-length: 11 + +hello world +""" + +class ConnectionClosed(Exception): + pass + + +def read_http(sock): + response_line = sock.readline() + if not response_line: + raise ConnectionClosed + raw_headers = sock.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 = sock.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( + httpd.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)) + + sock.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') + result = sock.read() + sock.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)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.close() + + def test_003_passing_non_int_to_read(self): + # This should go in test_wrappedfd + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + cancel = api.exc_after(1, RuntimeError) + self.assertRaises(TypeError, sock.read, "This shouldn't work") + cancel.cancel() + sock.close() + + def test_004_close_keepalive(self): + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') + read_http(sock) + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + self.assertRaises(ConnectionClosed, read_http, sock) + sock.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 + sock.write(request) + result = sock.readline() + status = result.split(' ')[1] + self.assertEqual(status, '414') + sock.close() + + def test_007_get_arg(self): + # define a new handler that does a get_arg as well as a read_body + def new_handle_request(req): + a = req.get_arg('a') + body = req.read_body() + req.write('a is %s, body is %s' % (a, body)) + self.site.handle_request = new_handle_request + + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + request = '\r\n'.join(( + 'POST /%s HTTP/1.0', + 'Host: localhost', + 'Content-Length: 3', + '', + 'a=a')) + sock.write(request) + + # send some junk after the actual request + sock.write('01234567890123456789') + reqline, headers, body = read_http(sock) + self.assertEqual(body, 'a is a, body is a=a') + sock.close() + + def test_008_correctresponse(self): + sock = api.connect_tcp( + ('127.0.0.1', 12346)) + + sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') + response_line_200,_,_ = read_http(sock) + sock.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') + response_line_404,_,_ = read_http(sock) + sock.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) + sock.close() + + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/httpd_test.pyc b/eventlet/httpd_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77e88248c62e869f77c7d7526f300877696061c9 GIT binary patch literal 8482 zcmcIpTXWmS6<(0K5tbasx5P~^8z-46jbh1;<0PIWEiF+NCo-jhs5n-gVjyrO!h!$> z04=*?&t&S%^r=tnQ>QYl68SCoSfiDJg&U`B$4zA)n|s;MW+(wr2U zuG(_Zo>Jz7+QCaT^^M|`J6qGLgTWcqIibu+8JixCy`(xbY;2uUos-JADtcLFyfmC~ zTA4Gdb4uN-sPv?AIOVJ?oj6$TWwkMTnqv8U4D#8DVxzl7(d+ny$%{+9hwg8Je$h=c zy_6>D{UFin83xkCbr;j#!z|kE7J4>(TVGjN_+U=2MoGsc+B3Ncvama+n@M=dT{qc& zl;=^J=qT4+lbP*@dN&J_!gS{JPG*cwcXZedvRyN$i&O{6L)|l3ju+DHB8ZYG+0}s# zv7(EaMHgi9bf-87GK0w-9prf$Mgi#QP8#<2O;Q8}*W-qAJzI2*zPM4oaq(?gq+^2E zjS^5ZdN8C9B5bx_Xl^o#LeiMiQ4+@e4tW?1$I*UNuE5tt47m%=`#E+%KIim)+KG1f zZDd=${&pPY5J@K@>Fs`jnK?T{lkhFxesavjj^*D45YnAu?Iipd;<9V79qg{ZoflAi6vVohX0qP1+q{JJR~mX_ zZMl8B?lp9CL$7;lpEs8pOZsAc1MQ1*`gXIuvbNdQnBmo1?K^sHS=U>4^k>c1(wuI5 zvFt+n-P^H#GBqU|-!1xr%R2HsoNw;JB! z3MkiaG*_GLJ9F-Gv)v-iz-cU^w!rl8d!b_WLwSFvWHa~w;HYX zC9H}*-S`|0y|GeXUFA}4eG}Y!5vQH&@zvWo>n-fzBHZa8|#u+OSJuQ;Vzh z=B+usRKHcfN$xzo1}dJ*X_h;Ed!@lHu2;wZV!OH4;x-o7T5S)_IqcGF4`03A+-S_{ zy4T#GK$g9=TXQbOi8nwUMDSj#VX08mBA}s0Fogo!#HI(l=%q$|6||t9)*a-;v^ zGYiyJMHNNRi)>>Y4Z#}QtfAS<($M6&$!!-PN%e~;wyi0&HsCtRZgXu!fjE^HoVgJd zCd9XVxZDjq`A<+7;3E)K2~buD+TWn3s=STa3Wh@#WQ|l7@#J?=JaSYd0Q|V33P(Mv zs3)M|sC#27ol_3lXk0)5A+WxN4$MbiRdvQxXB?A({a}hH%OUhnkb-p1PGW%;$U`RF z797Yt04_v-0pGHj?X>eSr${gaFYPCt`~+HEpaZ-;&En21xh-Z;^n#-6=Vq6%my3*J z)JPXF)`Lfzg0eoyAfQ?9Q&s@b?8xNId?g(uGmB9rMgAdb^P4$fcs}bK%rBL=n$PE1 zIB)J_h1e9M%Au%HoW#prAX;pEknclBbLh!k6v~-)-gM45Q%*=;29L=76?8zb5DX>m zP+mikQKK!*t4j4=h&3KVevx7;GG8@@kNSn!U@R*O`Eb6wSSAs zBQfN5NzNNjuHpLp!gfC0iN_kDLUntRI2L1uf=G_nayFs(wCRi<1zJUOhxDt ze|%C=cPr}gxOzOH9^t>L9w7sRKPvU{SD&fd#U%UgO{nw@EIY*px-Nj&V~k+&ntC)z z+K;DHf;10~jT#J;l%~{96TPi2D86QArL2eDnfII=awYmIajGUr_@B{Rp)GF#yRDjsZLbnl{VmIg-Pr4cHYJ7*3O|asZ0>OIW$SYW@SosCZ$qS$j;g0mf88%31;T(nPw~z{nAO1;viQF zDLOtBk=VJ1N+B@?Hsw32fW3<4`>;1L*J46NlTyd35-(_du;D>iVH2QOt&jp3LWHrw zB&X6AF3DeXeWA3p{osM0N541G*Ic@aB2IV3*Poj|OAPbIIFF<-kIA!uj

`4{kt} z_81%_9(MPy@U*V#QWptnOqh)8!e>=33NAzC}Jwq4j*9{ zMq4s$&ET=1qD?S>LuE?wM1YX#g&5a8f!i>>{m#|lyI7b68YbGcBe<~G`RO9lM z55#^hi~ZzpfQ6flww~83?e_ZoQNfXDI}0 zN%R=RF9s{W2zwIPyq4#s4|ESxJVa-PHlOARt3Hb!}X_c{sC@_=&v zxHv9*dx>NLHU7fFWu#j)_+4~K5LU3d8c=E4aVr;WgyZgq>imrFiv3x&JgZK)rcHN7 zX(SbNxZCPjT;2v7;`$FM?edXIvk>t7`x3=OHy-mptB|)@`v`?i5}0d=jE_sOr$Mk3 z>=l2{m|hS^5H5NT$<}pd5CuYdEagh1TI8I&C`#E1Vm>EZ$X(zQmI_}$=3u=6CPL4# z4uR_)CZ1CSWYIx+Ju|}B=Y{pWha4?ziT*j$!M5C7;D#aYB)=?}$M1coCIpW0jv8q~ zqMdmV`oT_7qOHfQVc#^e45w+ruXPnKh;WLs(eYu=4qL~fUxYytn%Jt8q7+ybm3m8P z9+zG&+maT9NqjRO?+f2ev#s25Oko5Qp*+VL8qPb11-~ohFabuG3!BsU(adz zSq~EUd&Yz0d`8Ej1&k48N)My@VGEKGT3Do+|!&uypF`)5v|6d?4i}Ym%NAs7RmjJ#@W6t1!C7w$@$(oqc*QnSdl!afy=-?zn7oHYVfnDJ+ z1B027rnM~Cix``PS)8`S9394Bv9G&Tnpk;42UWN}0N{i*nPFfSzvrb#Yltcg5F2;| zZ!JIIkQQrmebjA|45DBR!C#W%or*e-Nfzs?q=?%FMo)(6fWK3`mCtG3!r8jGyFdf1 z!^bZbqr)mEiE&s+AbZ|#S zk8j#o}{yztVvEw1Gg^AbEM6JzCcZs4G)!z znm0LgociQ7UFn-2_@znx9ew%FQK(97Y?}EH+!8kk@>g?asx!{Hfm>4PRL3HooPnT3 z`R~t-=w*ndC?#8>Lp+lvm45j24C}UKqU0#fz(ty!0jH7(!B|j&J8CdT@l-N-6uAHB z9O@kq5YQi7kPo2$s9frM5+^cpWa2b31DP^B+7$GcLziSee&G~|@Q)a~x^T6ihaP@| z`aLfHe=7O<5P3+J0n7)heT$WI`s5(_ItK{1&nahC_QX-S*p+aGdurodWfzlF?^71P zX3=1=%%a5tzmY3%n+1}Fr=UzW^+H#D-mgILE}ooO>jY2F6SZpXM9sxBU7M_(ti3A7 zdt9pH_g~*{&GK4WeA4&v8!ztM>5hZnA*^zV;2r_bqiYC4hl5{??9CR_jpjaod9){{ z*U>)6f}YQtQ2;w{5-47zakv;Qdu M`LWvh3)zhGKVcOh-v9sr literal 0 HcmV?d00001 diff --git a/eventlet/httpdate.py b/eventlet/httpdate.py new file mode 100644 index 0000000..e1f81f3 --- /dev/null +++ b/eventlet/httpdate.py @@ -0,0 +1,39 @@ +"""\ +@file httpdate.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 time + +__all__ = ['format_date_time'] + +# Weekday and month names for HTTP date/time formatting; always English! +_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +_monthname = [None, # Dummy so we can use 1-based month numbers + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + +def format_date_time(timestamp): + year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp) + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], day, _monthname[month], year, hh, mm, ss + ) diff --git a/eventlet/httpdate.pyc b/eventlet/httpdate.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53fc2bc5c022fab4d11fe452efb33a886292a29d GIT binary patch literal 1991 zcmbtVUys{F5T7LX*IY{}r7h~q=+H?iGIv!$MXC_0FL5p|i5=Nak2@hnj&~EAI(Fn; zUleqC0`Y}-rwhe3t*eeq=ToS?)_>Hm+Y z|&{)EmmbX^~KtMN-T@WRePFQk6NSq^6Ju zE2w2US*rMo*Mb#Its&2Qw0A0lTRyRi? ziLwmGeA6(>o92cx-OwR+)mTWn_%zCJUX(iDe`y|5{;rLI*9p%p-$p0Ef$zO@+IAZc zt$_4H6VIKn>kUIB58rab3-mf@xfl3@jn@ zffv}6zD=;M<92+?V)tz~Jf^Hm7<8>(Po*-fAyx0I`mp5EHCY@&OH+Nm0%sIZ^{=B-0s}4KntNT+6Up>*t)U^`z3WQ>I!5kzC7a?f;g( zM=HL<$6EHIQcG*Tmi=g{^|v?vRkl8_rdkGkuB8{Raoyrd>uny#HNrLM-KV!jukdJ* z0BH@b0e-E)Py4W{!Kx0c2CTMVwGFEsSna}U4^}joYar`zy$hdTH-JE|>$IcLEx6u> z>m8Wv!sDP>Crnp-LFT06Yrc$LMqP*$XH=ApiC5>pgO=j(>nG*n&4Ui;S%w}2$p~Zqp z)s>}Qe`(Sd^der-?qn!vUZ0d~anjxlU~wYK_=I24EY7&R+x+Vv##bPUzfg3b_GPV6 zYwprcs%12ySw0&1kM0aKOvvz98B{0SBIo`S+GwD=lKyX9VvjJtqXAxJ{CkBj)PM9& Q{h+?zct$lotUay&12{_SBjmRKx#!jcnnbZYEVW1ks&X6*A_$|*~pvg8>{&RFuC@I>7M zf_<}x_?M#K@K(ukrFC^tJy@^PMz3TI16j(uw1jERm6oMt^=0)?i`$ikZuSDce);m% zS7^SqVJym=mk@Hr(>hz(;FZ~hHRbhMC?#YGLcxmHeDMIcby{kk+pw%T2f2i7mDac1 z1}z~iAE4qjLIJtZsVGHx3n^sS*1|}=!ZJ!O^?h1%49+1{N@gO(dXUSkS@Tk-n)(qZ z1zo-3a2{`&IZq2qlvs_!?g;J$GHx^w(^_Oy!v;}iMUxYT-Ekq-V%vezjTXv6 zf^5-IZBujKk;CPG=urY^B1ih>cjM9ECYw>;D2A>>P06!M;^WwOi#@m1ehR*XmK zLf&!1WpmY~)CkqcZ7#ro*xc&&NUhSMfCb+QhJ8h8k!wc>*)Z(~C>X}qPUwOc!88oscmsC;=T3zCa~rO`cofWHz=+WC;~NNu;P^N2qvsE7aDSSH zZWKWfTHa(j_FVLNet$e0c>Wb!VxAwwF!m;1j78%BsNuGh=VIOvCT`dtVP)si8+-AM zZ4JHHr<%hc1P7*07<>KM*a=}e3#UQkV*3G>^*w(WVi$Mf`tb#JMIX3ta0AiE8IP%z z<;)O$Ncf;1Om9N(Y81mL7!O=@Ub=|Pxg5KjR!FKpcD#uV183q~5xNiptP)xjwm}Ei zBbQv%&%tj$_5z=1^aDQ*acd*XF#eEw?M1E)PUuA>$S@2hwncJc23E%+nCH8jDkQZL z=);X*2njYr(mO0LaGf#M!t3!ry1olb{EeS(3+Ig9M?=#k^E2Ah)5fQdkNN`5FN{@3 zI4c>8$`)(s787l9zHi~AqG-R{L920GF<+KkTewhST@{=Lm#JUKG*_E;6n4K#br;{v z81)KQy_v${(yR0P-e5Z|R8Q4ekKf_oD7b!H{A(QQV@y@)lFzBbm$aeT$y$f4qn0|u zRa#Z}Yc_+YM~U4}(5dOaVLr#PG@t7uT<@BMxMm^I4@nkNeuUaYjnLdM$(wbh%t{#_ z(OK~{*QCKM*XCU{S`&_(BXDk%sie{ysWEtNTo}DsVEEiX+QiRI93{*jMf$%=DWds3 b&cCMMKYWU7b%N%gebzqd93Onv{-*s8(47GX literal 0 HcmV?d00001 diff --git a/eventlet/kqueuehub.py b/eventlet/kqueuehub.py new file mode 100644 index 0000000..595e7b9 --- /dev/null +++ b/eventlet/kqueuehub.py @@ -0,0 +1,219 @@ +"""\ +@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()) diff --git a/eventlet/kqueuehub.pyc b/eventlet/kqueuehub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f81014a148ad0fd50c6ac2359df31ae4851fe9b0 GIT binary patch literal 6809 zcmb_hOK%(36}~g19vnR^`3WaZI(FlR0T??-A8FGxFfEQOCKM?QDa#fd5Y%uejYZ8+ zGs7rC#Z7G#Sp)^zO;O|@bdgQB-F2Nm(Op(WyDrdv-?@B9t{W7MEX~!ucg{WcJimMH z_)q^>uwMQ3pC5Q?_-_t>@8L;)ho-I6Hd;fqO|@^RZFF?sRNLmLpHbVHQ9rA;v(nF~ z{VCO*R@>9c;F_%3pHbaewLPoHQ))k_x|Z6uRCi8o&*@q0GOxDhReM_b3#x;4GwQig z&y;$+y+|SI`B}<_P1L ziF(Izu)CMqdG9Uz-FM#k$=hh&E7+AFZ2O_@`ib9)y}g264!t|ph94gUNfJb%9VGUi zANxDU_HNt?Q@>rXJF)NEQOEZ7TJf%5u+zwHg~xW!j}t72cG6Z52H~#VvOR2Tfim3# znI*FW&Xw3Tu{DwWvzbkDbM)rTu?y(OEp{Z`isLU8l# z(JT8XfX4mQrlxV=kw?J}La*CzQ-;y&Zg3C`ci?idMPfnnegX|BXTd&*+ChhZeNn5| z-{}SkOwta>dZ(X)GU24>hg=0c?!wrK-|br95I}Cxbt0Y6&_1vY_8*!k;mpxqbZ}Oa zAhA0AIK-AP3MkqUOeQ-%^}Uocl)MvlyU`I<^P;dFP~YTz%WB|NYbW~D7hSZg!zhKQ zVs3GO2%)_?e3k6Ax?Ow69~un%2BF2Nkvh<>zmueJe9-FJy(pIbUR3iA_Fr@CdTq6_ zRdgM@T(>ve+QV|mDcQG*b&PKn?5%QRt+v^)LE#pwjmLIv)h<>a+aH&!rGo7|+HjqE z-LAP-d3~c&b}(12u2eQl<>~|bKGs!h4ZBiaFE_xnQM0+>kX6~ix>bAKaaY#Bxp==^ zDK{P$tkrU(N}j7V*Dl%{MYmC2*{l>@dt=kxsMQ^8Ujo}|xw`6N7iZn6Htt|o%-POE z4D9+^u~OkyR&f*3yOhsfsck%V%MaEX_FAn{axi({fn>$|6-RFcrB*7%@_NB871xUo zD4lE9z{RylrlqsD)*Mc8zaoBD8s%D*YOK_%4Hv@#v~(NerCa5?Q?QF}xlV(uy0!I! zMRQ^WxPuASRUOTRrWON@JpvLMY!gb4WU)(5u>xLjPxXxZ?pVRU@UNOTfHdh88s~no zw5o?hpIzjC*bR2_CJAQHxc#saMLi*I1jwH>gW0wI&KOmJSL!~VBt+u_RRL{^(57LO z9bl*_GXT(xbf#3>8vSO~bKvZZYEQ|lSwbXN<%SRm9Bt3a%$zLAspr7!dF9sgM0AhJ z@S)5rc#`+gq)MGA6&UK7!A{dudot6^j5?4-&rFXx5J3Tk$*U@0?1~w(7$3EQRO}J= z!!GTygvqv_cyZ86qqr$hol>01WRh}aH46Q7Vp~5I1MYX)+Ug_$%yjbjtE5TZL+|cp z0?fS|w~y|Yh9H{UO=9n^|0(da>!*{>>0?4RYBmAVX|s8otS$02E*d%c8QY3Z6kE}0 zG?bh~hBg^2WonzTH54QcvO+aQa~iM`Urfo=j0$FXipkoXN^>f(IDVQ_(W0K6llP%s zIkN9OHcl7N^y0|#lSG@)m6)TsQrbRBB=Ed$l=v>K>*m-@^F6~CxEQA{bxhgp`l;8C zV^}C9NrEH7+Y>vx-0qxgEG|oA#iI`>o=zLUhuYmuLxhsWGT6A`4y%jtO z(r74p@3t~hLj@+CN%!j)5*nyx@H;S6I3vXlddF9X>%bvE*kHl5+6phJEYj$yr6N$` zUuY`h5!ucT$zh%^3?w0?sA?_6<#ewru*J+v4N6$Y)m~0*XQDteaEHj0<@tUyb)HHb`V>HG-L$-hz z>_%8BW<^fIU}Da(P30u`*gw`GPZf2J&?@!E^@g+FbRMlZ8>~(0VygsaSl8QYB5?d3 zi$MvaHxHX=0a2Vr^+f3r@Bg{ySs4TPS3bIJI7LYb$%o&#r zj~-+SFgL`DJLnK}1ZTg1ec`hpqq2-i!Ktfe`J>}yaWGCbsSu2<@GZD)V*Np8vQF|7 z_{MeOM>w%AcBm%Tu$jvsW7$3t{C)d&oVRdV(k6iK^g- z%kac9PCq&Kh^>4fp7tZDL6`>7?~%g8i2@BCzg7GJ?7M4UJVx12`Wis zB1HNp_Q$1M5B`S1!f5bU#%Ehkt%t)6A1j1t}T-Y=m9s+^@6$u$&Nwdd(Eyb;!Z%9@`O?TDVhA2L z0Um=3*!Ke8UPq<+G$Y6Duar9cyER`lMI+2FIxnAQFgE^+3{D&1u%Q{uYX*ALmlzGHidy4; zH1AnTnD;OL-Mn#TWZpk~i+QOXab0Zri~kG}eG2FZgY*wTjmSVaN_fGG9c;5KUbN&0 zpvyWL7D|llizG}hso*lt4LJ-tnMZUZp2Ha7C+0=8ivS;Bc59dsSLCFD4~CT$auSfu z(q~du3$W%(J@*#3BWbJG^TW2>v~Z1=at*V}{XUyF*bo9;K{X=2 z*Tu)JJW)s2MMS!`uyva5@b8x3-%wf+kt-ca=>Cu^Ku(a=Y_941>q-^JV&E|tCx(Nsc&y+rMIN!QCYqT+`YlN5*6lx@tQm{ zn7f8G%f@A?JFm(61->(RW{&>8NX>O~Qls)*UBnM(UN`2^^GwJ=sm8eecUYNmHM}#* zeSdV_6G-|QuL4742Lc1oPu(B^IdC;=A32Z#UjTZtlPZT;4`P5eupS*tupS*i_l){1 zHBc*;KIt03dw?LI8Tii50#Tkg1_&79*a+;m!mBvj7f+?t;XvtB90N%MP=>rg0bq{v z5{W#@%g>E00G#ML$;;1~mXenjBrlg#usqJo&ykBBc#d;@69b5e6rNG{UXuC$*A6uC z_y8HIFnwBu(a8m9`Q{}meDiH8e1lVB0B9MgEyUvxH{d*!5?_B^)H%nn&{uM)FyH2I zoKliaxS~kmsWbQv3GSj90ftjs6%+~Zsam55ZZ&`hL4+3*o&eG5bX&VgUN&Yx3REOM zznE$`X?vQFU{0d9X=CxoU;0{1c- z-t^^us@-LC$m-F3lg%4!#HN&XqPXf|Gyi&9kS r4@Kl0?eN392zQRkCbMAufj0h!ycx)MfN66cILe>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): + if not self.readers and not self.writers: + if seconds: + sleep(seconds) + return + + timer = event.timeout(seconds, lambda: None) + + status = event.loop() + if status == -1: + raise RuntimeError("does this ever happen?") + diff --git a/eventlet/libeventhub.pyc b/eventlet/libeventhub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6092c4238624a51649f1a76989e5dd9971980b22 GIT binary patch literal 5117 zcmb_g;d0x?5k8Or!b^(f(J)QojDBba}*xTFvc5iQwfB*NY{kMPq z>ya;t&lZtQs=}f< z6>+jGqM8`g^5-RSQWude2DXTK&XR$mrx1kHLnxJ{!8}@-tPeWtEB$lC(6P@bOxjat2IMYE>j#I5=GM4_tOOJI^ zW{LFTb2-(i!GvU#d0`yJ$I_EN^s>R3O(4c3_@YJbeO-6!qc!= zfu=K9#)jrI0}DvcraVo8a7^Et&6>_eQD^{35R&N8ECZ(@qOW6`1v@t33ZtXQhJ+A$ zv#ks5n1}EIHdw!a$PndhlAK<)BsBJTmd02TpnxMt02#0NO#2y8Nb@mbAvq({{3H%S zve$fN+kL$9M#*!{w&bx8CmB>_xY+@$MDEq%m6>=^BuBac4C{umO{BRwux>Uo85|#a z5#liA_1-me1MBa#Wv{#2KWeGAbb9hYbssxB?Hzfi)x-0hraW@`d)>pn1cz$5{im|K zD_ibU`Hkc5G-dnAfok`9va4)o|DfZvL37;g&f$*ZK9XCQ=XU$D+zEm(+h z+L*U1_uFcF50YD3PRHp#ZQ8p|-zAy5T_s!cpr!iG_F<=`G;d)>}X8^oU;IHX6I*CT zqeG)yEhz2fiJAt;(j}%KMVEQ7B!c?+8mYRCW`2t<6XK;1p(S2hG#Hk|gek-Fd;k@4N|g)@Yn;M;s#qYf#G z&97i|=7kyKkj~4nHH|1d*_AS#|z< zAFR*unvlxD0xLa~L{<`EnIL&q7s>UUT49J{3n%3QCJG)^MaMQGKc$(XH8bjtb>`2~ zl#VP|M1VXa#07F$bUO0A1#Zlk-f_9a52B>uXvg}j~H zLfRQ0S$Y=fp^tRgAQ7xIdl09h0YpNaoL!X>igbHO>yliND^EkRXB*8RrVHh#_k@*M zVx5-*nu$SsV(I4&_zWdV){v@c$kFD6%ri0A(@xjdf?LB-^^HWg{5uA$>#qcp?W z)(Zkfd55CwNvrB_g>(zwq<#=h;=^n_Y)os5A_;@zZdc zCFwpWhCD8;W$QyA{!uCa_9=u2=L@VA9$tJi4>U#_aPEBjrFaEYk$uqf0rI*eUI9iN z9eFak#V{kVe~e&9C|lp%=61yRai}7h0Ts?nd~^{=Z2dHuDvDsnDbPryxzZFUlgc-#zpN*H%qdo1u1DyHK$n}!*`}3QODsNU) z3f574UsMnSloUV4oeq(AF%JQWU7OF#Z+dic(7LYq2)!cTQq{iL4`0c9srd!owO{yp z%9o$B30?O%4leV40$v6;;|N7k)gvU<>;|S!(=g7403sK{p=r)deyC=sK~i-OY&`Q~ zsdporb^&1-#^VH89uHUMCJJqXT@(3}g_dI?uArmC_yeTfo7P$$+~g<1jr}ykAU~2< zk_IA^Q^5+M4>wWi0lyWoIxjuw8yFy#@dEyQgR3=S990X5e7+SKqa>aoov(qrN(Co8 z{1=>y^8$v{aD8C`Uw8q}49uB!c7!Gk!r_yVv zXsataz_~u+Gd)j6l4un5iZ+dI_XnqB5QWOloM&lZyYSowPOZ9Sn<<_-&wO*^& z8dR__n8P7{*Wg=-q79Q* z)-+0W#7I%4k9&Lgr~JZ!!zE3_Kxb4uIHz=*&+9BMCP=MhL_amKT~~^fsX_RI+-(nk egt3w6s&I4uw^mw1^|ewegJL5DP=S?K%0B?!(0>O2 literal 0 HcmV?d00001 diff --git a/eventlet/logutil.py b/eventlet/logutil.py new file mode 100644 index 0000000..0885042 --- /dev/null +++ b/eventlet/logutil.py @@ -0,0 +1,112 @@ +"""\ +@file logutil.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 syslog +import logging + + +def file_logger(filename): + """Create a logger. This sucks, the logging module sucks, but + it'll do for now. + """ + handler = logging.FileHandler(filename) + formatter = logging.Formatter() + handler.setFormatter(formatter) + log = logging.getLogger(filename) + log.addHandler(handler) + log.setLevel(logging.DEBUG) + return log, handler + + +def stream_logger(stream): + """Create a logger. This sucks.""" + handler = logging.StreamHandler(stream) + formatter = logging.Formatter() + handler.setFormatter(formatter) + log = logging.getLogger() + log.addHandler(handler) + log.setLevel(logging.DEBUG) + return log, handler + + +class LineLogger(object): + towrite = '' + def __init__(self, emit=None): + if emit is not None: + self.emit = emit + + def write(self, stuff): + self.towrite += stuff + if '\n' in self.towrite: + self.flush() + + def flush(self): + try: + newline = self.towrite.index('\n') + except ValueError: + newline = len(self.towrite) + while True: + self.emit(self.towrite[:newline]) + self.towrite = self.towrite[newline+1:] + try: + newline = self.towrite.index('\n') + except ValueError: + break + + def close(self): + pass + + def emit(self, *args): + pass + + +class SysLogger(LineLogger): + """A file-like object which writes to syslog. Can be inserted + as sys.stdin and sys.stderr to have logging output redirected + to syslog. + """ + def __init__(self, priority): + self.priority = priority + + def emit(self, line): + syslog.syslog(self.priority, line) + + +class TeeLogger(LineLogger): + def __init__(self, one, two): + self.one, self.two = one, two + + def emit(self, line): + self.one.emit(line) + self.two.emit(line) + + +class FileLogger(LineLogger): + def __init__(self, file): + self.file = file + + def emit(self, line): + self.file.write(line + '\n') + self.file.flush() + diff --git a/eventlet/logutil.pyc b/eventlet/logutil.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f024296496ce0fa39234cb598952101fd3db1020 GIT binary patch literal 5419 zcmcgvTXP#p74DH`#hy5qg5VU_^m>6+Kx;2lL8^dCMYd#Hg)F&RavU29F_v1^B=U@z z9>wdb>{GUQ;(`CbPvDhb#UFt0J2R3mz!tkDP`TUPr%#`IpL6D~f4}E{`;ULy>8t#= zi2pAz+@CNFlsd-jsX;-#@zgOE*}9;P3u>^SJ_o{r8Wd$wRD(rj7u66rB|_dSb#z=( zgFDKW1iCbX-cf_)Db$}qm(<|y6uL5lE|Yzye2-GSDnh(rIJSBmpH9+nyncS^zX&Gj zC{A=Oj^ejLr1ul+(m3+{t@!*h2~S6j@qr$;uiCztv(38K^vDtee$tK*^WkAmdXR&*NcAiC7&HgUKRpQJ$; zh0&=FbRTSeV5TFGaq%#{2oekAfexID`(XfjdJy*~XEsWMl>DfY)8%wz^;##tvG!D$ z3~Vs=!wA%@o*wCo5E@TXO-+-qPZ|{+M*Z<*KpCdT{K( zxjKqdh$`k52Z#`vSMwt`3dUo7VsnFmZy5OmP1S*RlM|Q1@nJC5=W!zZZmYQt{=0SE zX>WHAtER3S9ldYbFB`RbO|Mlu*soReVWYd-KIm#-m};wgq}$uN+B(v|ZnSC@U4ON2 z>Ya{m8^5u)-)z(YYqYkS2en3PM{nX@tKHSj#$KZfqTRM8!<LAkow zXg0b>6@R!4XRdjG)ew>x#PuYqi<(b_iPQs1k$y6fNyn6AIX zMt63r%_dp-)dNUxD4*VH?;n}Q&Td!lwwtv&fSYwlR^4pYGgeS)t66RARdlVoSKXm> zMz=x5_(aR3(}%ltg2=CmzpZYg-J%*>?N--dTY;9QJG*+==+rB^Y8o9HWZSg&Dn8AL z8=wv%xYw#@RA_23(99!1p}`KI^i&pIt5=(#1^2XWxNqGL|AAkt?;+11%cugz7IV(; zFr`X`o_dFz=Bdv-*%p}JJ}anqo(dP#J8V7W5wAGK19U-!i^Sj=Hzm1xXS&D9khzUK z-2oXU7TEzg1nKS6ChNK@Ib_m*UK-?FEjq_5v-#q8u$L(=(KrA17wII^;^C|6ftl(?b!I?m@}y(JLp_0S`vz1}md9XTr)Y^DKyD;oR+ z3*?$&D2hBQ^rQ&`1c28i72k(Wl91kmz)UHs({fBDex`%iD85L-)XKvgjwfy;N9GZ* z1m@Doa5xwJ7>~m1msmJPqQYa#pBiOjFp7GA!Qyh28bTkWRro$jtB28=Dy9YX+E?+a z^6*RXzyvKrhziPk^=Uw_#YdSDkX{7=PS%oTr#l4F<0m+S@%}xtKRll?TM}mbfOxMs z9ZHleK+fEtkP=}auWc18F&%rzPbJo02IGmXCkYw_-a)>TXzC^to5c^+&umVgMD}8g z*D(*Po5!H{0>e=(tadQu1>Bb7%2bpRt+tzoK;njd^2}sum6;!276GT5yy2 z8q&=BW^+bFK^3xO&RMGajWW(V ztm6EiUc?gfVsrBp5W`MoZZQ&o*zFjh@FFjx5h7-ljRC^WANzuQKxF%C0YiB+5$T>x z&RM*e9|ATPu}K_Ur%lI0Px?hOiXcMS^C3^@J3~rW mPL$PGFTZXH)P9u}{^!i`j`g@ySn*aCP=xQUK3QE^UH&IY%e>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()) diff --git a/eventlet/pools.py b/eventlet/pools.py new file mode 100644 index 0000000..5e89dc4 --- /dev/null +++ b/eventlet/pools.py @@ -0,0 +1,184 @@ +"""\ +@file pools.py +@author Donovan Preston, Aaron Brashears + +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 collections +import os +import socket + +from eventlet import api +from eventlet import channel +from eventlet import httpc + +class FanFailed(RuntimeError): + pass + + +class SomeFailed(FanFailed): + pass + + +class AllFailed(FanFailed): + pass + + +class Pool(object): + """ + When using the pool, if you do a get, you should ALWAYS do a put. + The pattern is: + + thing = self.pool.get() + try: + # do stuff + finally: + self.pool.put(thing) + """ + def __init__(self, min_size=0, max_size=4): + self.min_size = min_size + self.max_size = max_size + self.current_size = 0 + self.channel = channel.channel() + self.free_items = collections.deque() + for x in range(min_size): + self.current_size += 1 + self.free_items.append(self.create()) + + def get(self): + """Return an item from the pool, when one is available + """ + if self.free_items: + return self.free_items.popleft() + if self.current_size < self.max_size: + self.current_size += 1 + return self.create() + return self.channel.receive() + + def put(self, item): + """Put an item back into the pool, when done + """ + if self.current_size > self.max_size: + self.current_size -= 1 + return + + if self.channel.balance < 0: + self.channel.send(item) + else: + self.free_items.append(item) + + def resize(self, new_size): + """Resize the pool + """ + self.max_size = new_size + + def free(self): + """Return the number of free items in the pool. + """ + return len(self.free_items) + self.max_size - self.current_size + + def waiting(self): + """Return the number of routines waiting for a pool item. + """ + if self.channel.balance < 0: + return -self.channel.balance + return 0 + + def create(self): + """Generate a new pool item + """ + raise NotImplementedError("Implement in subclass") + + def fan(self, block, input_list): + chan = channel.channel() + results = [] + exceptional_results = 0 + for index, input_item in enumerate(input_list): + pool_item = self.get() + + ## Fan out + api.spawn( + self._invoke, block, pool_item, input_item, index, chan) + + ## Fan back in + for i in range(len(input_list)): + ## Wait for all guys to send to the queue + index, value = chan.receive() + if isinstance(value, Exception): + exceptional_results += 1 + results.append((index, value)) + + results.sort() + results = [value for index, value in results] + + if exceptional_results: + if exceptional_results == len(results): + raise AllFailed(results) + raise SomeFailed(results) + return results + + def _invoke(self, block, pool_item, input_item, index, chan): + try: + result = block(pool_item, input_item) + except Exception, e: + self.put(pool_item) + chan.send((index, e)) + return + self.put(pool_item) + chan.send((index, result)) + + +class Token(object): + pass + + +class TokenPool(Pool): + """A pool which gives out tokens, an object indicating that + the person who holds the token has a right to consume some + limited resource. + """ + def create(self): + return Token() + + +class ConnectionPool(Pool): + """A Pool which can limit the number of concurrent http operations + being made to a given server. + + *NOTE: *TODO: + + This does NOT currently keep sockets open. It discards the created + http object when it is put back in the pool. This is because we do + not yet have a combination of http clients and servers which can work + together to do HTTP keepalive sockets without errors. + """ + def __init__(self, proto, netloc, use_proxy, min_size=0, max_size=4): + self.proto = proto + self.netloc = netloc + self.use_proxy = use_proxy + Pool.__init__(self, min_size, max_size) + + def create(self): + return httpc.make_connection(self.proto, self.netloc, self.use_proxy) + + def put(self, item): + ## Discard item, create a new connection for the pool + Pool.put(self, self.create()) diff --git a/eventlet/pools.pyc b/eventlet/pools.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9774d6c6fa292036ea644e0f77c8c8c5ee51c774 GIT binary patch literal 7640 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 literal 0 HcmV?d00001 diff --git a/eventlet/pools_test.py b/eventlet/pools_test.py new file mode 100644 index 0000000..e604bc1 --- /dev/null +++ b/eventlet/pools_test.py @@ -0,0 +1,179 @@ +"""\ +@file test_pools.py +@author Donovan Preston, Aaron Brashears + +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 unittest + +from eventlet import api +from eventlet import channel +from eventlet import pools + + +class IntPool(pools.Pool): + def create(self): + self.current_integer = getattr(self, 'current_integer', 0) + 1 + return self.current_integer + + +class TestIntPool(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(min_size=0, max_size=4) + + def test_integers(self): + # Do not actually use this pattern in your code. The pool will be + # exhausted, and unrestoreable. + # If you do a get, you should ALWAYS do a put, probably like this: + # try: + # thing = self.pool.get() + # # do stuff + # finally: + # self.pool.put(thing) + + # with self.pool.some_api_name() as thing: + # # do stuff + self.assertEquals(self.pool.get(), 1) + self.assertEquals(self.pool.get(), 2) + self.assertEquals(self.pool.get(), 3) + self.assertEquals(self.pool.get(), 4) + + def test_free(self): + self.assertEquals(self.pool.free(), 4) + gotten = self.pool.get() + self.assertEquals(self.pool.free(), 3) + self.pool.put(gotten) + self.assertEquals(self.pool.free(), 4) + + def test_exhaustion(self): + waiter = channel.channel() + def consumer(): + gotten = None + try: + gotten = self.pool.get() + finally: + waiter.send(gotten) + + api.spawn(consumer) + + one, two, three, four = ( + self.pool.get(), self.pool.get(), self.pool.get(), self.pool.get()) + self.assertEquals(self.pool.free(), 0) + + # Let consumer run; nothing will be in the pool, so he will wait + api.sleep(0) + + # Wake consumer + self.pool.put(one) + + # wait for the consumer + self.assertEquals(waiter.receive(), one) + + def test_blocks_on_pool(self): + waiter = channel.channel() + def greedy(): + self.pool.get() + self.pool.get() + self.pool.get() + self.pool.get() + # No one should be waiting yet. + self.assertEquals(self.pool.waiting(), 0) + # The call to the next get will unschedule this routine. + self.pool.get() + # So this send should never be called. + waiter.send('Failed!') + + killable = api.spawn(greedy) + + # no one should be waiting yet. + self.assertEquals(self.pool.waiting(), 0) + + ## Wait for greedy + api.sleep(0) + + ## Greedy should be blocking on the last get + self.assertEquals(self.pool.waiting(), 1) + + ## Send will never be called, so balance should be 0. + self.assertEquals(waiter.balance, 0) + + api.kill(killable) + + +class TestAbstract(unittest.TestCase): + mode = 'static' + def test_abstract(self): + ## Going for 100% coverage here + ## A Pool cannot be used without overriding create() + pool = pools.Pool() + self.assertRaises(NotImplementedError, pool.get) + + +class TestIntPool2(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(min_size=3, max_size=3) + + def test_something(self): + self.assertEquals(len(self.pool.free_items), 3) + ## Cover the clause in get where we get from the free list instead of creating + ## an item on get + gotten = self.pool.get() + self.assertEquals(gotten, 1) + + +ALWAYS = RuntimeError('I always fail') +SOMETIMES = RuntimeError('I fail half the time') + + +class TestFan(unittest.TestCase): + mode = 'static' + def setUp(self): + self.pool = IntPool(max_size=2) + + def test_with_list(self): + list_of_input = ['agent-one', 'agent-two', 'agent-three'] + + def my_callable(pool_item, next_thing): + ## Do some "blocking" (yielding) thing + api.sleep(0.01) + return next_thing + + output = self.pool.fan(my_callable, list_of_input) + self.assertEquals(list_of_input, output) + + def test_all_fail(self): + def my_failure(pool_item, next_thing): + raise ALWAYS + self.assertRaises(pools.AllFailed, self.pool.fan, my_failure, range(4)) + + def test_some_fail(self): + def my_failing_callable(pool_item, next_thing): + if next_thing % 2: + raise SOMETIMES + return next_thing + self.assertRaises(pools.SomeFailed, self.pool.fan, my_failing_callable, range(4)) + + +if __name__ == '__main__': + unittest.main() + diff --git a/eventlet/pools_test.pyc b/eventlet/pools_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d25fea626529f31479192877d27eadc77e6fa97 GIT binary patch literal 7575 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 J%@5bT{{q$b>$m^_ literal 0 HcmV?d00001 diff --git a/eventlet/pylibsupport.py b/eventlet/pylibsupport.py new file mode 100644 index 0000000..1378e7a --- /dev/null +++ b/eventlet/pylibsupport.py @@ -0,0 +1,42 @@ +"""\ +@file pylibsupport.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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. +""" + + +from py.magic 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 + + + diff --git a/eventlet/runloop.py b/eventlet/runloop.py new file mode 100644 index 0000000..3fde1a4 --- /dev/null +++ b/eventlet/runloop.py @@ -0,0 +1,228 @@ +"""\ +@file runloop.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. +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 time +import bisect +import sys +import traceback + +import greenlet + +from eventlet.timer import Timer + + +class RunLoop(object): + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + def __init__(self, wait=None, clock=None): + if clock is None: + clock = self.default_clock() + self.clock = clock + if wait is None: + wait = self.default_wait + self.wait = wait + self.stopping = False + self.running = False + self.timers = [] + self.timers_by_greenlet = {} + self.next_timers = [] + self.observers = {} + self.observer_modes = { + 'entry': [], + 'before_timers': [], + 'before_waiting': [], + 'after_waiting': [], + 'exit': [], + } + + def default_wait(self, time): + return None + + def default_clock(self): + return time.time + + def default_sleep(self): + return 60.0 + + def sleep_until(self): + t = self.timers + if not t: + return None + return t[0][0] + + def run(self): + """Run the runloop until abort is called. + """ + if self.running: + raise RuntimeError("Already running!") + try: + self.running = True + self.stopping = False + self.fire_observers('entry') + while not self.stopping: + self.prepare_timers() + self.fire_observers('before_timers') + self.fire_timers(self.clock()) + self.prepare_timers() + wakeup_when = self.sleep_until() + if wakeup_when is None: + sleep_time = self.default_sleep() + else: + sleep_time = wakeup_when - self.clock() + if sleep_time > 0: + self.fire_observers('before_waiting') + self.wait(sleep_time) + self.fire_observers('after_waiting') + else: + self.wait(0) + else: + del self.timers[:] + del self.next_timers[:] + self.fire_observers('exit') + finally: + self.running = False + self.stopping = False + + def abort(self): + """Stop the runloop. If run is executing, it will exit after completing + the next runloop iteration. + """ + if self.running: + self.stopping = True + + def add_observer(self, observer, *modes): + """Add an event observer to this runloop with the given modes. + Valid modes are: + entry: The runloop is being entered. + before_timers: Before the expired timers for this iteration are executed. + before_waiting: Before waiting for the calculated wait_time + where nothing will happen. + after_waiting: After waiting, immediately before starting the top of the + runloop again. + exit: The runloop is exiting. + + If no mode is passed or mode is all, the observer will be fired for every + event type. + """ + if not modes or modes == ('all',): + modes = tuple(self.observer_modes) + self.observers[observer] = modes + for mode in modes: + self.observer_modes[mode].append(observer) + + def remove_observer(self, observer): + """Remove a previously registered observer from all event types. + """ + for mode in self.observers.pop(observer, ()): + self.observer_modes[mode].remove(observer) + + def squelch_observer_exception(self, observer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Removing observer: %r" % (observer,) + self.remove_observer(observer) + + def fire_observers(self, activity): + for observer in self.observer_modes[activity]: + try: + observer(self, activity) + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_observer_exception(observer, sys.exc_info()) + + def squelch_timer_exception(self, timer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Timer raised: %r" % (timer,) + + def _add_absolute_timer(self, when, info): + # the 0 placeholder makes it easy to bisect_right using (now, 1) + self.next_timers.append((when, 0, info)) + + def add_timer(self, timer): + scheduled_time = self.clock() + timer.seconds + self._add_absolute_timer(scheduled_time, timer) + current_greenlet = greenlet.getcurrent() + 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 + + + def prepare_timers(self): + ins = bisect.insort_right + t = self.timers + for item in self.next_timers: + ins(t, item) + del self.next_timers[:] + + def schedule_call(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = Timer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def fire_timers(self, when): + t = self.timers + last = bisect.bisect_right(t, (when, 1)) + i = 0 + for i in xrange(last): + timer = t[i][2] + try: + try: + timer() + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_timer_exception(timer, sys.exc_info()) + finally: + try: + del self.timers_by_greenlet[timer.greenlet][timer] + except KeyError: + pass + del t[:last] + + def cancel_timers(self, greenlet): + if greenlet not in self.timers_by_greenlet: + return + for timer in self.timers_by_greenlet[greenlet]: + if not timer.cancelled and timer.seconds: + ## If timer.seconds is 0, this isn't a timer, it's + ## actually eventlet's silly way of specifying whether + ## a coroutine is "ready to run" or not. + timer.cancel() + print 'Runloop cancelling left-over timer %s' % timer + del self.timers_by_greenlet[greenlet] + diff --git a/eventlet/runloop.pyc b/eventlet/runloop.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33965f6bb11e0fcc7e8567edd8b396c3c4e9e344 GIT binary patch literal 8842 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 W*AFPmlpM^LmJnv(x3pARDgO<1hU= 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() + diff --git a/eventlet/runloop_test.pyc b/eventlet/runloop_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bf430a7bf8cef9aece848cf93d031d61166a71f GIT binary patch literal 6357 zcmds5?QR>#6`ds|Qe4Z58#`$}nsjQnDV;Kw5uhy)7>Q|$vWQZoI;7%QKtZg?p|m!+ z%kD0%sIZHo31S;XAE3|Czx|Qd=xg)^+H>wMNm+G}08;v+ly^LHXXbv*J$LTz-~V;d zefz6_-0iFEw}Ss~W}ws_T1O3X>Y=0d&`J9_wU?Xq^J*_Y>o2LjCF$qY!(}xp zsJ()62208=DZi+O*u1R1RO$<*?(eNATT+99dXQ7$nsT_r<%|`4wUT~}@Hj1$!+)q3|@Ph;G=|04|SeYDxO&kuBBQLU8JkVYohkf4zKRpQh z)3FT_FQGiz73*?xX!X@jw&Ut`5i+pe$n^tovwAkAk9=4>O*Acy{62Y9v>)_G(*bpu zO^^JspGn|$@kQ)H^JxqVsAolw!+}5K|5nVJO!r5A43`XivfiI2SQ(Spw*hy-j&(RY zwxf{?4nFi2TNm2N8u|ymf&9!xF)2rf;rK;M{Ma2%qW~h|6s#D8aG6MaVEYLv)O;9@ zM&S`n>xaR>r@iqoYU{rKO{29@(e=-EOuf_5ZR0k!cbbhlXpL5_xx3M5-PL!nuhs7A zW@Ee21=DU@Q((rbQOCYbyhX*3(%`xSSy(QT3EX4~kh-l>{yqqf_u8oje? zcG{gf#BYFYtI^stkW$~Sx4JhV6*OJ{6a(Gasy3Sxs zsDpT?4$Z1}n)Or^jH)%OjqQrwsBTy9QahvD;9^`ZOSRMYw(2BNUKRgp-A21bGivQt z*I-zIm8Lu2dau!`S9H}hI&{dUX>V6tx)VFV9ZayVRZqFl)#9M}iC_sGwhN6Dp#lLC1Yz5PB#7SWZ6|yQBwtO^760BiQ42PjjTu zt|MNfo_uO}VjBA`UW2l!*Pi6WsmtQQIrVW)y@9Lm!LmBxP5QU^4gNiVNc_J3M9x%v zY^aZ+Qw;qn?n(TyH9T-b3k<`rEXzt9ilJ-}4pGwM;_tzWWE;ItDXVBwFRsFm@1~w) zxKJ;uB}D;Dd;T(iK2>tTJL)T3@CddvB!Pu2BD2^`J^Ku01%LvG033itQ5`I^68Lua z(owBGwVFMbsTqS7OmWl@2l1WMQ-W$b*qcmjFp$7ZBc6Ob+@O#AH6_A7J9Frc+_T=uD3f$&{HwIRyaq zb%E-&MTpTM5@LTtXAaiqdBt#Z)KmP+si%20lv)!Z_B@@@R@`Q!f+OS%aSU&rjUU@q2QV%2`2Xo6zw=MsfSxL)1`PtYjsLI%imm4sXo*1ZIf?|}RQAW?+CSja~m zBpZ2>t(eH_{C;Xj{X@J7>B-1?aTci(yv&dF&--@5SAo+J%;1qTKQ}1ug5&oGMMJNg zHbNqrFq9BJ7fT2Yd^h+8$YCPG9q+0|X(VWikK?QW9@r>Cgy4AaIvN}_+Gc@EA}+S} zq0!{PMN&;g)*GZXlw=>d+nC@C6_P7A_Heh0AaEC3(*(OfuasM7EKq+)aV$`0mtW7$ zLg=@ciYYC{?kj@bl5;sH_|08(O1Zb3EB|{G1H!DEcsUAcQShEXadD1fyszNqd;!I! z8HyKIs5?jT1@06Sr!;b3%}|_D1dPnKtv=ntA45`#x&?{>UrI&d`0gl{dx$8OxHnhP zOOO0qaVv2pHFN|4{~zEzq9u#_QcBhn_a%$F1#ZnX4u8t#XKV=01y;>_9BMYisrfk@ zChhV9qrV`H7&SZ}bDgtzvR9$=#pe7{l_OgKGl0DB3QSr^?Q7igCp7cx6*np3^Gj5r z>1z+d6&+Ma0M-Ghd3C%d??Y+%c`wjv390LWtHLYF!4H=s?o^~eMtgu=pMfIyDW;f0 z&9#!@C#4MdPR~$Z;D_Dh-4&UBD#?YPdD2#hk9vv(rcx6O7F>sys.stderr, str + +def main(): + import optparse + parser = optparse.OptionParser( + usage="usage: %prog [options]", + description="Simple saranwrap.Server wrapper") + parser.add_option( + '-c', '--child', default=False, action='store_true', + help='Wrap an object serialized via setattr.') + parser.add_option( + '-m', '--module', type='string', dest='module', default=None, + help='a module to load and export.') + parser.add_option( + '-l', '--logfile', type='string', dest='logfile', default=None, + help='file to log to.') + options, args = parser.parse_args() + global _g_logfile + if options.logfile: + _g_logfile = open(options.logfile, 'a') + if options.module: + export = api.named(options.module) + server = Server(sys.stdin, sys.stdout, export) + elif options.child: + server = Server(sys.stdin, sys.stdout, {}) + + # *HACK: some modules may emit on stderr, which breaks everything. + class NullSTDOut(object): + def write(a, b): + pass + sys.stderr = NullSTDOut() + sys.stdout = NullSTDOut() + + # Loop until EOF + server.loop() + if _g_logfile: + _g_logfile.close() + + +if __name__ == "__main__": + main() diff --git a/eventlet/saranwrap.pyc b/eventlet/saranwrap.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54b303b2ac7fab2fd9508743efe6842e6ce1b478 GIT binary patch literal 26715 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 F`yT_>N4o$3 literal 0 HcmV?d00001 diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py new file mode 100644 index 0000000..60e2bb6 --- /dev/null +++ b/eventlet/selecthub.py @@ -0,0 +1,173 @@ +"""\ +@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.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 = select.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()) diff --git a/eventlet/selecthub.pyc b/eventlet/selecthub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..328465592bf32ae1217c1ce43f2f2382daf81451 GIT binary patch literal 5935 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 TpO&lThfcLzEmbS)+Z)a|($H;d literal 0 HcmV?d00001 diff --git a/eventlet/timer.py b/eventlet/timer.py new file mode 100644 index 0000000..a91390a --- /dev/null +++ b/eventlet/timer.py @@ -0,0 +1,83 @@ +"""\ +@file timer.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 get_hub + +""" If true, captures a stack trace for each timer when constructed. This is +useful for debugging leaking timers, to find out where the timer was set up. """ +_g_debug = False + +class Timer(object): + __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback'] + def __init__(self, seconds, cb, *args, **kw): + """Create a timer. + seconds: The minimum number of seconds to wait before calling + cb: The callback to call when the timer has expired + *args: The arguments to pass to cb + **kw: The keyword arguments to pass to cb + + This timer will not be run unless it is scheduled in a runloop by + calling timer.schedule() or runloop.add_timer(timer). + """ + self.cancelled = False + self.seconds = seconds + self.tpl = cb, args, kw + self.called = False + if _g_debug: + import traceback, cStringIO + self.traceback = cStringIO.StringIO() + traceback.print_stack(file=self.traceback) + + def __repr__(self): + secs = getattr(self, 'seconds', None) + cb, args, kw = getattr(self, 'tpl', (None, None, None)) + retval = "Timer(%s, %s, *%s, **%s)" % ( + secs, cb, args, kw) + if _g_debug and hasattr(self, 'traceback'): + retval += '\n' + self.traceback.getvalue() + return retval + + def copy(self): + cb, args, kw = self.tpl + return self.__class__(self.seconds, cb, *args, **kw) + + def schedule(self): + """Schedule this timer to run in the current runloop. + """ + self.called = False + self.scheduled_time = get_hub().runloop.add_timer(self) + return self + + def __call__(self): + if not self.called: + self.called = True + cb, args, kw = self.tpl + cb(*args, **kw) + + def cancel(self): + """Prevent this timer from being called. If the timer has already + been called, has no effect. + """ + self.cancelled = True + self.called = True diff --git a/eventlet/timer.pyc b/eventlet/timer.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbd58e8a572732884129396a9fdd28f2f1748b59 GIT binary patch literal 4055 zcmcInTW{mW6&^~myke3j%>qf#EzpU90P;q)gI*F~+XgF3^qQzl85EtZjRFKOBZ)Od z3LJ7>1Ky`C`dIY8^tHdLf1v%&Atl=h`ck-pIGoFwGw1r9;XnTMf%)vO|9lmR=GVdd zB_91XnplWSw1$XV;>r+dOI)^uq1xiIBjUDDJ7SKtj`)iZ?}WIx?233t%=%s0UxL%i zd6FtwCM#7OtZ&ShK~*mELLTLzwAbrAP0HLfNBR1uNEXXd_M?6Ii?6==_0Q3KJ$Oj= z1FCNZa*|}R%A~8b3W{hskaiXwm}jb3C0ZwWCKD}}s!-vLTogf8s(2vhg;Fw~%V-%C z3pJ2sE`#hwu2rG2ArH$S$&zd#0~tXs6O`o=Y;-;^uY*E?IF^Cdd6Wd;C*wS-Rw^rl zlFlP_wd|KmCHH2{j=g<8BvwIcCK6nJ>1KZ&I#zdNCu9x}hp(cqo=T(v6NEihaaSoI5iGNa2Ni!t*JWtd7npBJO zEKW#Y{lYXoECpfyMzJn+EM$2JQQ6$=02ZS5YO|!5L7K`?H3q}ENoLa2MjdEZg}Q{} zlOUDryx{Y`ujT>HKe6O&dhER(x|XzO^30unXOFG1+#Aj?-W$l*ws$f;_arFXq2pc1 z>9HI-7xMSEGag9m4`;45o5`tb+NWm|+rpgfj3(z}+j%9Au+N!#a$=v_9+-MlNe4Ep zYzzC2<*DV4PQZD1WKV4GVqhNIo-2zv!UzRqw~qom1pPf*>q;%_%YZzwsY*_ z6zkM-yaSwyIca@|ft;NTClfl#9G*jZm*kV9>Dh&AzdG^c$#gQdFnMG_vfE!DZi>BzjA^s!Jo;sw)XzF+_h68BndfTn9?U^-@ zL)V^>L5|(&>A)m&Vh6Z`3HCWw&4oLL@ErTtBBZqtg<%__xJv zN6gV;roEx*2-qh7Ru~P;cg0(%R73u*c-s}XU6DK?e5Y*~?uevE=#OF2#k^r;26ndq z{D6F+{|?)a3I%wSL4*CKy!UJD@Iq4BuYk6zY9+I36{>=Avsq@C2>?5xn&-e>vJ`;h z0jDUena~>9g7#1!_=K`RfWxInE(0yqpVvvD;s^VmqXg)h1$vC*3~y@yJo5@4l0Uz? zuBoro&2?U&5d4>2+if1zj#`N81cCv&2(gMPlNEf4W1u0GmyNrDVXzw3(>!0x@Mg=Y z(Z4b1hQ7Zq0pbn$K@ivBQuO)A{e$gpP6~%gvY;B(Hz`IaVvd7UE0=n1iyDe1m&iEe z{DmK@uv)kje|413P))LhJ>~W7;61Fbu~7QJ+vuuK0m-MKzRa0(N$jJLC&}zQzPe`4 z?)zBlDAgS!qZi~B__vX6)=egAXt&>s;` zvuE^-kMZmppBWz-5kUjx9z7#G`UXv%FckK<%CyB>V>5%yN07J0A2HbyZ_)DAhw^Hh zQ62FKat0dZUkU^Hjm(HU;vL4%gn@hkj!?36M6yG9^yxRCrkugjj7{a-8DD<}X*>d| z{ug>6>3Pl#+I_Z`CI!qsdCGAdzHDufE-1@_E$rl(a>*&Yg7~ME+KUV&s(cfqmEz6r zheXGzt0L`^&D&c**#yP zL*^6g1EX;R%`mnlaKpwKet%o77bvumAH3&w!?|w2r2ewyKii6?*>3LETafbTi`zPpYnh=J` z=<~w}_ymi05l~lGfQpv4t;r0Lk literal 0 HcmV?d00001 diff --git a/eventlet/timer_test.py b/eventlet/timer_test.py new file mode 100644 index 0000000..496a884 --- /dev/null +++ b/eventlet/timer_test.py @@ -0,0 +1,66 @@ +"""\ +@file timer_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 unittest + +from eventlet import api, runloop, tests, timer + +class TestTimer(tests.TestCase): + mode = 'static' + def test_copy(self): + t = timer.Timer(0, lambda: None) + t2 = t.copy() + assert t.seconds == t2.seconds + assert t.tpl == t2.tpl + assert t.called == t2.called + + def test_cancel(self): + r = runloop.RunLoop() + called = [] + t = timer.Timer(0, lambda: called.append(True)) + t.cancel() + r.add_timer(t) + r.add_observer(lambda r, activity: r.abort(), 'after_waiting') + r.run() + assert not called + assert not r.running + + def test_schedule(self): + hub = api.get_hub() + r = hub.runloop + # clean up the runloop, preventing side effects from previous tests + # on this thread + if r.running: + r.abort() + api.sleep(0) + called = [] + t = timer.Timer(0, lambda: (called.append(True), hub.runloop.abort())) + t.schedule() + r.default_sleep = lambda: 0.0 + r.run() + assert called + assert not r.running + +if __name__ == '__main__': + unittest.main() diff --git a/eventlet/timer_test.pyc b/eventlet/timer_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8432a61c49f7dd0f5db018db391bb4997f7b8fc1 GIT binary patch literal 3787 zcmb_f-EQN?6&^~KY;(OEq-oHfZPS5~7Fs~H(_FMgun0m+^qQzl2_rgNyB7jQjw~h= zDR4$M3~X+?MY?^BzCv&NRDFT=JBPBoUZhuE?(%3jKj-&5AL)Pn=dt_r`+vVk#QJLC z_bc2che(CEfOJIK5Z^lD0t5VSif{t$WMv^UBj>u((;InN#f8;rSI#P}WjT|{JgzQvSK3m>#f@C*%HTjb zwQ*Kt#ifj80<$i5+Bwvia%QjNN@H^>V`IuBi=j`ZWwKi6!p4^DQB+2D>|D$J@%qI6 zGd7ayICrxG+O)h~lGhm`URg@+l)=z>lmjzc8d=sb6!A%ou>>z#MJhw_8m!2UW# zhGwqk<>G!MnQ>>Us(?u(1v}CbDPxP@=)}?tc|I%iyu7B+lCnrMir0MMx)GM*X?dkN zmb%nMY2hlTn+w2B)Ky(CnR%S&a;nz}gWarfY3eo{M7Nq6i^6AdE|+D+_TCNi0QOIO zIUXKIFFobUU@S*!_;t|t`*PnK<9WX;Uk1_1@GO$pp}a6Um&0S}h3E25LD=s~|IZ`k zkH>PT+~9OH2z<;1VQ+BO55gDn2XQbn7iFN3k)m7WU5l#pXJJng!aPMm;tD8aeV zueDIBIYFC>U<)Pe3_;)eBKy8KfG(6LyjQ*hH~TNHumh42wh0k0GfTWvt%|%XmzLO% zD96+zq9D7Axd?L+JtcVH%Yi(?ZJt4B@Fds{vOx?9&I31s)M&6pgh_*zP~+Nio6b?s zCJCt_N9gX+w1KcfWHr^Yiq{SCog-d>F&o5~-!*vL6t5a0YZ9R@w}oT3#9zUrw5!D{ ztshuv^9qyjTW8?+xI+$v8H7kk&_%wDahoj&-b6EDha}8lSQfg2UvVeQAMtp2X26(- zReF8cUsIqtG*xn_ufT@6ws(VjEN^#k_<0^LrfK}8#F&yH_{E^eKQ0QFfz1=QarKNQ z#xV8?_IL)ish9n#LR0gxWo{|u3Ho2B94Vg%z0E8KV;iLX^Y;}7A%;*iArF6r2jfBr z=VNEj`8ob=s!mBH56$-wb=6;^z>c^S;vbmd0s~Ng7yLGD2<#&?00CIq;`isK;;+}` zZ?6HDV!cr#Vw-oq5n_+azO#YK?!6hVF}1)uK%ne=L~cHYfEK_6d4s)ioiWZHWjJ0g zb&+y%qiVIr24z^$N#6V*fGFzKXSf*{3E8EaEHF?h&9B%$q;c&W7np*=OQfQ!cM>Oe3p9`0b5w2&%GQO7fj@0jZtFSO#aLmn!2i9%7UVgTAz+<>;!BRWRP@IW6YjBiD)pgtz?0Yn`gndL$}C`8K! z8Qn4Y`5S08KY|cIciVaDxXxqeBghYa0O$Y2whhjK@HZ&dhTWQ?d|KnRMmBA15uXyW z=`-;XUExrU@O4RDQ8xxC_npSrVYsJEyJ(NeE)!<`x%tMa>GIL02b$|1sAsqbnB0K! zx5$ui!#(f|49IN+!k$4PLI4p1ax?mlrDM$Vin5~U5LaCNm=)%5O;Op z(Zh;CHYm@A(%FWCyW6{u)?O{}x6EYH+0e&if-w`#kw*Vu3T?9<22{{Z8%am4@t literal 0 HcmV?d00001 diff --git a/eventlet/tls.py b/eventlet/tls.py new file mode 100644 index 0000000..3cb3921 --- /dev/null +++ b/eventlet/tls.py @@ -0,0 +1,57 @@ +"""\ +@file tls.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 threading +import weakref + +__all__ = ['local'] + +class _local(object): + """ + Crappy Python 2.3 compatible implementation of thread-local storage + """ + + __slots__ = ('__thread_dict__',) + + def __init__(self): + object.__setattr__(self, '__thread_dict__', weakref.WeakKeyDictionary()) + + def __getattr__(self, attr): + try: + return self.__thread_dict__[threading.currentThread()][attr] + except KeyError: + raise AttributeError(attr) + + def __setattr__(self, attr, value): + t = threading.currentThread() + try: + d = self.__thread_dict__[t] + except KeyError: + d = self.__thread_dict__[t] = {} + d[attr] = value + +try: + local = threading.local +except AttributeError: + local = _local diff --git a/eventlet/tls.pyc b/eventlet/tls.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6332d2cce270d574bab9ee2d51092db8bcc4914 GIT binary patch literal 2581 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 literal 0 HcmV?d00001 diff --git a/eventlet/tpool_test.py b/eventlet/tpool_test.py new file mode 100644 index 0000000..19442bc --- /dev/null +++ b/eventlet/tpool_test.py @@ -0,0 +1,70 @@ +"""\ +@file tpool_test.py + +Copyright (c) 2007, Linden Research, Inc. +Copyright (c) 2007, IBM Corp. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os, socket, time, threading +from eventlet import coros, api, tpool, tests + +from eventlet.tpool import erpc +from sys import stdout + +import random +r = random.WichmannHill() + +_g_debug = False + +def prnt(msg): + if _g_debug: + print msg + +class yadda(object): + def __init__(self): + pass + + def foo(self,when,n=None): + assert(n is not None) + prnt("foo: %s, %s" % (when,n)) + time.sleep(r.random()) + return n + +def sender_loop(pfx): + n = 0 + obj = tpool.Proxy(yadda()) + while n < 10: + api.sleep(0) + now = time.time() + prnt("%s: send (%s,%s)" % (pfx,now,n)) + rv = obj.foo(now,n=n) + prnt("%s: recv %s" % (pfx, rv)) + assert(n == rv) + api.sleep(0) + n += 1 + + +class TestTpool(tests.TestCase): + def test1(self): + pool = coros.CoroutinePool(max_size=10) + waiters = [] + for i in range(0,9): + waiters.append(pool.execute(sender_loop,i)) + for waiter in waiters: + waiter.wait() + + +if __name__ == '__main__': + tests.main() diff --git a/eventlet/tpool_test.pyc b/eventlet/tpool_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a8248e057d88048b4396e2ec959fd427a760aed GIT binary patch literal 3212 zcmb_e-)|d55T3OiC%z^j^cO!0Vil-xrPL060Tl$KNud_SMdy^LmXLM1Tie^5Z?CuO zI!5gaL5RPAe+2QyU&RBj@XemnP~fe#wR^iebGtM1eKUK%{;?KZ`RVsBV_N*q;rTh5 z{S#e6)JHEUsnC(2J_hBuLjB6=xJv!%>9|Jynjcr`Xo=D~_3M-_Q-7J#73!~0+Ms@e z1n*L#qkz(L)IV3wm*{Af(lzR@m2;f9PW^RK>!e?y6IilA>Q(BWr=(6lVtRp+Wl|Su zh|N}b1v(GfZ?v{$lNB}>qRnd`2Xix( zlV~n8L`nqoM|`qhb*>e1rHV*m_rvqH-dIPmuC1+ zEg88WkaFx?ezV=4&1S90+t@P2sGXJ`*!JDd?(J^x_I2191oyL4St|?mXsQdicQ}_( z4lCm55YbGdnKXrrMumdR8Mbm(Xs5H$mb7N*W>KMnL|a$r!>M~NJPXSu{QRc^0u*I( zbEhXey-j&*r`PFi1qYq|JNNGI%Y&UT-0ALjZujK9Q10IA?sfJ%_qv#UA$PhDu^ zgi-sx1uNS{GHdUdcsfy;vu#_%ZS@$#RJpUkbD87K&5O*vgH0^kPGVVjpXO1LL@}@M z4~a*&(Cpjj6fzwNPClnmb9~wP5w)mBVXw&+{?}XH8*iL?hZ&U)vB0Wy__93mAsgsS zyTRZ+-kVJ)5wls`_`F>-`x&|?FrDaOgYsn(PS6i1H_p5BM6hsJq01+{RwZ3y9_Tuk zC0>HG*W|ssmh1D-n49vd-9l?FW9}o{HJNhjnC)2L!&JLn6s8E-Mz7%XMEPjjRG~UB z@`Yp46kw>C_*jO)srGCPFj*&iS;69f!1DL;uxu2Gi}+s<-6m7sm9fmCi5d(Xn=u$n zOfpR|4cX~%l{+T#nL&Q^LZl2E*zjF6PsqAxR6U*8B5oeM-!Q<9^;B>QY4nu^E>A0T z{0jj+u<{|Y>*rJOpzVwVtCx8#sqylO5KnmA&|S4RrBzuXn_$pY zdo8@c5{@uS3Kc&Ft>7i%%&`fXcAhrh6y|vD;}x;AT=hx&eO~2NYur7u_4yPs(4LY@^o7hj=kDP z5#8r&J!U=ru}inm?5pVhRdi5jSo~jR0Q3bX00KyGLIUw00dO#5n~4I94h*C)y(Iv!a4z4EY@0JB>j z@DKxXj0Cy86k6AKGd;_anXiv@q1jdW|4Lt#j|q2vGU3+ zL;3fmLHnJ`m6ru3`^cYKAAEv=CoBQPy4aw)cv~!^FALQrh(ks>nVOEv;DL_ElPJsX=rj#EsXe$~M5$FF8&Vd1 z9wF;g?8>b5_^x1)*}oe>mpu(Rk{)*U@MQ+lSNTuMBL3-loMPWZSFdb~&B}(@sH}?B QO1;vkT@D)SZv<=NZ`AtAQ2+n{ literal 0 HcmV?d00001 diff --git a/eventlet/twistedsupport.py b/eventlet/twistedsupport.py new file mode 100644 index 0000000..5bbbc8b --- /dev/null +++ b/eventlet/twistedsupport.py @@ -0,0 +1,134 @@ +"""\ +@file twistedsupport.py +@author Donovan Preston + +Copyright (c) 2005-2006, 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 sys +import traceback + +from eventlet import api +from eventlet import timer + +from twisted.internet import posixbase +from twisted.internet.interfaces import IReactorFDSet + +try: + from zope.interface import implements + _working = True +except ImportError: + _working = False + def implements(*args, **kw): + pass + + +class TwistedTimer(object): + def __init__(self, timer): + self.timer = timer + + def cancel(self): + self.timer.cancel() + + def getTime(self): + return self.timer.scheduled_time + + def delay(self, seconds): + hub = api.get_hub() + new_time = hub.clock() - self.timer_scheduled_time + seconds + self.timer.cancel() + cb, args, kw = self.timer.tpl + self.timer = hub.schedule_call(new_time, cb, *args, **kw) + + def reset(self, new_time): + self.timer.cancel() + cb, args, kw = self.timer.tpl + self.timer = api.get_hub().schedule_call(new_time, cb, *args, **kw) + + def active(self): + return not self.timer.called + + +class EventletReactor(posixbase.PosixReactorBase): + implements(IReactorFDSet) + + def __init__(self, *args, **kw): + self._readers = {} + self._writers = {} + posixbase.PosixReactorBase.__init__(self, *args, **kw) + + def callLater(self, func, *args, **kw): + return TwistedTimer(api.call_after(func, *args, **kw)) + + def run(self): + self.running = True + self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None) + ## schedule a call way in the future, and cancel it in stop? + api.get_hub().runloop.run() + + def stop(self): + self._stopper.cancel() + posixbase.PosixReactorBase.stop(self) + api.get_hub().remove_descriptor(self._readers.keys()[0]) + api.get_hub().runloop.abort() + + def addReader(self, reader): + fileno = reader.fileno() + self._readers[fileno] = reader + api.get_hub().add_descriptor(fileno, read=self._got_read) + + def _got_read(self, fileno): + self._readers[fileno].doRead() + + def addWriter(self, writer): + fileno = writer.fileno() + self._writers[fileno] = writer + api.get_hub().add_descriptor(fileno, write=self._got_write) + + def _got_write(self, fileno): + self._writers[fileno].doWrite() + + def removeReader(self, reader): + fileno = reader.fileno() + if fileno in self._readers: + self._readers.pop(fileno) + api.get_hub().remove_descriptor(fileno) + + def removeWriter(self, writer): + fileno = writer.fileno() + if fileno in self._writers: + self._writers.pop(fileno) + api.get_hub().remove_descriptor(fileno) + + def removeAll(self): + return self._removeAll(self._readers.values(), self._writers.values()) + + +def emulate(): + if not _working: + raise RuntimeError, "Can't use twistedsupport because zope.interface is not installed." + reactor = EventletReactor() + from twisted.internet.main import installReactor + installReactor(reactor) + + +__all__ = ['emulate'] + diff --git a/eventlet/util.py b/eventlet/util.py new file mode 100644 index 0000000..657716c --- /dev/null +++ b/eventlet/util.py @@ -0,0 +1,214 @@ +"""\ +@file util.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 os +import fcntl +import socket +import errno +from errno import EWOULDBLOCK, EAGAIN + +try: + from OpenSSL import SSL +except ImportError: + class SSL(object): + class WantWriteError(object): + pass + + class WantReadError(object): + pass + + class ZeroReturnError(object): + pass + + class SysCallError(object): + pass + + +def g_log(*args): + import sys + import greenlet + from eventlet.greenlib import greenlet_id + g_id = greenlet_id() + if g_id is None: + if greenlet.getcurrent().parent is None: + ident = 'greenlet-main' + else: + g_id = id(greenlet.getcurrent()) + if g_id < 0: + g_id += 1 + ((sys.maxint + 1) << 1) + ident = '%08X' % (g_id,) + else: + ident = 'greenlet-%d' % (g_id,) + print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args))) + +CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, 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 + +def tcp_socket(): + s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) + set_nonblocking(s) + return s + + +__original_ssl__ = socket.ssl + + +def wrap_ssl(sock, certificate=None, private_key=None): + from OpenSSL import SSL + from eventlet import wrappedfd, util + context = SSL.Context(SSL.SSLv23_METHOD) + print certificate, private_key + if certificate is not None: + context.use_certificate_file(certificate) + if private_key is not None: + context.use_privatekey_file(private_key) + context.set_verify(SSL.VERIFY_NONE, lambda *x: True) + + ## TODO only do this on client sockets? how? + connection = SSL.Connection(context, sock) + connection.set_connect_state() + return wrappedfd.wrapped_fd(connection) + + +def wrap_socket_with_coroutine_socket(): + def new_socket(*args, **kw): + from eventlet import wrappedfd + s = __original_socket__(*args, **kw) + set_nonblocking(s) + return wrappedfd.wrapped_fd(s) + socket.socket = new_socket + + socket.ssl = wrap_ssl + + +def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): + set_reuse_addr(descriptor) + descriptor.bind(addr) + descriptor.listen(backlog) + return descriptor + +def socket_accept(descriptor): + try: + return descriptor.accept() + except socket.error, e: + if e[0] == EWOULDBLOCK: + return None + raise + +def socket_send(descriptor, data): + try: + return descriptor.send(data) + except socket.error, e: + if e[0] == EWOULDBLOCK: + return 0 + raise + except SSL.WantWriteError: + return 0 + except SSL.WantReadError: + return 0 + + +# 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] == EWOULDBLOCK: + return None + if e[0] in SOCKET_CLOSED: + return '' + raise + except SSL.WantReadError: + return None + except SSL.ZeroReturnError: + return '' + except SSL.SysCallError, e: + if e[0] == -1 or e[0] > 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] == 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_reuse_addr(descriptor): + try: + descriptor.setsockopt( + socket.SOL_SOCKET, + socket.SO_REUSEADDR, + descriptor.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1, + ) + except socket.error: + 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 + diff --git a/eventlet/util.pyc b/eventlet/util.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91cf029ecb09062aa4439e78e0fe5d4afafa1e70 GIT binary patch literal 8313 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 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 shutdown(self, *args, **kw): + fn = self.shutdown = self.fd.shutdown + 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): + self.fd.flush() diff --git a/eventlet/wrappedfd.pyc b/eventlet/wrappedfd.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c152c7ac6e015f7a090fe4f94592b288cff6e1e GIT binary patch literal 11895 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 Date: Sun, 16 Mar 2008 22:17:49 -0500 Subject: [PATCH 002/118] Working towards a timer implementation. --- eventlet/libeventhub.py | 18 ++++++++++++++---- eventlet/runloop.py | 5 ++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index 105db2c..c11e7d8 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -1,6 +1,5 @@ """\ -@file pollhub.py -@author Bob Ippolito +@file libeventhub.py Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007, Linden Research, Inc. @@ -48,11 +47,17 @@ import event class Hub(object): def __init__(self): - self.runloop = RunLoop(self.wait) self.readers = {} self.writers = {} + + self.runloop = RunLoop(self.wait) + self.greenlet = None event.init() + + for sig in 1, 2, 3, 4, 5, 6: + signal = event.signal(sig, self.raise_keyboard_interrupt) + signal.add() def stop(self): self.runloop.abort() @@ -107,13 +112,18 @@ class Hub(object): except Exception, e: print >>sys.stderr, "Exception while removing descriptor! %r" % (e,) + def raise_keyboard_interrupt(self): + print "in keyboardinterrupt" + raise KeyboardInterrupt() + def wait(self, seconds=None): if not self.readers and not self.writers: if seconds: sleep(seconds) return - + timer = event.timeout(seconds, lambda: None) + timer.add() status = event.loop() if status == -1: diff --git a/eventlet/runloop.py b/eventlet/runloop.py index 3fde1a4..cbd9c26 100644 --- a/eventlet/runloop.py +++ b/eventlet/runloop.py @@ -164,7 +164,7 @@ class RunLoop(object): # the 0 placeholder makes it easy to bisect_right using (now, 1) self.next_timers.append((when, 0, info)) - def add_timer(self, timer): + def dont_add_timer(self, timer): scheduled_time = self.clock() + timer.seconds self._add_absolute_timer(scheduled_time, timer) current_greenlet = greenlet.getcurrent() @@ -174,6 +174,9 @@ class RunLoop(object): timer.greenlet = current_greenlet return scheduled_time + def add_timer(self, timer): + import event + event.timeout(timer.seconds, timer).add() def prepare_timers(self): ins = bisect.insort_right From 258b2eee1c8b7b218cc8390436d201d89aabe3b7 Mon Sep 17 00:00:00 2001 From: rdw Date: Sun, 16 Mar 2008 22:30:22 -0500 Subject: [PATCH 003/118] Signal handling in libeventhub. --- eventlet/__init__.pyc | Bin 1247 -> 0 bytes eventlet/api.pyc | Bin 11907 -> 0 bytes eventlet/api_test.pyc | Bin 7015 -> 0 bytes eventlet/channel.pyc | Bin 3970 -> 0 bytes eventlet/coros.pyc | Bin 17398 -> 0 bytes eventlet/coros_test.pyc | Bin 13991 -> 0 bytes eventlet/db_pool.pyc | Bin 15281 -> 0 bytes eventlet/db_pool_test.pyc | Bin 12508 -> 0 bytes eventlet/greenlib.pyc | Bin 11433 -> 0 bytes eventlet/httpc.pyc | Bin 25004 -> 0 bytes eventlet/httpc_test.pyc | Bin 21095 -> 0 bytes eventlet/httpd.pyc | Bin 21726 -> 0 bytes eventlet/httpd_test.pyc | Bin 8482 -> 0 bytes eventlet/httpdate.pyc | Bin 1991 -> 0 bytes eventlet/jsonhttp.pyc | Bin 1857 -> 0 bytes eventlet/kqueuehub.pyc | Bin 6809 -> 0 bytes eventlet/libeventhub.py | 11 +++++++---- eventlet/libeventhub.pyc | Bin 5117 -> 0 bytes eventlet/logutil.pyc | Bin 5419 -> 0 bytes eventlet/pools.pyc | Bin 7640 -> 0 bytes eventlet/pools_test.pyc | Bin 7575 -> 0 bytes eventlet/processes.pyc | Bin 6497 -> 0 bytes eventlet/processes_test.pyc | Bin 5813 -> 0 bytes eventlet/runloop.pyc | Bin 8842 -> 0 bytes eventlet/runloop_test.pyc | Bin 6357 -> 0 bytes eventlet/saranwrap.pyc | Bin 26715 -> 0 bytes eventlet/saranwrap_test.pyc | Bin 14211 -> 0 bytes eventlet/selecthub.pyc | Bin 5935 -> 0 bytes eventlet/tests.pyc | Bin 3394 -> 0 bytes eventlet/timer.pyc | Bin 4055 -> 0 bytes eventlet/timer_test.pyc | Bin 3787 -> 0 bytes eventlet/tls.pyc | Bin 2581 -> 0 bytes eventlet/tpool.pyc | Bin 5364 -> 0 bytes eventlet/tpool_test.pyc | Bin 3212 -> 0 bytes eventlet/util.pyc | Bin 8313 -> 0 bytes eventlet/wrappedfd.pyc | Bin 11895 -> 0 bytes 36 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 eventlet/__init__.pyc delete mode 100644 eventlet/api.pyc delete mode 100644 eventlet/api_test.pyc delete mode 100644 eventlet/channel.pyc delete mode 100644 eventlet/coros.pyc delete mode 100644 eventlet/coros_test.pyc delete mode 100644 eventlet/db_pool.pyc delete mode 100644 eventlet/db_pool_test.pyc delete mode 100644 eventlet/greenlib.pyc delete mode 100644 eventlet/httpc.pyc delete mode 100644 eventlet/httpc_test.pyc delete mode 100644 eventlet/httpd.pyc delete mode 100644 eventlet/httpd_test.pyc delete mode 100644 eventlet/httpdate.pyc delete mode 100644 eventlet/jsonhttp.pyc delete mode 100644 eventlet/kqueuehub.pyc delete mode 100644 eventlet/libeventhub.pyc delete mode 100644 eventlet/logutil.pyc delete mode 100644 eventlet/pools.pyc delete mode 100644 eventlet/pools_test.pyc delete mode 100644 eventlet/processes.pyc delete mode 100644 eventlet/processes_test.pyc delete mode 100644 eventlet/runloop.pyc delete mode 100644 eventlet/runloop_test.pyc delete mode 100644 eventlet/saranwrap.pyc delete mode 100644 eventlet/saranwrap_test.pyc delete mode 100644 eventlet/selecthub.pyc delete mode 100644 eventlet/tests.pyc delete mode 100644 eventlet/timer.pyc delete mode 100644 eventlet/timer_test.pyc delete mode 100644 eventlet/tls.pyc delete mode 100644 eventlet/tpool.pyc delete mode 100644 eventlet/tpool_test.pyc delete mode 100644 eventlet/util.pyc delete mode 100644 eventlet/wrappedfd.pyc diff --git a/eventlet/__init__.pyc b/eventlet/__init__.pyc deleted file mode 100644 index fc4aeba0f37cd1fbeccc2674509670a0ef27480e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1247 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 b8vlbAvAkTZYn4S@CKjsoQx``Uz#{(xi0KF; diff --git a/eventlet/channel.pyc b/eventlet/channel.pyc deleted file mode 100644 index 287e2ee24166903afefa61a31ea0bd3e1ca1e2e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3970 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 PdVKN1#XFVRGv5CJosZBY diff --git a/eventlet/coros_test.pyc b/eventlet/coros_test.pyc deleted file mode 100644 index 0566c871ef766e45f0d2245f5c03228944e5a6fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13991 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 K;`Cy?;QS9Av*WA) diff --git a/eventlet/db_pool.pyc b/eventlet/db_pool.pyc deleted file mode 100644 index 57cb0a09336539756ef8051febf29d13ec5b39f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15281 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 diff --git a/eventlet/greenlib.pyc b/eventlet/greenlib.pyc deleted file mode 100644 index 03d7f4f4f43b47e51e9ebe6c6d40d04e52d696bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11433 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 Sf@`)@TW~{5=em{B~d4dq9lRcKXFx2u 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 F{|ywby_f(1 diff --git a/eventlet/httpc_test.pyc b/eventlet/httpc_test.pyc deleted file mode 100644 index ec2d16c495e53590f7a20d8b91f002d9dbb63b3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21095 zcmds9O>7)TcCO}-LvlpQ)E^~Ul-!oqHf>WPCHYs8y`spGG%+<)nv`X6ZH$N0O|qro z%&5CZ6m84#E(NaxY!D~e1iQBcNPr-RT=wcifLyZ39)bV~0_2)Qat(3_a!B%huX?(B zhGR>~3~k3GyI5USUG?hy*Q@IO#lH<_j(+;Zm68g7_Tv9VT>hW*C}k_PjMPwekGf;1 za*tZ>Q3mf*a-UMmDSh87_q}SlSKs%^eV zsywKc2bFQNa#Hm=8Ff3Os)v<<9}b8XgN+sy0P&8KR_-3Pt#p5b(*KZJ9>RS_Im2od z5A9J86`R~z-mB~(p@T}6#~Z(PS>_F7mM{^z3Q2lyscuzTzGD=UM{=$ij_=ewp!cr+_m+#%d9z`j|ZwNLD8+al{K?ymM~%lErWG*<5yRM&7$X^v27N8zglvO z=+CsPrTT_b35o&7QA z&%%;Z;ZvB$6cFt@<#Gl+xPV*c8bc>-h#%kvt|N$UIjw}DQIC= zfifBKwo?jNLdaLE<#KhCQ!7;~w#)hY=QEilR25gM_Z*pvrgEhk08~M@5I}&?R1K^A z^}E-&4jv9e}v(Y$Ug zyq%lN&Y34>7IA-K(!7~ly1H;<$wUilCckvcT)1q`DFZCa&9TlJ})m==8SoL##+kF-k6`U%kErn3_pi%^SS(G3!`MOW%Em?F)GT; z?Ay387q8CD&vU5E%nd+q5k7Nv;rcBrcjfAmd39laE{noTSwJ>(X+Eom!lY*BXL8pj z&AFLtGgkE5l|QI`ih$EQ>ha4E|=9atnD*V|F3GWZ`xav$U3?M{nj9vyjd=vRjXWakB z&x8Rj6Qqz7Yp%ZO!;L7L=386e4x+Z^RZEWVJH9T0ic(Y9+^qeN;A`|ti=g}TH;f@dU4I-@<)-Z z;~tOnp!J21lq!gs*AL|VJ!}9GhDcVuvK`1Ic^^aQ2lds}ct#NT)d@VMB=KKEZt8{) z?KkDwn^SY4I`gM|uQcV{gJvr`L7ZvQ)&WscC_rBZg~BBAK70_&pv1{EE}6$+6nxa9 zT+qZ&9~o*}{tO#f6Ug!td{Us_^ngd99Q2l;#mcab{Z&_P%YVgzDyzLN70%C9Ugc= z8u<+eieHlp3eoZ8Ntvki1gg6=J9>v9B*0l6ym}TT;WQ^W_mGB_;)YWw1jKZq08>&2 z>;;S5WsNX7h$IF|aviabMTESF%O^I{#$akN6%cQiioRQFFG???`jMh!r$naul-(xcj3dwD^8XSEY1#0YK}Q4Z36ee#ifZz@kkb?y_CZQ*9T6_>B_p_f>Z6nz zLj^qG7gR6EN)PzBc);wopCH%(B#o!x6oc2r5x##;MjhZlbMHN)-tAEjdesAT2LVCX zc&J|;{-{qKh7*05{onmW-DHobM3X^2$YPNm;w!K?w~FbN2?`&0ZA&9qXr zw}dJ4)c_{AUa_Uc8$R1*CPciL_)bt*cZ#;-S;87*|5`Cv7mh8{ji^~eD6=@IFwR@f zR@U=iSUD`5m>TT46Oe-eO92e$Z53)o*Yl+T%I+1*b!UQPh?!kr(tLna}DY2`J3YH<^vyeHqMn`=VBK-^= zzQCfimee+KRjX^c5<7#bj>cGigMEO4nk+Xf zCM2UzB;#Nt8K~Ao5}EiivQSL!pRygeCQd9OZ7D;E`D)5oA|vG2-6Y5``VR@B;l70e z@$Zr}Nf{ZDmi?M4G+8NJA}`M&)1E3U6;~v(3Vf)cM`~4Hv#*|>FeqnhQT%tw??`9_ z4!??#dlr4Tob^1D7nsm9h_#dCIB`4kMe8^ePGxQ4|n zEx|2<+tJ2(CqRxk?+!v~;7#qq!$NBp9!gxh@Zx0<=_OjP#wBFqw9&lb8xjyY(dVb8 z%GFY_ybko7KX>NbxmTz3g!!=8#(4f6O$SM@dC1U zP^uLW0STN63dFm?x}s1jBX%QA?=jr6A*+wBqMKS2KG2QYU1kW0GKRw6K_lW1f-G34 zS_amkT!~kRal^ACeezmnk<*^s9k*Pz=m&QnZn8muFxp&ov$BPJW063#Td}l~f3268 zoMJ+mvbctfxm9uF>QzPzOQ?|_qUjPY|7S?Fu!dsV zZlw*DeF%Yu;5L>A@FrLSZ#^v;uy4O)25E{|l~MMfWcDb8s#!7=hN_o`8K!0l;?)wS zUX~CwOAwWo5cTptu0XhmifG@98_I!iBKZtd2j)eE|5!;#+EAZD3>mqnAdyq@(RBIT z0bD)_q4|bAi26r!t{3!@S{BnQ(`)sYH4hO_B7IyXkaj|+7C!2TY$DSa%EgTpyZGiO z7{SM-jjBi_BoCr!6jQ{aMR1@2efb3aB|XpV-o zL=wlu>?~`DoF<7B1y0O9&_W8e2b~9$H%P=s^WuoK24p5kYniO|Ys^XCR0bqB^+quGNXl5ASlT*RF$6>Vs4jaRC5t@jaG zsr$m-8~&QCkTiRvf$9v4G(Zh379f{eDJkOwT$#h&;~pLAW907ptJUfoD@E^F{=ZwV z-}lEcY9h!N)@K^^nRZNOUBpAX#8`|fMJSO){!dWytzxe<01B7T&k_+aQ!&R?YJ#gQ z%0SFf)*NoFEE6uZyTwSttkgs@`se6b%v%#DQ7e+`MJS&`hOz`?i*?X)wH=9OdkrM3 zQazcA=pdFPi6@BS=O|tEc_JC%7+gIjeM}-eniD2NZx|}2Ro(O;-X+Qf5fLgK(KrZ< zh1vwh91)eIcENHVwtI}>wYV7VwGy%;A5;wC-Fjus5!7fEQp7f&=poH0@fxX_QCPl% zQKSi}X>z3UM?&2Z z1VQ94VUi7h=}!q!gBIy#S_tROp+T!W8<1xREkox<=n}Ar7HI9Rq6wTTHfcCgq)=W2 z*4d~9TUx-H6}ZL4U+fWxTh)pSwCK?4M<$MF5Lo`4eG)#!2r;ouDPV!1;Mb99uh=vq zC~%KJL{cWnW_fX_E7eMji`b5}MZ@mMP!A`@=}rs#SYs%L%tkxxfuegx@5X8~Ue|bUr7l zKu6h0d9fkHMuHE?1)3SZ7W=TDV9=W&7}IA8CB&8k=NBmT$-C5=qsD%*6eF5l$x;z@ zZsSHD1z7BJH}lxEy0j$1kgj=_lkR>aSu@j)pz2a$|LVHis0g`3(jb4 z;hYK%_%!g&V5mkuGCHQNvHb&9h-$;ZwjE;6DaCe3EU{LGDDO}KG~@Ry_e62L<_q>q zJ**I@O&ceHUr`0bD=z}%SCO<@mrxsOCBVv*7_X!rOs=#((|C@QBkvJ}Lz-5hq3dl5 z5|HQevJ_&7Xv5LS{1r+|xa5bhiS>I_ z5rqfdN3*Zw0a6sfRiQKUNlpkYB8wkQbfH#kD8KdO{#zmtSGX%9WQxcwBo;OfH+RJQ z5o$;D6t;^kerM$6Mun(RJ>K6CI4$4DAl>b0YoZZpgD1NlwX@En^MvfZkK0}JHUA#n zYhsXVV_(zSMU+zqB~adQGXIXC#7^b}6>uW%{C(ceFuBa+3X>d@x0oz2`3)vZOx|Yl z4im8-?=n|lQe?7%L^BaGT?n*UucMm>xO@`{HkW$wo0cmz*gx2Z-(FmU$PWzmi$<{= z+x2iRvi(69Drg5GWLb_R4~!v@Oc&2R1z{*LN2*b4&f@Z6oWl*mZM4C|v^J{xHtc)j zJd3#FI9vk06;sVMG#bjv`fzyaMO$)g_VmfoGwkv9V2ynN-j>a+*&cn81-lf`&F%nQ z0_|lj%3e)g#AnJ2CpaZLI^l+wxJ}&fvgAYq0cXGfXV1jb<$f4XXo?co9v7E{`cw#o z5=S|`z~`Y@+0bEVP(Lq$KLmEgT6j*aTr4?q>LH-J*Ep9_C{=Ao4j$>_Ov3W)S58EP zeUxL)=t#535dTD=wL3NnS7FRJZuDd4U0berPla0@-9KSfLa+Zw0B9$tOCMlp) zz500KlykoZ4P-aTr)8sFWYbzj4%Q8uw?Z4;+ILT*$O0|2qa`vbCy~PL{3(}RxD;>q z0+aV4!R{w#u|=T%VOvem-0CArYs_Ja$^xyFb(;w{KSW1hW7g3zk0BOS3sfKlDJ7?9 zSs~rE`^WQUR+))_RzpsBRFcjZ^&tBCS6mW_ecF66_m1c3 z!;vWHh9fwGZU%@?GLpi{$HO?ybpTfsBp~Foxcs|Fbl7EUdIx@!k(V&)A_sBc&b7re zUh0EI($-@Kp0#(T&l2=v)E%Mz7{k|5twUP(nA>D>pUD=Ji%1fS?rSWg%C3OqANx{~`&Qu9 zpZY)G)#7UI8$+r8Ogoh7kZkPZ&?V6V$kng)F_Nyz-iPS$NdiK9B(gUktLKD?ZsYry zX#Dt+hwkOPrYL2qM=0g+%#|$Xzd_4!J| z&H~;9^%AU2tZQwtdnV;2;}?YaGqc#1gh=t6mWvW*GDOnpu19@mQN{o!w;ZAEN&^TUeXkF2g!6PfwSSl^-4b2VZH4|jxhsQ9QEL@_fWs>3S`S4PJQ@Zh3&q(XRv!eMSR(Y=>@|Th+SW zll;@B=0?#{eKh6xrpc|xq<^0zLs*&rTFa-EI1jSll1QHKM*`{=##wWD__ zMsKr{Zp+Rl2+|!TO=kZRw?nfK;Oc%H;JwCd6W&H21`_$>nZ9tfda`x3ibZL(Mg>=5 zfx4;#0s5`Sn+ybSfhwT^y-Z~QKp)d%>3~9@5*6r(;Y!)H;7T{F+taQter1yUpKmMl zT%@%|4k(|iMYkL<6q>^qLA*+_yeMyt$cwD9$dwJqTG1qZh0(WR3{ex&g6au1HVsFjUdBQ9x1$H$(L70jd7#ZL8p>P=zKmK!_fsL z^bj2)#@OV}#5(__-BwEffuX-yonPkUwTr&%8viFiHa_aq(D)?J3c=^`5ymier}|6=Hv8q+ zj^1*TlNOrR1jmZjIa;vD3-MZ}3nF#_9ezkW;C~ppmopI@Jm7-n3XXkk7PpqLWr8mW zX-|g%&UWy0rh2#9^5;tpS3L#x=%}H$B-=6@l~ejSIm6%o_Xf=*IzQHUpxQO>*vqC zYF@dv>@tCH2umcG&z9?1 zQ*dh4(t0cFpk0S&=;-0gNsr_X9xQv4P*XCaSq6+-T(sK za>^gV4=jYP2M#Uh(~0_RfV*Srrl3c>10WIdK*MDHE|P$V0%Bf!d0Oxyh${Mg9n^^t zCBn7pRW}lwkDs%P-$RnvM;OHIY84v}0X_#NE&;wzOcXr z{k@(yIS-!-FWobOeQNzX1PAdT9K=JZOll-Gx@Yv|(f#8;7#$kz9o>(scl^ls!SRvt UkiKZ%?olUi!IyBQ3p zn`BGF>EU#bB<@KU=On;(;@B~KaK4bs#f}r_JBX3McD@56u;Cy-oF54SBrsqjL4W`W zi~vD?InVP{ch5-L3ml0i*~RMme5>BK-mj|aU;h2k(*OD7x31Mq`g08bzkw%yZ=N@O!69?IWTF$s!4H8ziBeXgSuoBmCia;-Bc?HGHb#vK_3Qnx z**J`Mz;MI_V`k&12?{10S2$s|Fcx$=XoBNr3vCRU$HqJ|=A(^C;}4tQgi=d+>Ph1t zG8?CiKVmjsGXAIuURH_2d5P1;M@K^@d_@^!dBz#zqi|+J4S_QGpU8mu(|El z+&Vxjp=7dyX5wfoxmRlkDD1nnIF9OJ4ehypRPXEt&7_vFKMp!}r<0w)J-3?HIQND+ z@q=2U6gJUX;AUCwy%4j~NnFlGJFK&f88>X!8y%l8WZ8{yH%xnAbeFXq3=G2)H%YE_5ox`N7?Iya!q@aW!Van9w?Vz48g^+JWjYf2jL#szkKje7hE2YvJ zvTB>r-9Tfp4{f5mFld|Dpbe+vO0pi12!)--v z_4o8JXVL%lvb(x`b!~mlE4z!U?uxhk;o?Gh!96#(iuZFf?)u`|_2rvuE=qWFm9>xD z<*V*o&!S8EK1^`K6l+i)k81k8|BK{EP6$nTmBF)?&|fqr6qP%n!5?;J;LYCFRy&$End66 z=3ZZ3S|}s&gEAnS`(UYTyTYL6m*y64UW_bNHKI zTU@Si81u`OH4m>d7^SzCS6yFREzh`f-r_1J zP=ph96NAoRaTm&SOK1z|sXT$c*;4oi_^C`oAedwb!79kVxxKh-Q;JBbwL<&SkC%GX zj^daFudl7GTzY%~Rje7R^sus|z6z?AW4 z%wtH7LF27X7f`g$p7^l)c|7rV5G2Ngj(K3rgMxVksqjEA)dJtq!tEgwokS{1^%>-y zLc^!K#*Z-rU_3aL_kQ4ndUqs!wAav z>p_d_MRQoKf+;4|YSM?SdgKR+RH9~(l#o&d|I$Ro$pBv4K@Ge#8ANm^sQKWmD!v){ z`_r5Ry@By;c`U8zwpFUFopvn_Jhtf_LNJZT2JsgVyL2-~W0%_gy-N!z-;FQD?fRwQ zE?96QNV<_s@_6j7UOpWj+x0jRf}=Ye#h!ai#M+ne#6-t{Gg8o8 zr-0OvrXOaCSicX{1|nAfb*VoHMJgm�N|O)n-WHu-QS$5{Y=@5M}yfypFI~`d1WX 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 v!T4PS13X6i{y9UFLWl diff --git a/eventlet/httpd_test.pyc b/eventlet/httpd_test.pyc deleted file mode 100644 index 77e88248c62e869f77c7d7526f300877696061c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8482 zcmcIpTXWmS6<(0K5tbasx5P~^8z-46jbh1;<0PIWEiF+NCo-jhs5n-gVjyrO!h!$> z04=*?&t&S%^r=tnQ>QYl68SCoSfiDJg&U`B$4zA)n|s;MW+(wr2U zuG(_Zo>Jz7+QCaT^^M|`J6qGLgTWcqIibu+8JixCy`(xbY;2uUos-JADtcLFyfmC~ zTA4Gdb4uN-sPv?AIOVJ?oj6$TWwkMTnqv8U4D#8DVxzl7(d+ny$%{+9hwg8Je$h=c zy_6>D{UFin83xkCbr;j#!z|kE7J4>(TVGjN_+U=2MoGsc+B3Ncvama+n@M=dT{qc& zl;=^J=qT4+lbP*@dN&J_!gS{JPG*cwcXZedvRyN$i&O{6L)|l3ju+DHB8ZYG+0}s# zv7(EaMHgi9bf-87GK0w-9prf$Mgi#QP8#<2O;Q8}*W-qAJzI2*zPM4oaq(?gq+^2E zjS^5ZdN8C9B5bx_Xl^o#LeiMiQ4+@e4tW?1$I*UNuE5tt47m%=`#E+%KIim)+KG1f zZDd=${&pPY5J@K@>Fs`jnK?T{lkhFxesavjj^*D45YnAu?Iipd;<9V79qg{ZoflAi6vVohX0qP1+q{JJR~mX_ zZMl8B?lp9CL$7;lpEs8pOZsAc1MQ1*`gXIuvbNdQnBmo1?K^sHS=U>4^k>c1(wuI5 zvFt+n-P^H#GBqU|-!1xr%R2HsoNw;JB! z3MkiaG*_GLJ9F-Gv)v-iz-cU^w!rl8d!b_WLwSFvWHa~w;HYX zC9H}*-S`|0y|GeXUFA}4eG}Y!5vQH&@zvWo>n-fzBHZa8|#u+OSJuQ;Vzh z=B+usRKHcfN$xzo1}dJ*X_h;Ed!@lHu2;wZV!OH4;x-o7T5S)_IqcGF4`03A+-S_{ zy4T#GK$g9=TXQbOi8nwUMDSj#VX08mBA}s0Fogo!#HI(l=%q$|6||t9)*a-;v^ zGYiyJMHNNRi)>>Y4Z#}QtfAS<($M6&$!!-PN%e~;wyi0&HsCtRZgXu!fjE^HoVgJd zCd9XVxZDjq`A<+7;3E)K2~buD+TWn3s=STa3Wh@#WQ|l7@#J?=JaSYd0Q|V33P(Mv zs3)M|sC#27ol_3lXk0)5A+WxN4$MbiRdvQxXB?A({a}hH%OUhnkb-p1PGW%;$U`RF z797Yt04_v-0pGHj?X>eSr${gaFYPCt`~+HEpaZ-;&En21xh-Z;^n#-6=Vq6%my3*J z)JPXF)`Lfzg0eoyAfQ?9Q&s@b?8xNId?g(uGmB9rMgAdb^P4$fcs}bK%rBL=n$PE1 zIB)J_h1e9M%Au%HoW#prAX;pEknclBbLh!k6v~-)-gM45Q%*=;29L=76?8zb5DX>m zP+mikQKK!*t4j4=h&3KVevx7;GG8@@kNSn!U@R*O`Eb6wSSAs zBQfN5NzNNjuHpLp!gfC0iN_kDLUntRI2L1uf=G_nayFs(wCRi<1zJUOhxDt ze|%C=cPr}gxOzOH9^t>L9w7sRKPvU{SD&fd#U%UgO{nw@EIY*px-Nj&V~k+&ntC)z z+K;DHf;10~jT#J;l%~{96TPi2D86QArL2eDnfII=awYmIajGUr_@B{Rp)GF#yRDjsZLbnl{VmIg-Pr4cHYJ7*3O|asZ0>OIW$SYW@SosCZ$qS$j;g0mf88%31;T(nPw~z{nAO1;viQF zDLOtBk=VJ1N+B@?Hsw32fW3<4`>;1L*J46NlTyd35-(_du;D>iVH2QOt&jp3LWHrw zB&X6AF3DeXeWA3p{osM0N541G*Ic@aB2IV3*Poj|OAPbIIFF<-kIA!uj

`4{kt} z_81%_9(MPy@U*V#QWptnOqh)8!e>=33NAzC}Jwq4j*9{ zMq4s$&ET=1qD?S>LuE?wM1YX#g&5a8f!i>>{m#|lyI7b68YbGcBe<~G`RO9lM z55#^hi~ZzpfQ6flww~83?e_ZoQNfXDI}0 zN%R=RF9s{W2zwIPyq4#s4|ESxJVa-PHlOARt3Hb!}X_c{sC@_=&v zxHv9*dx>NLHU7fFWu#j)_+4~K5LU3d8c=E4aVr;WgyZgq>imrFiv3x&JgZK)rcHN7 zX(SbNxZCPjT;2v7;`$FM?edXIvk>t7`x3=OHy-mptB|)@`v`?i5}0d=jE_sOr$Mk3 z>=l2{m|hS^5H5NT$<}pd5CuYdEagh1TI8I&C`#E1Vm>EZ$X(zQmI_}$=3u=6CPL4# z4uR_)CZ1CSWYIx+Ju|}B=Y{pWha4?ziT*j$!M5C7;D#aYB)=?}$M1coCIpW0jv8q~ zqMdmV`oT_7qOHfQVc#^e45w+ruXPnKh;WLs(eYu=4qL~fUxYytn%Jt8q7+ybm3m8P z9+zG&+maT9NqjRO?+f2ev#s25Oko5Qp*+VL8qPb11-~ohFabuG3!BsU(adz zSq~EUd&Yz0d`8Ej1&k48N)My@VGEKGT3Do+|!&uypF`)5v|6d?4i}Ym%NAs7RmjJ#@W6t1!C7w$@$(oqc*QnSdl!afy=-?zn7oHYVfnDJ+ z1B027rnM~Cix``PS)8`S9394Bv9G&Tnpk;42UWN}0N{i*nPFfSzvrb#Yltcg5F2;| zZ!JIIkQQrmebjA|45DBR!C#W%or*e-Nfzs?q=?%FMo)(6fWK3`mCtG3!r8jGyFdf1 z!^bZbqr)mEiE&s+AbZ|#S zk8j#o}{yztVvEw1Gg^AbEM6JzCcZs4G)!z znm0LgociQ7UFn-2_@znx9ew%FQK(97Y?}EH+!8kk@>g?asx!{Hfm>4PRL3HooPnT3 z`R~t-=w*ndC?#8>Lp+lvm45j24C}UKqU0#fz(ty!0jH7(!B|j&J8CdT@l-N-6uAHB z9O@kq5YQi7kPo2$s9frM5+^cpWa2b31DP^B+7$GcLziSee&G~|@Q)a~x^T6ihaP@| z`aLfHe=7O<5P3+J0n7)heT$WI`s5(_ItK{1&nahC_QX-S*p+aGdurodWfzlF?^71P zX3=1=%%a5tzmY3%n+1}Fr=UzW^+H#D-mgILE}ooO>jY2F6SZpXM9sxBU7M_(ti3A7 zdt9pH_g~*{&GK4WeA4&v8!ztM>5hZnA*^zV;2r_bqiYC4hl5{??9CR_jpjaod9){{ z*U>)6f}YQtQ2;w{5-47zakv;Qdu M`LWvh3)zhGKVcOh-v9sr diff --git a/eventlet/httpdate.pyc b/eventlet/httpdate.pyc deleted file mode 100644 index 53fc2bc5c022fab4d11fe452efb33a886292a29d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1991 zcmbtVUys{F5T7LX*IY{}r7h~q=+H?iGIv!$MXC_0FL5p|i5=Nak2@hnj&~EAI(Fn; zUleqC0`Y}-rwhe3t*eeq=ToS?)_>Hm+Y z|&{)EmmbX^~KtMN-T@WRePFQk6NSq^6Ju zE2w2US*rMo*Mb#Its&2Qw0A0lTRyRi? ziLwmGeA6(>o92cx-OwR+)mTWn_%zCJUX(iDe`y|5{;rLI*9p%p-$p0Ef$zO@+IAZc zt$_4H6VIKn>kUIB58rab3-mf@xfl3@jn@ zffv}6zD=;M<92+?V)tz~Jf^Hm7<8>(Po*-fAyx0I`mp5EHCY@&OH+Nm0%sIZ^{=B-0s}4KntNT+6Up>*t)U^`z3WQ>I!5kzC7a?f;g( zM=HL<$6EHIQcG*Tmi=g{^|v?vRkl8_rdkGkuB8{Raoyrd>uny#HNrLM-KV!jukdJ* z0BH@b0e-E)Py4W{!Kx0c2CTMVwGFEsSna}U4^}joYar`zy$hdTH-JE|>$IcLEx6u> z>m8Wv!sDP>Crnp-LFT06Yrc$LMqP*$XH=ApiC5>pgO=j(>nG*n&4Ui;S%w}2$p~Zqp z)s>}Qe`(Sd^der-?qn!vUZ0d~anjxlU~wYK_=I24EY7&R+x+Vv##bPUzfg3b_GPV6 zYwprcs%12ySw0&1kM0aKOvvz98B{0SBIo`S+GwD=lKyX9VvjJtqXAxJ{CkBj)PM9& Q{h+?zct$lotUay&12{_SBjmRKx#!jcnnbZYEVW1ks&X6*A_$|*~pvg8>{&RFuC@I>7M zf_<}x_?M#K@K(ukrFC^tJy@^PMz3TI16j(uw1jERm6oMt^=0)?i`$ikZuSDce);m% zS7^SqVJym=mk@Hr(>hz(;FZ~hHRbhMC?#YGLcxmHeDMIcby{kk+pw%T2f2i7mDac1 z1}z~iAE4qjLIJtZsVGHx3n^sS*1|}=!ZJ!O^?h1%49+1{N@gO(dXUSkS@Tk-n)(qZ z1zo-3a2{`&IZq2qlvs_!?g;J$GHx^w(^_Oy!v;}iMUxYT-Ekq-V%vezjTXv6 zf^5-IZBujKk;CPG=urY^B1ih>cjM9ECYw>;D2A>>P06!M;^WwOi#@m1ehR*XmK zLf&!1WpmY~)CkqcZ7#ro*xc&&NUhSMfCb+QhJ8h8k!wc>*)Z(~C>X}qPUwOc!88oscmsC;=T3zCa~rO`cofWHz=+WC;~NNu;P^N2qvsE7aDSSH zZWKWfTHa(j_FVLNet$e0c>Wb!VxAwwF!m;1j78%BsNuGh=VIOvCT`dtVP)si8+-AM zZ4JHHr<%hc1P7*07<>KM*a=}e3#UQkV*3G>^*w(WVi$Mf`tb#JMIX3ta0AiE8IP%z z<;)O$Ncf;1Om9N(Y81mL7!O=@Ub=|Pxg5KjR!FKpcD#uV183q~5xNiptP)xjwm}Ei zBbQv%&%tj$_5z=1^aDQ*acd*XF#eEw?M1E)PUuA>$S@2hwncJc23E%+nCH8jDkQZL z=);X*2njYr(mO0LaGf#M!t3!ry1olb{EeS(3+Ig9M?=#k^E2Ah)5fQdkNN`5FN{@3 zI4c>8$`)(s787l9zHi~AqG-R{L920GF<+KkTewhST@{=Lm#JUKG*_E;6n4K#br;{v z81)KQy_v${(yR0P-e5Z|R8Q4ekKf_oD7b!H{A(QQV@y@)lFzBbm$aeT$y$f4qn0|u zRa#Z}Yc_+YM~U4}(5dOaVLr#PG@t7uT<@BMxMm^I4@nkNeuUaYjnLdM$(wbh%t{#_ z(OK~{*QCKM*XCU{S`&_(BXDk%sie{ysWEtNTo}DsVEEiX+QiRI93{*jMf$%=DWds3 b&cCMMKYWU7b%N%gebzqd93Onv{-*s8(47GX diff --git a/eventlet/kqueuehub.pyc b/eventlet/kqueuehub.pyc deleted file mode 100644 index f81014a148ad0fd50c6ac2359df31ae4851fe9b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6809 zcmb_hOK%(36}~g19vnR^`3WaZI(FlR0T??-A8FGxFfEQOCKM?QDa#fd5Y%uejYZ8+ zGs7rC#Z7G#Sp)^zO;O|@bdgQB-F2Nm(Op(WyDrdv-?@B9t{W7MEX~!ucg{WcJimMH z_)q^>uwMQ3pC5Q?_-_t>@8L;)ho-I6Hd;fqO|@^RZFF?sRNLmLpHbVHQ9rA;v(nF~ z{VCO*R@>9c;F_%3pHbaewLPoHQ))k_x|Z6uRCi8o&*@q0GOxDhReM_b3#x;4GwQig z&y;$+y+|SI`B}<_P1L ziF(Izu)CMqdG9Uz-FM#k$=hh&E7+AFZ2O_@`ib9)y}g264!t|ph94gUNfJb%9VGUi zANxDU_HNt?Q@>rXJF)NEQOEZ7TJf%5u+zwHg~xW!j}t72cG6Z52H~#VvOR2Tfim3# znI*FW&Xw3Tu{DwWvzbkDbM)rTu?y(OEp{Z`isLU8l# z(JT8XfX4mQrlxV=kw?J}La*CzQ-;y&Zg3C`ci?idMPfnnegX|BXTd&*+ChhZeNn5| z-{}SkOwta>dZ(X)GU24>hg=0c?!wrK-|br95I}Cxbt0Y6&_1vY_8*!k;mpxqbZ}Oa zAhA0AIK-AP3MkqUOeQ-%^}Uocl)MvlyU`I<^P;dFP~YTz%WB|NYbW~D7hSZg!zhKQ zVs3GO2%)_?e3k6Ax?Ow69~un%2BF2Nkvh<>zmueJe9-FJy(pIbUR3iA_Fr@CdTq6_ zRdgM@T(>ve+QV|mDcQG*b&PKn?5%QRt+v^)LE#pwjmLIv)h<>a+aH&!rGo7|+HjqE z-LAP-d3~c&b}(12u2eQl<>~|bKGs!h4ZBiaFE_xnQM0+>kX6~ix>bAKaaY#Bxp==^ zDK{P$tkrU(N}j7V*Dl%{MYmC2*{l>@dt=kxsMQ^8Ujo}|xw`6N7iZn6Htt|o%-POE z4D9+^u~OkyR&f*3yOhsfsck%V%MaEX_FAn{axi({fn>$|6-RFcrB*7%@_NB871xUo zD4lE9z{RylrlqsD)*Mc8zaoBD8s%D*YOK_%4Hv@#v~(NerCa5?Q?QF}xlV(uy0!I! zMRQ^WxPuASRUOTRrWON@JpvLMY!gb4WU)(5u>xLjPxXxZ?pVRU@UNOTfHdh88s~no zw5o?hpIzjC*bR2_CJAQHxc#saMLi*I1jwH>gW0wI&KOmJSL!~VBt+u_RRL{^(57LO z9bl*_GXT(xbf#3>8vSO~bKvZZYEQ|lSwbXN<%SRm9Bt3a%$zLAspr7!dF9sgM0AhJ z@S)5rc#`+gq)MGA6&UK7!A{dudot6^j5?4-&rFXx5J3Tk$*U@0?1~w(7$3EQRO}J= z!!GTygvqv_cyZ86qqr$hol>01WRh}aH46Q7Vp~5I1MYX)+Ug_$%yjbjtE5TZL+|cp z0?fS|w~y|Yh9H{UO=9n^|0(da>!*{>>0?4RYBmAVX|s8otS$02E*d%c8QY3Z6kE}0 zG?bh~hBg^2WonzTH54QcvO+aQa~iM`Urfo=j0$FXipkoXN^>f(IDVQ_(W0K6llP%s zIkN9OHcl7N^y0|#lSG@)m6)TsQrbRBB=Ed$l=v>K>*m-@^F6~CxEQA{bxhgp`l;8C zV^}C9NrEH7+Y>vx-0qxgEG|oA#iI`>o=zLUhuYmuLxhsWGT6A`4y%jtO z(r74p@3t~hLj@+CN%!j)5*nyx@H;S6I3vXlddF9X>%bvE*kHl5+6phJEYj$yr6N$` zUuY`h5!ucT$zh%^3?w0?sA?_6<#ewru*J+v4N6$Y)m~0*XQDteaEHj0<@tUyb)HHb`V>HG-L$-hz z>_%8BW<^fIU}Da(P30u`*gw`GPZf2J&?@!E^@g+FbRMlZ8>~(0VygsaSl8QYB5?d3 zi$MvaHxHX=0a2Vr^+f3r@Bg{ySs4TPS3bIJI7LYb$%o&#r zj~-+SFgL`DJLnK}1ZTg1ec`hpqq2-i!Ktfe`J>}yaWGCbsSu2<@GZD)V*Np8vQF|7 z_{MeOM>w%AcBm%Tu$jvsW7$3t{C)d&oVRdV(k6iK^g- z%kac9PCq&Kh^>4fp7tZDL6`>7?~%g8i2@BCzg7GJ?7M4UJVx12`Wis zB1HNp_Q$1M5B`S1!f5bU#%Ehkt%t)6A1j1t}T-Y=m9s+^@6$u$&Nwdd(Eyb;!Z%9@`O?TDVhA2L z0Um=3*!Ke8UPq<+G$Y6Duar9cyER`lMI+2FIxnAQFgE^+3{D&1u%Q{uYX*ALmlzGHidy4; zH1AnTnD;OL-Mn#TWZpk~i+QOXab0Zri~kG}eG2FZgY*wTjmSVaN_fGG9c;5KUbN&0 zpvyWL7D|llizG}hso*lt4LJ-tnMZUZp2Ha7C+0=8ivS;Bc59dsSLCFD4~CT$auSfu z(q~du3$W%(J@*#3BWbJG^TW2>v~Z1=at*V}{XUyF*bo9;K{X=2 z*Tu)JJW)s2MMS!`uyva5@b8x3-%wf+kt-ca=>Cu^Ku(a=Y_941>q-^JV&E|tCx(Nsc&y+rMIN!QCYqT+`YlN5*6lx@tQm{ zn7f8G%f@A?JFm(61->(RW{&>8NX>O~Qls)*UBnM(UN`2^^GwJ=sm8eecUYNmHM}#* zeSdV_6G-|QuL4742Lc1oPu(B^IdC;=A32Z#UjTZtlPZT;4`P5eupS*tupS*i_l){1 zHBc*;KIt03dw?LI8Tii50#Tkg1_&79*a+;m!mBvj7f+?t;XvtB90N%MP=>rg0bq{v z5{W#@%g>E00G#ML$;;1~mXenjBrlg#usqJo&ykBBc#d;@69b5e6rNG{UXuC$*A6uC z_y8HIFnwBu(a8m9`Q{}meDiH8e1lVB0B9MgEyUvxH{d*!5?_B^)H%nn&{uM)FyH2I zoKliaxS~kmsWbQv3GSj90ftjs6%+~Zsam55ZZ&`hL4+3*o&eG5bX&VgUN&Yx3REOM zznE$`X?vQFU{0d9X=CxoU;0{1c- z-t^^us@-LC$m-F3lg%4!#HN&XqPXf|Gyi&9kS r4@Kl0?eN392zQRkCbMAufj0h!ycx)MfN66cILe>sys.stderr, "Exception while removing descriptor! %r" % (e,) def raise_keyboard_interrupt(self): - print "in keyboardinterrupt" - raise KeyboardInterrupt() - + self.interrupted = True + def wait(self, seconds=None): + if self.interrupted: + raise KeyboardInterrupt() + if not self.readers and not self.writers: if seconds: sleep(seconds) diff --git a/eventlet/libeventhub.pyc b/eventlet/libeventhub.pyc deleted file mode 100644 index 6092c4238624a51649f1a76989e5dd9971980b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5117 zcmb_g;d0x?5k8Or!b^(f(J)QojDBba}*xTFvc5iQwfB*NY{kMPq z>ya;t&lZtQs=}f< z6>+jGqM8`g^5-RSQWude2DXTK&XR$mrx1kHLnxJ{!8}@-tPeWtEB$lC(6P@bOxjat2IMYE>j#I5=GM4_tOOJI^ zW{LFTb2-(i!GvU#d0`yJ$I_EN^s>R3O(4c3_@YJbeO-6!qc!= zfu=K9#)jrI0}DvcraVo8a7^Et&6>_eQD^{35R&N8ECZ(@qOW6`1v@t33ZtXQhJ+A$ zv#ks5n1}EIHdw!a$PndhlAK<)BsBJTmd02TpnxMt02#0NO#2y8Nb@mbAvq({{3H%S zve$fN+kL$9M#*!{w&bx8CmB>_xY+@$MDEq%m6>=^BuBac4C{umO{BRwux>Uo85|#a z5#liA_1-me1MBa#Wv{#2KWeGAbb9hYbssxB?Hzfi)x-0hraW@`d)>pn1cz$5{im|K zD_ibU`Hkc5G-dnAfok`9va4)o|DfZvL37;g&f$*ZK9XCQ=XU$D+zEm(+h z+L*U1_uFcF50YD3PRHp#ZQ8p|-zAy5T_s!cpr!iG_F<=`G;d)>}X8^oU;IHX6I*CT zqeG)yEhz2fiJAt;(j}%KMVEQ7B!c?+8mYRCW`2t<6XK;1p(S2hG#Hk|gek-Fd;k@4N|g)@Yn;M;s#qYf#G z&97i|=7kyKkj~4nHH|1d*_AS#|z< zAFR*unvlxD0xLa~L{<`EnIL&q7s>UUT49J{3n%3QCJG)^MaMQGKc$(XH8bjtb>`2~ zl#VP|M1VXa#07F$bUO0A1#Zlk-f_9a52B>uXvg}j~H zLfRQ0S$Y=fp^tRgAQ7xIdl09h0YpNaoL!X>igbHO>yliND^EkRXB*8RrVHh#_k@*M zVx5-*nu$SsV(I4&_zWdV){v@c$kFD6%ri0A(@xjdf?LB-^^HWg{5uA$>#qcp?W z)(Zkfd55CwNvrB_g>(zwq<#=h;=^n_Y)os5A_;@zZdc zCFwpWhCD8;W$QyA{!uCa_9=u2=L@VA9$tJi4>U#_aPEBjrFaEYk$uqf0rI*eUI9iN z9eFak#V{kVe~e&9C|lp%=61yRai}7h0Ts?nd~^{=Z2dHuDvDsnDbPryxzZFUlgc-#zpN*H%qdo1u1DyHK$n}!*`}3QODsNU) z3f574UsMnSloUV4oeq(AF%JQWU7OF#Z+dic(7LYq2)!cTQq{iL4`0c9srd!owO{yp z%9o$B30?O%4leV40$v6;;|N7k)gvU<>;|S!(=g7403sK{p=r)deyC=sK~i-OY&`Q~ zsdporb^&1-#^VH89uHUMCJJqXT@(3}g_dI?uArmC_yeTfo7P$$+~g<1jr}ykAU~2< zk_IA^Q^5+M4>wWi0lyWoIxjuw8yFy#@dEyQgR3=S990X5e7+SKqa>aoov(qrN(Co8 z{1=>y^8$v{aD8C`Uw8q}49uB!c7!Gk!r_yVv zXsataz_~u+Gd)j6l4un5iZ+dI_XnqB5QWOloM&lZyYSowPOZ9Sn<<_-&wO*^& z8dR__n8P7{*Wg=-q79Q* z)-+0W#7I%4k9&Lgr~JZ!!zE3_Kxb4uIHz=*&+9BMCP=MhL_amKT~~^fsX_RI+-(nk egt3w6s&I4uw^mw1^|ewegJL5DP=S?K%0B?!(0>O2 diff --git a/eventlet/logutil.pyc b/eventlet/logutil.pyc deleted file mode 100644 index f024296496ce0fa39234cb598952101fd3db1020..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5419 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 diff --git a/eventlet/pools_test.pyc b/eventlet/pools_test.pyc deleted file mode 100644 index 5d25fea626529f31479192877d27eadc77e6fa97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7575 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 J%@5bT{{q$b>$m^_ diff --git a/eventlet/runloop.pyc b/eventlet/runloop.pyc deleted file mode 100644 index 33965f6bb11e0fcc7e8567edd8b396c3c4e9e344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8842 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 F`yT_>N4o$3 diff --git a/eventlet/selecthub.pyc b/eventlet/selecthub.pyc deleted file mode 100644 index 328465592bf32ae1217c1ce43f2f2382daf81451..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5935 zcmb_g&2uA174MN`TN;1tXB-wP&@qH)3&ooakQAu}qP53s5m|DzQ$npStu>(}q|_qz3u z|1P`V`~5#3`6ByU!uJPw%wNz9gy^7k#2_b*9nnF@_H&|>oAmRdlb`epqEndk=R{|Y z`+0FZFTw@USrB1Sbc(_uE(LL15}_+Pt{u;b<3$lJiO!M;mqlmU&SKvc(OD6Ld7-a~ zL#$g6p9}Gs5RW_8Nvu}6NYXwnK?^R?yhz2^6N*mov{9#4bB7fc8)yYX< zOb|yhFmk99-9MK{NiRzEpdt?wtz~>D{b4UT(iNG;vKO7pkxmR2#Qn4vL_u^Ud(wwo zE-2F>*qHb*J?kYJ!~@wgCia6K_{l-+k56=z_EOr9R5r4l4z*lsWh>V1@+Je_3*8_B zH!UZx1_9}7-gn(LUiJF%r6 zL}hcc16T;_)$ElS_QFv1b!IT^8$>QmP1J#Q*LG@cFl{#_ZJ4pDd04k4cRaPR4bIhvwR)}n zxZ-Zr+705l)l{-7cdM#h+t{mDmE7G^yUmt|?Ki=;QEO}|?BeZsjrKZr#hmm$!a%mR ztMxi<35VBI%TD zf{SvA%t|Nsw>_Go{i^tEv}?@z=p@i_p0 zNvKwt6!A%VdPvI+Jf@B&72*Q=I^r{jI^CQY@=P~B=@cfNxk+b!(t#|N7$OMZIB{>3 zU9^g{PYYEL zk2O;_;z&Q^4xKrO7y+UV%lH=DnD@}Tw`Z`;y<~89Z!-g6bI&CHJ^d-LIMnH^^K1d) z4Z2+beA?~4Nvv0huXEEWP5-}c+Fyf&?8VA<0OGb9LNUcz#p<>)HS@mBAc*@ z(|GhYSehal;keGSv+A%V$oKR#@t9j^tR)a+7x?7FA7b^0m)@z z!9ugMQF%>DD1bftc4U)WN&u;2qzCj0R_cJy>P6(B#9Gs8$;&LmQ}CP5YM;I z6XyI2a56WEAL8MfvznWC@`#ERXU_2n8R;QG*}UIH2hnhm7Z(L_h?esOHhAz%_!Fck zI?@URr0ohTav4O>!jq1jnOiB;Us5vyziQMLg_{zoP@YU*^R5 zD>M1;K$$z3+mGHy@a8yp*Ex94Z!i_ymQkD@Oet)5W5PM$7>Ad^nviZ&IOi+ub*?zh z{^{RDfvi=aM4{Apjk(j*Jh4W?AU!?yv5yX64#Vu1$U?X*$cqAn zB*WBduJ)VEa|N10)pz!zyYSr1`j`3HI!;f(8?9qk!jCPsL&FeuD2g#Uo6iJ)U%rtT zDwN*Dm^k{=wT|QQdD_=OTjmN!9wcDXeSlj^rD~J}Q9AR8`Y}-utx0iqNYomQY1wq2 zGTCC5N{WO4yuz|q<0bV5czuG$5FiQo;Wqo^x~&})Xe2m$;@{ZU0vhUpg#)A!b#ej* zrcPd57R1v(^MQcFA5p&f$rsMYpqLYv2>*HUF^Cq#Wl>y~#04L>o)ks=SDY7c{6k7% z@FxuT{6&K=kY9_0rx$bLm;2G`wBq23j=$5N3HBnG#q;pi#p%%}Hu~7d*yyPO~hxpyWo{=e7)XSI< zx-bGIXIg!qsD40=L~})vOrWRnm;*FodXT*?mQc3eL{u*VVT#%J+za2O+_JNhvov$a zf|vJK9G|e6P?^lbS<#qg1!6rr7LO!)j)-qeP$z?>3C`2Ebwidw$PJr7^OQj6_qj$O zxBzwRCwSX-j*xi9>=ci(rNF@#$}~pgcq{LZW$r4nCWo3-yqh^ZZMxg^eSiVIg< zEQvdK7FmX$kyF9Drc|^gm*sv6yV!2+)W!9lR8I zR#7PO*(k+Tl*7)1T8~}`HjAeGV;d76YZN@HC4g5P6$#%bLmai}g;&E>ozI)n>(u_Us1g@bljhJ zN~O|#DPLN;nMZrGR4m;u)3)rTZWljCLTDmaKc$B5MCvXz_tCHy?2jM{L!roj_E?nN zn`bwZ$xV7ayX*6>0IEQ%_<~Am#(ny6k0rc9jrkJ#z#U;K`VuVS>NL`6MQHBY2T(^+nUo?OiQzcP&SZ_% zMai|8$xMSG9hgWZDmfF87(%TJN;8HSosP_9lt~cBBGNh?stEE#oDTD;OiW~bnsh& z2eE)G?AkCzNpQ^cbJq=V z6b;e~X?-bDoume)+Hl(hSR;z8&X4psnoPt%&I1O$DsdULa0lMy18tCa6-~q}&8)uv zb`u2_wmi}6?uEzoz!QE?90uJl{ifFxkLx|`AJ@dOAGW$jp#Vit?}R6!yC>?M6Y<*b zG;6|pa~OENp6CXye{k6LJ>dLKqkYu$JNx1l_&VKCwEY7=gwU`nSm9ih?}2Ym9C$&a z1_HbTGKVK*AxP8eWYgO@@0f%Vw$c{NcF{2m8nFX$fB zTn;BVARR)$*YOG|9BLb&4;cXo2kZz=FKiJ_uil0%q^I+(^zFFn-}rP4cdCAWird`p z&o@almDnufWSC~D9y)-^_^sgAz)xRbxjG?3q!u0ceTh`bV#@U1q2}9P(ye3nXt>LC zHHH!=cpL;@1ykY`ZIJHRD8PU=$(uR7dETpe8J#vXWf~)!V3EeKy4OH0zQq=|E(#=9SCi26( z?+pFEOkdm*3s!4(#$Nq{H_f+yquXU3(fc>}VM6b>uD1x0z?N-aq2uHcEtw5EcPagZ z9DFdlar$7t_3p2fl*oC5o!X>$l{wNEaPJPOExK8;5@0>H-(jWdF5RF|Ar_vjlKO;^ zGZd}4hoaSv1A&gYPwK&(`L`(aD&4}Lsx59y2b#DFJ3)MGzKdFRkqG@yEF#DgEx0Fh z_vevb4E-7=p5bwUNuZ`v8S^lmT#IOA@HSvtSKu>JnMRFAn5q~m_A$U{dlpy$m_}KS z3BXf&{v5IDbdFkQSm6un>ZU%T>Svjmq!z{xI>h}v9A(AhTZw)RkJ6i*}n%^H*!s0Q8sw*yJh-5S% zR+~*=LTF+PvYp7xTA1aDCBBSwk8`2*D4*B}>_rnTZP&otiQ{Qu2JnQju7pTw(a+dU zKIz+sm2C%$ZqY|@g75J;d)5EVPv6?EbA|r;A3WAID*?iDG#Tz=I#)*8K8AM}tgV;5$7+23!EX#fng9ClUcY3 zMA7CuX(Da>K9mO!Oq)|y@iFuP&!gbyyl`*-7#a)xul-%Kp}#5&`ZfEYd08o~qpPnw TpO&lThfcLzEmbS)+Z)a|($H;d diff --git a/eventlet/timer.pyc b/eventlet/timer.pyc deleted file mode 100644 index cbd58e8a572732884129396a9fdd28f2f1748b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4055 zcmcInTW{mW6&^~myke3j%>qf#EzpU90P;q)gI*F~+XgF3^qQzl85EtZjRFKOBZ)Od z3LJ7>1Ky`C`dIY8^tHdLf1v%&Atl=h`ck-pIGoFwGw1r9;XnTMf%)vO|9lmR=GVdd zB_91XnplWSw1$XV;>r+dOI)^uq1xiIBjUDDJ7SKtj`)iZ?}WIx?233t%=%s0UxL%i zd6FtwCM#7OtZ&ShK~*mELLTLzwAbrAP0HLfNBR1uNEXXd_M?6Ii?6==_0Q3KJ$Oj= z1FCNZa*|}R%A~8b3W{hskaiXwm}jb3C0ZwWCKD}}s!-vLTogf8s(2vhg;Fw~%V-%C z3pJ2sE`#hwu2rG2ArH$S$&zd#0~tXs6O`o=Y;-;^uY*E?IF^Cdd6Wd;C*wS-Rw^rl zlFlP_wd|KmCHH2{j=g<8BvwIcCK6nJ>1KZ&I#zdNCu9x}hp(cqo=T(v6NEihaaSoI5iGNa2Ni!t*JWtd7npBJO zEKW#Y{lYXoECpfyMzJn+EM$2JQQ6$=02ZS5YO|!5L7K`?H3q}ENoLa2MjdEZg}Q{} zlOUDryx{Y`ujT>HKe6O&dhER(x|XzO^30unXOFG1+#Aj?-W$l*ws$f;_arFXq2pc1 z>9HI-7xMSEGag9m4`;45o5`tb+NWm|+rpgfj3(z}+j%9Au+N!#a$=v_9+-MlNe4Ep zYzzC2<*DV4PQZD1WKV4GVqhNIo-2zv!UzRqw~qom1pPf*>q;%_%YZzwsY*_ z6zkM-yaSwyIca@|ft;NTClfl#9G*jZm*kV9>Dh&AzdG^c$#gQdFnMG_vfE!DZi>BzjA^s!Jo;sw)XzF+_h68BndfTn9?U^-@ zL)V^>L5|(&>A)m&Vh6Z`3HCWw&4oLL@ErTtBBZqtg<%__xJv zN6gV;roEx*2-qh7Ru~P;cg0(%R73u*c-s}XU6DK?e5Y*~?uevE=#OF2#k^r;26ndq z{D6F+{|?)a3I%wSL4*CKy!UJD@Iq4BuYk6zY9+I36{>=Avsq@C2>?5xn&-e>vJ`;h z0jDUena~>9g7#1!_=K`RfWxInE(0yqpVvvD;s^VmqXg)h1$vC*3~y@yJo5@4l0Uz? zuBoro&2?U&5d4>2+if1zj#`N81cCv&2(gMPlNEf4W1u0GmyNrDVXzw3(>!0x@Mg=Y z(Z4b1hQ7Zq0pbn$K@ivBQuO)A{e$gpP6~%gvY;B(Hz`IaVvd7UE0=n1iyDe1m&iEe z{DmK@uv)kje|413P))LhJ>~W7;61Fbu~7QJ+vuuK0m-MKzRa0(N$jJLC&}zQzPe`4 z?)zBlDAgS!qZi~B__vX6)=egAXt&>s;` zvuE^-kMZmppBWz-5kUjx9z7#G`UXv%FckK<%CyB>V>5%yN07J0A2HbyZ_)DAhw^Hh zQ62FKat0dZUkU^Hjm(HU;vL4%gn@hkj!?36M6yG9^yxRCrkugjj7{a-8DD<}X*>d| z{ug>6>3Pl#+I_Z`CI!qsdCGAdzHDufE-1@_E$rl(a>*&Yg7~ME+KUV&s(cfqmEz6r zheXGzt0L`^&D&c**#yP zL*^6g1EX;R%`mnlaKpwKet%o77bvumAH3&w!?|w2r2ewyKii6?*>3LETafbTi`zPpYnh=J` z=<~w}_ymi05l~lGfQpv4t;r0Lk diff --git a/eventlet/timer_test.pyc b/eventlet/timer_test.pyc deleted file mode 100644 index 8432a61c49f7dd0f5db018db391bb4997f7b8fc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3787 zcmb_f-EQN?6&^~KY;(OEq-oHfZPS5~7Fs~H(_FMgun0m+^qQzl2_rgNyB7jQjw~h= zDR4$M3~X+?MY?^BzCv&NRDFT=JBPBoUZhuE?(%3jKj-&5AL)Pn=dt_r`+vVk#QJLC z_bc2che(CEfOJIK5Z^lD0t5VSif{t$WMv^UBj>u((;InN#f8;rSI#P}WjT|{JgzQvSK3m>#f@C*%HTjb zwQ*Kt#ifj80<$i5+Bwvia%QjNN@H^>V`IuBi=j`ZWwKi6!p4^DQB+2D>|D$J@%qI6 zGd7ayICrxG+O)h~lGhm`URg@+l)=z>lmjzc8d=sb6!A%ou>>z#MJhw_8m!2UW# zhGwqk<>G!MnQ>>Us(?u(1v}CbDPxP@=)}?tc|I%iyu7B+lCnrMir0MMx)GM*X?dkN zmb%nMY2hlTn+w2B)Ky(CnR%S&a;nz}gWarfY3eo{M7Nq6i^6AdE|+D+_TCNi0QOIO zIUXKIFFobUU@S*!_;t|t`*PnK<9WX;Uk1_1@GO$pp}a6Um&0S}h3E25LD=s~|IZ`k zkH>PT+~9OH2z<;1VQ+BO55gDn2XQbn7iFN3k)m7WU5l#pXJJng!aPMm;tD8aeV zueDIBIYFC>U<)Pe3_;)eBKy8KfG(6LyjQ*hH~TNHumh42wh0k0GfTWvt%|%XmzLO% zD96+zq9D7Axd?L+JtcVH%Yi(?ZJt4B@Fds{vOx?9&I31s)M&6pgh_*zP~+Nio6b?s zCJCt_N9gX+w1KcfWHr^Yiq{SCog-d>F&o5~-!*vL6t5a0YZ9R@w}oT3#9zUrw5!D{ ztshuv^9qyjTW8?+xI+$v8H7kk&_%wDahoj&-b6EDha}8lSQfg2UvVeQAMtp2X26(- zReF8cUsIqtG*xn_ufT@6ws(VjEN^#k_<0^LrfK}8#F&yH_{E^eKQ0QFfz1=QarKNQ z#xV8?_IL)ish9n#LR0gxWo{|u3Ho2B94Vg%z0E8KV;iLX^Y;}7A%;*iArF6r2jfBr z=VNEj`8ob=s!mBH56$-wb=6;^z>c^S;vbmd0s~Ng7yLGD2<#&?00CIq;`isK;;+}` zZ?6HDV!cr#Vw-oq5n_+azO#YK?!6hVF}1)uK%ne=L~cHYfEK_6d4s)ioiWZHWjJ0g zb&+y%qiVIr24z^$N#6V*fGFzKXSf*{3E8EaEHF?h&9B%$q;c&W7np*=OQfQ!cM>Oe3p9`0b5w2&%GQO7fj@0jZtFSO#aLmn!2i9%7UVgTAz+<>;!BRWRP@IW6YjBiD)pgtz?0Yn`gndL$}C`8K! z8Qn4Y`5S08KY|cIciVaDxXxqeBghYa0O$Y2whhjK@HZ&dhTWQ?d|KnRMmBA15uXyW z=`-;XUExrU@O4RDQ8xxC_npSrVYsJEyJ(NeE)!<`x%tMa>GIL02b$|1sAsqbnB0K! zx5$ui!#(f|49IN+!k$4PLI4p1ax?mlrDM$Vin5~U5LaCNm=)%5O;Op z(Zh;CHYm@A(%FWCyW6{u)?O{}x6EYH+0e&if-w`#kw*Vu3T?9<22{{Z8%am4@t diff --git a/eventlet/tls.pyc b/eventlet/tls.pyc deleted file mode 100644 index f6332d2cce270d574bab9ee2d51092db8bcc4914..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2581 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 diff --git a/eventlet/tpool_test.pyc b/eventlet/tpool_test.pyc deleted file mode 100644 index 0a8248e057d88048b4396e2ec959fd427a760aed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3212 zcmb_e-)|d55T3OiC%z^j^cO!0Vil-xrPL060Tl$KNud_SMdy^LmXLM1Tie^5Z?CuO zI!5gaL5RPAe+2QyU&RBj@XemnP~fe#wR^iebGtM1eKUK%{;?KZ`RVsBV_N*q;rTh5 z{S#e6)JHEUsnC(2J_hBuLjB6=xJv!%>9|Jynjcr`Xo=D~_3M-_Q-7J#73!~0+Ms@e z1n*L#qkz(L)IV3wm*{Af(lzR@m2;f9PW^RK>!e?y6IilA>Q(BWr=(6lVtRp+Wl|Su zh|N}b1v(GfZ?v{$lNB}>qRnd`2Xix( zlV~n8L`nqoM|`qhb*>e1rHV*m_rvqH-dIPmuC1+ zEg88WkaFx?ezV=4&1S90+t@P2sGXJ`*!JDd?(J^x_I2191oyL4St|?mXsQdicQ}_( z4lCm55YbGdnKXrrMumdR8Mbm(Xs5H$mb7N*W>KMnL|a$r!>M~NJPXSu{QRc^0u*I( zbEhXey-j&*r`PFi1qYq|JNNGI%Y&UT-0ALjZujK9Q10IA?sfJ%_qv#UA$PhDu^ zgi-sx1uNS{GHdUdcsfy;vu#_%ZS@$#RJpUkbD87K&5O*vgH0^kPGVVjpXO1LL@}@M z4~a*&(Cpjj6fzwNPClnmb9~wP5w)mBVXw&+{?}XH8*iL?hZ&U)vB0Wy__93mAsgsS zyTRZ+-kVJ)5wls`_`F>-`x&|?FrDaOgYsn(PS6i1H_p5BM6hsJq01+{RwZ3y9_Tuk zC0>HG*W|ssmh1D-n49vd-9l?FW9}o{HJNhjnC)2L!&JLn6s8E-Mz7%XMEPjjRG~UB z@`Yp46kw>C_*jO)srGCPFj*&iS;69f!1DL;uxu2Gi}+s<-6m7sm9fmCi5d(Xn=u$n zOfpR|4cX~%l{+T#nL&Q^LZl2E*zjF6PsqAxR6U*8B5oeM-!Q<9^;B>QY4nu^E>A0T z{0jj+u<{|Y>*rJOpzVwVtCx8#sqylO5KnmA&|S4RrBzuXn_$pY zdo8@c5{@uS3Kc&Ft>7i%%&`fXcAhrh6y|vD;}x;AT=hx&eO~2NYur7u_4yPs(4LY@^o7hj=kDP z5#8r&J!U=ru}inm?5pVhRdi5jSo~jR0Q3bX00KyGLIUw00dO#5n~4I94h*C)y(Iv!a4z4EY@0JB>j z@DKxXj0Cy86k6AKGd;_anXiv@q1jdW|4Lt#j|q2vGU3+ zL;3fmLHnJ`m6ru3`^cYKAAEv=CoBQPy4aw)cv~!^FALQrh(ks>nVOEv;DL_ElPJsX=rj#EsXe$~M5$FF8&Vd1 z9wF;g?8>b5_^x1)*}oe>mpu(Rk{)*U@MQ+lSNTuMBL3-loMPWZSFdb~&B}(@sH}?B QO1;vkT@D)SZv<=NZ`AtAQ2+n{ diff --git a/eventlet/util.pyc b/eventlet/util.pyc deleted file mode 100644 index 91cf029ecb09062aa4439e78e0fe5d4afafa1e70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8313 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 Date: Sun, 16 Mar 2008 22:33:55 -0500 Subject: [PATCH 004/118] Turns out only signal 2 was needed. --- eventlet/libeventhub.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index b9478f8..99b99ef 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -56,9 +56,8 @@ class Hub(object): self.greenlet = None event.init() - for sig in 1, 2, 15: - signal = event.signal(sig, self.raise_keyboard_interrupt) - signal.add() + signal = event.signal(2, self.raise_keyboard_interrupt) + signal.add() def stop(self): self.runloop.abort() From f81ed5c63f593dd3ea0af00b8ae95db42146594e Mon Sep 17 00:00:00 2001 From: donovan Date: Sun, 16 Mar 2008 21:16:45 -0700 Subject: [PATCH 005/118] Add a dumb test server to httpd. --- eventlet/httpd.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 7d73756..15b8d24 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -582,3 +582,14 @@ def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAU sock.close() except socket.error: 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()) + From 9a0bfd7045571e52959417f93140a4f04f43984f Mon Sep 17 00:00:00 2001 From: nat Date: Mon, 17 Mar 2008 00:51:25 -0400 Subject: [PATCH 006/118] Fold Runloop into libeventhub.Hub. Remove hub.runloop.etc from other library modules. --- eventlet/api_test.py | 4 +- eventlet/coros.py | 2 +- eventlet/httpd.py | 2 +- eventlet/libeventhub.py | 191 ++++++++++++++++++++++++++++++++++--- eventlet/timer.py | 2 +- eventlet/timer_test.py | 36 +++---- eventlet/twistedsupport.py | 4 +- 7 files changed, 205 insertions(+), 36 deletions(-) diff --git a/eventlet/api_test.py b/eventlet/api_test.py index b2c371a..77f71e8 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -33,9 +33,9 @@ def check_hub(): api.sleep(0) assert not api.get_hub().descriptors, repr(api.get_hub().descriptors) # Stop the runloop - api.get_hub().runloop.abort() + api.get_hub().abort() api.sleep(0) - assert not api.get_hub().runloop.running + assert not api.get_hub().running class TestApi(tests.TestCase): diff --git a/eventlet/coros.py b/eventlet/coros.py index c53f807..4552ad8 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -263,7 +263,7 @@ class CoroutinePool(pools.Pool): sender.reset() (evt, func, args, kw) = recvd 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) def _safe_apply(self, evt, func, args, kw): diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 7d73756..4890196 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -501,7 +501,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): body=err.body) finally: # 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 if not request.response_written(): diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index 105db2c..2a22909 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -1,5 +1,5 @@ """\ -@file pollhub.py +@file libeventhub.py @author Bob Ippolito Copyright (c) 2005-2006, Bob Ippolito @@ -27,10 +27,10 @@ import sys import socket import errno import traceback -from time import sleep +import time from eventlet import greenlib -from eventlet.runloop import RunLoop, Timer +from eventlet.timer import Timer import greenlet @@ -47,25 +47,37 @@ import event class Hub(object): - def __init__(self): - self.runloop = RunLoop(self.wait) + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + def __init__(self, clock=time.time): + self.clock = clock self.readers = {} self.writers = {} self.greenlet = None + self.stopping = False + self.running = False + self.timers = [] + self.timers_by_greenlet = {} + self.next_timers = [] + self.observers = {} + self.observer_modes = { + 'entry': [], + 'before_timers': [], + 'before_waiting': [], + 'after_waiting': [], + 'exit': [], + } event.init() def stop(self): - self.runloop.abort() + self.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,),) + args = ((self.run,),) else: args = () try: @@ -110,7 +122,7 @@ class Hub(object): def wait(self, seconds=None): if not self.readers and not self.writers: if seconds: - sleep(seconds) + time.sleep(seconds) return timer = event.timeout(seconds, lambda: None) @@ -119,3 +131,160 @@ class Hub(object): if status == -1: raise RuntimeError("does this ever happen?") + def default_sleep(self): + return 60.0 + + def sleep_until(self): + t = self.timers + if not t: + return None + return t[0][0] + + def run(self): + """Run the runloop until abort is called. + """ + if self.running: + raise RuntimeError("Already running!") + try: + self.running = True + self.stopping = False + self.fire_observers('entry') + while not self.stopping: + self.prepare_timers() + self.fire_observers('before_timers') + self.fire_timers(self.clock()) + self.prepare_timers() + wakeup_when = self.sleep_until() + if wakeup_when is None: + sleep_time = self.default_sleep() + else: + sleep_time = wakeup_when - self.clock() + if sleep_time > 0: + self.fire_observers('before_waiting') + self.wait(sleep_time) + self.fire_observers('after_waiting') + else: + self.wait(0) + else: + del self.timers[:] + del self.next_timers[:] + self.fire_observers('exit') + finally: + self.running = False + self.stopping = False + + def abort(self): + """Stop the runloop. If run is executing, it will exit after completing + the next runloop iteration. + """ + if self.running: + self.stopping = True + + def add_observer(self, observer, *modes): + """Add an event observer to this runloop with the given modes. + Valid modes are: + entry: The runloop is being entered. + before_timers: Before the expired timers for this iteration are executed. + before_waiting: Before waiting for the calculated wait_time + where nothing will happen. + after_waiting: After waiting, immediately before starting the top of the + runloop again. + exit: The runloop is exiting. + + If no mode is passed or mode is all, the observer will be fired for every + event type. + """ + if not modes or modes == ('all',): + modes = tuple(self.observer_modes) + self.observers[observer] = modes + for mode in modes: + self.observer_modes[mode].append(observer) + + def remove_observer(self, observer): + """Remove a previously registered observer from all event types. + """ + for mode in self.observers.pop(observer, ()): + self.observer_modes[mode].remove(observer) + + def squelch_observer_exception(self, observer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Removing observer: %r" % (observer,) + self.remove_observer(observer) + + def fire_observers(self, activity): + for observer in self.observer_modes[activity]: + try: + observer(self, activity) + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_observer_exception(observer, sys.exc_info()) + + def squelch_timer_exception(self, timer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Timer raised: %r" % (timer,) + + def _add_absolute_timer(self, when, info): + # the 0 placeholder makes it easy to bisect_right using (now, 1) + self.next_timers.append((when, 0, info)) + + def add_timer(self, timer): + scheduled_time = self.clock() + timer.seconds + self._add_absolute_timer(scheduled_time, timer) + current_greenlet = greenlet.getcurrent() + 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 + + def prepare_timers(self): + ins = bisect.insort_right + t = self.timers + for item in self.next_timers: + ins(t, item) + del self.next_timers[:] + + def schedule_call(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = Timer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def fire_timers(self, when): + t = self.timers + last = bisect.bisect_right(t, (when, 1)) + i = 0 + for i in xrange(last): + timer = t[i][2] + try: + try: + timer() + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_timer_exception(timer, sys.exc_info()) + finally: + try: + del self.timers_by_greenlet[timer.greenlet][timer] + except KeyError: + pass + del t[:last] + + def cancel_timers(self, greenlet): + if greenlet not in self.timers_by_greenlet: + return + for timer in self.timers_by_greenlet[greenlet]: + if not timer.cancelled and timer.seconds: + ## If timer.seconds is 0, this isn't a timer, it's + ## actually eventlet's silly way of specifying whether + ## a coroutine is "ready to run" or not. + timer.cancel() + print 'Runloop cancelling left-over timer %s' % timer + del self.timers_by_greenlet[greenlet] diff --git a/eventlet/timer.py b/eventlet/timer.py index a91390a..fd5e004 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -66,7 +66,7 @@ class Timer(object): """Schedule this timer to run in the current runloop. """ self.called = False - self.scheduled_time = get_hub().runloop.add_timer(self) + self.scheduled_time = get_hub().add_timer(self) return self def __call__(self): diff --git a/eventlet/timer_test.py b/eventlet/timer_test.py index 496a884..d917703 100644 --- a/eventlet/timer_test.py +++ b/eventlet/timer_test.py @@ -24,7 +24,7 @@ THE SOFTWARE. import unittest -from eventlet import api, runloop, tests, timer +from eventlet import api, tests, timer class TestTimer(tests.TestCase): mode = 'static' @@ -35,32 +35,32 @@ class TestTimer(tests.TestCase): assert t.tpl == t2.tpl assert t.called == t2.called - def test_cancel(self): - r = runloop.RunLoop() - called = [] - t = timer.Timer(0, lambda: called.append(True)) - t.cancel() - r.add_timer(t) - r.add_observer(lambda r, activity: r.abort(), 'after_waiting') - r.run() - assert not called - assert not r.running +## def test_cancel(self): +## r = runloop.RunLoop() +## called = [] +## t = timer.Timer(0, lambda: called.append(True)) +## t.cancel() +## r.add_timer(t) +## r.add_observer(lambda r, activity: r.abort(), 'after_waiting') +## r.run() +## assert not called +## assert not r.running def test_schedule(self): hub = api.get_hub() - r = hub.runloop +## r = hub.runloop # clean up the runloop, preventing side effects from previous tests # on this thread - if r.running: - r.abort() + if hub.running: + hub.abort() api.sleep(0) called = [] - t = timer.Timer(0, lambda: (called.append(True), hub.runloop.abort())) + t = timer.Timer(0, lambda: (called.append(True), hub.abort())) t.schedule() - r.default_sleep = lambda: 0.0 - r.run() + hub.default_sleep = lambda: 0.0 + hub.run() assert called - assert not r.running + assert not hub.running if __name__ == '__main__': unittest.main() diff --git a/eventlet/twistedsupport.py b/eventlet/twistedsupport.py index 5bbbc8b..dfd57fb 100644 --- a/eventlet/twistedsupport.py +++ b/eventlet/twistedsupport.py @@ -82,13 +82,13 @@ class EventletReactor(posixbase.PosixReactorBase): self.running = True self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None) ## schedule a call way in the future, and cancel it in stop? - api.get_hub().runloop.run() + api.get_hub().run() def stop(self): self._stopper.cancel() posixbase.PosixReactorBase.stop(self) api.get_hub().remove_descriptor(self._readers.keys()[0]) - api.get_hub().runloop.abort() + api.get_hub().abort() def addReader(self, reader): fileno = reader.fileno() From 82e4281074d734e79b52613cd4d7b69207ca451d Mon Sep 17 00:00:00 2001 From: donovan Date: Sun, 16 Mar 2008 22:34:12 -0700 Subject: [PATCH 007/118] Yay, libeventhub works\! --- eventlet/libeventhub.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index 2f51407..b676c87 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -21,6 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import bisect import sys import socket import errno @@ -53,8 +54,6 @@ class Hub(object): self.writers = {} self.interrupted = False - self.runloop = RunLoop(self.wait) - self.greenlet = None self.stopping = False self.running = False @@ -69,6 +68,7 @@ class Hub(object): 'after_waiting': [], 'exit': [], } + event.init() signal = event.signal(2, self.raise_keyboard_interrupt) @@ -130,11 +130,6 @@ class Hub(object): def wait(self, seconds=None): if self.interrupted: raise KeyboardInterrupt() - - if not self.readers and not self.writers: - if seconds: - time.sleep(seconds) - return timer = event.timeout(seconds, lambda: None) timer.add() @@ -143,6 +138,8 @@ class Hub(object): if status == -1: raise RuntimeError("does this ever happen?") + timer.delete() + def default_sleep(self): return 60.0 @@ -242,12 +239,7 @@ class Hub(object): def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds - self._add_absolute_timer(scheduled_time, timer) - current_greenlet = greenlet.getcurrent() - 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 + event.timeout(timer.seconds, timer).add() return scheduled_time def prepare_timers(self): From dd2d82f818f556cdefb60229fb3718337628ae55 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 12:44:43 -0500 Subject: [PATCH 008/118] Initial refactoring of base Hub class --- eventlet/hub.py | 269 ++++++++++++++++++++++++++++++++++++++++++ eventlet/pollhub.py | 124 ++++++------------- eventlet/selecthub.py | 123 ++----------------- 3 files changed, 313 insertions(+), 203 deletions(-) create mode 100644 eventlet/hub.py diff --git a/eventlet/hub.py b/eventlet/hub.py new file mode 100644 index 0000000..8e1ea21 --- /dev/null +++ b/eventlet/hub.py @@ -0,0 +1,269 @@ +"""\ +@file hub.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 sys +import socket +import errno +import traceback +import time + +from eventlet import greenlib +from eventlet.timer import Timer + +class Hub(object): + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) + + def __init__(self, clock=time.time): + self.readers = {} + self.writers = {} + self.excs = {} + self.descriptor_queue = {} + + self.clock = clock + self.greenlet = None + self.stopping = False + self.running = False + self.timers = [] + self.timers_by_greenlet = {} + self.next_timers = [] + self.observers = {} + self.observer_modes = { + 'entry': [], + 'before_timers': [], + 'before_waiting': [], + 'after_waiting': [], + 'exit': [], + } + + 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.excs[fileno][2] + except KeyError: + pass + if exc is not None: + try: + exc(fileno) + except self.SYSTEM_EXCEPTIONS: + self.squelch_exception(fileno, sys.exc_info()) + + def stop(self): + self.process_queue() + 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 process_queue(self): + pass + + def wait(self, seconds=None): + raise NotImplementedError() + + def default_sleep(self): + return 60.0 + + def sleep_until(self): + t = self.timers + if not t: + return None + return t[0][0] + + def run(self): + """Run the runloop until abort is called. + """ + if self.running: + raise RuntimeError("Already running!") + try: + self.running = True + self.stopping = False + self.fire_observers('entry') + while not self.stopping: + self.prepare_timers() + self.fire_observers('before_timers') + self.fire_timers(self.clock()) + self.prepare_timers() + wakeup_when = self.sleep_until() + if wakeup_when is None: + sleep_time = self.default_sleep() + else: + sleep_time = wakeup_when - self.clock() + if sleep_time > 0: + self.fire_observers('before_waiting') + self.wait(sleep_time) + self.fire_observers('after_waiting') + else: + self.wait(0) + else: + del self.timers[:] + del self.next_timers[:] + self.fire_observers('exit') + finally: + self.running = False + self.stopping = False + + def abort(self): + """Stop the runloop. If run is executing, it will exit after completing + the next runloop iteration. + """ + if self.running: + self.stopping = True + + def add_observer(self, observer, *modes): + """Add an event observer to this runloop with the given modes. + Valid modes are: + entry: The runloop is being entered. + before_timers: Before the expired timers for this iteration are executed. + before_waiting: Before waiting for the calculated wait_time + where nothing will happen. + after_waiting: After waiting, immediately before starting the top of the + runloop again. + exit: The runloop is exiting. + + If no mode is passed or mode is all, the observer will be fired for every + event type. + """ + if not modes or modes == ('all',): + modes = tuple(self.observer_modes) + self.observers[observer] = modes + for mode in modes: + self.observer_modes[mode].append(observer) + + def remove_observer(self, observer): + """Remove a previously registered observer from all event types. + """ + for mode in self.observers.pop(observer, ()): + self.observer_modes[mode].remove(observer) + + def squelch_observer_exception(self, observer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Removing observer: %r" % (observer,) + self.remove_observer(observer) + + def fire_observers(self, activity): + for observer in self.observer_modes[activity]: + try: + observer(self, activity) + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_observer_exception(observer, sys.exc_info()) + + def squelch_timer_exception(self, timer, exc_info): + traceback.print_exception(*exc_info) + print >>sys.stderr, "Timer raised: %r" % (timer,) + + def _add_absolute_timer(self, when, info): + # the 0 placeholder makes it easy to bisect_right using (now, 1) + self.next_timers.append((when, 0, info)) + + def add_timer(self, timer): + scheduled_time = self.clock() + timer.seconds + event.timeout(timer.seconds, timer).add() + return scheduled_time + + def prepare_timers(self): + ins = bisect.insort_right + t = self.timers + for item in self.next_timers: + ins(t, item) + del self.next_timers[:] + + def schedule_call(self, seconds, cb, *args, **kw): + """Schedule a callable to be called after 'seconds' seconds have + elapsed. + seconds: The number of seconds to wait. + cb: The callable to call after the given time. + *args: Arguments to pass to the callable when called. + **kw: Keyword arguments to pass to the callable when called. + """ + t = Timer(seconds, cb, *args, **kw) + self.add_timer(t) + return t + + def fire_timers(self, when): + t = self.timers + last = bisect.bisect_right(t, (when, 1)) + i = 0 + for i in xrange(last): + timer = t[i][2] + try: + try: + timer() + except self.SYSTEM_EXCEPTIONS: + raise + except: + self.squelch_timer_exception(timer, sys.exc_info()) + finally: + try: + del self.timers_by_greenlet[timer.greenlet][timer] + except KeyError: + pass + del t[:last] + + def cancel_timers(self, greenlet): + if greenlet not in self.timers_by_greenlet: + return + for timer in self.timers_by_greenlet[greenlet]: + if not timer.cancelled and timer.seconds: + ## If timer.seconds is 0, this isn't a timer, it's + ## actually eventlet's silly way of specifying whether + ## a coroutine is "ready to run" or not. + timer.cancel() + print 'Runloop cancelling left-over timer %s' % timer + del self.timers_by_greenlet[greenlet] diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index 0540837..3c4ef8e 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -31,80 +31,22 @@ import traceback from time import sleep from eventlet import greenlib -from eventlet.runloop import RunLoop, Timer - -import greenlet +from eventlet import hub EXC_MASK = select.POLLERR | select.POLLHUP | select.POLLNVAL READ_MASK = select.POLLIN WRITE_MASK = select.POLLOUT -class Hub(object): +class Hub(hub.Hub): def __init__(self): - self.runloop = RunLoop(self.wait) - self.descriptor_queue = {} - self.descriptors = {} - self.greenlet = None + super(Hub, self).__init__() 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 + readers = self.readers + writers = self.writers + excs = self.excs + reg = self.poll.register unreg = self.poll.unregister rm = READ_MASK @@ -112,16 +54,13 @@ class Hub(object): for fileno, rwe in self.descriptor_queue.iteritems(): read, write, exc = rwe if read is None and write is None and exc is None: + for dct in (readers, writers, excs): + dct.pop(fileno, None) try: - del d[fileno] - except KeyError: + unreg(fileno) + except socket.error: + #print "squelched socket err on unreg", fileno pass - else: - try: - unreg(fileno) - except socket.error: -# print "squelched socket err on unreg", fileno - pass else: mask = 0 if read is not None: @@ -129,24 +68,29 @@ class Hub(object): 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 + + oldr = readers.get(fileno) + oldw = writers.get(fileno) + olde = excs.get(fileno) + + if oldr is not None: + oldmask |= rm + if oldw is not None: + oldmask |= wm if mask != oldmask: reg(fileno, mask) - d[fileno] = rwe + for op, dct in ((read, self.readers), (write, self.writers), (exc, self.excs)): + dct[fileno] = op self.descriptor_queue.clear() def wait(self, seconds=None): self.process_queue() - - if not self.descriptors: + + readers = self.readers + writers = self.writers + excs = self.excs + + if not readers and not writers and not excs: if seconds: sleep(seconds) return @@ -156,14 +100,12 @@ class Hub(object): if e.args[0] == errno.EINTR: return raise - SYSTEM_EXCEPTIONS = self.runloop.SYSTEM_EXCEPTIONS - dct = self.descriptors + SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS for fileno, event in presult: - try: - read, write, exc = dct[fileno] - except KeyError: - continue + read = readers.get(fileno) + write = writers.get(fileno) + exc = excs.get(fileno) if read is not None and event & READ_MASK: try: diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index 60e2bb6..41c4434 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -31,138 +31,37 @@ import time from bisect import insort, bisect_left from eventlet import greenlib -from eventlet.runloop import RunLoop, Timer +from eventlet import hub 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,) - +class Hub(hub.Hub): 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 + for op, dct in ((read, self.readers), (write, self.writers), (exc, self.excs)): + if op is not None: + dct[fileno] = op 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 + dct.pop(fileno, None) 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 + 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.runloop.SYSTEM_EXCEPTIONS + SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS for observed, events in ((readers, r), (writers, w)): for fileno in events: try: From 735e2acfa5a7ada677fce819efda3bc595025da2 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 12:58:43 -0500 Subject: [PATCH 009/118] Untested final naive refactoring of Hubs --- eventlet/hub.py | 24 ++++++------------ eventlet/pollhub.py | 58 ++++++++++++++++--------------------------- eventlet/selecthub.py | 29 +++++++++++++++------- 3 files changed, 49 insertions(+), 62 deletions(-) diff --git a/eventlet/hub.py b/eventlet/hub.py index 8e1ea21..312ddfe 100644 --- a/eventlet/hub.py +++ b/eventlet/hub.py @@ -38,7 +38,6 @@ class Hub(object): self.readers = {} self.writers = {} self.excs = {} - self.descriptor_queue = {} self.clock = clock self.greenlet = None @@ -57,26 +56,17 @@ class Hub(object): } 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 + self.readers[fileno] = read + self.writers[fileno] = write + self.excs[fileno] = exc def remove_descriptor(self, fileno): - self.descriptor_queue[fileno] = None, None, None + self.readers.pop(fileno, None) + self.writers.pop(fileno, None) + self.excs.pop(fileno, 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][2] - except KeyError: - pass + exc = self.excs.get(fileno) if exc is not None: try: exc(fileno) diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index 3c4ef8e..de8974b 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -42,46 +42,32 @@ class Hub(hub.Hub): super(Hub, self).__init__() self.poll = select.poll() - def process_queue(self): - readers = self.readers - writers = self.writers - excs = self.excs - - reg = self.poll.register - unreg = self.poll.unregister + def add_descriptor(self, fileno, read=None, write=None, exc=None): + super(Hub, self).add_descriptor(fileno, read, write, exc) + 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: - for dct in (readers, writers, excs): - dct.pop(fileno, None) - 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 - oldr = readers.get(fileno) - oldw = writers.get(fileno) - olde = excs.get(fileno) + mask = 0 + if read is not None: + mask |= READ_MASK + if write is not None: + mask |= WRITE_MASK + + oldmask = 0 + if readers.get(fileno) is not None: + oldmask |= READ_MASK + if writers.get(fileno) is not None: + oldmask |= WRITE_MASK + + if mask != oldmask: + # Only need to re-register this fileno if the mask changes + self.poll.register(fileno, mask) - if oldr is not None: - oldmask |= rm - if oldw is not None: - oldmask |= wm - if mask != oldmask: - reg(fileno, mask) - for op, dct in ((read, self.readers), (write, self.writers), (exc, self.excs)): - dct[fileno] = op - self.descriptor_queue.clear() + + def remove_descriptor(self, fileno): + super(Hub, self).remove_descriptor(fileno) + self.poll.unregister(fileno) def wait(self, seconds=None): self.process_queue() diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index 41c4434..aa4acad 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -36,15 +36,26 @@ from eventlet import hub import greenlet class Hub(hub.Hub): - def process_queue(self): - for fileno, rwe in self.descriptor_queue.iteritems(): - read, write, exc = rwe - for op, dct in ((read, self.readers), (write, self.writers), (exc, self.excs)): - if op is not None: - dct[fileno] = op - else: - dct.pop(fileno, None) - self.descriptor_queue.clear() + def add_descriptor(self, fileno, read=None, write=None, exc=None): + self.readers[fileno] = read + self.writers[fileno] = write + self.excs[fileno] = exc + + def remove_descriptor(self, fileno): + self.readers[fileno] = None + self.writers[fileno] = None + self.excs[fileno] = 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 = self.excs.get(fileno) + if exc is not None: + try: + exc(fileno) + except self.SYSTEM_EXCEPTIONS: + self.squelch_exception(fileno, sys.exc_info()) def wait(self, seconds=None): self.process_queue() From 52e899eea237843689e0841895ba6590ea0bb540 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 14:13:40 -0500 Subject: [PATCH 010/118] Libeventhub tested, works as before. --- eventlet/hub.py | 11 ++- eventlet/libeventhub.py | 202 ++-------------------------------------- eventlet/pollhub.py | 8 +- 3 files changed, 18 insertions(+), 203 deletions(-) diff --git a/eventlet/hub.py b/eventlet/hub.py index 312ddfe..e5e53f2 100644 --- a/eventlet/hub.py +++ b/eventlet/hub.py @@ -28,6 +28,8 @@ import errno import traceback import time +import greenlet + from eventlet import greenlib from eventlet.timer import Timer @@ -103,7 +105,7 @@ class Hub(object): pass def wait(self, seconds=None): - raise NotImplementedError() + raise NotImplementedError("Implement this in a subclass") def default_sleep(self): return 60.0 @@ -204,7 +206,12 @@ class Hub(object): def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds - event.timeout(timer.seconds, timer).add() + self._add_absolute_timer(scheduled_time, timer) + current_greenlet = greenlet.getcurrent() + 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 def prepare_timers(self): diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index b676c87..c4c5b5f 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -30,6 +30,7 @@ import time from eventlet import greenlib from eventlet.timer import Timer +from eventlet import hub import greenlet @@ -41,55 +42,21 @@ try: except ImportError: pass - import event -class Hub(object): +class Hub(hub.Hub): SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) def __init__(self, clock=time.time): - self.clock = clock - self.readers = {} - self.writers = {} + super(Hub, self).__init__(clock) self.interrupted = False - - self.greenlet = None - self.stopping = False - self.running = False - self.timers = [] - self.timers_by_greenlet = {} - self.next_timers = [] - self.observers = {} - self.observer_modes = { - 'entry': [], - 'before_timers': [], - 'before_waiting': [], - 'after_waiting': [], - 'exit': [], - } - event.init() - signal = event.signal(2, self.raise_keyboard_interrupt) + # catch SIGINT + signal = event.signal(2, self.signal_received, 2) signal.add() - 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 add_descriptor(self, fileno, read=None, write=None, exc=None): if read: @@ -115,16 +82,8 @@ class Hub(object): evt, cb = tpl evt.delete() cb(fileno) - - 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 raise_keyboard_interrupt(self): + def signal_received(self, signal): self.interrupted = True def wait(self, seconds=None): @@ -140,155 +99,6 @@ class Hub(object): timer.delete() - def default_sleep(self): - return 60.0 - - def sleep_until(self): - t = self.timers - if not t: - return None - return t[0][0] - - def run(self): - """Run the runloop until abort is called. - """ - if self.running: - raise RuntimeError("Already running!") - try: - self.running = True - self.stopping = False - self.fire_observers('entry') - while not self.stopping: - self.prepare_timers() - self.fire_observers('before_timers') - self.fire_timers(self.clock()) - self.prepare_timers() - wakeup_when = self.sleep_until() - if wakeup_when is None: - sleep_time = self.default_sleep() - else: - sleep_time = wakeup_when - self.clock() - if sleep_time > 0: - self.fire_observers('before_waiting') - self.wait(sleep_time) - self.fire_observers('after_waiting') - else: - self.wait(0) - else: - del self.timers[:] - del self.next_timers[:] - self.fire_observers('exit') - finally: - self.running = False - self.stopping = False - - def abort(self): - """Stop the runloop. If run is executing, it will exit after completing - the next runloop iteration. - """ - if self.running: - self.stopping = True - - def add_observer(self, observer, *modes): - """Add an event observer to this runloop with the given modes. - Valid modes are: - entry: The runloop is being entered. - before_timers: Before the expired timers for this iteration are executed. - before_waiting: Before waiting for the calculated wait_time - where nothing will happen. - after_waiting: After waiting, immediately before starting the top of the - runloop again. - exit: The runloop is exiting. - - If no mode is passed or mode is all, the observer will be fired for every - event type. - """ - if not modes or modes == ('all',): - modes = tuple(self.observer_modes) - self.observers[observer] = modes - for mode in modes: - self.observer_modes[mode].append(observer) - - def remove_observer(self, observer): - """Remove a previously registered observer from all event types. - """ - for mode in self.observers.pop(observer, ()): - self.observer_modes[mode].remove(observer) - - def squelch_observer_exception(self, observer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Removing observer: %r" % (observer,) - self.remove_observer(observer) - - def fire_observers(self, activity): - for observer in self.observer_modes[activity]: - try: - observer(self, activity) - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_observer_exception(observer, sys.exc_info()) - - def squelch_timer_exception(self, timer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Timer raised: %r" % (timer,) - - def _add_absolute_timer(self, when, info): - # the 0 placeholder makes it easy to bisect_right using (now, 1) - self.next_timers.append((when, 0, info)) - def add_timer(self, timer): - scheduled_time = self.clock() + timer.seconds event.timeout(timer.seconds, timer).add() - return scheduled_time - def prepare_timers(self): - ins = bisect.insort_right - t = self.timers - for item in self.next_timers: - ins(t, item) - del self.next_timers[:] - - def schedule_call(self, seconds, cb, *args, **kw): - """Schedule a callable to be called after 'seconds' seconds have - elapsed. - seconds: The number of seconds to wait. - cb: The callable to call after the given time. - *args: Arguments to pass to the callable when called. - **kw: Keyword arguments to pass to the callable when called. - """ - t = Timer(seconds, cb, *args, **kw) - self.add_timer(t) - return t - - def fire_timers(self, when): - t = self.timers - last = bisect.bisect_right(t, (when, 1)) - i = 0 - for i in xrange(last): - timer = t[i][2] - try: - try: - timer() - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_timer_exception(timer, sys.exc_info()) - finally: - try: - del self.timers_by_greenlet[timer.greenlet][timer] - except KeyError: - pass - del t[:last] - - def cancel_timers(self, greenlet): - if greenlet not in self.timers_by_greenlet: - return - for timer in self.timers_by_greenlet[greenlet]: - if not timer.cancelled and timer.seconds: - ## If timer.seconds is 0, this isn't a timer, it's - ## actually eventlet's silly way of specifying whether - ## a coroutine is "ready to run" or not. - timer.cancel() - print 'Runloop cancelling left-over timer %s' % timer - del self.timers_by_greenlet[greenlet] diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index de8974b..54f5426 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -38,16 +38,13 @@ READ_MASK = select.POLLIN WRITE_MASK = select.POLLOUT class Hub(hub.Hub): - def __init__(self): - super(Hub, self).__init__() + 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) - rm = READ_MASK - wm = WRITE_MASK - mask = 0 if read is not None: mask |= READ_MASK @@ -89,6 +86,7 @@ class Hub(hub.Hub): SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS for fileno, event in presult: + for dct, mask in ((readers, READ_MASK), (writers, WRITE_ASMK read = readers.get(fileno) write = writers.get(fileno) exc = excs.get(fileno) From 3e0a167f2abba1b5e727f50eef8d5a08ce043f87 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 14:27:18 -0500 Subject: [PATCH 011/118] Selecthub works again. --- eventlet/api_test.py | 4 +++- eventlet/pollhub.py | 36 +++++++++--------------------------- eventlet/runloop.py | 6 +----- eventlet/selecthub.py | 25 +++---------------------- 4 files changed, 16 insertions(+), 55 deletions(-) diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 77f71e8..445391a 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -31,7 +31,9 @@ def check_hub(): # Clear through the descriptor queue api.sleep(0) api.sleep(0) - assert not api.get_hub().descriptors, repr(api.get_hub().descriptors) + hub = api.get_hub() + for dct in hub.readers, hub.writers, hub.excs: + assert not dct, repr(dct) # Stop the runloop api.get_hub().abort() api.sleep(0) diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index 54f5426..a4005a6 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -86,30 +86,12 @@ class Hub(hub.Hub): SYSTEM_EXCEPTIONS = self.SYSTEM_EXCEPTIONS for fileno, event in presult: - for dct, mask in ((readers, READ_MASK), (writers, WRITE_ASMK - read = readers.get(fileno) - write = writers.get(fileno) - exc = excs.get(fileno) - - 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()) + 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()) diff --git a/eventlet/runloop.py b/eventlet/runloop.py index cbd9c26..24d7d81 100644 --- a/eventlet/runloop.py +++ b/eventlet/runloop.py @@ -164,7 +164,7 @@ class RunLoop(object): # the 0 placeholder makes it easy to bisect_right using (now, 1) self.next_timers.append((when, 0, info)) - def dont_add_timer(self, timer): + def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds self._add_absolute_timer(scheduled_time, timer) current_greenlet = greenlet.getcurrent() @@ -174,10 +174,6 @@ class RunLoop(object): timer.greenlet = current_greenlet return scheduled_time - def add_timer(self, timer): - import event - event.timeout(timer.seconds, timer).add() - def prepare_timers(self): ins = bisect.insort_right t = self.timers diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index aa4acad..0eae7d3 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -36,27 +36,6 @@ from eventlet import hub import greenlet class Hub(hub.Hub): - def add_descriptor(self, fileno, read=None, write=None, exc=None): - self.readers[fileno] = read - self.writers[fileno] = write - self.excs[fileno] = exc - - def remove_descriptor(self, fileno): - self.readers[fileno] = None - self.writers[fileno] = None - self.excs[fileno] = 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 = self.excs.get(fileno) - if exc is not None: - try: - exc(fileno) - except self.SYSTEM_EXCEPTIONS: - self.squelch_exception(fileno, sys.exc_info()) - def wait(self, seconds=None): self.process_queue() readers = self.readers @@ -76,7 +55,9 @@ class Hub(hub.Hub): for observed, events in ((readers, r), (writers, w)): for fileno in events: try: - observed[fileno](fileno) + cb = observed.get(fileno) + if cb is not None: + cb(fileno) except SYSTEM_EXCEPTIONS: raise except: From dc290c5df71bcbea3806bdae6fb011cc0e31d5f6 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 14:34:35 -0500 Subject: [PATCH 012/118] Refactoring to get rid of process_queue method. --- eventlet/hub.py | 4 ---- eventlet/pollhub.py | 20 ++++++++------------ eventlet/selecthub.py | 4 ---- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/eventlet/hub.py b/eventlet/hub.py index e5e53f2..41e04a6 100644 --- a/eventlet/hub.py +++ b/eventlet/hub.py @@ -76,7 +76,6 @@ class Hub(object): self.squelch_exception(fileno, sys.exc_info()) def stop(self): - self.process_queue() self.abort() if self.greenlet is not greenlet.getcurrent(): self.switch() @@ -100,9 +99,6 @@ class Hub(object): self.remove_descriptor(fileno) except Exception, e: print >>sys.stderr, "Exception while removing descriptor! %r" % (e,) - - def process_queue(self): - pass def wait(self, seconds=None): raise NotImplementedError("Implement this in a subclass") diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index a4005a6..17755df 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -45,21 +45,19 @@ class Hub(hub.Hub): 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 - - oldmask = 0 - if readers.get(fileno) is not None: - oldmask |= READ_MASK - if writers.get(fileno) is not None: - oldmask |= WRITE_MASK - - if mask != oldmask: - # Only need to re-register this fileno if the mask changes - self.poll.register(fileno, mask) + return mask def remove_descriptor(self, fileno): @@ -67,8 +65,6 @@ class Hub(hub.Hub): self.poll.unregister(fileno) def wait(self, seconds=None): - self.process_queue() - readers = self.readers writers = self.writers excs = self.excs diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index 0eae7d3..50ffa99 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -26,18 +26,14 @@ 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 hub import greenlet class Hub(hub.Hub): def wait(self, seconds=None): - self.process_queue() readers = self.readers writers = self.writers excs = self.excs From 9dc8a70b7a9063998c7ade49365f1fe028b6e9ed Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 22:53:05 -0500 Subject: [PATCH 013/118] Refactoring of wrappedfd classes into GreenSocket, GreenFile, GreenPipe. What a PITA. --- .hgignore | 1 + eventlet/api.py | 17 +-- eventlet/api_test.py | 21 ++-- eventlet/httpd.py | 11 +- eventlet/httpd_test.py | 66 +++++----- eventlet/hub.py | 6 +- eventlet/processes.py | 7 +- eventlet/processes_test.py | 2 +- eventlet/selecthub.py | 3 +- eventlet/tpool.py | 2 +- eventlet/util.py | 41 +----- eventlet/wrappedfd.py | 252 ++++++++++++++++++++++--------------- eventlet/wrappedfd_test.py | 105 ++++++++++++++++ 13 files changed, 337 insertions(+), 197 deletions(-) create mode 100644 .hgignore create mode 100644 eventlet/wrappedfd_test.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..0b29f33 --- /dev/null +++ b/.hgignore @@ -0,0 +1 @@ +glob:*.pyc \ No newline at end of file diff --git a/eventlet/api.py b/eventlet/api.py index b14fc00..d4571eb 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -69,7 +69,7 @@ def tcp_listener(address): each incoming connection. """ from eventlet import wrappedfd, util - socket = wrappedfd.wrapped_fd(util.tcp_socket()) + socket = wrappedfd.GreenSocket(util.tcp_socket()) util.socket_bind_and_listen(socket, address) return socket @@ -95,7 +95,7 @@ def connect_tcp(address): Create a TCP connection to address (host, port) and return the socket. """ from eventlet import wrappedfd, util - desc = wrappedfd.wrapped_fd(util.tcp_socket()) + desc = wrappedfd.GreenSocket(util.tcp_socket()) desc.connect(address) return desc @@ -187,12 +187,13 @@ def exc_after(seconds, exc): def get_default_hub(): - try: - import eventlet.libeventhub - except ImportError: - pass - else: - return eventlet.libeventhub + if False: + try: + import eventlet.libeventhub + except ImportError: + pass + else: + return eventlet.libeventhub try: import eventlet.kqueuehub except ImportError: diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 445391a..8680396 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -49,15 +49,14 @@ class TestApi(tests.TestCase): check_hub() - def dont_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 test_connect_tcp(self): def accept_once(listenfd): try: conn, addr = listenfd.accept() - conn.write('hello\n') + fd = conn.makefile() conn.close() + fd.write('hello\n') + fd.close() finally: listenfd.close() @@ -65,10 +64,12 @@ class TestApi(tests.TestCase): api.spawn(accept_once, server) client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) - assert client.readline() == 'hello\n' - - assert client.read() == '' + fd = client.makefile() client.close() + assert fd.readline() == 'hello\n' + + assert fd.read() == '' + fd.close() check_hub() @@ -100,7 +101,7 @@ class TestApi(tests.TestCase): bound_port = server.getsockname()[1] try: - desc = wrappedfd.wrapped_fd(util.tcp_socket()) + desc = wrappedfd.GreenSocket(util.tcp_socket()) api.trampoline(desc, read=True, write=True, timeout=0.1) except api.TimeoutError: pass # test passed @@ -119,7 +120,7 @@ class TestApi(tests.TestCase): def go(): client = util.tcp_socket() - desc = wrappedfd.wrapped_fd(client) + desc = wrappedfd.GreenSocket(client) desc.connect(('127.0.0.1', bound_port)) try: api.trampoline(desc, read=True, write=True, timeout=0.1) diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 017cd6a..91bf43c 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -412,12 +412,17 @@ class Timeout(RuntimeError): class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): def __init__(self, request, client_address, server): - self.socket = self.request = self.rfile = self.wfile = request + self.rfile = self.wfile = request.makefile() + request.close() # close this now so that when rfile and wfile are closed, the socket gets closed self.client_address = client_address self.server = server self.set_response_code(None, 200, None) self.protocol_version = server.max_http_version + def close(self): + self.rfile.close() + self.wfile.close() + def set_response_code(self, request, code, message): self._code = code if message is not None: @@ -526,9 +531,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): if not request.response_written(): request.response(500) 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 - self.socket.close() + self.close() class Server(BaseHTTPServer.HTTPServer): diff --git a/eventlet/httpd_test.py b/eventlet/httpd_test.py index e7e0fbc..f5434c6 100644 --- a/eventlet/httpd_test.py +++ b/eventlet/httpd_test.py @@ -68,10 +68,11 @@ class ConnectionClosed(Exception): def read_http(sock): - response_line = sock.readline() + fd = sock.makefile() + response_line = fd.readline() if not response_line: 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 headers = dict() for x in raw_headers.split('\r\n'): @@ -81,7 +82,7 @@ def read_http(sock): if CONTENT_LENGTH in headers: num = int(headers[CONTENT_LENGTH]) - body = sock.read(num) + body = fd.read(num) #print body else: body = None @@ -103,10 +104,11 @@ class TestHttpd(tests.TestCase): def test_001_server(self): sock = api.connect_tcp( ('127.0.0.1', 12346)) - - sock.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - result = sock.read() - sock.close() + + 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')) @@ -114,35 +116,38 @@ class TestHttpd(tests.TestCase): def test_002_keepalive(self): sock = api.connect_tcp( ('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) - 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) - sock.close() + fd.close() def test_003_passing_non_int_to_read(self): # This should go in test_wrappedfd sock = api.connect_tcp( ('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) - self.assertRaises(TypeError, sock.read, "This shouldn't work") + self.assertRaises(TypeError, fd.read, "This shouldn't work") cancel.cancel() - sock.close() + fd.close() def test_004_close_keepalive(self): sock = api.connect_tcp( ('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) - 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) - 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) - sock.close() + fd.close() def skip_test_005_run_apachebench(self): url = 'http://localhost:12346/' @@ -159,11 +164,12 @@ class TestHttpd(tests.TestCase): path_parts.append('path') path = '/'.join(path_parts) request = 'GET /%s HTTP/1.0\r\nHost: localhost\r\n\r\n' % path - sock.write(request) - result = sock.readline() + fd = sock.makefile() + fd.write(request) + result = fd.readline() status = result.split(' ')[1] self.assertEqual(status, '414') - sock.close() + fd.close() def test_007_get_arg(self): # 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', '', 'a=a')) - sock.write(request) + fd = sock.makefile() + fd.write(request) # send some junk after the actual request - sock.write('01234567890123456789') + fd.write('01234567890123456789') reqline, headers, body = read_http(sock) self.assertEqual(body, 'a is a, body is a=a') - sock.close() + fd.close() def test_008_correctresponse(self): sock = api.connect_tcp( ('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) - 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) - 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) self.assertEqual(response_line_200,response_line_test) - sock.close() + fd.close() if __name__ == '__main__': diff --git a/eventlet/hub.py b/eventlet/hub.py index 41e04a6..5000691 100644 --- a/eventlet/hub.py +++ b/eventlet/hub.py @@ -58,9 +58,9 @@ class Hub(object): } def add_descriptor(self, fileno, read=None, write=None, exc=None): - self.readers[fileno] = read - self.writers[fileno] = write - self.excs[fileno] = exc + self.readers[fileno] = read or self.readers.get(fileno) + self.writers[fileno] = write or self.writers.get(fileno) + self.excs[fileno] = exc or self.excs.get(fileno) def remove_descriptor(self, fileno): self.readers.pop(fileno, None) diff --git a/eventlet/processes.py b/eventlet/processes.py index c51d1e7..d67e1c2 100644 --- a/eventlet/processes.py +++ b/eventlet/processes.py @@ -55,9 +55,9 @@ class Process(object): child_stdin = self.popen4.tochild util.set_nonblocking(child_stdout_stderr) util.set_nonblocking(child_stdin) - self.child_stdout_stderr = wrappedfd.wrapped_file(child_stdout_stderr) + self.child_stdout_stderr = wrappedfd.GreenPipe(child_stdout_stderr) 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 = wrappedfd.GreenPipe(child_stdin) self.child_stdin.newlines = '\n' self.sendall = self.child_stdin.write @@ -86,8 +86,9 @@ class Process(object): return result def write(self, stuff): - written = self.child_stdin.send(stuff) + written = 0 try: + written = self.child_stdin.write(stuff) self.child_stdin.flush() except ValueError, e: ## File was closed diff --git a/eventlet/processes_test.py b/eventlet/processes_test.py index 2cbab7d..7830a2e 100644 --- a/eventlet/processes_test.py +++ b/eventlet/processes_test.py @@ -111,7 +111,7 @@ class TestDyingProcessesLeavePool(tests.TestCase): class TestProcessLivesForever(tests.TestCase): mode = 'static' def setUp(self): - self.pool = processes.ProcessPool('yes', max_size=1) + self.pool = processes.ProcessPool('python', ['-c', 'print "y"; print "y"'], max_size=1) def test_reading_twice_from_same_process(self): proc = self.pool.get() diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index 50ffa99..41fd58f 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -1,6 +1,5 @@ """\ @file selecthub.py -@author Bob Ippolito Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007, Linden Research, Inc. @@ -49,9 +48,11 @@ class Hub(hub.Hub): 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: diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 6ca505d..0474c8b 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -28,7 +28,7 @@ from eventlet.api import trampoline, get_hub _rpipe, _wpipe = os.pipe() _rfile = os.fdopen(_rpipe,"r",0) -_wrap_rfile = wrappedfd.wrapped_file(_rfile) +_wrap_rfile = wrappedfd.GreenPipe(_rfile) util.set_nonblocking(_rfile) def _signal_t2e(): diff --git a/eventlet/util.py b/eventlet/util.py index 657716c..5d1c232 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -27,7 +27,6 @@ import os import fcntl import socket import errno -from errno import EWOULDBLOCK, EAGAIN try: from OpenSSL import SSL @@ -63,7 +62,7 @@ def g_log(*args): ident = 'greenlet-%d' % (g_id,) print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args))) -CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, EWOULDBLOCK) +CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK) CONNECT_SUCCESS = (0, errno.EISCONN) def socket_connect(descriptor, address): err = descriptor.connect_ex(address) @@ -98,7 +97,7 @@ def wrap_ssl(sock, certificate=None, private_key=None): ## TODO only do this on client sockets? how? connection = SSL.Connection(context, sock) connection.set_connect_state() - return wrappedfd.wrapped_fd(connection) + return wrappedfd.GreenSocket(connection) def wrap_socket_with_coroutine_socket(): @@ -106,7 +105,7 @@ def wrap_socket_with_coroutine_socket(): from eventlet import wrappedfd s = __original_socket__(*args, **kw) set_nonblocking(s) - return wrappedfd.wrapped_fd(s) + return wrappedfd.GreenSocket(s) socket.socket = new_socket socket.ssl = wrap_ssl @@ -122,7 +121,7 @@ def socket_accept(descriptor): try: return descriptor.accept() except socket.error, e: - if e[0] == EWOULDBLOCK: + if e[0] == errno.EWOULDBLOCK: return None raise @@ -130,7 +129,7 @@ def socket_send(descriptor, data): try: return descriptor.send(data) except socket.error, e: - if e[0] == EWOULDBLOCK: + if e[0] == errno.EWOULDBLOCK: return 0 raise except SSL.WantWriteError: @@ -145,7 +144,7 @@ def socket_recv(descriptor, buflen): try: return descriptor.recv(buflen) except socket.error, e: - if e[0] == EWOULDBLOCK: + if e[0] == errno.EWOULDBLOCK: return None if e[0] in SOCKET_CLOSED: return '' @@ -160,34 +159,6 @@ def socket_recv(descriptor, buflen): raise -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_reuse_addr(descriptor): try: descriptor.setsockopt( diff --git a/eventlet/wrappedfd.py b/eventlet/wrappedfd.py index 855f4f4..3ba7230 100644 --- a/eventlet/wrappedfd.py +++ b/eventlet/wrappedfd.py @@ -28,6 +28,7 @@ from eventlet import util BUFFER_SIZE = 4096 import socket, errno +from errno import EWOULDBLOCK, EAGAIN def higher_order_recv(recv_func): @@ -63,83 +64,62 @@ def higher_order_send(send_func): return send +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 -class RefCount(object): - def __init__(self): - self._count = 1 - def increment(self): - self._count += 1 +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 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+' +class GreenSocket(object): is_secure = False - - def __init__(self, fd, refcount = None): - self._closed = False + + def __init__(self, fd): 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) - + self.recvcount = 0 + self.recvbuffer = '' + self._closed = False + + 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 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 shutdown(self, *args, **kw): - fn = self.shutdown = self.fd.shutdown - 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: @@ -149,57 +129,119 @@ class wrapped_fd(object): # 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) - + + 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) + util.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(util.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(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 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) + + # TODO settimeout + # TODO gettimeout + + def setsockopt(self, *args, **kw): + fn = self.setsockopt = self.fd.setsockopt + return fn(*args, **kw) + + def shutdown(self, *args, **kw): + fn = self.shutdown = self.fd.shutdown + return fn(*args, **kw) + + +class GreenFile(object): + newlines = '\r\n' + mode = 'wb+' + def __init__(self, fd): + self.sock = fd + + def close(self): + self.sock.close() + + def fileno(self): + return self.sock.fileno() + + # TODO next + # TODO flush + def write(self, data): - return self.sendall(data) + return self.sock.sendall(data) def readuntil(self, terminator, size=None): - buf, self.recvbuffer = self.recvbuffer, '' + 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.recvbuffer = buf[:found], buf[found:] + chunk, self.sock.recvbuffer = buf[:found], buf[found:] return chunk checked = max(0, len(buf) - (len(terminator) - 1)) - d = self.recv(BUFFER_SIZE) + d = self.sock.recv(BUFFER_SIZE) if not d: break buf += d @@ -208,14 +250,14 @@ class wrapped_fd(object): found = buf.find(terminator, checked) if found != -1: found += len(terminator) - chunk, self.recvbuffer = buf[:found], buf[found:] + chunk, self.sock.recvbuffer = buf[:found], buf[found:] return chunk checked = len(buf) - d = self.recv(BUFFER_SIZE) + d = self.sock.recv(BUFFER_SIZE) if not d: break buf += d - chunk, self.recvbuffer = buf[:size], buf[size:] + chunk, self.sock.recvbuffer = buf[:size], buf[size:] return chunk def readline(self, size=None): @@ -249,18 +291,18 @@ class wrapped_fd(object): 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, '' + buf, self.sock.recvbuffer = self.sock.recvbuffer, '' lst = [buf] if size is None: while True: - d = self.recv(BUFFER_SIZE) + d = self.sock.recv(BUFFER_SIZE) if not d: break lst.append(d) else: buflen = len(buf) while buflen < size: - d = self.recv(BUFFER_SIZE) + d = self.sock.recv(BUFFER_SIZE) if not d: break buflen += len(d) @@ -269,20 +311,24 @@ class wrapped_fd(object): d = lst[-1] overbite = buflen - size if overbite: - lst[-1], self.recvbuffer = d[:-overbite], d[-overbite:] + lst[-1], self.sock.recvbuffer = d[:-overbite], d[-overbite:] else: - lst[-1], self.recvbuffer = d, '' + lst[-1], self.sock.recvbuffer = d, '' return ''.join(lst) - def makefile(self, *args, **kw): - self._refcount.increment() - return type(self)(self.fd, refcount = self._refcount) + +class MetaSocket(GreenSocket): + recv = higher_order_recv(file_recv) + + send = higher_order_send(file_send) -class wrapped_file(wrapped_fd): - recv = higher_order_recv(util.file_recv) - - send = higher_order_send(util.file_send) +class GreenPipe(GreenFile): + def __init__(self, fd): + self.fd = MetaSocket(fd) + self.recv = self.fd.recv + self.send = self.fd.send + super(GreenPipe, self).__init__(self.fd) def flush(self): - self.fd.flush() + self.fd.fd.flush() diff --git a/eventlet/wrappedfd_test.py b/eventlet/wrappedfd_test.py new file mode 100644 index 0000000..880e03f --- /dev/null +++ b/eventlet/wrappedfd_test.py @@ -0,0 +1,105 @@ +"""\ +@file wrappedfd_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, wrappedfd, util +import socket + +# TODO try and reuse unit tests from within Python itself + +class TestWrappedFd(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() From 1e0cf35e52f568b801d22a707d4c9ff41e70677a Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 23:04:35 -0500 Subject: [PATCH 014/118] More cleanup of util and wrappedfd. --- eventlet/processes_test.py | 2 +- eventlet/util.py | 50 ------------------------ eventlet/wrappedfd.py | 80 ++++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 59 deletions(-) diff --git a/eventlet/processes_test.py b/eventlet/processes_test.py index 7830a2e..7bf9b97 100644 --- a/eventlet/processes_test.py +++ b/eventlet/processes_test.py @@ -111,7 +111,7 @@ class TestDyingProcessesLeavePool(tests.TestCase): class TestProcessLivesForever(tests.TestCase): mode = 'static' def setUp(self): - self.pool = processes.ProcessPool('python', ['-c', 'print "y"; print "y"'], 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): proc = self.pool.get() diff --git a/eventlet/util.py b/eventlet/util.py index 5d1c232..8cdbf4d 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -62,15 +62,6 @@ def g_log(*args): ident = 'greenlet-%d' % (g_id,) 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 @@ -116,47 +107,6 @@ def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): descriptor.bind(addr) descriptor.listen(backlog) 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 - - -# 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: - if e[0] == -1 or e[0] > 0: - raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) - raise def set_reuse_addr(descriptor): diff --git a/eventlet/wrappedfd.py b/eventlet/wrappedfd.py index 3ba7230..bc2851e 100644 --- a/eventlet/wrappedfd.py +++ b/eventlet/wrappedfd.py @@ -31,6 +31,8 @@ import socket, errno from errno import EWOULDBLOCK, EAGAIN +__all__ = ['GreenSocket', 'GreenFile', 'GreenPipe'] + def higher_order_recv(recv_func): def recv(self, buflen): buf = self.recvbuffer @@ -64,6 +66,60 @@ def higher_order_send(send_func): 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): + 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 + + +# 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: + if e[0] == -1 or e[0] > 0: + raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) + raise + + def file_recv(fd, buflen): try: return fd.read(buflen) @@ -106,7 +162,7 @@ class GreenSocket(object): def accept(self): fd = self.fd while True: - res = util.socket_accept(fd) + res = socket_accept(fd) if res is not None: client, addr = res util.set_nonblocking(client) @@ -132,7 +188,7 @@ class GreenSocket(object): def connect(self, address): fd = self.fd - connect = util.socket_connect + connect = socket_connect while not connect(fd, address): trampoline(fd, read=True, write=True) @@ -173,7 +229,7 @@ class GreenSocket(object): def makefile(self, mode = None, bufsize = None): return GreenFile(self.dup()) - recv = higher_order_recv(util.socket_recv) + recv = higher_order_recv(socket_recv) def recvfrom(self, *args): trampoline(self.fd, read=True) @@ -182,7 +238,7 @@ class GreenSocket(object): # TODO recvfrom_into # TODO recv_into - send = higher_order_send(util.socket_send) + send = higher_order_send(socket_send) def sendall(self, data): fd = self.fd @@ -317,7 +373,9 @@ class GreenFile(object): return ''.join(lst) -class MetaSocket(GreenSocket): +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) @@ -325,10 +383,16 @@ class MetaSocket(GreenSocket): class GreenPipe(GreenFile): def __init__(self, fd): - self.fd = MetaSocket(fd) - self.recv = self.fd.recv - self.send = self.fd.send + 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() From 7a80d955265adce62a5693cb094d943589d84368 Mon Sep 17 00:00:00 2001 From: rdw Date: Mon, 17 Mar 2008 23:06:23 -0500 Subject: [PATCH 015/118] Whoops, we should use libeventhub if it's there --- eventlet/api.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index d4571eb..f95761c 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -187,13 +187,12 @@ def exc_after(seconds, exc): def get_default_hub(): - if False: - try: - import eventlet.libeventhub - except ImportError: - pass - else: - return eventlet.libeventhub + try: + import eventlet.libeventhub + except ImportError: + pass + else: + return eventlet.libeventhub try: import eventlet.kqueuehub except ImportError: From 93c33c428cc650606f1d2dc058aaf465c561a5d1 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 00:38:17 -0500 Subject: [PATCH 016/118] Renamed base Hub class to BaseHub, for clarity. --- eventlet/hub.py | 2 +- eventlet/libeventhub.py | 2 +- eventlet/pollhub.py | 2 +- eventlet/selecthub.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eventlet/hub.py b/eventlet/hub.py index 5000691..9273ee6 100644 --- a/eventlet/hub.py +++ b/eventlet/hub.py @@ -33,7 +33,7 @@ import greenlet from eventlet import greenlib from eventlet.timer import Timer -class Hub(object): +class BaseHub(object): SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) def __init__(self, clock=time.time): diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index c4c5b5f..1cfd592 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -45,7 +45,7 @@ except ImportError: import event -class Hub(hub.Hub): +class Hub(hub.BaseHub): SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) def __init__(self, clock=time.time): diff --git a/eventlet/pollhub.py b/eventlet/pollhub.py index 17755df..790dd3b 100644 --- a/eventlet/pollhub.py +++ b/eventlet/pollhub.py @@ -37,7 +37,7 @@ EXC_MASK = select.POLLERR | select.POLLHUP | select.POLLNVAL READ_MASK = select.POLLIN WRITE_MASK = select.POLLOUT -class Hub(hub.Hub): +class Hub(hub.BaseHub): def __init__(self, clock=time.time): super(Hub, self).__init__(clock) self.poll = select.poll() diff --git a/eventlet/selecthub.py b/eventlet/selecthub.py index 41fd58f..798afbb 100644 --- a/eventlet/selecthub.py +++ b/eventlet/selecthub.py @@ -31,7 +31,7 @@ from eventlet import hub import greenlet -class Hub(hub.Hub): +class Hub(hub.BaseHub): def wait(self, seconds=None): readers = self.readers writers = self.writers From bec535123d2f4cc6b702935c0d0d5c7e45bd6636 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 01:36:10 -0500 Subject: [PATCH 017/118] Fixed some exc-related issues, some others remain. --- eventlet/api_test.py | 8 ++++---- eventlet/libeventhub.py | 15 +++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 8680396..73a9d50 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -32,8 +32,9 @@ def check_hub(): api.sleep(0) api.sleep(0) hub = api.get_hub() - for dct in hub.readers, hub.writers, hub.excs: - assert not dct, repr(dct) + for nm in 'readers', 'writers', 'excs': + dct = getattr(hub, nm) + assert not dct, "hub.%s not empty: %s" % (nm, dct) # Stop the runloop api.get_hub().abort() api.sleep(0) @@ -74,12 +75,11 @@ class TestApi(tests.TestCase): check_hub() def test_server(self): + connected = [] server = api.tcp_listener(('0.0.0.0', 0)) bound_port = server.getsockname()[1] - connected = [] def accept_twice((conn, addr)): - print 'connected' connected.append(True) conn.close() if len(connected) == 2: diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index 1cfd592..411b6a9 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -34,6 +34,9 @@ from eventlet import hub import greenlet +# XXX for debugging only +#raise ImportError() + try: # use rel if it's available import rel @@ -68,20 +71,16 @@ class Hub(hub.BaseHub): evt = event.write(fileno, write, fileno) evt.add() self.writers[fileno] = evt, write + + if exc: + self.excs[fileno] = exc 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() - - def exc_descriptor(self, fileno): - for queue in (self.readers, self.writers): - tpl = queue.pop(fileno, None) - if tpl is not None: - evt, cb = tpl - evt.delete() - cb(fileno) + self.excs.pop(fileno, None) def signal_received(self, signal): self.interrupted = True From 179b6851abd130cf0e0f6e40d6d4d7f96a416448 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 12:10:26 -0500 Subject: [PATCH 018/118] Fixed a strange bug that happens if you squelch a KeyboardInterrupt. --- eventlet/libeventhub.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/eventlet/libeventhub.py b/eventlet/libeventhub.py index 411b6a9..d05a19a 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/libeventhub.py @@ -86,17 +86,18 @@ class Hub(hub.BaseHub): self.interrupted = True def wait(self, seconds=None): - if self.interrupted: - raise KeyboardInterrupt() - timer = event.timeout(seconds, lambda: None) timer.add() status = event.loop() if status == -1: raise RuntimeError("does this ever happen?") - + timer.delete() + + if self.interrupted: + self.interrupted = False + raise KeyboardInterrupt() def add_timer(self, timer): event.timeout(timer.seconds, timer).add() From a85b3f38cd423d4d48ea9552619ce2edf1f4c380 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 18 Mar 2008 10:13:13 -0700 Subject: [PATCH 019/118] Full nginx and the nginx mod_wsgi support. It is really, really, really fast. --- eventlet/api.py | 24 ++++++++++---- eventlet/nginx_mod_wsgi_support.py | 45 +++++++++++++++++++++++++ eventlet/nginxhub.py | 53 ++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 eventlet/nginx_mod_wsgi_support.py create mode 100644 eventlet/nginxhub.py diff --git a/eventlet/api.py b/eventlet/api.py index d4571eb..607cfdd 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -187,19 +187,28 @@ def exc_after(seconds, exc): def get_default_hub(): - if False: - try: - import eventlet.libeventhub - except ImportError: - pass - else: - return eventlet.libeventhub + ## TODO some sort of plugin system? + try: + import eventlet.nginxhub + except ImportError: + pass + else: + return eventlet.nginxhub + + try: + import eventlet.libeventhub + except ImportError: + pass + else: + return eventlet.libeventhub + try: import eventlet.kqueuehub except ImportError: pass else: return eventlet.kqueuehub + import select if hasattr(select, 'poll'): import eventlet.pollhub @@ -208,6 +217,7 @@ def get_default_hub(): import eventlet.selecthub return eventlet.selecthub + def use_hub(mod=None): if mod is None: mod = get_default_hub() diff --git a/eventlet/nginx_mod_wsgi_support.py b/eventlet/nginx_mod_wsgi_support.py new file mode 100644 index 0000000..31e54de --- /dev/null +++ b/eventlet/nginx_mod_wsgi_support.py @@ -0,0 +1,45 @@ + +import sys + +from eventlet import api +from eventlet import httpc + + +def real_application(env, start_response): + #result = httpc.get('http://127.0.0.1:8081/') + start_response('200 OK', [('Content-type', 'text/plain')]) + #sys.stderr.write("RESULT %r" % (result, )) + return 'hi' + + +def wrap_application(master, env, start_response): + result = real_application(env, start_response) + ## Should catch exception and return here? + #sys.stderr.write("RESULT2 %r" % (result, )) + master.switch((result, None)) + return None, None + + +def application(env, start_response): + hub = api.get_hub() + + hub.poll_register = env['ngx.poll_register'] + hub.poll_unregister = env['ngx.poll_unregister'] + hub.sleep = env['ngx.sleep'] + hub.current_application = api.getcurrent() + + slave = api.greenlet.greenlet(wrap_application) + result = slave.switch( + hub.current_application, env, start_response) + + while True: + #sys.stderr.write("RESULT3 %r" % (result, )) + if result is None or result == (None, None): + yield '' + else: + if isinstance(result, tuple): + yield result[0] + else: + yield result + return + result = hub.switch() diff --git a/eventlet/nginxhub.py b/eventlet/nginxhub.py new file mode 100644 index 0000000..137a994 --- /dev/null +++ b/eventlet/nginxhub.py @@ -0,0 +1,53 @@ +"""\ +@file nginxhub.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 eventlet import greenlib +from eventlet import hub + + +WSGI_POLLIN = 0x01 +WSGI_POLLOUT = 0x04 + + +class Hub(hub.Hub): + def add_descriptor(self, fileno, read=None, write=None, exc=None): + super(Hub, self).add_descriptor(fileno, read, write, exc) + + if read is not None: + self.poll_register(fileno, WSGI_POLLIN) + elif write is not None: + self.poll_register(fileno, WSGI_POLLOUT) + + def remove_descriptor(self, fileno): + super(Hub, self).remove_descriptor(fileno) + + self.poll_unregister(fileno) + + def wait(self, seconds=None): + if seconds is not None: + self.sleep(int(seconds*1000)) + + greenlib.switch(self.current_application) + From ab7763795c32df2667ac6eeaab40a8cdba4d41cf Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 12:48:46 -0500 Subject: [PATCH 020/118] Moved hubs into subdirectory. --- eventlet/api.py | 31 ++- eventlet/hubs/__init__.py | 0 eventlet/{ => hubs}/hub.py | 0 eventlet/{libeventhub.py => hubs/libevent.py} | 4 +- eventlet/{nginxhub.py => hubs/nginx.py} | 6 +- eventlet/{pollhub.py => hubs/poll.py} | 4 +- eventlet/{selecthub.py => hubs/select.py} | 4 +- eventlet/kqueuehub.py | 219 ------------------ 8 files changed, 23 insertions(+), 245 deletions(-) create mode 100644 eventlet/hubs/__init__.py rename eventlet/{ => hubs}/hub.py (100%) rename eventlet/{libeventhub.py => hubs/libevent.py} (98%) rename eventlet/{nginxhub.py => hubs/nginx.py} (96%) rename eventlet/{pollhub.py => hubs/poll.py} (98%) rename eventlet/{selecthub.py => hubs/select.py} (97%) delete mode 100644 eventlet/kqueuehub.py diff --git a/eventlet/api.py b/eventlet/api.py index 607cfdd..4ded121 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -189,33 +189,30 @@ def exc_after(seconds, exc): def get_default_hub(): ## TODO some sort of plugin system? try: - import eventlet.nginxhub + import eventlet.hubs.nginx + return eventlet.hubs.nginx + except ImportError: + pass + + try: + import eventlet.hubs.libevent + return eventlet.hubs.libevent except ImportError: pass - else: - return eventlet.nginxhub try: - import eventlet.libeventhub + import eventlet.hubs.kqueue + return eventlet.hubs.kqueue except ImportError: pass - else: - return eventlet.libeventhub - - try: - import eventlet.kqueuehub - except ImportError: - pass - else: - return eventlet.kqueuehub import select if hasattr(select, 'poll'): - import eventlet.pollhub - return eventlet.pollhub + import eventlet.hubs.poll + return eventlet.hubs.poll else: - import eventlet.selecthub - return eventlet.selecthub + import eventlet.hubs.select + return eventlet.hubs.select def use_hub(mod=None): diff --git a/eventlet/hubs/__init__.py b/eventlet/hubs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eventlet/hub.py b/eventlet/hubs/hub.py similarity index 100% rename from eventlet/hub.py rename to eventlet/hubs/hub.py diff --git a/eventlet/libeventhub.py b/eventlet/hubs/libevent.py similarity index 98% rename from eventlet/libeventhub.py rename to eventlet/hubs/libevent.py index d05a19a..cdbec69 100644 --- a/eventlet/libeventhub.py +++ b/eventlet/hubs/libevent.py @@ -1,5 +1,5 @@ """\ -@file libeventhub.py +@file libevent.py Copyright (c) 2007, Linden Research, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -30,7 +30,7 @@ import time from eventlet import greenlib from eventlet.timer import Timer -from eventlet import hub +from eventlet.hubs import hub import greenlet diff --git a/eventlet/nginxhub.py b/eventlet/hubs/nginx.py similarity index 96% rename from eventlet/nginxhub.py rename to eventlet/hubs/nginx.py index 137a994..b23dc04 100644 --- a/eventlet/nginxhub.py +++ b/eventlet/hubs/nginx.py @@ -1,5 +1,5 @@ """\ -@file nginxhub.py +@file nginx.py @author Donovan Preston Copyright (c) 2008, Linden Research, Inc. @@ -24,14 +24,14 @@ THE SOFTWARE. """ from eventlet import greenlib -from eventlet import hub +from eventlet.hubs import hub WSGI_POLLIN = 0x01 WSGI_POLLOUT = 0x04 -class Hub(hub.Hub): +class Hub(hub.BaseHub): def add_descriptor(self, fileno, read=None, write=None, exc=None): super(Hub, self).add_descriptor(fileno, read, write, exc) diff --git a/eventlet/pollhub.py b/eventlet/hubs/poll.py similarity index 98% rename from eventlet/pollhub.py rename to eventlet/hubs/poll.py index 790dd3b..065f31b 100644 --- a/eventlet/pollhub.py +++ b/eventlet/hubs/poll.py @@ -1,5 +1,5 @@ """\ -@file pollhub.py +@file poll.py @author Bob Ippolito Copyright (c) 2005-2006, Bob Ippolito @@ -31,7 +31,7 @@ import traceback from time import sleep from eventlet import greenlib -from eventlet import hub +from eventlet.hubs import hub EXC_MASK = select.POLLERR | select.POLLHUP | select.POLLNVAL READ_MASK = select.POLLIN diff --git a/eventlet/selecthub.py b/eventlet/hubs/select.py similarity index 97% rename from eventlet/selecthub.py rename to eventlet/hubs/select.py index 798afbb..c155ff8 100644 --- a/eventlet/selecthub.py +++ b/eventlet/hubs/select.py @@ -1,5 +1,5 @@ """\ -@file selecthub.py +@file select.py Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007, Linden Research, Inc. @@ -27,7 +27,7 @@ import select import errno import time -from eventlet import hub +from eventlet.hubs import hub import greenlet diff --git a/eventlet/kqueuehub.py b/eventlet/kqueuehub.py deleted file mode 100644 index 595e7b9..0000000 --- a/eventlet/kqueuehub.py +++ /dev/null @@ -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()) From 5be20e076792c1d3fe1bd60ba92317589e56d4a5 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 18 Mar 2008 10:55:32 -0700 Subject: [PATCH 021/118] Only install the nginx hub if we're actually using nginx, because the import always works, so that wasn't a good test of whether to use it or not. --- eventlet/api.py | 9 +-------- eventlet/nginx_mod_wsgi_support.py | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index 4ded121..7e5f2ee 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -186,14 +186,7 @@ def exc_after(seconds, exc): return call_after(seconds, switch, getcurrent(), None, exc) -def get_default_hub(): - ## TODO some sort of plugin system? - try: - import eventlet.hubs.nginx - return eventlet.hubs.nginx - except ImportError: - pass - +def get_default_hub(): try: import eventlet.hubs.libevent return eventlet.hubs.libevent diff --git a/eventlet/nginx_mod_wsgi_support.py b/eventlet/nginx_mod_wsgi_support.py index 31e54de..b7b7b06 100644 --- a/eventlet/nginx_mod_wsgi_support.py +++ b/eventlet/nginx_mod_wsgi_support.py @@ -4,6 +4,8 @@ import sys from eventlet import api from eventlet import httpc +from eventlet.hubs import nginx + def real_application(env, start_response): #result = httpc.get('http://127.0.0.1:8081/') @@ -23,6 +25,9 @@ def wrap_application(master, env, start_response): def application(env, start_response): hub = api.get_hub() + if not isinstance(hub, nginx.Hub): + api.use_hub(nginx) + hub.poll_register = env['ngx.poll_register'] hub.poll_unregister = env['ngx.poll_unregister'] hub.sleep = env['ngx.sleep'] From 60d2d7c9b189867ddac9012a42a59d40ebf9a4d7 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 14:04:47 -0500 Subject: [PATCH 022/118] Fixed timer cleanup on libevent. --- eventlet/hubs/hub.py | 10 +++++++--- eventlet/hubs/libevent.py | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index 9273ee6..b9c0d2f 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -203,12 +203,16 @@ class BaseHub(object): def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds self._add_absolute_timer(scheduled_time, timer) + timer.greenlet = current_greenlet + self.track_timer(timer) + return scheduled_time + + def track_timer(self, timer): current_greenlet = greenlet.getcurrent() 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 + def prepare_timers(self): ins = bisect.insort_right @@ -258,5 +262,5 @@ class BaseHub(object): ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. timer.cancel() - print 'Runloop cancelling left-over timer %s' % timer + print 'Hub cancelling left-over timer %s' % timer del self.timers_by_greenlet[greenlet] diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index cdbec69..2040fd4 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -101,4 +101,5 @@ class Hub(hub.BaseHub): def add_timer(self, timer): event.timeout(timer.seconds, timer).add() + self.track_timer(timer) From 9d270ae6cc7bac3a9215472d002199f9998be867 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 14:10:57 -0500 Subject: [PATCH 023/118] Reversed order of imports because rel is annoying. --- eventlet/hubs/libevent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 2040fd4..14887ff 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -38,14 +38,14 @@ import greenlet #raise ImportError() try: - # use rel if it's available + import event +except ImportError: + # use rel if pyevent isn't available + # (rel prints out some annoying notice upon initialization) import rel rel.initialize() rel.override() -except ImportError: - pass - -import event + import event class Hub(hub.BaseHub): From 91dee30ebdbaa9d7b07ff522f8bbbe2c83b4b521 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 14:23:38 -0500 Subject: [PATCH 024/118] Renamed wrappedfd.py to greenio.py. --- eventlet/api.py | 8 ++++---- eventlet/api_test.py | 6 +++--- eventlet/{wrappedfd.py => greenio.py} | 3 +-- eventlet/{wrappedfd_test.py => greenio_test.py} | 8 ++++---- eventlet/httpd_test.py | 2 +- eventlet/processes.py | 6 +++--- eventlet/tpool.py | 4 ++-- eventlet/util.py | 8 ++++---- 8 files changed, 22 insertions(+), 23 deletions(-) rename eventlet/{wrappedfd.py => greenio.py} (99%) rename eventlet/{wrappedfd_test.py => greenio_test.py} (97%) diff --git a/eventlet/api.py b/eventlet/api.py index 7e5f2ee..b51b88b 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -68,8 +68,8 @@ def tcp_listener(address): which accepts connections forever and spawns greenlets for each incoming connection. """ - from eventlet import wrappedfd, util - socket = wrappedfd.GreenSocket(util.tcp_socket()) + from eventlet import greenio, util + socket = greenio.GreenSocket(util.tcp_socket()) util.socket_bind_and_listen(socket, address) return socket @@ -94,8 +94,8 @@ def connect_tcp(address): """ Create a TCP connection to address (host, port) and return the socket. """ - from eventlet import wrappedfd, util - desc = wrappedfd.GreenSocket(util.tcp_socket()) + from eventlet import greenio, util + desc = greenio.GreenSocket(util.tcp_socket()) desc.connect(address) return desc diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 73a9d50..6557554 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -23,7 +23,7 @@ THE SOFTWARE. """ from eventlet import tests -from eventlet import api, wrappedfd, util +from eventlet import api, greenio, util import socket @@ -101,7 +101,7 @@ class TestApi(tests.TestCase): bound_port = server.getsockname()[1] try: - desc = wrappedfd.GreenSocket(util.tcp_socket()) + desc = greenio.GreenSocket(util.tcp_socket()) api.trampoline(desc, read=True, write=True, timeout=0.1) except api.TimeoutError: pass # test passed @@ -120,7 +120,7 @@ class TestApi(tests.TestCase): def go(): client = util.tcp_socket() - desc = wrappedfd.GreenSocket(client) + desc = greenio.GreenSocket(client) desc.connect(('127.0.0.1', bound_port)) try: api.trampoline(desc, read=True, write=True, timeout=0.1) diff --git a/eventlet/wrappedfd.py b/eventlet/greenio.py similarity index 99% rename from eventlet/wrappedfd.py rename to eventlet/greenio.py index bc2851e..db8f590 100644 --- a/eventlet/wrappedfd.py +++ b/eventlet/greenio.py @@ -1,6 +1,5 @@ """\ -@file wrappedfd.py -@author Bob Ippolito +@file greenio.py Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007, Linden Research, Inc. diff --git a/eventlet/wrappedfd_test.py b/eventlet/greenio_test.py similarity index 97% rename from eventlet/wrappedfd_test.py rename to eventlet/greenio_test.py index 880e03f..455a8f8 100644 --- a/eventlet/wrappedfd_test.py +++ b/eventlet/greenio_test.py @@ -1,5 +1,5 @@ """\ -@file wrappedfd_test.py +@file greenio_test.py Copyright (c) 2006-2007, Linden Research, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,12 +22,12 @@ THE SOFTWARE. """ from eventlet import tests -from eventlet import api, wrappedfd, util +from eventlet import api, greenio, util import socket # TODO try and reuse unit tests from within Python itself -class TestWrappedFd(tests.TestCase): +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 @@ -100,6 +100,6 @@ class TestWrappedFd(tests.TestCase): assert fd.read() == '' timer.cancel() - + if __name__ == '__main__': tests.main() diff --git a/eventlet/httpd_test.py b/eventlet/httpd_test.py index f5434c6..3ca4d80 100644 --- a/eventlet/httpd_test.py +++ b/eventlet/httpd_test.py @@ -125,7 +125,7 @@ class TestHttpd(tests.TestCase): fd.close() 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( ('127.0.0.1', 12346)) diff --git a/eventlet/processes.py b/eventlet/processes.py index d67e1c2..35c7dd1 100644 --- a/eventlet/processes.py +++ b/eventlet/processes.py @@ -28,7 +28,7 @@ import signal from eventlet import util, pools -from eventlet import wrappedfd +from eventlet import greenio class DeadProcess(RuntimeError): pass @@ -55,9 +55,9 @@ class Process(object): child_stdin = self.popen4.tochild util.set_nonblocking(child_stdout_stderr) util.set_nonblocking(child_stdin) - self.child_stdout_stderr = wrappedfd.GreenPipe(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_stdin = wrappedfd.GreenPipe(child_stdin) + self.child_stdin = greenio.GreenPipe(child_stdin) self.child_stdin.newlines = '\n' self.sendall = self.child_stdin.write diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 0474c8b..aa5e4ad 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -23,12 +23,12 @@ import Queue from sys import stdout from Queue import Empty, Queue -from eventlet import api, coros, httpc, httpd, util, wrappedfd +from eventlet import api, coros, httpc, httpd, util, greenio from eventlet.api import trampoline, get_hub _rpipe, _wpipe = os.pipe() _rfile = os.fdopen(_rpipe,"r",0) -_wrap_rfile = wrappedfd.GreenPipe(_rfile) +_wrap_rfile = greenio.GreenPipe(_rfile) util.set_nonblocking(_rfile) def _signal_t2e(): diff --git a/eventlet/util.py b/eventlet/util.py index 8cdbf4d..994ff4c 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -76,7 +76,7 @@ __original_ssl__ = socket.ssl def wrap_ssl(sock, certificate=None, private_key=None): from OpenSSL import SSL - from eventlet import wrappedfd, util + from eventlet import greenio, util context = SSL.Context(SSL.SSLv23_METHOD) print certificate, private_key if certificate is not None: @@ -88,15 +88,15 @@ def wrap_ssl(sock, certificate=None, private_key=None): ## TODO only do this on client sockets? how? connection = SSL.Connection(context, sock) connection.set_connect_state() - return wrappedfd.GreenSocket(connection) + return greenio.GreenSocket(connection) def wrap_socket_with_coroutine_socket(): def new_socket(*args, **kw): - from eventlet import wrappedfd + from eventlet import greenio s = __original_socket__(*args, **kw) set_nonblocking(s) - return wrappedfd.GreenSocket(s) + return greenio.GreenSocket(s) socket.socket = new_socket socket.ssl = wrap_ssl From 538b89d9fc48b01cadeb4483381c98e781f05865 Mon Sep 17 00:00:00 2001 From: nat Date: Tue, 18 Mar 2008 15:24:31 -0400 Subject: [PATCH 025/118] Move all *support.py modules into support/*.py; fix references --- eventlet/api.py | 8 ++++---- eventlet/support/__init__.py | 12 ++++++++++++ .../nginx_mod_wsgi.py} | 0 eventlet/{pylibsupport.py => support/pylib.py} | 2 +- .../{stacklesssupport.py => support/stackless.py} | 0 eventlet/{twistedsupport.py => support/twisted.py} | 4 ++-- 6 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 eventlet/support/__init__.py rename eventlet/{nginx_mod_wsgi_support.py => support/nginx_mod_wsgi.py} (100%) rename eventlet/{pylibsupport.py => support/pylib.py} (98%) rename eventlet/{stacklesssupport.py => support/stackless.py} (100%) rename eventlet/{twistedsupport.py => support/twisted.py} (97%) diff --git a/eventlet/api.py b/eventlet/api.py index 4ded121..41fcb61 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -34,13 +34,13 @@ try: import greenlet except ImportError: try: - import pylibsupport - pylibsupport.emulate() + import support.pylib + support.pylib.emulate() greenlet = sys.modules['greenlet'] except ImportError: try: - import stacklesssupport - stacklesssupport.emulate() + import support.stackless + support.stackless.emulate() greenlet = sys.modules['greenlet'] except ImportError: raise ImportError("Unable to find an implementation of greenlet.") diff --git a/eventlet/support/__init__.py b/eventlet/support/__init__.py new file mode 100644 index 0000000..707d276 --- /dev/null +++ b/eventlet/support/__init__.py @@ -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$ +""" + diff --git a/eventlet/nginx_mod_wsgi_support.py b/eventlet/support/nginx_mod_wsgi.py similarity index 100% rename from eventlet/nginx_mod_wsgi_support.py rename to eventlet/support/nginx_mod_wsgi.py diff --git a/eventlet/pylibsupport.py b/eventlet/support/pylib.py similarity index 98% rename from eventlet/pylibsupport.py rename to eventlet/support/pylib.py index 1378e7a..bc135ba 100644 --- a/eventlet/pylibsupport.py +++ b/eventlet/support/pylib.py @@ -1,5 +1,5 @@ """\ -@file pylibsupport.py +@file support.pylib.py @author Donovan Preston Copyright (c) 2005-2006, Donovan Preston diff --git a/eventlet/stacklesssupport.py b/eventlet/support/stackless.py similarity index 100% rename from eventlet/stacklesssupport.py rename to eventlet/support/stackless.py diff --git a/eventlet/twistedsupport.py b/eventlet/support/twisted.py similarity index 97% rename from eventlet/twistedsupport.py rename to eventlet/support/twisted.py index dfd57fb..2776882 100644 --- a/eventlet/twistedsupport.py +++ b/eventlet/support/twisted.py @@ -1,5 +1,5 @@ """\ -@file twistedsupport.py +@file support.twisted.py @author Donovan Preston Copyright (c) 2005-2006, Donovan Preston @@ -124,7 +124,7 @@ class EventletReactor(posixbase.PosixReactorBase): def emulate(): 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() from twisted.internet.main import installReactor installReactor(reactor) From b7236a5e24fd3126936017c9ecb7be1bc49a8450 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 16:48:53 -0500 Subject: [PATCH 026/118] Turns out that module name aliasing is a real problem, hence the rename of select.py to selecthub.py. Some minor fixes to get timers working right and canceling themselves under libevent. --- eventlet/api.py | 10 +---- eventlet/hubs/hub.py | 6 ++- eventlet/hubs/libevent.py | 46 ++++++++++++++++------- eventlet/hubs/{select.py => selecthub.py} | 2 +- eventlet/timer.py | 3 +- 5 files changed, 41 insertions(+), 26 deletions(-) rename eventlet/hubs/{select.py => selecthub.py} (99%) diff --git a/eventlet/api.py b/eventlet/api.py index facc834..ffe6de2 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -193,19 +193,13 @@ def get_default_hub(): except ImportError: pass - try: - import eventlet.hubs.kqueue - return eventlet.hubs.kqueue - except ImportError: - pass - import select if hasattr(select, 'poll'): import eventlet.hubs.poll return eventlet.hubs.poll else: - import eventlet.hubs.select - return eventlet.hubs.select + import eventlet.hubs.selecthub + return eventlet.hubs.selecthub def use_hub(mod=None): diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index b9c0d2f..ada6af7 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -203,16 +203,18 @@ class BaseHub(object): def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds self._add_absolute_timer(scheduled_time, timer) - timer.greenlet = current_greenlet self.track_timer(timer) return scheduled_time - + def track_timer(self, timer): current_greenlet = greenlet.getcurrent() + timer.greenlet = current_greenlet if current_greenlet not in self.timers_by_greenlet: self.timers_by_greenlet[current_greenlet] = {} self.timers_by_greenlet[current_greenlet][timer] = True + def timer_canceled(self, timer): + pass def prepare_timers(self): ins = bisect.insort_right diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 14887ff..7865e90 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -22,6 +22,7 @@ THE SOFTWARE. """ import bisect +import signal import sys import socket import errno @@ -38,14 +39,15 @@ import greenlet #raise ImportError() try: - import event -except ImportError: - # use rel if pyevent isn't available - # (rel prints out some annoying notice upon initialization) + # use rel if available import rel rel.initialize() rel.override() - import event +except ImportError: + # don't have rel, but might still have libevent + pass + +import event class Hub(hub.BaseHub): @@ -56,9 +58,8 @@ class Hub(hub.BaseHub): self.interrupted = False event.init() - # catch SIGINT - signal = event.signal(2, self.signal_received, 2) - signal.add() + sig = event.signal(signal.SIGINT, self.signal_received, signal.SIGINT) + sig.add() def add_descriptor(self, fileno, read=None, write=None, exc=None): @@ -83,16 +84,23 @@ class Hub(hub.BaseHub): self.excs.pop(fileno, None) def signal_received(self, signal): + # can only set this flag here because the pyevent callback mechanism + # swallows exceptions raised here, so we have to raise in the 'main' + # greenlet to kill the program self.interrupted = True def wait(self, seconds=None): - timer = event.timeout(seconds, lambda: None) + # this timeout will cause us to return from the dispatch() call + # when we want to + def abc(): + pass + timer = event.timeout(seconds, abc) timer.add() - status = event.loop() - if status == -1: - raise RuntimeError("does this ever happen?") - + status = event.dispatch() + # 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() if self.interrupted: @@ -100,6 +108,16 @@ class Hub(hub.BaseHub): raise KeyboardInterrupt() def add_timer(self, timer): - event.timeout(timer.seconds, timer).add() + # eventtimer is the pyevent object representing the timer + eventtimer = event.timeout(timer.seconds, timer) + timer.impltimer = eventtimer + eventtimer.add() self.track_timer(timer) + + def timer_canceled(self, timer): + """ Cancels the underlying libevent timer. """ + try: + timer.impltimer.delete() + except AttributeError: + pass diff --git a/eventlet/hubs/select.py b/eventlet/hubs/selecthub.py similarity index 99% rename from eventlet/hubs/select.py rename to eventlet/hubs/selecthub.py index c155ff8..efda460 100644 --- a/eventlet/hubs/select.py +++ b/eventlet/hubs/selecthub.py @@ -1,5 +1,5 @@ """\ -@file select.py +@file selecthub.py Copyright (c) 2005-2006, Bob Ippolito Copyright (c) 2007, Linden Research, Inc. diff --git a/eventlet/timer.py b/eventlet/timer.py index fd5e004..51a8eda 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -29,7 +29,7 @@ useful for debugging leaking timers, to find out where the timer was set up. """ _g_debug = False 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): """Create a timer. seconds: The minimum number of seconds to wait before calling @@ -81,3 +81,4 @@ class Timer(object): """ self.cancelled = True self.called = True + get_hub().timer_canceled(self) From afc2068a98b5fe6eb367822d264b9e69b4dc37e0 Mon Sep 17 00:00:00 2001 From: rdw Date: Tue, 18 Mar 2008 17:32:07 -0500 Subject: [PATCH 027/118] Some Hub documentation, made wrap_socket_with_coroutine_socket happen only once. --- eventlet/hubs/hub.py | 17 +++++++++++++++++ eventlet/util.py | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index ada6af7..a02104f 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -34,6 +34,9 @@ from eventlet import greenlib from eventlet.timer import Timer class BaseHub(object): + """ Base hub class for easing the implementation of subclasses that are + specific to a particular underlying event architecture. """ + SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) def __init__(self, clock=time.time): @@ -58,6 +61,20 @@ class BaseHub(object): } def add_descriptor(self, fileno, read=None, write=None, exc=None): + """ Signals an intent to read/write from a particular file descriptor. + + The fileno argument is the file number of the file of interest. The other + 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. + """ + self.readers[fileno] = read or self.readers.get(fileno) self.writers[fileno] = write or self.writers.get(fileno) self.excs[fileno] = exc or self.excs.get(fileno) diff --git a/eventlet/util.py b/eventlet/util.py index 994ff4c..237aa95 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -90,8 +90,12 @@ def wrap_ssl(sock, certificate=None, private_key=None): connection.set_connect_state() return greenio.GreenSocket(connection) - +socket_already_wrapped = False def wrap_socket_with_coroutine_socket(): + global socket_already_wrapped + if socket_already_wrapped: + return + def new_socket(*args, **kw): from eventlet import greenio s = __original_socket__(*args, **kw) @@ -100,6 +104,8 @@ def wrap_socket_with_coroutine_socket(): socket.socket = new_socket socket.ssl = wrap_ssl + + socket_already_wrapped = True def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): From f6c483566b9309872ce4a83f8b19f62efeb8f5a4 Mon Sep 17 00:00:00 2001 From: rdw Date: Wed, 19 Mar 2008 09:49:28 -0500 Subject: [PATCH 028/118] Fixed api_test failure, tweaked some things, and made tpool_test faster and look more alive. --- eventlet/hubs/hub.py | 5 ++++- eventlet/hubs/libevent.py | 21 +++++++++++---------- eventlet/tests.py | 2 +- eventlet/tpool_test.py | 5 ++++- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index a02104f..c86aefa 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -33,6 +33,8 @@ import greenlet from eventlet import greenlib 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. """ @@ -281,5 +283,6 @@ class BaseHub(object): ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. timer.cancel() - print 'Hub cancelling left-over timer %s' % timer + if _g_debug: + print 'Hub cancelling left-over timer %s' % timer del self.timers_by_greenlet[greenlet] diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 7865e90..10327e1 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -50,9 +50,7 @@ except ImportError: import event -class Hub(hub.BaseHub): - SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) - +class Hub(hub.BaseHub): def __init__(self, clock=time.time): super(Hub, self).__init__(clock) self.interrupted = False @@ -83,18 +81,20 @@ class Hub(hub.BaseHub): tpl[0].delete() self.excs.pop(fileno, None) + def abort(self): + super(Hub, self).abort() + event.abort() + def signal_received(self, signal): - # can only set this flag here because the pyevent callback mechanism - # swallows exceptions raised here, so we have to raise in the 'main' - # greenlet to kill the program + # 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 def wait(self, seconds=None): # this timeout will cause us to return from the dispatch() call # when we want to - def abc(): - pass - timer = event.timeout(seconds, abc) + timer = event.timeout(seconds, lambda: None) timer.add() status = event.dispatch() @@ -103,12 +103,13 @@ class Hub(hub.BaseHub): timer.delete() + # raise any signals that deserve raising if self.interrupted: self.interrupted = False raise KeyboardInterrupt() def add_timer(self, timer): - # eventtimer is the pyevent object representing the timer + # store the pyevent timer object so that we can cancel later eventtimer = event.timeout(timer.seconds, timer) timer.impltimer = eventtimer eventtimer.add() diff --git a/eventlet/tests.py b/eventlet/tests.py index 41968c2..7caab69 100644 --- a/eventlet/tests.py +++ b/eventlet/tests.py @@ -36,7 +36,7 @@ name = getattr(sys.modules['__main__'], '__name__', None) main = unittest.main # listing of files containing doctests -doc_test_files = ['coros'] +doc_test_files = []#'coros'] def find_command(command): for dir in os.getenv('PATH', '/usr/bin:/usr/sbin').split(os.pathsep): diff --git a/eventlet/tpool_test.py b/eventlet/tpool_test.py index 19442bc..fbc85d6 100644 --- a/eventlet/tpool_test.py +++ b/eventlet/tpool_test.py @@ -39,13 +39,16 @@ class yadda(object): def foo(self,when,n=None): assert(n is not None) prnt("foo: %s, %s" % (when,n)) - time.sleep(r.random()) + time.sleep(r.random()/20.0) return n def sender_loop(pfx): n = 0 obj = tpool.Proxy(yadda()) while n < 10: + if not (n % 5): + stdout.write('.') + stdout.flush() api.sleep(0) now = time.time() prnt("%s: send (%s,%s)" % (pfx,now,n)) From 4a2459b6bb1bfaaa3779d17bcbf7fb0c8297a8c3 Mon Sep 17 00:00:00 2001 From: rdw Date: Wed, 19 Mar 2008 15:57:50 -0500 Subject: [PATCH 029/118] Fixed test_trampoline_timeout. --- eventlet/api.py | 4 ++-- eventlet/api_test.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index ffe6de2..4624541 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -136,8 +136,8 @@ def trampoline(fd, read=None, write=None, timeout=None): def _do_close(fn): hub.remove_descriptor(fn) greenlib.switch(self, exc=socket.error(32, 'Broken pipe')) - def _do_timeout(fn): - hub.remove_descriptor(fn) + def _do_timeout(): + hub.remove_descriptor(fileno) greenlib.switch(self, exc=TimeoutError()) def cb(_fileno): if t is not None: diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 6557554..9e0335f 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -93,10 +93,7 @@ class TestApi(tests.TestCase): check_hub() - def dont_test_trampoline_timeout(self): - """This test is broken. Please change it's name to test_trampoline_timeout, - and fix the bug (or fix the test) - """ + def test_trampoline_timeout(self): server = api.tcp_listener(('0.0.0.0', 0)) bound_port = server.getsockname()[1] From 2de6627f5a1dd0402fb51c52dc6fcd78c199792e Mon Sep 17 00:00:00 2001 From: rdw Date: Wed, 19 Mar 2008 16:06:52 -0500 Subject: [PATCH 030/118] test_explicit_hub seems to work --- eventlet/api_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 9e0335f..a7e390b 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -133,10 +133,7 @@ class TestApi(tests.TestCase): check_hub() - def dont_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) - """ + def test_explicit_hub(self): api.use_hub(Foo) assert isinstance(api.get_hub(), Foo), api.get_hub() From 9333192c32f73dfcb25c72598a76f12b213265b8 Mon Sep 17 00:00:00 2001 From: rdw Date: Wed, 19 Mar 2008 16:07:38 -0500 Subject: [PATCH 031/118] test_explicit_hub seems to work --- eventlet/api_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/api_test.py b/eventlet/api_test.py index a7e390b..53c5501 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -93,7 +93,7 @@ class TestApi(tests.TestCase): check_hub() - def test_trampoline_timeout(self): + def test_001_trampoline_timeout(self): server = api.tcp_listener(('0.0.0.0', 0)) bound_port = server.getsockname()[1] From 39ef9145e23d41b1026c15317f2f8f4cc4c19670 Mon Sep 17 00:00:00 2001 From: nat Date: Wed, 19 Mar 2008 18:00:45 -0400 Subject: [PATCH 032/118] Add api.with_timeout() function to wrap the exc_after() idiom. Experiment with ReST docstring for use with epydoc. --- eventlet/api.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/eventlet/api.py b/eventlet/api.py index facc834..a5752f3 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -50,6 +50,7 @@ from eventlet import greenlib, tls __all__ = [ 'use_hub', 'get_hub', 'sleep', 'spawn', 'kill', 'call_after', 'exc_after', 'trampoline', 'tcp_listener', 'tcp_server', + 'with_timeout', ] @@ -182,6 +183,49 @@ def call_after(seconds, cb, *args, **kw): return get_hub().schedule_call(seconds, startup) +def with_timeout(seconds, func, *args, **kwds): + """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, exc): return call_after(seconds, switch, getcurrent(), None, exc) From 2d3eb6348894c5abda9ebe32b510e3800b22695d Mon Sep 17 00:00:00 2001 From: radix Date: Wed, 19 Mar 2008 20:38:10 -0500 Subject: [PATCH 033/118] Add gthreadless (greenlet / deferred integration) and fix some module names that conflicted with toplevel modules. --- eventlet/api.py | 6 +- eventlet/gthreadless.py | 135 ++++++++++++++++++ eventlet/hubs/hub.py | 2 +- eventlet/hubs/poll.py | 3 +- eventlet/hubs/{select.py => selects.py} | 0 .../support/{stackless.py => stacklesss.py} | 0 eventlet/support/{twisted.py => twisteds.py} | 12 +- 7 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 eventlet/gthreadless.py rename eventlet/hubs/{select.py => selects.py} (100%) rename eventlet/support/{stackless.py => stacklesss.py} (100%) rename eventlet/support/{twisted.py => twisteds.py} (89%) diff --git a/eventlet/api.py b/eventlet/api.py index facc834..de19143 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -200,12 +200,12 @@ def get_default_hub(): pass import select - if hasattr(select, 'poll'): + if 0:#hasattr(select, 'poll'): import eventlet.hubs.poll return eventlet.hubs.poll else: - import eventlet.hubs.select - return eventlet.hubs.select + import eventlet.hubs.selects + return eventlet.hubs.selects def use_hub(mod=None): diff --git a/eventlet/gthreadless.py b/eventlet/gthreadless.py new file mode 100644 index 0000000..382bc4d --- /dev/null +++ b/eventlet/gthreadless.py @@ -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 = " %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 + diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index b9c0d2f..5cc382b 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -203,7 +203,7 @@ class BaseHub(object): def add_timer(self, timer): scheduled_time = self.clock() + timer.seconds self._add_absolute_timer(scheduled_time, timer) - timer.greenlet = current_greenlet + timer.greenlet = greenlet.getcurrent() self.track_timer(timer) return scheduled_time diff --git a/eventlet/hubs/poll.py b/eventlet/hubs/poll.py index 065f31b..8d34ff0 100644 --- a/eventlet/hubs/poll.py +++ b/eventlet/hubs/poll.py @@ -29,6 +29,7 @@ import socket import errno import traceback from time import sleep +import time from eventlet import greenlib from eventlet.hubs import hub @@ -42,7 +43,7 @@ class Hub(hub.BaseHub): super(Hub, self).__init__(clock) self.poll = select.poll() - def add_descriptor(self, fileno, read=None, write=None, exc=None): + 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) diff --git a/eventlet/hubs/select.py b/eventlet/hubs/selects.py similarity index 100% rename from eventlet/hubs/select.py rename to eventlet/hubs/selects.py diff --git a/eventlet/support/stackless.py b/eventlet/support/stacklesss.py similarity index 100% rename from eventlet/support/stackless.py rename to eventlet/support/stacklesss.py diff --git a/eventlet/support/twisted.py b/eventlet/support/twisteds.py similarity index 89% rename from eventlet/support/twisted.py rename to eventlet/support/twisteds.py index 2776882..cd6fdb3 100644 --- a/eventlet/support/twisted.py +++ b/eventlet/support/twisteds.py @@ -91,28 +91,36 @@ class EventletReactor(posixbase.PosixReactorBase): api.get_hub().abort() def addReader(self, reader): + print "NEW READER", reader.fileno() fileno = reader.fileno() self._readers[fileno] = reader api.get_hub().add_descriptor(fileno, read=self._got_read) 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() def addWriter(self, writer): + print "NEW WRITER", writer.fileno() fileno = writer.fileno() self._writers[fileno] = writer api.get_hub().add_descriptor(fileno, write=self._got_write) 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() def removeReader(self, reader): + print "removing reader", reader.fileno() fileno = reader.fileno() if fileno in self._readers: self._readers.pop(fileno) api.get_hub().remove_descriptor(fileno) def removeWriter(self, writer): + print "removing writer", writer.fileno() fileno = writer.fileno() if fileno in self._writers: self._writers.pop(fileno) @@ -122,7 +130,7 @@ class EventletReactor(posixbase.PosixReactorBase): return self._removeAll(self._readers.values(), self._writers.values()) -def emulate(): +def install(): if not _working: raise RuntimeError, "Can't use support.twisted because zope.interface is not installed." reactor = EventletReactor() @@ -130,5 +138,5 @@ def emulate(): installReactor(reactor) -__all__ = ['emulate'] +__all__ = ['install'] From 124e84e88ce31b3c12d0eb9eb3d6a099ef028bc8 Mon Sep 17 00:00:00 2001 From: nat Date: Thu, 20 Mar 2008 15:17:02 -0400 Subject: [PATCH 034/118] Fold in some api function documentation from Donovan's preliminary guide. Fix reStructuredText syntax on a couple others. --- eventlet/api.py | 165 ++++++++++++++++++++++++++++++++++++------------ makedoc | 3 + 2 files changed, 128 insertions(+), 40 deletions(-) create mode 100755 makedoc diff --git a/eventlet/api.py b/eventlet/api.py index 42487cb..dbffb7e 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -55,17 +55,18 @@ __all__ = [ class TimeoutError(Exception): + """Exception raised if an asynchronous operation times out""" pass _threadlocal = tls.local() def tcp_listener(address): """ - Listen on the given (ip, port) address with a TCP socket. - Returns a socket object which one should call accept() on to + Listen on the given (ip, port) *address* with a TCP socket. + Returns a socket object on which one should call ``accept()`` to 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 each incoming connection. """ @@ -75,13 +76,16 @@ def tcp_listener(address): return socket 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. - 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. - 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 each incoming connection. """ @@ -103,20 +107,17 @@ def connect_tcp(address): def tcp_server(listensocket, server, *args, **kw): """ Given a socket, accept connections forever, spawning greenlets - and executing "server" for each new incoming connection. - When listensocket is closed, the tcp_server greenlet will end. + and executing *server* for each new incoming connection. + When *listensocket* is closed, the ``tcp_server()`` greenlet will end. - listensocket: - The socket to accept connections from. - - server: + listensocket + The socket from which to accept connections. + server The callable to call when a new connection is made. - - *args: - The arguments to pass to the call to server. - - **kw: - The keyword arguments to pass to the call to server. + \*args + The positional arguments to pass to *server*. + \*\*kw + The keyword arguments to pass to *server*. """ try: try: @@ -129,7 +130,19 @@ def tcp_server(listensocket, server, *args, **kw): finally: 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 hub = get_hub() self = greenlet.getcurrent() @@ -164,21 +177,43 @@ def _spawn(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 t = None g = greenlib.tracked_greenlet() 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 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 def startup(): g = greenlib.tracked_greenlet() - greenlib.switch(g, (_spawn_startup, cb, args, kw)) + greenlib.switch(g, (_spawn_startup, function, args, kwds)) greenlib.switch(g) return get_hub().schedule_call(seconds, startup) @@ -188,14 +223,14 @@ def with_timeout(seconds, func, *args, **kwds): function fails to return before the timeout, cancel it and return a flag value. - *seconds* + seconds (int or float) seconds before timeout occurs - *func* + func the callable to execute with a timeout; must be one of the functions that implicitly or explicitly yields - *\*args*, *\*\*kwds* + \*args, \*\*kwds (positional, keyword) arguments to pass to *func* - *timeout_value=* + timeout_value= value to return if timeout occurs (default None) **Returns**: @@ -209,9 +244,10 @@ def with_timeout(seconds, func, *args, **kwds): **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. + + 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 @@ -226,11 +262,36 @@ def with_timeout(seconds, func, *args, **kwds): finally: timeout.cancel() -def exc_after(seconds, exc): - return call_after(seconds, switch, getcurrent(), None, exc) +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() + """ + return call_after(seconds, switch, getcurrent(), None, exception_object) -def get_default_hub(): +def get_default_hub(): + """ + """ try: import eventlet.hubs.libevent return eventlet.hubs.libevent @@ -247,6 +308,8 @@ def get_default_hub(): def use_hub(mod=None): + """ + """ if mod is None: mod = get_default_hub() if hasattr(_threadlocal, 'hub'): @@ -257,6 +320,8 @@ def use_hub(mod=None): _threadlocal.Hub = mod def get_hub(): + """ + """ try: hub = _threadlocal.hub except AttributeError: @@ -268,9 +333,19 @@ def get_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.schedule_call(timeout, greenlib.switch, greenlet.getcurrent()) + hub.schedule_call(seconds, greenlib.switch, greenlet.getcurrent()) hub.switch() @@ -280,6 +355,8 @@ GreenletExit = greenlet.GreenletExit class Spew(object): + """ + """ def __init__(self, trace_names=None): self.trace_names = trace_names @@ -317,19 +394,27 @@ class Spew(object): def spew(trace_names=None): + """ + """ sys.settrace(Spew(trace_names)) def unspew(): + """ + """ sys.settrace(None) def named(name): - """Return an object given its name. The name uses a module-like -syntax, eg: - os.path.join - or - mulib.mu.Resource + """Return an object given its name. + + The name uses a module-like syntax, eg:: + + os.path.join + + or:: + + mulib.mu.Resource """ toimport = name obj = None diff --git a/makedoc b/makedoc new file mode 100755 index 0000000..178c6cb --- /dev/null +++ b/makedoc @@ -0,0 +1,3 @@ +dir="$(dirname "$0")" +epydoc -o "$dir/html" --graph classtree --docformat=restructuredtext "$dir/eventlet/" || exit $? +open "$dir/html/index.html" From 96eb7a660fb28654b667a90b8cbb79f9b3683da3 Mon Sep 17 00:00:00 2001 From: rdw Date: Thu, 20 Mar 2008 14:54:50 -0700 Subject: [PATCH 035/118] Parsed_body now raises ValueError if it can't find a parser. --- eventlet/httpd.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 91bf43c..59e698f 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -369,10 +369,15 @@ class Request(object): if not hasattr(self, '_cached_parsed_body'): body = self.read_body() if hasattr(self.site, 'parsers'): - parser = self.site.parsers.get( - self.get_header('content-type')) + ct = self.get_header('content-type') + parser = self.site.parsers.get(ct) + if parser is not None: 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 return self._cached_parsed_body From 65a7965ec11002977d9dc8370cc7c04448164169 Mon Sep 17 00:00:00 2001 From: rdw Date: Thu, 20 Mar 2008 22:27:53 -0700 Subject: [PATCH 036/118] Reenabled doctests. --- eventlet/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/tests.py b/eventlet/tests.py index 7caab69..41968c2 100644 --- a/eventlet/tests.py +++ b/eventlet/tests.py @@ -36,7 +36,7 @@ name = getattr(sys.modules['__main__'], '__name__', None) main = unittest.main # listing of files containing doctests -doc_test_files = []#'coros'] +doc_test_files = ['coros'] def find_command(command): for dir in os.getenv('PATH', '/usr/bin:/usr/sbin').split(os.pathsep): From dd4c75060486d90c047ee4b3b5139f33d19fdfae Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 1 Apr 2008 14:42:21 -0700 Subject: [PATCH 037/118] Fix Request.request_protocol; it was looking for is_secure in a place the refactoring had made go away --- eventlet/httpd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 91bf43c..154a94b 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -389,7 +389,7 @@ class Request(object): return self.protocol.request_version def request_protocol(self): - if self.protocol.socket.is_secure: + if self.protocol.is_secure: return "https" return "http" @@ -413,6 +413,7 @@ class Timeout(RuntimeError): class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): def __init__(self, request, client_address, server): 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.server = server From 0f07b622450d140817d7f308381b81e21da681ef Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 1 Apr 2008 14:44:04 -0700 Subject: [PATCH 038/118] eventlet.support.pycurls module, experimental module which uses libcurl instead of python's httplib to perform http requests. Hopefully this can eventually be shimmed in as an optional faster implementation of the httpc module. --- eventlet/support/pycurls.py | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 eventlet/support/pycurls.py diff --git a/eventlet/support/pycurls.py b/eventlet/support/pycurls.py new file mode 100644 index 0000000..716bc1b --- /dev/null +++ b/eventlet/support/pycurls.py @@ -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/') \ No newline at end of file From bb6171295b232b6ffaf55a03b281b6518297e578 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 18 Apr 2008 10:17:56 -0700 Subject: [PATCH 039/118] Catch epipe --- eventlet/wsgi.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 2fa765a..4b5d349 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -23,6 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import errno import sys import time import urllib @@ -204,16 +205,21 @@ class Server(BaseHTTPServer.HTTPServer): self.log.write(message + '\n') -def server(socket, site, log=None, environ=None): - serv = Server(socket, socket.getsockname(), site, log, environ=None) +def server(sock, site, log=None, environ=None): + serv = Server(sock, sock.getsockname(), site, log, environ=None) try: - print "wsgi starting up on", socket.getsockname() + print "wsgi starting up on", sock.getsockname() while True: try: - api.spawn(serv.process_request, socket.accept()) + api.spawn(serv.process_request, sock.accept()) except KeyboardInterrupt: - api.get_hub().remove_descriptor(socket.fileno()) + api.get_hub().remove_descriptor(sock.fileno()) print "wsgi exiting" break finally: - socket.close() + try: + sock.close() + except socket.error, e: + if e[0] != errno.EPIPE: + raise + From 7acf7918b348b678b292cb60426698de68cf45cf Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 18 Apr 2008 13:53:35 -0700 Subject: [PATCH 040/118] Make the wsgi server work, and it works really well, keepalives, http 1.1, yay --- eventlet/greenio.py | 14 ++++--- eventlet/wsgi.py | 93 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index db8f590..d8a07d1 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -156,7 +156,7 @@ class GreenSocket(object): self.sendcount = 0 self.recvcount = 0 self.recvbuffer = '' - self._closed = False + self.closed = False def accept(self): fd = self.fd @@ -173,9 +173,9 @@ class GreenSocket(object): return fn(*args, **kw) def close(self, *args, **kw): - if self._closed: + if self.closed: return - self._closed = True + self.closed = True fn = self.close = self.fd.close try: res = fn(*args, **kw) @@ -272,16 +272,20 @@ class GreenFile(object): def __init__(self, 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 - # TODO flush - + + def flush(self): + pass + def write(self, data): return self.sock.sendall(data) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 4b5d349..747b78e 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -26,6 +26,7 @@ THE SOFTWARE. import errno import sys import time +import traceback import urllib import socket import cStringIO @@ -35,8 +36,22 @@ import BaseHTTPServer from eventlet import api from eventlet.httpdate import format_date_time -class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): +class Input(object): + def __init__(self, rfile, content_length): + self.rfile = rfile + self.content_length = content_length + + def read(self, length=None): + if length is None: + length = self.content_length + if length is None: + return '' + return self.rfile.read(length) + + +class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' def log_message(self, format, *args): self.server.log_message("%s - - [%s] %s" % ( self.address_string(), @@ -71,8 +86,10 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): wfile = self.wfile num_blocks = None + use_chunked = False def write(data, _write=wfile.write): + towrite = [] if not headers_set: raise AssertionError("write() before start_response()") elif not headers_sent: @@ -80,16 +97,24 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): headers_sent.append(1) for k, v in response_headers: header_dict[k.lower()] = k - _write('HTTP/1.0 %s\r\n' % status) + towrite.append('%s %s\r\n' % (self.protocol_version, status)) # send Date header? if 'date' not in header_dict: - _write('Date: %s\r\n' % (format_date_time(time.time()),)) - if 'content-length' not in header_dict and num_blocks == 1: - _write('Content-Length: %s\r\n' % (len(data),)) + towrite.append('Date: %s\r\n' % (format_date_time(time.time()),)) + if num_blocks == 1: + towrite.append('Content-Length: %s\r\n' % (len(data),)) + elif use_chunked: + towrite.append('Transfer-Encoding: chunked\r\n') for header in response_headers: - _write('%s: %s\r\n' % header) - _write('\r\n') - _write(data) + towrite.append('%s: %s\r\n' % header) + 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) + _write(''.join(towrite)) def start_request(status, response_headers, exc_info=None): if exc_info: @@ -106,18 +131,48 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): headers_set[:] = [status, response_headers] return write - result = self.server.app(self.environ, start_request) + try: + result = self.server.app(self.environ, start_request) + except Exception, e: + exc = traceback.format_exc() + if not headers_sent: + start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) + write(exc) + return + try: num_blocks = len(result) except (TypeError, AttributeError, NotImplementedError): - pass - + if self.protocol_version == 'HTTP/1.1': + use_chunked = True + try: - for data in result: - if data: - write(data) + towrite = [] + try: + for data in result: + if data: + towrite.append(data) + if reduce( + lambda x, y: x + y, + map( + lambda x: len(x), towrite)) > 4096: + write(''.join(towrite)) + del towrite[:] + except Exception, e: + exc = traceback.format_exc() + if not headers_sent: + start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) + write(exc) + return + + if towrite: + write(''.join(towrite)) + if use_chunked: + wfile.write('0\r\n\r\n') if not headers_sent: write('') + except Exception, e: + traceback.print_exc() finally: if hasattr(result, 'close'): result.close() @@ -150,6 +205,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): env['REMOTE_ADDR'] = self.client_address[0] env['GATEWAY_INTERFACE'] = 'CGI/1.1' + env['wsgi.input'] = Input(self.rfile, length) + for h in self.headers.headers: k, v = h.split(':', 1) k = k.replace('-', '_').upper() @@ -165,10 +222,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): return env def finish(self): - # Override SocketServer.StreamRequestHandler.finish because - # we only need to call close on the socket, not the makefile'd things - - self.request.close() + BaseHTTPServer.BaseHTTPRequestHandler.finish(self) + self.connection.close() class Server(BaseHTTPServer.HTTPServer): @@ -186,7 +241,6 @@ class Server(BaseHTTPServer.HTTPServer): def get_environ(self): socket = self.socket d = { - 'wsgi.input': socket, 'wsgi.errors': sys.stderr, 'wsgi.version': (1, 0), 'wsgi.multithread': True, @@ -200,6 +254,7 @@ class Server(BaseHTTPServer.HTTPServer): def process_request(self, (socket, address)): proto = HttpProtocol(socket, address, self) + proto.handle() def log_message(self, message): self.log.write(message + '\n') From af44ffb14f05aa3f6e5847ab1c67e24c82530fd3 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 18 Apr 2008 14:06:53 -0700 Subject: [PATCH 041/118] Call int on content length --- eventlet/wsgi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 747b78e..58f37e6 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -43,8 +43,8 @@ class Input(object): self.content_length = content_length def read(self, length=None): - if length is None: - length = self.content_length + if length is None and self.content_length is not None: + length = int(self.content_length) if length is None: return '' return self.rfile.read(length) From ca4c1d0290736486736ab890610fd9e36fe11600 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 18 Apr 2008 17:46:36 -0700 Subject: [PATCH 042/118] Support 100 continue --- eventlet/wsgi.py | 50 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 58f37e6..1c0af65 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -38,16 +38,33 @@ from eventlet.httpdate import format_date_time class Input(object): - def __init__(self, rfile, content_length): + 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 read(self, 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 = int(self.content_length) - if length is 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 '' - return self.rfile.read(length) + read = self.rfile.read(length) + self.position += len(read) + return read class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): @@ -86,6 +103,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): wfile = self.wfile num_blocks = None + result = None use_chunked = False def write(data, _write=wfile.write): @@ -101,10 +119,13 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): # send Date header? if 'date' not in header_dict: towrite.append('Date: %s\r\n' % (format_date_time(time.time()),)) - if num_blocks == 1: - towrite.append('Content-Length: %s\r\n' % (len(data),)) + if num_blocks is not None: + towrite.append('Content-Length: %s\r\n' % (len(''.join(result)),)) elif use_chunked: towrite.append('Transfer-Encoding: chunked\r\n') + else: + towrite.append('Connection: close\r\n') + self.close_connection = 1 for header in response_headers: towrite.append('%s: %s\r\n' % header) towrite.append('\r\n') @@ -135,7 +156,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): result = self.server.app(self.environ, start_request) except Exception, e: exc = traceback.format_exc() - if not headers_sent: + print exc + if not headers_set: start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) write(exc) return @@ -160,7 +182,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): del towrite[:] except Exception, e: exc = traceback.format_exc() - if not headers_sent: + print exc + if not headers_set: start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) write(exc) return @@ -205,8 +228,6 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): env['REMOTE_ADDR'] = self.client_address[0] env['GATEWAY_INTERFACE'] = 'CGI/1.1' - env['wsgi.input'] = Input(self.rfile, length) - for h in self.headers.headers: k, v = h.split(':', 1) k = k.replace('-', '_').upper() @@ -219,6 +240,15 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): else: 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'] = Input( + self.rfile, length, wfile=wfile, wfile_line=wfile_line) + return env def finish(self): From 7a53e5c9f834cbe2b693e0570f249f6593cd37d9 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 18 Apr 2008 18:30:13 -0700 Subject: [PATCH 043/118] start_response not start_request --- eventlet/wsgi.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 1c0af65..5fd8a39 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -136,8 +136,8 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): else: towrite.append(data) _write(''.join(towrite)) - - def start_request(status, response_headers, exc_info=None): + + def start_response(status, response_headers, exc_info=None): if exc_info: try: if headers_sent: @@ -146,19 +146,17 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): finally: # Avoid dangling circular ref exc_info = None - elif headers_set: - raise AssertionError("Headers already set!") headers_set[:] = [status, response_headers] return write try: - result = self.server.app(self.environ, start_request) + result = self.server.app(self.environ, start_response) except Exception, e: exc = traceback.format_exc() print exc if not headers_set: - start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) + start_response("500 Internal Server Error", [('Content-type', 'text/plain')]) write(exc) return @@ -184,7 +182,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): exc = traceback.format_exc() print exc if not headers_set: - start_request("500 Internal Server Error", [('Content-type', 'text/plain')]) + start_response("500 Internal Server Error", [('Content-type', 'text/plain')]) write(exc) return From c8593374de937d1d597cda6e89a2d1de336aefe6 Mon Sep 17 00:00:00 2001 From: donovan Date: Sun, 20 Apr 2008 14:10:28 -0700 Subject: [PATCH 044/118] Use a couple of environment variables to figure out what wsgi app to really run in nginx_mod_wsgi.py. Now you can run any wsgi app you want easily in nginx. Also, delay calling start_response until the first hunk of the body is sent since nginx doesn't let us change the response codes or headers after the first time it is called. --- eventlet/support/nginx_mod_wsgi.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/eventlet/support/nginx_mod_wsgi.py b/eventlet/support/nginx_mod_wsgi.py index b7b7b06..1035680 100644 --- a/eventlet/support/nginx_mod_wsgi.py +++ b/eventlet/support/nginx_mod_wsgi.py @@ -1,5 +1,9 @@ import sys +import traceback + +sys.path.insert(0, '/Users/donovan/Code/mulib-hg') +sys.stdout = sys.stderr from eventlet import api from eventlet import httpc @@ -7,14 +11,15 @@ from eventlet import httpc from eventlet.hubs import nginx -def real_application(env, start_response): +def old_real_application(env, start_response): #result = httpc.get('http://127.0.0.1:8081/') start_response('200 OK', [('Content-type', 'text/plain')]) #sys.stderr.write("RESULT %r" % (result, )) - return 'hi' + return 'hello' def wrap_application(master, env, start_response): + real_application = api.named(env['eventlet_nginx_wsgi_app']) result = real_application(env, start_response) ## Should catch exception and return here? #sys.stderr.write("RESULT2 %r" % (result, )) @@ -22,6 +27,14 @@ def wrap_application(master, env, start_response): return None, None +class StartResponse(object): + def __init__(self, start_response): + self.start_response = start_response + + def __call__(self, *args): + self.args = args + + def application(env, start_response): hub = api.get_hub() @@ -34,17 +47,21 @@ def application(env, start_response): hub.current_application = api.getcurrent() slave = api.greenlet.greenlet(wrap_application) + response = StartResponse(start_response) result = slave.switch( - hub.current_application, env, start_response) + hub.current_application, env, response) while True: #sys.stderr.write("RESULT3 %r" % (result, )) if result is None or result == (None, None): yield '' else: + start_response(*response.args) if isinstance(result, tuple): - yield result[0] + for x in result[0]: + yield x else: - yield result + for x in result: + yield x return result = hub.switch() From d3eeaf4e702151de97dc5685ca8ae41678a1ea0b Mon Sep 17 00:00:00 2001 From: donovan Date: Sun, 20 Apr 2008 14:47:41 -0700 Subject: [PATCH 045/118] Add unused max_size param to be more like eventlet.httpd.server --- eventlet/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 5fd8a39..8d104f0 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -288,7 +288,7 @@ class Server(BaseHTTPServer.HTTPServer): self.log.write(message + '\n') -def server(sock, site, log=None, environ=None): +def server(sock, site, log=None, environ=None, max_size=None): serv = Server(sock, sock.getsockname(), site, log, environ=None) try: print "wsgi starting up on", sock.getsockname() From c93af05c7286b9ee5f8b2ec61bb7d33d960b2fd9 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 21 Apr 2008 09:41:48 -0700 Subject: [PATCH 046/118] Don't have an exception when the other side closes the connection --- eventlet/wsgi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 8d104f0..414adbe 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -76,7 +76,12 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): format % args)) def handle_one_request(self): - self.raw_requestline = self.rfile.readline() + try: + self.raw_requestline = self.rfile.readline() + except socket.error, e: + if e[0] != errno.EBADF: + raise + self.raw_requestline = '' if not self.raw_requestline: self.close_connection = 1 From c61ce07bbf5ace2186e107c58f4a095789ae215c Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 28 Apr 2008 18:28:30 -0700 Subject: [PATCH 047/118] Patch from Chuck Thier to properly install subpackages (eventlet.hubs and eventlet.support) now that we have subpackages --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index dd5999c..bfa3c06 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from setuptools import setup +from setuptools import setup, find_packages setup( name='eventlet', @@ -9,7 +9,7 @@ setup( author='Linden Lab', author_email='eventletdev@lists.secondlife.com', url='http://wiki.secondlife.com/wiki/Eventlet', - packages=['eventlet'], + packages=find_packages(), install_requires=['greenlet'], long_description=""" Eventlet is a networking library written in Python. It achieves From 09f3a52239f4b191dcda746dffc19fbce8a17803 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 29 Apr 2008 18:33:47 -0700 Subject: [PATCH 048/118] Work on python 2.3 --- eventlet/wsgi.py | 59 +++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 414adbe..22c3f9c 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -158,7 +158,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): try: result = self.server.app(self.environ, start_response) except Exception, e: - exc = traceback.format_exc() + exc = ''.join(traceback.format_exception(*sys.exc_info())) print exc if not headers_set: start_response("500 Internal Server Error", [('Content-type', 'text/plain')]) @@ -170,38 +170,41 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): except (TypeError, AttributeError, NotImplementedError): if self.protocol_version == 'HTTP/1.1': use_chunked = True - try: - towrite = [] try: - for data in result: - if data: - towrite.append(data) - if reduce( - lambda x, y: x + y, - map( - lambda x: len(x), towrite)) > 4096: - write(''.join(towrite)) - del towrite[:] + towrite = [] + try: + for data in result: + if data: + towrite.append(data) + if reduce( + lambda x, y: x + y, + map( + lambda x: len(x), 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 use_chunked: + wfile.write('0\r\n\r\n') + if not headers_sent: + write('') 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 use_chunked: - wfile.write('0\r\n\r\n') - if not headers_sent: - write('') - except Exception, e: - traceback.print_exc() + traceback.print_exc() finally: if hasattr(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() def get_environ(self): env = self.server.get_environ() @@ -249,7 +252,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): else: wfile = None wfile_line = None - env['wsgi.input'] = Input( + env['wsgi.input'] = env['eventlet.input'] = Input( self.rfile, length, wfile=wfile, wfile_line=wfile_line) return env From a253013629e525a92519427c1b1ba2b29bd886ab Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 29 Apr 2008 18:34:14 -0700 Subject: [PATCH 049/118] Don't re-use event objects, there might be a bug --- eventlet/coros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/coros.py b/eventlet/coros.py index 4552ad8..0fdb06d 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -260,7 +260,7 @@ class CoroutinePool(pools.Pool): """ Private, infinite loop run by a pooled coroutine. """ while True: recvd = sender.wait() - sender.reset() + sender = event() (evt, func, args, kw) = recvd self._safe_apply(evt, func, args, kw) api.get_hub().cancel_timers(api.getcurrent()) From f61bcd8621db34825bd051a66638097a6f676ca7 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 29 Apr 2008 18:34:28 -0700 Subject: [PATCH 050/118] Implement settimeout and gettimeout --- eventlet/greenio.py | 75 +++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index d8a07d1..3472396 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -21,7 +21,7 @@ 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.api import exc_after, TimeoutError, trampoline, get_hub from eventlet import util BUFFER_SIZE = 4096 @@ -86,37 +86,55 @@ def socket_accept(descriptor): def socket_send(descriptor, data): + timeout = descriptor.gettimeout() + if timeout: + cancel = exc_after(timeout, TimeoutError) + else: + cancel = None try: - return descriptor.send(data) - except socket.error, e: - if e[0] == errno.EWOULDBLOCK: + try: + return descriptor.send(data) + except socket.error, e: + if e[0] == errno.EWOULDBLOCK: + return 0 + raise + except SSL.WantWriteError: return 0 - raise - except SSL.WantWriteError: - return 0 - except SSL.WantReadError: - return 0 + except 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: - return descriptor.recv(buflen) - except socket.error, e: - if e[0] == errno.EWOULDBLOCK: + 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 - if e[0] in SOCKET_CLOSED: + except SSL.ZeroReturnError: return '' - raise - except SSL.WantReadError: - return None - except SSL.ZeroReturnError: - return '' - except SSL.SysCallError, e: - if e[0] == -1 or e[0] > 0: - raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) - raise + except SSL.SysCallError, e: + if e[0] == -1 or e[0] > 0: + raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) + raise + finally: + if cancel: + cancel.cancel() def file_recv(fd, buflen): @@ -149,7 +167,7 @@ def file_send(fd, data): class GreenSocket(object): is_secure = False - + timeout = None def __init__(self, fd): self.fd = fd self._fileno = fd.fileno() @@ -254,9 +272,6 @@ class GreenSocket(object): fn = self.setblocking = self.fd.setblocking return fn(*args, **kw) - # TODO settimeout - # TODO gettimeout - def setsockopt(self, *args, **kw): fn = self.setsockopt = self.fd.setsockopt return fn(*args, **kw) @@ -264,7 +279,13 @@ class GreenSocket(object): def shutdown(self, *args, **kw): fn = self.shutdown = self.fd.shutdown return fn(*args, **kw) - + + def settimeout(self, howlong): + self.timeout = howlong + + def gettimeout(self): + return self.timeout + class GreenFile(object): newlines = '\r\n' From ad70bd6e426de2928345a2b74414e768a8e9fa3d Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 30 Apr 2008 18:53:19 -0700 Subject: [PATCH 051/118] Support ssl objects properly again. Support wsgi.server(max_http_version=) --- eventlet/greenio.py | 87 ++++++++++++++++++++++++++++----------------- eventlet/util.py | 2 +- eventlet/wsgi.py | 13 +++++-- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index 3472396..9257a55 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -98,9 +98,9 @@ def socket_send(descriptor, data): if e[0] == errno.EWOULDBLOCK: return 0 raise - except SSL.WantWriteError: + except util.SSL.WantWriteError: return 0 - except SSL.WantReadError: + except util.SSL.WantReadError: return 0 finally: if cancel: @@ -124,11 +124,11 @@ def socket_recv(descriptor, buflen): if e[0] in SOCKET_CLOSED: return '' raise - except SSL.WantReadError: + except util.SSL.WantReadError: return None - except SSL.ZeroReturnError: + except util.SSL.ZeroReturnError: return '' - except SSL.SysCallError, e: + except util.SSL.SysCallError, e: if e[0] == -1 or e[0] > 0: raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) raise @@ -286,7 +286,36 @@ class GreenSocket(object): 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+' @@ -368,33 +397,7 @@ class GreenFile(object): 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.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) + read = read class GreenPipeSocket(GreenSocket): @@ -420,3 +423,21 @@ class GreenPipe(GreenFile): 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() + diff --git a/eventlet/util.py b/eventlet/util.py index 237aa95..acbc126 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -88,7 +88,7 @@ def wrap_ssl(sock, certificate=None, private_key=None): ## TODO only do this on client sockets? how? connection = SSL.Connection(context, sock) connection.set_connect_state() - return greenio.GreenSocket(connection) + return greenio.GreenSSL(connection) socket_already_wrapped = False def wrap_socket_with_coroutine_socket(): diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 22c3f9c..34bfa65 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -37,6 +37,9 @@ from eventlet import api from eventlet.httpdate import format_date_time +DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' + + class Input(object): def __init__(self, rfile, content_length, wfile=None, wfile_line=None): self.rfile = rfile @@ -76,6 +79,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): format % args)) def handle_one_request(self): + if self.server.max_http_version: + self.protocol_version = self.server.max_http_version + try: self.raw_requestline = self.rfile.readline() except socket.error, e: @@ -263,7 +269,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): class Server(BaseHTTPServer.HTTPServer): - def __init__(self, socket, address, app, log, environ=None): + def __init__(self, socket, address, app, log, environ=None, max_http_version=None): self.socket = socket self.address = address if log: @@ -273,6 +279,7 @@ class Server(BaseHTTPServer.HTTPServer): self.log = sys.stderr self.app = app self.environ = environ + self.max_http_version = max_http_version def get_environ(self): socket = self.socket @@ -296,8 +303,8 @@ class Server(BaseHTTPServer.HTTPServer): self.log.write(message + '\n') -def server(sock, site, log=None, environ=None, max_size=None): - serv = Server(sock, sock.getsockname(), site, log, environ=None) +def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): + serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version) try: print "wsgi starting up on", sock.getsockname() while True: From 298815d6b96cb92ec00dab6f7d59836f7f7b7d3f Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 1 May 2008 09:55:54 -0700 Subject: [PATCH 052/118] Don't write two content-length headers --- eventlet/wsgi.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 34bfa65..9e6cc9f 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -127,18 +127,20 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): for k, v in response_headers: header_dict[k.lower()] = k 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? if 'date' not in header_dict: towrite.append('Date: %s\r\n' % (format_date_time(time.time()),)) if num_blocks is not None: - towrite.append('Content-Length: %s\r\n' % (len(''.join(result)),)) + if 'content-length' not in header_dict: + towrite.append('Content-Length: %s\r\n' % (len(''.join(result)),)) elif use_chunked: towrite.append('Transfer-Encoding: chunked\r\n') else: towrite.append('Connection: close\r\n') self.close_connection = 1 - for header in response_headers: - towrite.append('%s: %s\r\n' % header) towrite.append('\r\n') if use_chunked: From 001936a6f250b02bf25881d9fd48e294fc45b38a Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 8 May 2008 09:17:08 -0700 Subject: [PATCH 053/118] Update the nginx mod_wsgi support to the latest mod_wsgi semantics; there's a bug that's currently preventing it from working, though. --- eventlet/hubs/nginx.py | 147 ++++++++++++++++++++++++++--- eventlet/support/nginx_mod_wsgi.py | 67 ------------- 2 files changed, 133 insertions(+), 81 deletions(-) delete mode 100644 eventlet/support/nginx_mod_wsgi.py diff --git a/eventlet/hubs/nginx.py b/eventlet/hubs/nginx.py index b23dc04..b25ab39 100644 --- a/eventlet/hubs/nginx.py +++ b/eventlet/hubs/nginx.py @@ -23,31 +23,150 @@ 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 add_descriptor(self, fileno, read=None, write=None, exc=None): - super(Hub, self).add_descriptor(fileno, read, write, exc) + 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) - if read is not None: - self.poll_register(fileno, WSGI_POLLIN) - elif write is not None: - self.poll_register(fileno, WSGI_POLLOUT) - def remove_descriptor(self, fileno): super(Hub, self).remove_descriptor(fileno) - self.poll_unregister(fileno) - - def wait(self, seconds=None): - if seconds is not None: - self.sleep(int(seconds*1000)) + try: + self.poll_unregister(self._connection_wrappers[fileno]) + except RuntimeError: + pass - greenlib.switch(self.current_application) + 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) diff --git a/eventlet/support/nginx_mod_wsgi.py b/eventlet/support/nginx_mod_wsgi.py deleted file mode 100644 index 1035680..0000000 --- a/eventlet/support/nginx_mod_wsgi.py +++ /dev/null @@ -1,67 +0,0 @@ - -import sys -import traceback - -sys.path.insert(0, '/Users/donovan/Code/mulib-hg') -sys.stdout = sys.stderr - -from eventlet import api -from eventlet import httpc - -from eventlet.hubs import nginx - - -def old_real_application(env, start_response): - #result = httpc.get('http://127.0.0.1:8081/') - start_response('200 OK', [('Content-type', 'text/plain')]) - #sys.stderr.write("RESULT %r" % (result, )) - return 'hello' - - -def wrap_application(master, env, start_response): - real_application = api.named(env['eventlet_nginx_wsgi_app']) - result = real_application(env, start_response) - ## Should catch exception and return here? - #sys.stderr.write("RESULT2 %r" % (result, )) - master.switch((result, None)) - return None, None - - -class StartResponse(object): - def __init__(self, start_response): - self.start_response = start_response - - def __call__(self, *args): - self.args = args - - -def application(env, start_response): - hub = api.get_hub() - - if not isinstance(hub, nginx.Hub): - api.use_hub(nginx) - - hub.poll_register = env['ngx.poll_register'] - hub.poll_unregister = env['ngx.poll_unregister'] - hub.sleep = env['ngx.sleep'] - hub.current_application = api.getcurrent() - - slave = api.greenlet.greenlet(wrap_application) - response = StartResponse(start_response) - result = slave.switch( - hub.current_application, env, response) - - while True: - #sys.stderr.write("RESULT3 %r" % (result, )) - if result is None or result == (None, None): - yield '' - 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 = hub.switch() From 2a2f8b9deea7b5f8ecc54e123584e9610504d0c2 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 08:51:41 -0700 Subject: [PATCH 054/118] Add 'ready' and 'killall' from svn --- eventlet/coros.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/eventlet/coros.py b/eventlet/coros.py index 4552ad8..a55c717 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -124,6 +124,14 @@ class event(object): raise self._exc return self._result + def ready(self): + """ Return true if the wait() call will return immediately. + Used to avoid waiting for things that might take a while to time out. + For example, you can put a bunch of events into a list, and then visit + them all repeatedly, calling ready() until one returns True, and then + you can wait() on that one.""" + return self._result is not NOT_USED + def cancel(self, waiter): """Raise an exception into a coroutine which called wait() an this event instead of returning a value @@ -341,6 +349,10 @@ class CoroutinePool(pools.Pool): """ self._execute(None, func, args, kw) + def killall(self): + for g in self._greenlets: + api.kill(g) + class pipe(object): """ Implementation of pipe using events. Not tested! Not used, either.""" From 6d7d754c46e18536697bc8072355d0e7ecc30b76 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 08:52:14 -0700 Subject: [PATCH 055/118] Actually limit the number of simultaneous requests in eventlet.wsgi --- eventlet/wsgi.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 414adbe..616935b 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -35,6 +35,10 @@ import BaseHTTPServer from eventlet import api from eventlet.httpdate import format_date_time +from eventlet import pools + + +DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024 class Input(object): @@ -265,7 +269,6 @@ class Server(BaseHTTPServer.HTTPServer): self.address = address if log: self.log = log - log.write = log.info else: self.log = sys.stderr self.app = app @@ -295,11 +298,14 @@ class Server(BaseHTTPServer.HTTPServer): def server(sock, site, log=None, environ=None, max_size=None): serv = Server(sock, sock.getsockname(), site, log, environ=None) + if max_size is None: + max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS + pool = pools.CoroutinePool(max_size=max_size) try: print "wsgi starting up on", sock.getsockname() while True: try: - api.spawn(serv.process_request, sock.accept()) + pool.execute_async(lambda: serv.process_request(sock.accept())) except KeyboardInterrupt: api.get_hub().remove_descriptor(sock.fileno()) print "wsgi exiting" From 7e972abf98709ab931f4446e7b4668d9e588da3f Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 08:52:53 -0700 Subject: [PATCH 056/118] Clean up a little bit, rename erpc 'execute', but still keep a binding for erpc --- eventlet/tpool.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/eventlet/tpool.py b/eventlet/tpool.py index aa5e4ad..31eeb4a 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -17,22 +17,24 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os, socket, time, threading +import os, threading import Queue from sys import stdout from Queue import Empty, Queue -from eventlet import api, coros, httpc, httpd, util, greenio +from eventlet import api, coros, httpc, httpd, greenio from eventlet.api import trampoline, get_hub _rpipe, _wpipe = os.pipe() _rfile = os.fdopen(_rpipe,"r",0) -_wrap_rfile = greenio.GreenPipe(_rfile) -util.set_nonblocking(_rfile) +## Work whether or not wrap_pipe_with_coroutine_pipe was called +if not isinstance(_rfile, greenio.GreenPipe): + _rfile = greenio.GreenPipe(_rfile) + def _signal_t2e(): - nwritten = os.write(_wpipe,' ') + nwritten = greenio.__original_write__(_wpipe,' ') _reqq = Queue(maxsize=-1) _rspq = Queue(maxsize=-1) @@ -79,11 +81,18 @@ def erecv(e): raise e 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) rv = erecv(e) return rv +## TODO deprecate +erpc = execute + + class Proxy(object): """ 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. @@ -102,9 +111,9 @@ class Proxy(object): if kwargs.pop('nonblocking',False): rv = f(*args, **kwargs) else: - rv = erpc(f,*args,**kwargs) + rv = execute(f,*args,**kwargs) if type(rv) in self._autowrap: - return Proxy(rv) + return Proxy(rv, self._autowrap) else: return rv return doit From 03f8d9261819b5558bd092d758a2524edb267075 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 08:53:25 -0700 Subject: [PATCH 057/118] fix the backdoor --- eventlet/backdoor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eventlet/backdoor.py b/eventlet/backdoor.py index 8f792dd..50b5d7d 100644 --- a/eventlet/backdoor.py +++ b/eventlet/backdoor.py @@ -76,7 +76,9 @@ class SocketConsole(greenlib.GreenletContext): def backdoor((conn, addr), locals=None): host, port = addr print "backdoor to %s:%s" % (host, port) - ctx = SocketConsole(conn) + fl = conn.makefile("rw") + fl.newlines = '\n' + ctx = SocketConsole(fl) ctx.register() try: console = InteractiveConsole(locals) From 3db9d703f57ec978ab3cb26d738dc0e0177b3a1e Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 08:56:13 -0700 Subject: [PATCH 058/118] Add support for Process.wait; clean up the way set_nonblocking is called and move it out of util into greenio; Add wrap_pipes_with_coroutine_pipes (UNTESTED) --- eventlet/greenio.py | 39 +++++++++++++++++++------ eventlet/processes.py | 58 ++++++++++++++++++++++++++++++++---- eventlet/util.py | 68 +++++++++++++++++++++++++++++++------------ 3 files changed, 132 insertions(+), 33 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index d8a07d1..2817ce6 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -22,11 +22,15 @@ 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 +import errno +import os +import socket +import fcntl + + from errno import EWOULDBLOCK, EAGAIN @@ -89,7 +93,7 @@ def socket_send(descriptor, data): try: return descriptor.send(data) except socket.error, e: - if e[0] == errno.EWOULDBLOCK: + if e[0] == errno.EWOULDBLOCK or e[0] == errno.ENOTCONN: return 0 raise except SSL.WantWriteError: @@ -145,26 +149,38 @@ def file_send(fd, data): 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 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 - util.set_nonblocking(client) + set_nonblocking(client) return type(self)(client), addr trampoline(fd, read=True, write=True) @@ -197,7 +213,7 @@ class GreenSocket(object): def dup(self, *args, **kw): sock = self.fd.dup(*args, **kw) - util.set_nonblocking(sock) + set_nonblocking(sock) return type(self)(sock) def fileno(self, *args, **kw): @@ -264,16 +280,18 @@ class GreenSocket(object): def shutdown(self, *args, **kw): fn = self.shutdown = self.fd.shutdown return fn(*args, **kw) - + + class GreenFile(object): newlines = '\r\n' mode = 'wb+' def __init__(self, fd): + set_nonblocking(fd) self.sock = fd self.closed = False - + def close(self): self.sock.close() self.closed = True @@ -386,6 +404,7 @@ class GreenPipeSocket(GreenSocket): class GreenPipe(GreenFile): def __init__(self, fd): + set_nonblocking(fd) self.fd = GreenPipeSocket(fd) super(GreenPipe, self).__init__(self.fd) @@ -399,3 +418,5 @@ class GreenPipe(GreenFile): def flush(self): self.fd.fd.flush() + + diff --git a/eventlet/processes.py b/eventlet/processes.py index 35c7dd1..f00b2ae 100644 --- a/eventlet/processes.py +++ b/eventlet/processes.py @@ -21,19 +21,63 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +import errno import os import popen2 import signal +import sys - -from eventlet import util, pools +from eventlet import coros +from eventlet import pools from eventlet import greenio + class DeadProcess(RuntimeError): pass +CHILD_PIDS = [] + +CHILD_EVENTS = {} + + +def sig_child(signal, frame): + for child_pid in CHILD_PIDS: + try: + pid, code = os.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): process_number = 0 def __init__(self, command, args, dead_callback=lambda:None): @@ -51,10 +95,11 @@ class Process(object): ## We use popen4 so that read() will read from either stdout or stderr self.popen4 = popen2.Popen4([self.command] + self.args) + self.event = _add_child_pid(self.popen4.pid) child_stdout_stderr = self.popen4.fromchild child_stdin = self.popen4.tochild - util.set_nonblocking(child_stdout_stderr) - util.set_nonblocking(child_stdin) + greenio.set_nonblocking(child_stdout_stderr) + greenio.set_nonblocking(child_stdin) 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_stdin = greenio.GreenPipe(child_stdin) @@ -116,6 +161,9 @@ class Process(object): def getpid(self): return self.popen4.pid + def wait(self): + return self.event.wait() + class ProcessPool(pools.Pool): def __init__(self, command, args=None, min_size=0, max_size=4): diff --git a/eventlet/util.py b/eventlet/util.py index 237aa95..d9a63b1 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -24,7 +24,6 @@ THE SOFTWARE. """ import os -import fcntl import socket import errno @@ -67,7 +66,6 @@ __original_socket__ = socket.socket def tcp_socket(): s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) - set_nonblocking(s) return s @@ -90,6 +88,7 @@ def wrap_ssl(sock, certificate=None, private_key=None): connection.set_connect_state() return greenio.GreenSocket(connection) + socket_already_wrapped = False def wrap_socket_with_coroutine_socket(): global socket_already_wrapped @@ -98,9 +97,7 @@ def wrap_socket_with_coroutine_socket(): def new_socket(*args, **kw): from eventlet import greenio - s = __original_socket__(*args, **kw) - set_nonblocking(s) - return greenio.GreenSocket(s) + return greenio.GreenSocket(__original_socket__(*args, **kw)) socket.socket = new_socket socket.ssl = wrap_ssl @@ -108,6 +105,53 @@ def wrap_socket_with_coroutine_socket(): 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[pid] + 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 + + def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): set_reuse_addr(descriptor) descriptor.bind(addr) @@ -124,18 +168,4 @@ def set_reuse_addr(descriptor): ) except socket.error: 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 From 22b5143ee9060fb6f783ccbb777e6e84df92d3f6 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 10:27:10 -0700 Subject: [PATCH 059/118] Whooooops. --- eventlet/wsgi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 983dda6..0e2ba39 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -35,7 +35,7 @@ import BaseHTTPServer from eventlet import api from eventlet.httpdate import format_date_time -from eventlet import pools +from eventlet import coros DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024 @@ -313,7 +313,7 @@ def server(sock, site, log=None, environ=None, max_size=None, max_http_version=D serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version) if max_size is None: max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS - pool = pools.CoroutinePool(max_size=max_size) + pool = coros.CoroutinePool(max_size=max_size) try: print "wsgi starting up on", sock.getsockname() while True: From caf46df3519605942b94d3695b78f251dd3af852 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 12:09:06 -0700 Subject: [PATCH 060/118] Allow for overriding the protocol object --- eventlet/wsgi.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 0e2ba39..e3fd027 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -101,6 +101,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): return self.environ = self.get_environ() + self.application = self.server.app try: self.handle_one_response() except socket.error, e: @@ -168,7 +169,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): return write try: - result = self.server.app(self.environ, start_response) + result = self.application(self.environ, start_response) except Exception, e: exc = ''.join(traceback.format_exception(*sys.exc_info())) print exc @@ -275,7 +276,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): class Server(BaseHTTPServer.HTTPServer): - def __init__(self, socket, address, app, log, environ=None, max_http_version=None): + def __init__(self, socket, address, app, log=None, environ=None, max_http_version=None, protocol=HttpProtocol): self.socket = socket self.address = address if log: @@ -284,7 +285,8 @@ class Server(BaseHTTPServer.HTTPServer): self.log = sys.stderr self.app = app self.environ = environ - self.max_http_version = max_http_version + self.max_http_version = max_http_version + self.protocol = protocol def get_environ(self): socket = self.socket @@ -301,7 +303,7 @@ class Server(BaseHTTPServer.HTTPServer): return d def process_request(self, (socket, address)): - proto = HttpProtocol(socket, address, self) + proto = self.protocol(socket, address, self) proto.handle() def log_message(self, message): @@ -309,8 +311,8 @@ class Server(BaseHTTPServer.HTTPServer): -def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): - serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version) +def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION, protocol=HttpProtocol): + serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version, protocol=protocol) if max_size is None: max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS pool = coros.CoroutinePool(max_size=max_size) From 96dfd16585fe31d1ba8b7b6d70cf85b6fc13c55c Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 9 May 2008 13:23:52 -0700 Subject: [PATCH 061/118] Add optional connection argument --- eventlet/httpc.py | 76 ++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/eventlet/httpc.py b/eventlet/httpc.py index 63c6caf..5d15e5e 100644 --- a/eventlet/httpc.py +++ b/eventlet/httpc.py @@ -44,9 +44,7 @@ try: def from_http_time(t, defaultdate=None): return int(DateTime.Parser.DateTimeFromString( t, defaultdate=defaultdate).gmticks()) - except ImportError: - import calendar parse_formats = (HTTP_TIME_FORMAT, # RFC 1123 '%A, %d-%b-%y %H:%M:%S GMT', # RFC 850 @@ -453,7 +451,7 @@ class HttpSuite(object): self.loader = loader 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.''' params = _LocalParams(params, instance=self) @@ -482,7 +480,7 @@ class HttpSuite(object): else: 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 if self.loader is not None: @@ -501,8 +499,10 @@ class HttpSuite(object): klass = status_to_error_map.get(response.status, ConnectionError) raise klass(params) - def _get_response_body(self, params): - connection = connect(params.url, params.use_proxy) + def _get_response_body(self, params, connection): + print "CONENCTO", connection + if connection is None: + connection = connect(params.url, params.use_proxy) connection.request(params.method, params.path, params.body, params.headers) params.response = connection.getresponse() @@ -512,30 +512,40 @@ class HttpSuite(object): return params.response, params.response_body - def request(self, params): - return self.request_(params)[-1] + def request(self, params, connection=None): + return self.request_(params, connection=connection)[-1] - def head_(self, url, headers=None, use_proxy=False, ok=None, aux=None): - return self.request_(_Params(url, 'HEAD', headers=headers, - loader=self.loader, dumper=self.dumper, - use_proxy=use_proxy, ok=ok, aux=aux)) + def head_( + self, url, headers=None, use_proxy=False, + ok=None, aux=None, connection=None): + return self.request_( + _Params( + url, 'HEAD', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux), + connection) def head(self, *args, **kwargs): return self.head_(*args, **kwargs)[-1] - def get_(self, url, headers=None, use_proxy=False, ok=None, aux=None): + def get_( + self, url, headers=None, use_proxy=False, ok=None, + aux=None, connection=None): if headers is None: headers = {} headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0' - return self.request_(_Params(url, 'GET', headers=headers, - loader=self.loader, dumper=self.dumper, - use_proxy=use_proxy, ok=ok, aux=aux)) + return self.request_( + _Params( + url, 'GET', headers=headers, + loader=self.loader, dumper=self.dumper, + use_proxy=use_proxy, ok=ok, aux=aux), + connection) def get(self, *args, **kwargs): return self.get_(*args, **kwargs)[-1] def put_(self, url, data, headers=None, content_type=None, ok=None, - aux=None): + aux=None, connection=None): if headers is None: headers = {} if 'content-type' not in headers: @@ -544,22 +554,29 @@ class HttpSuite(object): else: headers['content-type'] = content_type headers['accept'] = headers['content-type']+';q=1,*/*;q=0' - return self.request_(_Params(url, 'PUT', body=data, headers=headers, - loader=self.loader, dumper=self.dumper, - ok=ok, aux=aux)) + return self.request_( + _Params( + url, 'PUT', body=data, headers=headers, + loader=self.loader, dumper=self.dumper, + ok=ok, aux=aux), + connection) def put(self, *args, **kwargs): return self.put_(*args, **kwargs)[-1] - def delete_(self, url, ok=None, aux=None): - return self.request_(_Params(url, 'DELETE', loader=self.loader, - dumper=self.dumper, ok=ok, aux=aux)) + def delete_(self, url, ok=None, aux=None, connection=None): + return self.request_( + _Params( + url, 'DELETE', loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux), + connection) def delete(self, *args, **kwargs): return self.delete_(*args, **kwargs)[-1] - def post_(self, url, data='', headers=None, content_type=None, ok=None, - aux=None): + def post_( + self, url, data='', headers=None, content_type=None,ok=None, + aux=None, connection=None): if headers is None: headers = {} if 'content-type' not in headers: @@ -568,9 +585,12 @@ class HttpSuite(object): else: headers['content-type'] = content_type headers['accept'] = headers['content-type']+';q=1,*/*;q=0' - return self.request_(_Params(url, 'POST', body=data, - headers=headers, loader=self.loader, - dumper=self.dumper, ok=ok, aux=aux)) + return self.request_( + _Params( + url, 'POST', body=data, + headers=headers, loader=self.loader, + dumper=self.dumper, ok=ok, aux=aux), + connection) def post(self, *args, **kwargs): return self.post_(*args, **kwargs)[-1] From 7910f8532f65960d7cc7de15bf17dd534a0df7a2 Mon Sep 17 00:00:00 2001 From: donovan Date: Sat, 17 May 2008 17:11:59 -0700 Subject: [PATCH 062/118] I broke the threadpool when I refactored it before --- eventlet/tpool.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 31eeb4a..6717148 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -34,7 +34,8 @@ if not isinstance(_rfile, greenio.GreenPipe): def _signal_t2e(): - nwritten = greenio.__original_write__(_wpipe,' ') + from eventlet import util + nwritten = util.__original_write__(_wpipe,' ') _reqq = Queue(maxsize=-1) _rspq = Queue(maxsize=-1) @@ -42,7 +43,7 @@ _rspq = Queue(maxsize=-1) def tpool_trampoline(): global _reqq, _rspq while(True): - _c = _wrap_rfile.recv(1) + _c = _rfile.recv(1) assert(_c != "") while not _rspq.empty(): try: @@ -71,6 +72,7 @@ def tworker(): _rspq.put((e,rv)) _signal_t2e() + def erecv(e): rv = e.wait() if isinstance(rv,tuple) and len(rv) == 4 and isinstance(rv[0],Exception): @@ -81,6 +83,7 @@ def erecv(e): raise e return rv + def execute(meth,*args, **kwargs): """Execute method in a thread, blocking the current coroutine until the method completes. From 0126774236028cae94b8199f4d5fa6026cdc2d43 Mon Sep 17 00:00:00 2001 From: donovan Date: Sat, 17 May 2008 17:13:01 -0700 Subject: [PATCH 063/118] Add a boolean to spew to turn off the local and global debugging in case it's causing an exception in repr --- eventlet/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index 12c8bf8..35fe6a4 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -357,8 +357,9 @@ GreenletExit = greenlet.GreenletExit class Spew(object): """ """ - def __init__(self, trace_names=None): + def __init__(self, trace_names=None, show_values=True): self.trace_names = trace_names + self.show_values = show_values def __call__(self, frame, event, arg): if event == 'line': @@ -380,6 +381,8 @@ class Spew(object): frame.f_code.co_name, frame.f_lasti) if self.trace_names is None or name in self.trace_names: print '%s:%s: %s' % (name, lineno, line.rstrip()) + if not self.show_values: + return self details = '\t' tokens = line.translate( string.maketrans(' ,.()', '\0' * 5)).split('\0') @@ -393,10 +396,10 @@ class Spew(object): return self -def spew(trace_names=None): +def spew(trace_names=None, show_values=False): """ """ - sys.settrace(Spew(trace_names)) + sys.settrace(Spew(trace_names, show_values)) def unspew(): From efd87d5036a8b4ab2d143b661af04c6a19dc0964 Mon Sep 17 00:00:00 2001 From: donovan Date: Sat, 17 May 2008 17:21:13 -0700 Subject: [PATCH 064/118] Use the threadpool to execute gethostbyname and getaddrinfo --- eventlet/util.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/eventlet/util.py b/eventlet/util.py index 323d3d6..d856866 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -63,6 +63,8 @@ def g_log(*args): __original_socket__ = socket.socket +__original_gethostbyname__ = socket.gethostbyname +__original_getaddrinfo__ = socket.getaddrinfo def tcp_socket(): s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) @@ -101,7 +103,18 @@ def wrap_socket_with_coroutine_socket(): socket.socket = new_socket socket.ssl = wrap_ssl - + + 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 + socket_already_wrapped = True From b263973043cf0f1deb819bc33b59b196a222ba5f Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 19 May 2008 14:23:13 -0700 Subject: [PATCH 065/118] Write an access log line in eventlet.wsgi. Just never got implemented. --- eventlet/wsgi.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index e3fd027..6dd6e42 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -24,6 +24,7 @@ THE SOFTWARE. """ import errno +import os import sys import time import traceback @@ -77,7 +78,8 @@ class Input(object): class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' 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.log_date_time_string(), format % args)) @@ -112,6 +114,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): raise def handle_one_response(self): + start = time.time() headers_set = [] headers_sent = [] # set of lowercase header names that were sent @@ -121,7 +124,9 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): num_blocks = None result = None use_chunked = False - + length = [0] + status_code = [200] + def write(data, _write=wfile.write): towrite = [] if not headers_set: @@ -153,9 +158,12 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): towrite.append("%x\r\n%s\r\n" % (len(data), data)) else: towrite.append(data) - _write(''.join(towrite)) + joined = ''.join(towrite) + length[0] = length[0] + len(joined) + _write(joined) def start_response(status, response_headers, exc_info=None): + status_code[0] = status if exc_info: try: if headers_sent: @@ -218,6 +226,10 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): 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): env = self.server.get_environ() @@ -286,7 +298,8 @@ class Server(BaseHTTPServer.HTTPServer): self.app = app self.environ = environ self.max_http_version = max_http_version - self.protocol = protocol + self.protocol = protocol + self.pid = os.getpid() def get_environ(self): socket = self.socket @@ -307,17 +320,17 @@ class Server(BaseHTTPServer.HTTPServer): proto.handle() def log_message(self, message): + print "log_message", message, self.log self.log.write(message + '\n') - def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION, protocol=HttpProtocol): serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version, protocol=protocol) if max_size is None: max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS pool = coros.CoroutinePool(max_size=max_size) try: - print "wsgi starting up on", sock.getsockname() + print os.getpid(), "wsgi starting up on", sock.getsockname() while True: try: pool.execute_async(lambda: serv.process_request(sock.accept())) From 726879d73e05dcdcdd25bcaaf95b4bcf65801c6c Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 19 May 2008 15:27:31 -0700 Subject: [PATCH 066/118] Actually abort the main loop on sigint. --- eventlet/hubs/libevent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 10327e1..46f8d3e 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -90,6 +90,7 @@ class Hub(hub.BaseHub): # 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 From ec139e201d70a470ae62611839f4d1bf538fbacc Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 20 May 2008 15:54:07 -0700 Subject: [PATCH 067/118] Put the pid at the beginning of this logline. --- eventlet/wsgi.py | 2 +- setup.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 6dd6e42..0f6992e 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -330,7 +330,7 @@ def server(sock, site, log=None, environ=None, max_size=None, max_http_version=D max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS pool = coros.CoroutinePool(max_size=max_size) try: - print os.getpid(), "wsgi starting up on", sock.getsockname() + print "(%s) wsgi starting up on %s" % (os.getpid(), sock.getsockname()) while True: try: pool.execute_async(lambda: serv.process_request(sock.accept())) diff --git a/setup.py b/setup.py index bfa3c06..70e16d2 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,8 @@ #!/usr/bin/env python -from setuptools import setup, find_packages + +from setuptools import Extension, find_packages, setup + setup( name='eventlet', @@ -26,3 +28,9 @@ setup( "Development Status :: 4 - Beta"] ) + + +setup(name="sendmsg", + version="1.0", + ext_modules=[Extension("sendmsg", ["src/sendmsg.c"])]) + From 70e4dd89d6f8a943fa617374d57590de8bc4b630 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 09:51:03 -0700 Subject: [PATCH 068/118] Some tweaks I made while importing back into svn --- eventlet/api.py | 2 +- eventlet/httpc.py | 1 - eventlet/runloop.py | 227 --------------------------------------- eventlet/runloop_test.py | 157 --------------------------- setup.py | 9 +- 5 files changed, 2 insertions(+), 394 deletions(-) delete mode 100644 eventlet/runloop.py delete mode 100644 eventlet/runloop_test.py diff --git a/eventlet/api.py b/eventlet/api.py index 35fe6a4..0e9f98e 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -299,7 +299,7 @@ def get_default_hub(): pass import select - if 0:#hasattr(select, 'poll'): + if hasattr(select, 'poll'): import eventlet.hubs.poll return eventlet.hubs.poll else: diff --git a/eventlet/httpc.py b/eventlet/httpc.py index 5d15e5e..34497b4 100644 --- a/eventlet/httpc.py +++ b/eventlet/httpc.py @@ -500,7 +500,6 @@ class HttpSuite(object): raise klass(params) def _get_response_body(self, params, connection): - print "CONENCTO", connection if connection is None: connection = connect(params.url, params.use_proxy) connection.request(params.method, params.path, params.body, diff --git a/eventlet/runloop.py b/eventlet/runloop.py deleted file mode 100644 index 24d7d81..0000000 --- a/eventlet/runloop.py +++ /dev/null @@ -1,227 +0,0 @@ -"""\ -@file runloop.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. -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 time -import bisect -import sys -import traceback - -import greenlet - -from eventlet.timer import Timer - - -class RunLoop(object): - SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) - - def __init__(self, wait=None, clock=None): - if clock is None: - clock = self.default_clock() - self.clock = clock - if wait is None: - wait = self.default_wait - self.wait = wait - self.stopping = False - self.running = False - self.timers = [] - self.timers_by_greenlet = {} - self.next_timers = [] - self.observers = {} - self.observer_modes = { - 'entry': [], - 'before_timers': [], - 'before_waiting': [], - 'after_waiting': [], - 'exit': [], - } - - def default_wait(self, time): - return None - - def default_clock(self): - return time.time - - def default_sleep(self): - return 60.0 - - def sleep_until(self): - t = self.timers - if not t: - return None - return t[0][0] - - def run(self): - """Run the runloop until abort is called. - """ - if self.running: - raise RuntimeError("Already running!") - try: - self.running = True - self.stopping = False - self.fire_observers('entry') - while not self.stopping: - self.prepare_timers() - self.fire_observers('before_timers') - self.fire_timers(self.clock()) - self.prepare_timers() - wakeup_when = self.sleep_until() - if wakeup_when is None: - sleep_time = self.default_sleep() - else: - sleep_time = wakeup_when - self.clock() - if sleep_time > 0: - self.fire_observers('before_waiting') - self.wait(sleep_time) - self.fire_observers('after_waiting') - else: - self.wait(0) - else: - del self.timers[:] - del self.next_timers[:] - self.fire_observers('exit') - finally: - self.running = False - self.stopping = False - - def abort(self): - """Stop the runloop. If run is executing, it will exit after completing - the next runloop iteration. - """ - if self.running: - self.stopping = True - - def add_observer(self, observer, *modes): - """Add an event observer to this runloop with the given modes. - Valid modes are: - entry: The runloop is being entered. - before_timers: Before the expired timers for this iteration are executed. - before_waiting: Before waiting for the calculated wait_time - where nothing will happen. - after_waiting: After waiting, immediately before starting the top of the - runloop again. - exit: The runloop is exiting. - - If no mode is passed or mode is all, the observer will be fired for every - event type. - """ - if not modes or modes == ('all',): - modes = tuple(self.observer_modes) - self.observers[observer] = modes - for mode in modes: - self.observer_modes[mode].append(observer) - - def remove_observer(self, observer): - """Remove a previously registered observer from all event types. - """ - for mode in self.observers.pop(observer, ()): - self.observer_modes[mode].remove(observer) - - def squelch_observer_exception(self, observer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Removing observer: %r" % (observer,) - self.remove_observer(observer) - - def fire_observers(self, activity): - for observer in self.observer_modes[activity]: - try: - observer(self, activity) - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_observer_exception(observer, sys.exc_info()) - - def squelch_timer_exception(self, timer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Timer raised: %r" % (timer,) - - def _add_absolute_timer(self, when, info): - # the 0 placeholder makes it easy to bisect_right using (now, 1) - self.next_timers.append((when, 0, info)) - - def add_timer(self, timer): - scheduled_time = self.clock() + timer.seconds - self._add_absolute_timer(scheduled_time, timer) - current_greenlet = greenlet.getcurrent() - 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 - - def prepare_timers(self): - ins = bisect.insort_right - t = self.timers - for item in self.next_timers: - ins(t, item) - del self.next_timers[:] - - def schedule_call(self, seconds, cb, *args, **kw): - """Schedule a callable to be called after 'seconds' seconds have - elapsed. - seconds: The number of seconds to wait. - cb: The callable to call after the given time. - *args: Arguments to pass to the callable when called. - **kw: Keyword arguments to pass to the callable when called. - """ - t = Timer(seconds, cb, *args, **kw) - self.add_timer(t) - return t - - def fire_timers(self, when): - t = self.timers - last = bisect.bisect_right(t, (when, 1)) - i = 0 - for i in xrange(last): - timer = t[i][2] - try: - try: - timer() - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_timer_exception(timer, sys.exc_info()) - finally: - try: - del self.timers_by_greenlet[timer.greenlet][timer] - except KeyError: - pass - del t[:last] - - def cancel_timers(self, greenlet): - if greenlet not in self.timers_by_greenlet: - return - for timer in self.timers_by_greenlet[greenlet]: - if not timer.cancelled and timer.seconds: - ## If timer.seconds is 0, this isn't a timer, it's - ## actually eventlet's silly way of specifying whether - ## a coroutine is "ready to run" or not. - timer.cancel() - print 'Runloop cancelling left-over timer %s' % timer - del self.timers_by_greenlet[greenlet] - diff --git a/eventlet/runloop_test.py b/eventlet/runloop_test.py deleted file mode 100644 index 4827c32..0000000 --- a/eventlet/runloop_test.py +++ /dev/null @@ -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() - diff --git a/setup.py b/setup.py index 70e16d2..fdd7d7c 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -from setuptools import Extension, find_packages, setup +from setuptools import find_packages, setup setup( @@ -27,10 +27,3 @@ setup( "Intended Audience :: Developers", "Development Status :: 4 - Beta"] ) - - - -setup(name="sendmsg", - version="1.0", - ext_modules=[Extension("sendmsg", ["src/sendmsg.c"])]) - From 6001fc197c00d565ef152bd3f1d26b1f33ff1953 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 10:03:56 -0700 Subject: [PATCH 069/118] Add stackless pypy support --- eventlet/api.py | 12 ++++++-- eventlet/support/stacklesspypys.py | 44 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 eventlet/support/stacklesspypys.py diff --git a/eventlet/api.py b/eventlet/api.py index 0e9f98e..8882399 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -39,11 +39,17 @@ except ImportError: greenlet = sys.modules['greenlet'] except ImportError: try: - import support.stackless - support.stackless.emulate() + import support.stacklesspypys + support.stacklesspypys.emulate() greenlet = sys.modules['greenlet'] except ImportError: - raise ImportError("Unable to find an implementation of greenlet.") + try: + import support.stackless + support.stackless.emulate() + greenlet = sys.modules['greenlet'] + except ImportError: + raise ImportError("Unable to find an implementation of greenlet.") + from eventlet import greenlib, tls diff --git a/eventlet/support/stacklesspypys.py b/eventlet/support/stacklesspypys.py new file mode 100644 index 0000000..9403d21 --- /dev/null +++ b/eventlet/support/stacklesspypys.py @@ -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 + + + + + + From ec60a5123d5ba6fa19720da9e86508e20d63b23d Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 10:15:21 -0700 Subject: [PATCH 070/118] Add missing import --- eventlet/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eventlet/util.py b/eventlet/util.py index d856866..35dbf78 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -24,6 +24,7 @@ THE SOFTWARE. """ import os +import select import socket import errno From ec0bbda709eee29c6edcf847390a700520a21dbf Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 10:19:50 -0700 Subject: [PATCH 071/118] Fix this test --- eventlet/processes_test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eventlet/processes_test.py b/eventlet/processes_test.py index 7bf9b97..c99526c 100644 --- a/eventlet/processes_test.py +++ b/eventlet/processes_test.py @@ -100,8 +100,12 @@ class TestDyingProcessesLeavePool(tests.TestCase): def test_dead_process_not_inserted_into_pool(self): proc = self.pool.get() try: - result = proc.read() - self.assertEquals(result, 'hello\n') + try: + result = proc.read() + self.assertEquals(result, 'hello\n') + result = proc.read() + except processes.DeadProcess: + pass finally: self.pool.put(proc) proc2 = self.pool.get() From 1a033df99cb8eede992d9d63edb1d62db30f664a Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 10:59:28 -0700 Subject: [PATCH 072/118] Ok, maybe the if False was there for a reason, pollhub seems broken at the moment --- eventlet/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/api.py b/eventlet/api.py index 8882399..d446cdd 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -305,7 +305,7 @@ def get_default_hub(): pass import select - if hasattr(select, 'poll'): + if False: #hasattr(select, 'poll'): pollhub is broken at the moment import eventlet.hubs.poll return eventlet.hubs.poll else: From 3991daef04adf8940e9ddcfe8bc63cd7fc20c6f6 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 21 May 2008 14:54:50 -0700 Subject: [PATCH 073/118] remove print --- eventlet/wsgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 0f6992e..ae47767 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -320,7 +320,6 @@ class Server(BaseHTTPServer.HTTPServer): proto.handle() def log_message(self, message): - print "log_message", message, self.log self.log.write(message + '\n') From a9f9486f896d2771c3356eea406f921030e7bcef Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 22 May 2008 07:35:09 -0700 Subject: [PATCH 074/118] merge up to svn branch r120 --- eventlet/coros.py | 35 ++++++++++++--------- eventlet/coros_test.py | 29 +++++++++++++++++ eventlet/httpc.py | 71 ++++++++++++++++++++++++++++++++++++++++-- eventlet/saranwrap.py | 6 +++- eventlet/tls.py | 13 +++++++- eventlet/util.py | 36 +++++++++++++++++++++ examples/echoserver.py | 4 +-- setup.py | 1 + 8 files changed, 174 insertions(+), 21 deletions(-) diff --git a/eventlet/coros.py b/eventlet/coros.py index 4276e09..89e69f6 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -95,6 +95,14 @@ class event(object): self.epoch = time.time() self._result = NOT_USED self._waiters = {} + + def ready(self): + """ Return true if the wait() call will return immediately. + Used to avoid waiting for things that might take a while to time out. + For example, you can put a bunch of events into a list, and then visit + them all repeatedly, calling ready() until one returns True, and then + you can wait() on that one.""" + return self._result is not NOT_USED def wait(self): """Wait until another coroutine calls send. @@ -124,14 +132,6 @@ class event(object): raise self._exc return self._result - def ready(self): - """ Return true if the wait() call will return immediately. - Used to avoid waiting for things that might take a while to time out. - For example, you can put a bunch of events into a list, and then visit - them all repeatedly, calling ready() until one returns True, and then - you can wait() on that one.""" - return self._result is not NOT_USED - def cancel(self, waiter): """Raise an exception into a coroutine which called wait() an this event instead of returning a value @@ -266,13 +266,18 @@ class CoroutinePool(pools.Pool): def _main_loop(self, sender): """ Private, infinite loop run by a pooled coroutine. """ - while True: - recvd = sender.wait() - sender = event() - (evt, func, args, kw) = recvd - self._safe_apply(evt, func, args, kw) - api.get_hub().cancel_timers(api.getcurrent()) - self.put(sender) + try: + while True: + recvd = sender.wait() + sender = event() + (evt, func, args, kw) = recvd + self._safe_apply(evt, func, args, kw) + api.get_hub().cancel_timers(api.getcurrent()) + self.put(sender) + finally: + # if we get here, something broke badly, and all we can really + # do is try to keep the pool from leaking items + self.put(self.create()) def _safe_apply(self, evt, func, args, kw): """ Private method that runs the function, catches exceptions, and diff --git a/eventlet/coros_test.py b/eventlet/coros_test.py index a72bc7a..ae83724 100644 --- a/eventlet/coros_test.py +++ b/eventlet/coros_test.py @@ -25,6 +25,8 @@ from eventlet import tests from eventlet import timer from eventlet import coros, api +import sys + class TestEvent(tests.TestCase): mode = 'static' def setUp(self): @@ -179,6 +181,33 @@ class TestCoroutinePool(tests.TestCase): pool.execute_async(reenter_async) evt.wait() + def test_horrible_main_loop_death(self): + # testing the case that causes the run_forever + # method to exit unwantedly + pool = coros.CoroutinePool(min_size=1, max_size=1) + def crash(*args, **kw): + raise RuntimeError("Whoa") + class FakeFile(object): + write = crash + + # we're going to do this by causing the traceback.print_exc in + # safe_apply to raise an exception and thus exit _main_loop + normal_err = sys.stderr + try: + sys.stderr = FakeFile() + waiter = pool.execute(crash) + self.assertRaises(RuntimeError, waiter.wait) + # the pool should have something free at this point since the + # waiter returned + self.assertEqual(pool.free(), 1) + # shouldn't block when trying to get + t = api.exc_after(0.1, api.TimeoutError) + self.assert_(pool.get()) + t.cancel() + finally: + sys.stderr = normal_err + + class IncrActor(coros.Actor): def received(self, evt): diff --git a/eventlet/httpc.py b/eventlet/httpc.py index 34497b4..23d94dd 100644 --- a/eventlet/httpc.py +++ b/eventlet/httpc.py @@ -23,6 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import copy import datetime import httplib import os.path @@ -218,9 +219,20 @@ class _LocalParams(_Params): setattr(self, k, v) def __getattr__(self, key): + if key == '__setstate__': return return getattr(self._delegate, key) - + def __reduce__(self): + params = copy.copy(self._delegate) + kwargs = copy.copy(self.__dict__) + assert(kwargs.has_key('_delegate')) + del kwargs['_delegate'] + if hasattr(params,'aux'): del params.aux + return (_LocalParams,(params,),kwargs) + + def __setitem__(self, k, item): + setattr(self, k, item) + class ConnectionError(Exception): """Detailed exception class for reporting on http connection problems. @@ -321,6 +333,14 @@ class TemporaryRedirect(Retriable): class BadRequest(ConnectionError): """ 400 Bad Request """ pass + +class Unauthorized(ConnectionError): + """ 401 Unauthorized """ + pass + +class PaymentRequired(ConnectionError): + """ 402 Payment Required """ + pass class Forbidden(ConnectionError): @@ -332,11 +352,42 @@ class NotFound(ConnectionError): """ 404 Not Found """ pass +class RequestTimeout(ConnectionError): + """ 408 RequestTimeout """ + pass + class Gone(ConnectionError): """ 410 Gone """ pass +class LengthRequired(ConnectionError): + """ 411 Length Required """ + pass + +class RequestEntityTooLarge(ConnectionError): + """ 413 Request Entity Too Large """ + pass + +class RequestURITooLong(ConnectionError): + """ 414 Request-URI Too Long """ + pass + +class UnsupportedMediaType(ConnectionError): + """ 415 Unsupported Media Type """ + pass + +class RequestedRangeNotSatisfiable(ConnectionError): + """ 416 Requested Range Not Satisfiable """ + pass + +class ExpectationFailed(ConnectionError): + """ 417 Expectation Failed """ + pass + +class NotImplemented(ConnectionError): + """ 501 Not Implemented """ + pass class ServiceUnavailable(Retriable): """ 503 Service Unavailable """ @@ -349,6 +400,9 @@ class GatewayTimeout(Retriable): def url(self): return self.params._delegate.url +class HTTPVersionNotSupported(ConnectionError): + """ 505 HTTP Version Not Supported """ + pass class InternalServerError(ConnectionError): """ 500 Internal Server Error """ @@ -362,7 +416,9 @@ class InternalServerError(ConnectionError): traceback = llsd.parse(self.params.response_body) except: traceback = self.params.response_body - if isinstance(traceback, dict): + if(isinstance(traceback, dict) + and 'stack-trace' in traceback + and 'description' in traceback): body = traceback traceback = "Traceback (most recent call last):\n" for frame in body['stack-trace']: @@ -387,12 +443,23 @@ status_to_error_map = { 304: NotModified, 307: TemporaryRedirect, 400: BadRequest, + 401: Unauthorized, + 402: PaymentRequired, 403: Forbidden, 404: NotFound, + 408: RequestTimeout, 410: Gone, + 411: LengthRequired, + 413: RequestEntityTooLarge, + 414: RequestURITooLong, + 415: UnsupportedMediaType, + 416: RequestedRangeNotSatisfiable, + 417: ExpectationFailed, 500: InternalServerError, + 501: NotImplemented, 503: ServiceUnavailable, 504: GatewayTimeout, + 505: HTTPVersionNotSupported, } scheme_to_factory_map = { diff --git a/eventlet/saranwrap.py b/eventlet/saranwrap.py index 43e5fe3..cf52112 100644 --- a/eventlet/saranwrap.py +++ b/eventlet/saranwrap.py @@ -670,8 +670,12 @@ def main(): # *HACK: some modules may emit on stderr, which breaks everything. class NullSTDOut(object): - def write(a, b): + def noop(*args): pass + write = noop + read = noop + flush = noop + sys.stderr = NullSTDOut() sys.stdout = NullSTDOut() diff --git a/eventlet/tls.py b/eventlet/tls.py index 3cb3921..0016fbd 100644 --- a/eventlet/tls.py +++ b/eventlet/tls.py @@ -22,7 +22,18 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 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 __all__ = ['local'] diff --git a/eventlet/util.py b/eventlet/util.py index 35dbf78..31bdb46 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -165,6 +165,42 @@ def wrap_pipes_with_coroutine_pipes(): os.fork = new_fork os.waitpid = new_waitpid +__original_select__ = select.select + + +def fake_select(r, w, e, timeout): + """This is to cooperate with people who are trying to do blocking + reads with a timeout. This only works if r, w, and e aren't + bigger than len 1, and if either r or w is populated. + + Install this with wrap_select_with_coroutine_select, + which makes the global select.select into fake_select. + """ + from eventlet import api + + assert len(r) <= 1 + assert len(w) <= 1 + assert len(e) <= 1 + + if w and r: + raise RuntimeError('fake_select doesn\'t know how to do that yet') + + try: + if r: + api.trampoline(r[0], read=True, timeout=timeout) + return r, [], [] + else: + api.trampoline(w[0], write=True, timeout=timeout) + return [], w, [] + except api.TimeoutError, e: + return [], [], [] + except: + return [], [], e + + +def wrap_select_with_coroutine_select(): + select.select = fake_select + def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): set_reuse_addr(descriptor) diff --git a/examples/echoserver.py b/examples/echoserver.py index 3fe0191..8c1813a 100644 --- a/examples/echoserver.py +++ b/examples/echoserver.py @@ -32,7 +32,7 @@ THE SOFTWARE. from eventlet import api -def handle_socket(client): +def handle_socket(reader, writer): print "client connected" while True: # pass through every non-eof line @@ -47,6 +47,6 @@ server = api.tcp_listener(('0.0.0.0', 6000)) while True: new_sock, address = server.accept() # 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() diff --git a/setup.py b/setup.py index fdd7d7c..25f340d 100644 --- a/setup.py +++ b/setup.py @@ -27,3 +27,4 @@ setup( "Intended Audience :: Developers", "Development Status :: 4 - Beta"] ) + From 6644bc0a78619865e29f1c9ad7d4a9366cb065b5 Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 29 May 2008 16:13:50 -0700 Subject: [PATCH 075/118] World's stupidest bug. --- eventlet/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eventlet/util.py b/eventlet/util.py index 31bdb46..d6a27e9 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -145,10 +145,10 @@ def wrap_pipes_with_coroutine_pipes(): api.trampoline(fd, write=True) return __original_write__(fd, *args, **kw) def new_fork(*args, **kwargs): - pid = __original_fork__ + pid = __original_fork__() if pid: processes._add_child_pid(pid) - return pid + return pid def new_waitpid(pid, options): from eventlet import processes evt = processes.CHILD_EVENTS[pid] From 75853995ebe93899d4f32db003b105741fd3ef3b Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 29 May 2008 16:14:37 -0700 Subject: [PATCH 076/118] Don't print a message saying we cancelled a timer that we didn't really, if it just fired normally. --- eventlet/hubs/hub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index c86aefa..f9637bf 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -278,7 +278,7 @@ class BaseHub(object): if greenlet not in self.timers_by_greenlet: return for timer in self.timers_by_greenlet[greenlet]: - 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 ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. From 65c09922e6bff006670bf73dc1e5f52fd7746f76 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 30 May 2008 10:56:32 -0700 Subject: [PATCH 077/118] Add the concept of 'tracked_events' to CoroutinePool and CoroutinePool.wait, the method you use to drain the events --- eventlet/coros.py | 30 +++++++++++++++++++++++++++++- eventlet/coros_test.py | 16 +++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/eventlet/coros.py b/eventlet/coros.py index 89e69f6..04aad61 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -260,8 +260,13 @@ class CoroutinePool(pools.Pool): 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() + if track_events: + self._tracked_events = [] + self._next_event = None + else: + self._tracked_events = None super(CoroutinePool, self).__init__(min_size, max_size) def _main_loop(self, sender): @@ -286,6 +291,13 @@ class CoroutinePool(pools.Pool): result = func(*args, **kw) if evt is not None: 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: # we're printing this out to see if it ever happens # in practice @@ -354,6 +366,22 @@ class CoroutinePool(pools.Pool): """ 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 None: + result = self._tracked_events.pop(0) + if not self._tracked_events: + self._next_event = event() + return result + return self._next_event.wait() + def killall(self): for g in self._greenlets: api.kill(g) diff --git a/eventlet/coros_test.py b/eventlet/coros_test.py index ae83724..fa08761 100644 --- a/eventlet/coros_test.py +++ b/eventlet/coros_test.py @@ -110,6 +110,7 @@ class TestEvent(tests.TestCase): api.exc_after(0.001, api.TimeoutError) self.assertRaises(api.TimeoutError, evt.wait) + class TestCoroutinePool(tests.TestCase): mode = 'static' def setUp(self): @@ -206,14 +207,23 @@ class TestCoroutinePool(tests.TestCase): t.cancel() finally: 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) + t = api.exc_after(10, RuntimeError) + for y in range(6): + pool.wait() + t.cancel() + class IncrActor(coros.Actor): def received(self, evt): self.value = getattr(self, 'value', 0) + 1 if evt: evt.send() + class TestActor(tests.TestCase): mode = 'static' def setUp(self): @@ -277,7 +287,6 @@ class TestActor(tests.TestCase): for evt in waiters: evt.wait() self.assertEqual(msgs, [1,2,3,4,5]) - def test_raising_received(self): msgs = [] @@ -325,5 +334,6 @@ class TestActor(tests.TestCase): self.assertEqual(total[0], 3) self.assertEqual(self.actor._pool.free(), 2) + if __name__ == '__main__': tests.main() From e3c6528772ea5c582ddf1e2fa237a76ea538c0d4 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 30 May 2008 13:14:19 -0700 Subject: [PATCH 078/118] Fix a bug --- eventlet/coros.py | 18 ++++++++++++------ eventlet/coros_test.py | 10 ++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/eventlet/coros.py b/eventlet/coros.py index 04aad61..245c658 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -295,6 +295,7 @@ class CoroutinePool(pools.Pool): if self._next_event is None: self._tracked_events.append(result) else: + ne = self._next_event self._next_event = None ne.send(result) @@ -375,12 +376,17 @@ class CoroutinePool(pools.Pool): """ assert self._tracked_events is not None, ( "Must pass track_events=True to the constructor to use CoroutinePool.wait()") - if self._next_event is None: - result = self._tracked_events.pop(0) - if not self._tracked_events: - self._next_event = event() - return result - return self._next_event.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 not self._tracked_events: + self._next_event = event() + return result def killall(self): for g in self._greenlets: diff --git a/eventlet/coros_test.py b/eventlet/coros_test.py index fa08761..0055580 100644 --- a/eventlet/coros_test.py +++ b/eventlet/coros_test.py @@ -212,10 +212,16 @@ class TestCoroutinePool(tests.TestCase): pool = coros.CoroutinePool(track_events=True) for x in range(6): pool.execute(lambda n: n, x) - t = api.exc_after(10, RuntimeError) for y in range(6): pool.wait() - t.cancel() + + 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): From 5d2a4b54c4636e95b26047a326999545fafd88a2 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 3 Jun 2008 11:32:22 -0700 Subject: [PATCH 079/118] Send exceptions across CoroutinePool.wait --- eventlet/coros.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/eventlet/coros.py b/eventlet/coros.py index 245c658..0d9e8bb 100644 --- a/eventlet/coros.py +++ b/eventlet/coros.py @@ -44,6 +44,11 @@ class Cancelled(RuntimeError): pass +class ExceptionWrapper(object): + def __init__(self, e): + self.e = e + + NOT_USED = object() @@ -283,7 +288,7 @@ class CoroutinePool(pools.Pool): # if we get here, something broke badly, and all we can really # do is try to keep the pool from leaking items self.put(self.create()) - + def _safe_apply(self, evt, func, args, kw): """ Private method that runs the function, catches exceptions, and passes back the return value in the event.""" @@ -311,7 +316,14 @@ class CoroutinePool(pools.Pool): traceback.print_exc() if evt is not None: 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): """ Private implementation of the execute methods. """ @@ -384,6 +396,9 @@ class CoroutinePool(pools.Pool): 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 From 443cacf3454b307b7d16b9907e0fac8a93c24a16 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 3 Jun 2008 11:34:09 -0700 Subject: [PATCH 080/118] Wrap socket.fromfd and write a monkeypatch for paste's threadlocal storage which makes it into coroutine local storage --- eventlet/httpc.py | 2 +- eventlet/util.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/eventlet/httpc.py b/eventlet/httpc.py index 23d94dd..b5cbf3a 100644 --- a/eventlet/httpc.py +++ b/eventlet/httpc.py @@ -274,7 +274,7 @@ class UnparseableResponse(ConnectionError): Exception.__init__(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) __str__ = __repr__ diff --git a/eventlet/util.py b/eventlet/util.py index d6a27e9..a3036ef 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -66,6 +66,7 @@ def g_log(*args): __original_socket__ = socket.socket __original_gethostbyname__ = socket.gethostbyname __original_getaddrinfo__ = socket.getaddrinfo +__original_fromfd__ = socket.fromfd def tcp_socket(): s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) @@ -116,6 +117,12 @@ def wrap_socket_with_coroutine_socket(): __original_getaddrinfo__, *args, **kw) socket.getaddrinfo = new_getaddrinfo + def new_fromfd(*args, **kw): + print "fromfd", args, kw + from eventlet import greenio + return greenio.GreenSocket(__original_fromfd__(*args, **kw)) + socket.fromfd = new_fromfd + socket_already_wrapped = True @@ -202,6 +209,47 @@ def wrap_select_with_coroutine_select(): select.select = fake_select +def wrap_paste_tls_with_corols(): + """Paste uses threadlocals, in a module called paste.util.threadinglocal. + If you are running Paste under an eventlet web server that uses greenlets + instead of threads, you can use wrap_paste_tls_with_corols to replace + the paste.util.threadinglocal.local class with one that uses the current + greenlet id as the 'thread' id instead of the current thread. + """ + from eventlet import api + from paste.util import threadinglocal + + 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())) + + threadinglocal.local = local + + def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): set_reuse_addr(descriptor) descriptor.bind(addr) From 1637ce726c16fd5c3e451e8950b527f888d29b82 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 3 Jun 2008 12:40:13 -0700 Subject: [PATCH 081/118] Use a more generic threading local monkey patch --- eventlet/util.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/eventlet/util.py b/eventlet/util.py index a3036ef..6547c8d 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -26,6 +26,7 @@ THE SOFTWARE. import os import select import socket +import sys import errno try: @@ -118,7 +119,6 @@ def wrap_socket_with_coroutine_socket(): socket.getaddrinfo = new_getaddrinfo def new_fromfd(*args, **kw): - print "fromfd", args, kw from eventlet import greenio return greenio.GreenSocket(__original_fromfd__(*args, **kw)) socket.fromfd = new_fromfd @@ -209,36 +209,37 @@ def wrap_select_with_coroutine_select(): select.select = fake_select -def wrap_paste_tls_with_corols(): - """Paste uses threadlocals, in a module called paste.util.threadinglocal. - If you are running Paste under an eventlet web server that uses greenlets - instead of threads, you can use wrap_paste_tls_with_corols to replace - the paste.util.threadinglocal.local class with one that uses the current - greenlet id as the 'thread' id instead of the current thread. +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 - from paste.util import threadinglocal - 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] @@ -247,7 +248,7 @@ def wrap_paste_tls_with_corols(): "No variable %s defined for thread %s" % (attr, g())) - threadinglocal.local = local + threading.local = local def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): From 1cfaf3a0a1350b674c91795900dbd0efc6349ebb Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:06:56 -0700 Subject: [PATCH 082/118] Use the correct write function in tpool; use a dict instead of a list for speed of testing membership; fix a bug in where accept is called in wsgi; keep track of outstanding requests in wsgi; add server_event to wsgi.server so other coroutines can get the Server instance --- eventlet/tpool.py | 4 +++- eventlet/wsgi.py | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 6717148..574ee5c 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -35,7 +35,7 @@ if not isinstance(_rfile, greenio.GreenPipe): def _signal_t2e(): from eventlet import util - nwritten = util.__original_write__(_wpipe,' ') + nwritten = util.__original_write__(_wpipe, ' ') _reqq = Queue(maxsize=-1) _rspq = Queue(maxsize=-1) @@ -104,6 +104,8 @@ class Proxy(object): code only. """ def __init__(self, obj,autowrap=()): self._obj = obj + if isinstance(autowrap, (list, tuple)): + autowrap = dict([(x, True) for x in autowrap]) self._autowrap = autowrap def __getattr__(self,attr_name): diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index ae47767..3b0110a 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -105,13 +105,17 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): self.environ = self.get_environ() self.application = self.server.app try: - self.handle_one_response() - except socket.error, e: - # Broken pipe, connection reset by peer - if e[0] in (32, 54): - pass - else: - raise + self.server.outstanding_requests += 1 + try: + self.handle_one_response() + except socket.error, e: + # Broken pipe, connection reset by peer + if e[0] in (32, 54): + pass + else: + raise + finally: + self.server.outstanding_requests -= 1 def handle_one_response(self): start = time.time() @@ -289,6 +293,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): class Server(BaseHTTPServer.HTTPServer): def __init__(self, socket, address, app, log=None, environ=None, max_http_version=None, protocol=HttpProtocol): + self.outstanding_requests = 0 self.socket = socket self.address = address if log: @@ -323,8 +328,10 @@ class Server(BaseHTTPServer.HTTPServer): self.log.write(message + '\n') -def server(sock, site, log=None, environ=None, max_size=None, max_http_version=DEFAULT_MAX_HTTP_VERSION, protocol=HttpProtocol): +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(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) @@ -332,7 +339,8 @@ def server(sock, site, log=None, environ=None, max_size=None, max_http_version=D print "(%s) wsgi starting up on %s" % (os.getpid(), sock.getsockname()) while True: try: - pool.execute_async(lambda: serv.process_request(sock.accept())) + client_socket = sock.accept() + pool.execute_async(serv.process_request, client_socket) except KeyboardInterrupt: api.get_hub().remove_descriptor(sock.fileno()) print "wsgi exiting" From a025e0478ffea2d1cc1441bfff8a2bbbbae93399 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:07:46 -0700 Subject: [PATCH 083/118] Remove .svn directories --- .svn/all-wcprops | 17 - .svn/entries | 58 -- .svn/format | 1 - .svn/text-base/README.svn-base | 66 -- .svn/text-base/setup.py.svn-base | 28 - eventlet/.svn/all-wcprops | 245 ------- eventlet/.svn/dir-prop-base | 9 - eventlet/.svn/entries | 541 -------------- eventlet/.svn/format | 1 - eventlet/.svn/prop-base/__init__.py.svn-base | 5 - eventlet/.svn/prop-base/api.py.svn-base | 5 - eventlet/.svn/prop-base/api_test.py.svn-base | 5 - eventlet/.svn/prop-base/backdoor.py.svn-base | 5 - eventlet/.svn/prop-base/channel.py.svn-base | 5 - eventlet/.svn/prop-base/coros.py.svn-base | 5 - .../.svn/prop-base/coros_test.py.svn-base | 5 - eventlet/.svn/prop-base/greenlib.py.svn-base | 5 - eventlet/.svn/prop-base/httpc.py.svn-base | 5 - eventlet/.svn/prop-base/httpd.py.svn-base | 5 - .../.svn/prop-base/httpd_test.py.svn-base | 5 - eventlet/.svn/prop-base/httpdate.py.svn-base | 5 - eventlet/.svn/prop-base/jsonhttp.py.svn-base | 5 - eventlet/.svn/prop-base/kqueuehub.py.svn-base | 5 - eventlet/.svn/prop-base/logutil.py.svn-base | 5 - eventlet/.svn/prop-base/pollhub.py.svn-base | 5 - eventlet/.svn/prop-base/pools.py.svn-base | 5 - .../.svn/prop-base/pools_test.py.svn-base | 5 - eventlet/.svn/prop-base/processes.py.svn-base | 5 - .../.svn/prop-base/processes_test.py.svn-base | 5 - .../.svn/prop-base/pylibsupport.py.svn-base | 5 - eventlet/.svn/prop-base/runloop.py.svn-base | 5 - .../.svn/prop-base/runloop_test.py.svn-base | 5 - eventlet/.svn/prop-base/selecthub.py.svn-base | 5 - .../prop-base/stacklesssupport.py.svn-base | 5 - eventlet/.svn/prop-base/tests.py.svn-base | 5 - eventlet/.svn/prop-base/timer.py.svn-base | 5 - .../.svn/prop-base/timer_test.py.svn-base | 5 - eventlet/.svn/prop-base/tls.py.svn-base | 5 - .../.svn/prop-base/twistedsupport.py.svn-base | 5 - eventlet/.svn/prop-base/util.py.svn-base | 5 - eventlet/.svn/prop-base/wrappedfd.py.svn-base | 5 - eventlet/.svn/prop-base/wsgi.py.svn-base | 5 - eventlet/.svn/text-base/__init__.py.svn-base | 24 - eventlet/.svn/text-base/api.py.svn-base | 309 -------- eventlet/.svn/text-base/api_test.py.svn-base | 164 ----- eventlet/.svn/text-base/backdoor.py.svn-base | 85 --- eventlet/.svn/text-base/channel.py.svn-base | 98 --- eventlet/.svn/text-base/coros.py.svn-base | 457 ------------ .../.svn/text-base/coros_test.py.svn-base | 300 -------- eventlet/.svn/text-base/db_pool.py.svn-base | 220 ------ .../.svn/text-base/db_pool_test.py.svn-base | 309 -------- eventlet/.svn/text-base/greenlib.py.svn-base | 331 --------- eventlet/.svn/text-base/httpc.py.svn-base | 597 --------------- .../.svn/text-base/httpc_test.py.svn-base | 396 ---------- eventlet/.svn/text-base/httpd.py.svn-base | 584 --------------- .../.svn/text-base/httpd_test.py.svn-base | 207 ------ eventlet/.svn/text-base/httpdate.py.svn-base | 39 - eventlet/.svn/text-base/jsonhttp.py.svn-base | 40 - eventlet/.svn/text-base/kqueuehub.py.svn-base | 219 ------ eventlet/.svn/text-base/logutil.py.svn-base | 112 --- eventlet/.svn/text-base/pollhub.py.svn-base | 189 ----- eventlet/.svn/text-base/pools.py.svn-base | 184 ----- .../.svn/text-base/pools_test.py.svn-base | 179 ----- eventlet/.svn/text-base/processes.py.svn-base | 141 ---- .../.svn/text-base/processes_test.py.svn-base | 134 ---- .../.svn/text-base/pylibsupport.py.svn-base | 42 -- eventlet/.svn/text-base/runloop.py.svn-base | 228 ------ .../.svn/text-base/runloop_test.py.svn-base | 157 ---- eventlet/.svn/text-base/saranwrap.py.svn-base | 685 ------------------ .../.svn/text-base/saranwrap_test.py.svn-base | 316 -------- eventlet/.svn/text-base/selecthub.py.svn-base | 173 ----- .../text-base/stacklesssupport.py.svn-base | 110 --- eventlet/.svn/text-base/tests.py.svn-base | 89 --- eventlet/.svn/text-base/timer.py.svn-base | 83 --- .../.svn/text-base/timer_test.py.svn-base | 66 -- eventlet/.svn/text-base/tls.py.svn-base | 57 -- eventlet/.svn/text-base/tpool.py.svn-base | 123 ---- .../.svn/text-base/tpool_test.py.svn-base | 70 -- .../.svn/text-base/twistedsupport.py.svn-base | 134 ---- eventlet/.svn/text-base/util.py.svn-base | 214 ------ eventlet/.svn/text-base/wrappedfd.py.svn-base | 284 -------- eventlet/.svn/text-base/wsgi.py.svn-base | 219 ------ eventlet/.svn/tmp/tempfile.2.tmp | 584 --------------- examples/.svn/all-wcprops | 17 - examples/.svn/entries | 54 -- examples/.svn/format | 1 - .../.svn/prop-base/echoserver.py.svn-base | 5 - .../.svn/prop-base/webcrawler.py.svn-base | 5 - .../.svn/text-base/echoserver.py.svn-base | 52 -- .../.svn/text-base/webcrawler.py.svn-base | 55 -- 90 files changed, 10272 deletions(-) delete mode 100644 .svn/all-wcprops delete mode 100644 .svn/entries delete mode 100644 .svn/format delete mode 100644 .svn/text-base/README.svn-base delete mode 100644 .svn/text-base/setup.py.svn-base delete mode 100644 eventlet/.svn/all-wcprops delete mode 100644 eventlet/.svn/dir-prop-base delete mode 100644 eventlet/.svn/entries delete mode 100644 eventlet/.svn/format delete mode 100644 eventlet/.svn/prop-base/__init__.py.svn-base delete mode 100644 eventlet/.svn/prop-base/api.py.svn-base delete mode 100644 eventlet/.svn/prop-base/api_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/backdoor.py.svn-base delete mode 100644 eventlet/.svn/prop-base/channel.py.svn-base delete mode 100644 eventlet/.svn/prop-base/coros.py.svn-base delete mode 100644 eventlet/.svn/prop-base/coros_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/greenlib.py.svn-base delete mode 100644 eventlet/.svn/prop-base/httpc.py.svn-base delete mode 100644 eventlet/.svn/prop-base/httpd.py.svn-base delete mode 100644 eventlet/.svn/prop-base/httpd_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/httpdate.py.svn-base delete mode 100644 eventlet/.svn/prop-base/jsonhttp.py.svn-base delete mode 100644 eventlet/.svn/prop-base/kqueuehub.py.svn-base delete mode 100644 eventlet/.svn/prop-base/logutil.py.svn-base delete mode 100644 eventlet/.svn/prop-base/pollhub.py.svn-base delete mode 100644 eventlet/.svn/prop-base/pools.py.svn-base delete mode 100644 eventlet/.svn/prop-base/pools_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/processes.py.svn-base delete mode 100644 eventlet/.svn/prop-base/processes_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/pylibsupport.py.svn-base delete mode 100644 eventlet/.svn/prop-base/runloop.py.svn-base delete mode 100644 eventlet/.svn/prop-base/runloop_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/selecthub.py.svn-base delete mode 100644 eventlet/.svn/prop-base/stacklesssupport.py.svn-base delete mode 100644 eventlet/.svn/prop-base/tests.py.svn-base delete mode 100644 eventlet/.svn/prop-base/timer.py.svn-base delete mode 100644 eventlet/.svn/prop-base/timer_test.py.svn-base delete mode 100644 eventlet/.svn/prop-base/tls.py.svn-base delete mode 100644 eventlet/.svn/prop-base/twistedsupport.py.svn-base delete mode 100644 eventlet/.svn/prop-base/util.py.svn-base delete mode 100644 eventlet/.svn/prop-base/wrappedfd.py.svn-base delete mode 100644 eventlet/.svn/prop-base/wsgi.py.svn-base delete mode 100644 eventlet/.svn/text-base/__init__.py.svn-base delete mode 100644 eventlet/.svn/text-base/api.py.svn-base delete mode 100644 eventlet/.svn/text-base/api_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/backdoor.py.svn-base delete mode 100644 eventlet/.svn/text-base/channel.py.svn-base delete mode 100644 eventlet/.svn/text-base/coros.py.svn-base delete mode 100644 eventlet/.svn/text-base/coros_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/db_pool.py.svn-base delete mode 100644 eventlet/.svn/text-base/db_pool_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/greenlib.py.svn-base delete mode 100644 eventlet/.svn/text-base/httpc.py.svn-base delete mode 100644 eventlet/.svn/text-base/httpc_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/httpd.py.svn-base delete mode 100644 eventlet/.svn/text-base/httpd_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/httpdate.py.svn-base delete mode 100644 eventlet/.svn/text-base/jsonhttp.py.svn-base delete mode 100644 eventlet/.svn/text-base/kqueuehub.py.svn-base delete mode 100644 eventlet/.svn/text-base/logutil.py.svn-base delete mode 100644 eventlet/.svn/text-base/pollhub.py.svn-base delete mode 100644 eventlet/.svn/text-base/pools.py.svn-base delete mode 100644 eventlet/.svn/text-base/pools_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/processes.py.svn-base delete mode 100644 eventlet/.svn/text-base/processes_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/pylibsupport.py.svn-base delete mode 100644 eventlet/.svn/text-base/runloop.py.svn-base delete mode 100644 eventlet/.svn/text-base/runloop_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/saranwrap.py.svn-base delete mode 100644 eventlet/.svn/text-base/saranwrap_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/selecthub.py.svn-base delete mode 100644 eventlet/.svn/text-base/stacklesssupport.py.svn-base delete mode 100644 eventlet/.svn/text-base/tests.py.svn-base delete mode 100644 eventlet/.svn/text-base/timer.py.svn-base delete mode 100644 eventlet/.svn/text-base/timer_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/tls.py.svn-base delete mode 100644 eventlet/.svn/text-base/tpool.py.svn-base delete mode 100644 eventlet/.svn/text-base/tpool_test.py.svn-base delete mode 100644 eventlet/.svn/text-base/twistedsupport.py.svn-base delete mode 100644 eventlet/.svn/text-base/util.py.svn-base delete mode 100644 eventlet/.svn/text-base/wrappedfd.py.svn-base delete mode 100644 eventlet/.svn/text-base/wsgi.py.svn-base delete mode 100644 eventlet/.svn/tmp/tempfile.2.tmp delete mode 100644 examples/.svn/all-wcprops delete mode 100644 examples/.svn/entries delete mode 100644 examples/.svn/format delete mode 100644 examples/.svn/prop-base/echoserver.py.svn-base delete mode 100644 examples/.svn/prop-base/webcrawler.py.svn-base delete mode 100644 examples/.svn/text-base/echoserver.py.svn-base delete mode 100644 examples/.svn/text-base/webcrawler.py.svn-base diff --git a/.svn/all-wcprops b/.svn/all-wcprops deleted file mode 100644 index 57eb68b..0000000 --- a/.svn/all-wcprops +++ /dev/null @@ -1,17 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 32 -/svn/eventlet/!svn/ver/100/trunk -END -setup.py -K 25 -svn:wc:ra_dav:version-url -V 40 -/svn/eventlet/!svn/ver/83/trunk/setup.py -END -README -K 25 -svn:wc:ra_dav:version-url -V 38 -/svn/eventlet/!svn/ver/83/trunk/README -END diff --git a/.svn/entries b/.svn/entries deleted file mode 100644 index f8865c0..0000000 --- a/.svn/entries +++ /dev/null @@ -1,58 +0,0 @@ -8 - -dir -100 -https://svn.secondlife.com/svn/eventlet/trunk -https://svn.secondlife.com/svn/eventlet - - - -2008-03-05T09:26:30.084594Z -100 -which.linden - - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -cde37729-5338-0410-b9e2-c56166a61366 - -eventlet -dir - -setup.py -file - - - - -2008-02-20T19:57:15.000000Z -82a68910db8376655db41f89b3829524 -2008-01-28T22:21:28.111792Z -83 -which.linden - -README -file - - - - -2008-02-20T19:57:15.000000Z -8d9257b4ae3863579afebab7b38fa8ea -2008-01-28T22:21:28.111792Z -83 -which.linden - -examples -dir - diff --git a/.svn/format b/.svn/format deleted file mode 100644 index 45a4fb7..0000000 --- a/.svn/format +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/.svn/text-base/README.svn-base b/.svn/text-base/README.svn-base deleted file mode 100644 index 18094ae..0000000 --- a/.svn/text-base/README.svn-base +++ /dev/null @@ -1,66 +0,0 @@ -= eventlet = - -Eventlet is a networking library written in Python. It achieves high -scalability by using non-blocking io while at the same time retaining -high programmer usability by using coroutines to make the non-blocking -io operations appear blocking at the source code level. - -The wiki at http://wiki.secondlife.com/wiki/Eventlet is likely to be a -more current source of information than this README. Questions, -patches, and general discussion go to the eventlet mailing list: -https://lists.secondlife.com/cgi-bin/mailman/listinfo/eventletdev - -== requirements === - -Eventlet runs on Python version 2.3 or greater, with the following dependenceis: -* [http://cheeseshop.python.org/pypi/greenlet -* (if running python versions < 2.4) collections.py from the 2.4 distribution or later - -== limitations == - -* Sorely lacking in documentation -* Not enough test coverage -- the goal is 100%, but we are not there yet. -* Eventlet does not currently run on stackless using tasklets, though -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 == - -% python ->>> from eventlet import api ->>> help(api) - -Also, look at the examples in the examples directory. - -== eventlet history == - -eventlet began life as Donovan Preston was talking to Bob Ippolito -about coroutine-based non-blocking networking frameworks in -Python. Most non-blocking frameworks require you to run the "main -loop" in order to perform all network operations, but Donovan wondered -if a library written using a trampolining style could get away with -transparently running the main loop any time i/o was required, -stopping the main loop once no more i/o was scheduled. Bob spent a few -days during PyCon 2005 writing a proof-of-concept. He named it -eventlet, after the coroutine implementation it used, -[[greenlet]]. Donovan began using eventlet as a light-weight network -library for his spare-time project Pavel, and also began writing some -unittests. - -* http://svn.red-bean.com/bob/eventlet/trunk/ -* http://soundfarmer.com/Pavel/trunk/ - -When Donovan started at Linden Lab in May of 2006, he added eventlet -as an svn external in the indra/lib/python directory, to be a -dependency of the yet-to-be-named [[backbone]] project (at the time, -it was named restserv). However, including eventlet as an svn external -meant that any time the externally hosted project had hosting issues, -Linden developers were not able to perform svn updates. Thus, the -eventlet source was imported into the linden source tree at the same -location, and became a fork. - -Bob Ippolito has ceased working on eventlet and has stated his desire -for Linden to take its fork forward to the open source world as "the" -eventlet. diff --git a/.svn/text-base/setup.py.svn-base b/.svn/text-base/setup.py.svn-base deleted file mode 100644 index dd5999c..0000000 --- a/.svn/text-base/setup.py.svn-base +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - -setup( - name='eventlet', - version='0.2', - description='Coroutine-based networking library', - author='Linden Lab', - author_email='eventletdev@lists.secondlife.com', - url='http://wiki.secondlife.com/wiki/Eventlet', - packages=['eventlet'], - install_requires=['greenlet'], - long_description=""" - Eventlet is a networking library written in Python. It achieves - high scalability by using non-blocking io while at the same time - retaining high programmer usability by using coroutines to make - the non-blocking io operations appear blocking at the source code - level.""", - classifiers=[ - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Topic :: Internet", - "Topic :: Software Development :: Libraries :: Python Modules", - "Intended Audience :: Developers", - "Development Status :: 4 - Beta"] - ) - diff --git a/eventlet/.svn/all-wcprops b/eventlet/.svn/all-wcprops deleted file mode 100644 index bb8cae0..0000000 --- a/eventlet/.svn/all-wcprops +++ /dev/null @@ -1,245 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 41 -/svn/eventlet/!svn/ver/100/trunk/eventlet -END -stacklesssupport.py -K 25 -svn:wc:ra_dav:version-url -V 59 -/svn/eventlet/!svn/ver/3/trunk/eventlet/stacklesssupport.py -END -pools_test.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/3/trunk/eventlet/pools_test.py -END -pools.py -K 25 -svn:wc:ra_dav:version-url -V 50 -/svn/eventlet/!svn/ver/101/trunk/eventlet/pools.py -END -tpool_test.py -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/eventlet/!svn/ver/86/trunk/eventlet/tpool_test.py -END -saranwrap_test.py -K 25 -svn:wc:ra_dav:version-url -V 58 -/svn/eventlet/!svn/ver/80/trunk/eventlet/saranwrap_test.py -END -runloop_test.py -K 25 -svn:wc:ra_dav:version-url -V 56 -/svn/eventlet/!svn/ver/68/trunk/eventlet/runloop_test.py -END -saranwrap.py -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/eventlet/!svn/ver/100/trunk/eventlet/saranwrap.py -END -runloop.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/91/trunk/eventlet/runloop.py -END -kqueuehub.py -K 25 -svn:wc:ra_dav:version-url -V 52 -/svn/eventlet/!svn/ver/3/trunk/eventlet/kqueuehub.py -END -__init__.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/3/trunk/eventlet/__init__.py -END -tests.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/eventlet/!svn/ver/88/trunk/eventlet/tests.py -END -tls.py -K 25 -svn:wc:ra_dav:version-url -V 46 -/svn/eventlet/!svn/ver/3/trunk/eventlet/tls.py -END -pollhub.py -K 25 -svn:wc:ra_dav:version-url -V 50 -/svn/eventlet/!svn/ver/3/trunk/eventlet/pollhub.py -END -api.py -K 25 -svn:wc:ra_dav:version-url -V 47 -/svn/eventlet/!svn/ver/37/trunk/eventlet/api.py -END -wsgi.py -K 25 -svn:wc:ra_dav:version-url -V 47 -/svn/eventlet/!svn/ver/3/trunk/eventlet/wsgi.py -END -pylibsupport.py -K 25 -svn:wc:ra_dav:version-url -V 55 -/svn/eventlet/!svn/ver/3/trunk/eventlet/pylibsupport.py -END -wrappedfd.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/87/trunk/eventlet/wrappedfd.py -END -util.py -K 25 -svn:wc:ra_dav:version-url -V 47 -/svn/eventlet/!svn/ver/3/trunk/eventlet/util.py -END -selecthub.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/16/trunk/eventlet/selecthub.py -END -timer_test.py -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/eventlet/!svn/ver/68/trunk/eventlet/timer_test.py -END -timer.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/eventlet/!svn/ver/92/trunk/eventlet/timer.py -END -twistedsupport.py -K 25 -svn:wc:ra_dav:version-url -V 57 -/svn/eventlet/!svn/ver/3/trunk/eventlet/twistedsupport.py -END -httpc_test.py -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/eventlet/!svn/ver/70/trunk/eventlet/httpc_test.py -END -httpc.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/eventlet/!svn/ver/95/trunk/eventlet/httpc.py -END -httpd_test.py -K 25 -svn:wc:ra_dav:version-url -V 54 -/svn/eventlet/!svn/ver/97/trunk/eventlet/httpd_test.py -END -coros_test.py -K 25 -svn:wc:ra_dav:version-url -V 55 -/svn/eventlet/!svn/ver/101/trunk/eventlet/coros_test.py -END -tpool.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/eventlet/!svn/ver/93/trunk/eventlet/tpool.py -END -processes_test.py -K 25 -svn:wc:ra_dav:version-url -V 57 -/svn/eventlet/!svn/ver/3/trunk/eventlet/processes_test.py -END -processes.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/16/trunk/eventlet/processes.py -END -api_test.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/3/trunk/eventlet/api_test.py -END -channel.py -K 25 -svn:wc:ra_dav:version-url -V 50 -/svn/eventlet/!svn/ver/3/trunk/eventlet/channel.py -END -jsonhttp.py -K 25 -svn:wc:ra_dav:version-url -V 52 -/svn/eventlet/!svn/ver/75/trunk/eventlet/jsonhttp.py -END -db_pool_test.py -K 25 -svn:wc:ra_dav:version-url -V 56 -/svn/eventlet/!svn/ver/85/trunk/eventlet/db_pool_test.py -END -httpdate.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/3/trunk/eventlet/httpdate.py -END -db_pool.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/93/trunk/eventlet/db_pool.py -END -logutil.py -K 25 -svn:wc:ra_dav:version-url -V 50 -/svn/eventlet/!svn/ver/3/trunk/eventlet/logutil.py -END -httpd.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/eventlet/!svn/ver/97/trunk/eventlet/httpd.py -END -backdoor.py -K 25 -svn:wc:ra_dav:version-url -V 51 -/svn/eventlet/!svn/ver/3/trunk/eventlet/backdoor.py -END -coros.py -K 25 -svn:wc:ra_dav:version-url -V 50 -/svn/eventlet/!svn/ver/101/trunk/eventlet/coros.py -END -greenlib.py -K 25 -svn:wc:ra_dav:version-url -V 52 -/svn/eventlet/!svn/ver/96/trunk/eventlet/greenlib.py -END diff --git a/eventlet/.svn/dir-prop-base b/eventlet/.svn/dir-prop-base deleted file mode 100644 index 96ae2a7..0000000 --- a/eventlet/.svn/dir-prop-base +++ /dev/null @@ -1,9 +0,0 @@ -K 10 -svn:ignore -V 16 -*.pyc -*~ - -*.tmp - -END diff --git a/eventlet/.svn/entries b/eventlet/.svn/entries deleted file mode 100644 index c9b2174..0000000 --- a/eventlet/.svn/entries +++ /dev/null @@ -1,541 +0,0 @@ -8 - -dir -100 -https://svn.secondlife.com/svn/eventlet/trunk/eventlet -https://svn.secondlife.com/svn/eventlet - - - -2008-03-05T09:26:30.084594Z -100 -which.linden -has-props - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -cde37729-5338-0410-b9e2-c56166a61366 - -stacklesssupport.py -file - - - - -2008-02-20T19:57:15.000000Z -6c51017e724b0fadba816ed73de7648f -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -pools_test.py -file - - - - -2008-02-20T19:57:15.000000Z -3835ff82a7af6edc7c09d276c67cd5db -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -pools.py -file -101 - - - -2008-03-08T01:16:18.000000Z -3734d5d23e28b08aaf0ec7df1f97b164 -2008-03-11T02:26:32.072967Z -101 -which.linden -has-props - -tpool_test.py -file - - - - -2008-02-20T19:57:15.000000Z -c5cb15c24b12acc0bcc14694a74de48b -2008-02-05T01:39:10.353080Z -86 -which.linden - -saranwrap_test.py -file - - - - -2008-02-20T19:57:15.000000Z -f99f25b424a2fc6780dfa0ab6a69eba4 -2008-01-24T00:14:38.439330Z -80 -which.linden - -runloop_test.py -file - - - - -2008-02-20T19:57:15.000000Z -e724370d70719da2834273b0bd5e84fc -2007-12-13T19:39:23.000239Z -68 -donovan.linden -has-props - -saranwrap.py -file - - - - -2008-03-05T09:22:18.000000Z -9b5f20eb03a7d45981c5d32e944008e4 -2008-03-05T09:26:30.084594Z -100 -which.linden - -runloop.py -file - - - - -2008-02-20T19:57:15.000000Z -0313cf6b7844ced258f7f6918d923435 -2008-02-19T06:10:56.251879Z -91 -which.linden -has-props - -kqueuehub.py -file - - - - -2008-02-20T19:57:15.000000Z -c68edf1618f20648e93e0a5a9b841b42 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -__init__.py -file - - - - -2008-02-20T19:57:15.000000Z -d2fd03ba7a2f3c38b210f598e9e0f52e -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -tests.py -file - - - - -2008-02-20T19:57:15.000000Z -7e2f8dcedb9825b705c2267a74dc9a56 -2008-02-14T03:13:16.377716Z -88 -which.linden -has-props - -tls.py -file - - - - -2008-02-20T19:57:15.000000Z -dc5cb5394ea3a29319f7302e51a7807c -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -pollhub.py -file - - - - -2008-02-20T19:57:15.000000Z -a6ac6144487b6d5f5df84262f5079955 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -api.py -file - - - - -2008-02-20T19:57:15.000000Z -dcff6139f8203bc7a1827207e06eb423 -2007-10-27T07:49:23.267939Z -37 -donovan.linden -has-props - -wsgi.py -file - - - - -2008-02-20T19:57:15.000000Z -7ce7f2d3c85243ec3ecaf1049aedbae4 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -pylibsupport.py -file - - - - -2008-02-20T19:57:15.000000Z -cf6f3b08989af055c8746292c5c8fc91 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -wrappedfd.py -file - - - - -2008-02-20T19:57:15.000000Z -5a1e9d228852226773e6c4ea1c17de6f -2008-02-13T20:04:45.776541Z -87 -nat.linden -has-props - -util.py -file - - - - -2008-02-20T19:57:15.000000Z -2dc700683fa2156f0b5c04cc0549da11 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -selecthub.py -file - - - - -2008-02-20T19:57:15.000000Z -86e451fd018d86d8b015f34d4f20fd3b -2007-10-08T07:12:35.632195Z -16 -which.linden -has-props - -timer_test.py -file - - - - -2008-02-20T19:57:15.000000Z -57ad2ed0e3cdc5b0825c05d7419ac133 -2007-12-13T19:39:23.000239Z -68 -donovan.linden -has-props - -timer.py -file - - - - -2008-02-20T19:57:15.000000Z -ebe5204bb1c220e903962b6abe0b24b4 -2008-02-19T06:15:19.940493Z -92 -which.linden -has-props - -twistedsupport.py -file - - - - -2008-02-20T19:57:15.000000Z -19b766b248cd310896dde84bedba6d3e -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -httpc_test.py -file - - - - -2008-02-20T19:57:15.000000Z -5ee0387bc46dd1f8cdff0d7059aa9bf6 -2007-12-14T00:34:51.703611Z -70 -sardonyx.linden - -httpc.py -file - - - - -2008-03-03T02:05:45.000000Z -76f48ad7feb782f4c1f0807acfecd688 -2008-02-29T22:22:47.916198Z -95 -donovan.linden -has-props - -httpd_test.py -file - - - - -2008-02-20T20:28:52.000000Z -147ad0bb4398ebf9427a1fc2b7843763 -2008-03-03T02:08:25.755808Z -97 -which.linden -has-props - -coros_test.py -file -101 - - - -2008-03-08T09:13:27.000000Z -7a922fe4bea9a193c5dee62ce86aa64c -2008-03-11T02:26:32.072967Z -101 -which.linden -has-props - -tpool.py -file - - - - -2008-02-20T19:57:15.000000Z -954ac5ec927128a5ccfe20c486c73db4 -2008-02-19T23:24:32.369211Z -93 -seeping.blister - -processes_test.py -file - - - - -2008-02-20T19:57:15.000000Z -42cd89f0b207638ed21c18db0d53b6f6 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -processes.py -file - - - - -2008-02-20T19:57:15.000000Z -4871f77ae4a77479cb4c16eff8b00897 -2007-10-08T07:12:35.632195Z -16 -which.linden -has-props - -api_test.py -file - - - - -2008-02-20T19:57:15.000000Z -fc0dd790c0de74912408bc5cc1c6087f -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -channel.py -file - - - - -2008-02-20T19:57:15.000000Z -6ef49c57d8fe793f488775fb7c7d2013 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -jsonhttp.py -file - - - - -2008-02-20T19:57:15.000000Z -a2f7e287f8df9cc66a34a5f701aa3edb -2008-01-10T00:10:39.631442Z -75 -which.linden -has-props - -db_pool_test.py -file - - - - -2008-02-20T19:57:15.000000Z -f9dcfdeb653bde2fc4bde41ee5e2d1d0 -2008-02-05T01:34:59.036551Z -85 -which.linden - -httpdate.py -file - - - - -2008-02-20T19:57:15.000000Z -9fb5c627d69fcdf9d952d38f8510f252 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -db_pool.py -file - - - - -2008-02-20T19:57:15.000000Z -e0fa71cdef20bba5cd3902a3093ca7da -2008-02-19T23:24:32.369211Z -93 -seeping.blister - -logutil.py -file - - - - -2008-02-20T19:57:15.000000Z -06f2ae2b2973cb742a8ae707fd504214 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -httpd.py -file - - - - -2008-03-03T02:05:45.000000Z -c072047f7b212b0407940805031dda86 -2008-03-03T02:08:25.755808Z -97 -which.linden -has-props - -backdoor.py -file - - - - -2008-02-20T19:57:15.000000Z -e06e68e073115125d5d209b75302e224 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -coros.py -file -101 - - - -2008-03-10T22:11:13.000000Z -0c7c37dbe89ebcb51958528b006903aa -2008-03-11T02:26:32.072967Z -101 -which.linden -has-props - -greenlib.py -file - - - - -2008-03-03T02:05:45.000000Z -996debf62c9f5dacc2dee99ed2568a25 -2008-03-02T23:59:20.899444Z -96 -which.linden -has-props - diff --git a/eventlet/.svn/format b/eventlet/.svn/format deleted file mode 100644 index 45a4fb7..0000000 --- a/eventlet/.svn/format +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/eventlet/.svn/prop-base/__init__.py.svn-base b/eventlet/.svn/prop-base/__init__.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/__init__.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/api.py.svn-base b/eventlet/.svn/prop-base/api.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/api.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/api_test.py.svn-base b/eventlet/.svn/prop-base/api_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/api_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/backdoor.py.svn-base b/eventlet/.svn/prop-base/backdoor.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/backdoor.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/channel.py.svn-base b/eventlet/.svn/prop-base/channel.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/channel.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/coros.py.svn-base b/eventlet/.svn/prop-base/coros.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/coros.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/coros_test.py.svn-base b/eventlet/.svn/prop-base/coros_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/coros_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/greenlib.py.svn-base b/eventlet/.svn/prop-base/greenlib.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/greenlib.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/httpc.py.svn-base b/eventlet/.svn/prop-base/httpc.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/httpc.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/httpd.py.svn-base b/eventlet/.svn/prop-base/httpd.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/httpd.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/httpd_test.py.svn-base b/eventlet/.svn/prop-base/httpd_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/httpd_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/httpdate.py.svn-base b/eventlet/.svn/prop-base/httpdate.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/httpdate.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/jsonhttp.py.svn-base b/eventlet/.svn/prop-base/jsonhttp.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/jsonhttp.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/kqueuehub.py.svn-base b/eventlet/.svn/prop-base/kqueuehub.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/kqueuehub.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/logutil.py.svn-base b/eventlet/.svn/prop-base/logutil.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/logutil.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/pollhub.py.svn-base b/eventlet/.svn/prop-base/pollhub.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/pollhub.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/pools.py.svn-base b/eventlet/.svn/prop-base/pools.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/pools.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/pools_test.py.svn-base b/eventlet/.svn/prop-base/pools_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/pools_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/processes.py.svn-base b/eventlet/.svn/prop-base/processes.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/processes.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/processes_test.py.svn-base b/eventlet/.svn/prop-base/processes_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/processes_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/pylibsupport.py.svn-base b/eventlet/.svn/prop-base/pylibsupport.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/pylibsupport.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/runloop.py.svn-base b/eventlet/.svn/prop-base/runloop.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/runloop.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/runloop_test.py.svn-base b/eventlet/.svn/prop-base/runloop_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/runloop_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/selecthub.py.svn-base b/eventlet/.svn/prop-base/selecthub.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/selecthub.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/stacklesssupport.py.svn-base b/eventlet/.svn/prop-base/stacklesssupport.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/stacklesssupport.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/tests.py.svn-base b/eventlet/.svn/prop-base/tests.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/tests.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/timer.py.svn-base b/eventlet/.svn/prop-base/timer.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/timer.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/timer_test.py.svn-base b/eventlet/.svn/prop-base/timer_test.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/timer_test.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/tls.py.svn-base b/eventlet/.svn/prop-base/tls.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/tls.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/twistedsupport.py.svn-base b/eventlet/.svn/prop-base/twistedsupport.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/twistedsupport.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/util.py.svn-base b/eventlet/.svn/prop-base/util.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/util.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/wrappedfd.py.svn-base b/eventlet/.svn/prop-base/wrappedfd.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/wrappedfd.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/prop-base/wsgi.py.svn-base b/eventlet/.svn/prop-base/wsgi.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/eventlet/.svn/prop-base/wsgi.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/eventlet/.svn/text-base/__init__.py.svn-base b/eventlet/.svn/text-base/__init__.py.svn-base deleted file mode 100644 index 5021bea..0000000 --- a/eventlet/.svn/text-base/__init__.py.svn-base +++ /dev/null @@ -1,24 +0,0 @@ -"""\ -@file __init__.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. -""" - -# This text exists only for the purpose of not making a complete -# mockery of the above copyright header. diff --git a/eventlet/.svn/text-base/api.py.svn-base b/eventlet/.svn/text-base/api.py.svn-base deleted file mode 100644 index e9877fa..0000000 --- a/eventlet/.svn/text-base/api.py.svn-base +++ /dev/null @@ -1,309 +0,0 @@ -"""\ -@file api.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 socket -import string -import linecache -import inspect -import traceback - -try: - import greenlet -except ImportError: - try: - import pylibsupport - pylibsupport.emulate() - greenlet = sys.modules['greenlet'] - except ImportError: - try: - import stacklesssupport - stacklesssupport.emulate() - greenlet = sys.modules['greenlet'] - except ImportError: - raise ImportError("Unable to find an implementation of greenlet.") - -from eventlet import greenlib, tls - -__all__ = [ - 'use_hub', 'get_hub', 'sleep', 'spawn', 'kill', - 'call_after', 'exc_after', 'trampoline', 'tcp_listener', 'tcp_server', -] - - -class TimeoutError(Exception): - pass - -_threadlocal = tls.local() - -def tcp_listener(address): - """ - Listen on the given (ip, port) address with a TCP socket. - Returns a socket object which one should call accept() on to - accept a connection on the newly bound socket. - - Generally, the returned socket will be passed to tcp_server, - which accepts connections forever and spawns greenlets for - each incoming connection. - """ - from eventlet import wrappedfd, util - socket = wrappedfd.wrapped_fd(util.tcp_socket()) - util.socket_bind_and_listen(socket, address) - return socket - -def ssl_listener(address, certificate, private_key): - """Listen on the given (ip, port) address with a TCP socket that - can do SSL. - - Returns a socket object which one should call accept() on to - accept a connection on the newly bound socket. - - Generally, the returned socket will be passed to tcp_server, - which accepts connections forever and spawns greenlets for - each incoming connection. - """ - from eventlet import util - socket = util.wrap_ssl(util.tcp_socket(), certificate, private_key) - util.socket_bind_and_listen(socket, address) - socket.is_secure = True - return socket - -def connect_tcp(address): - """ - Create a TCP connection to address (host, port) and return the socket. - """ - from eventlet import wrappedfd, util - desc = wrappedfd.wrapped_fd(util.tcp_socket()) - desc.connect(address) - return desc - -def tcp_server(listensocket, server, *args, **kw): - """ - Given a socket, accept connections forever, spawning greenlets - and executing "server" for each new incoming connection. - When listensocket is closed, the tcp_server greenlet will end. - - listensocket: - The socket to accept connections from. - - server: - The callable to call when a new connection is made. - - *args: - The arguments to pass to the call to server. - - **kw: - The keyword arguments to pass to the call to server. - """ - try: - try: - while True: - spawn(server, listensocket.accept(), *args, **kw) - except socket.error, e: - # Broken pipe means it was shutdown - if e[0] != 32: - raise - finally: - listensocket.close() - -def trampoline(fd, read=None, write=None, timeout=None): - t = None - hub = get_hub() - self = greenlet.getcurrent() - fileno = getattr(fd, 'fileno', lambda: fd)() - def _do_close(fn): - hub.remove_descriptor(fn) - greenlib.switch(self, exc=socket.error(32, 'Broken pipe')) - def _do_timeout(fn): - hub.remove_descriptor(fn) - greenlib.switch(self, exc=TimeoutError()) - def cb(_fileno): - if t is not None: - t.cancel() - hub.remove_descriptor(fileno) - greenlib.switch(self, fd) - if timeout is not None: - t = hub.schedule_call(timeout, _do_timeout) - hub.add_descriptor(fileno, read and cb, write and cb, _do_close) - return hub.switch() - -def _spawn_startup(cb, args, kw, cancel=None): - try: - greenlib.switch(greenlet.getcurrent().parent) - cancel = None - finally: - if cancel is not None: - cancel() - return cb(*args, **kw) - -def _spawn(g): - g.parent = greenlet.getcurrent() - greenlib.switch(g) - - -def spawn(cb, *args, **kw): - # killable - t = None - g = greenlib.tracked_greenlet() - t = get_hub().schedule_call(0, _spawn, g) - greenlib.switch(g, (_spawn_startup, cb, args, kw, t.cancel)) - return g - -kill = greenlib.kill - -def call_after(seconds, cb, *args, **kw): - # cancellable - def startup(): - g = greenlib.tracked_greenlet() - greenlib.switch(g, (_spawn_startup, cb, args, kw)) - greenlib.switch(g) - return get_hub().schedule_call(seconds, startup) - - -def exc_after(seconds, exc): - return call_after(seconds, switch, getcurrent(), None, exc) - - -def get_default_hub(): - try: - import eventlet.kqueuehub - except ImportError: - pass - else: - return eventlet.kqueuehub - import select - if hasattr(select, 'poll'): - import eventlet.pollhub - return eventlet.pollhub - else: - import eventlet.selecthub - return eventlet.selecthub - -def use_hub(mod=None): - if mod is None: - mod = get_default_hub() - if hasattr(_threadlocal, 'hub'): - del _threadlocal.hub - if hasattr(mod, 'Hub'): - _threadlocal.Hub = mod.Hub - else: - _threadlocal.Hub = mod - -def get_hub(): - try: - hub = _threadlocal.hub - except AttributeError: - try: - _threadlocal.Hub - except AttributeError: - use_hub() - hub = _threadlocal.hub = _threadlocal.Hub() - return hub - - -def sleep(timeout=0): - hub = get_hub() - hub.schedule_call(timeout, greenlib.switch, greenlet.getcurrent()) - hub.switch() - - -switch = greenlib.switch -getcurrent = greenlet.getcurrent -GreenletExit = greenlet.GreenletExit - - -class Spew(object): - def __init__(self, trace_names=None): - self.trace_names = trace_names - - def __call__(self, frame, event, arg): - if event == 'line': - lineno = frame.f_lineno - if '__file__' in frame.f_globals: - filename = frame.f_globals['__file__'] - if (filename.endswith('.pyc') or - filename.endswith('.pyo')): - filename = filename[:-1] - name = frame.f_globals['__name__'] - line = linecache.getline(filename, lineno) - else: - name = '[unknown]' - try: - src = inspect.getsourcelines(frame) - line = src[lineno] - except IOError: - line = 'Unknown code named [%s]. VM instruction #%d' % ( - frame.f_code.co_name, frame.f_lasti) - if self.trace_names is None or name in self.trace_names: - print '%s:%s: %s' % (name, lineno, line.rstrip()) - details = '\t' - tokens = line.translate( - string.maketrans(' ,.()', '\0' * 5)).split('\0') - for tok in tokens: - if tok in frame.f_globals: - details += '%s=%r ' % (tok, frame.f_globals[tok]) - if tok in frame.f_locals: - details += '%s=%r ' % (tok, frame.f_locals[tok]) - if details.strip(): - print details - return self - - -def spew(trace_names=None): - sys.settrace(Spew(trace_names)) - - -def unspew(): - sys.settrace(None) - - -def named(name): - """Return an object given its name. The name uses a module-like -syntax, eg: - os.path.join - or - mulib.mu.Resource - """ - toimport = name - obj = None - while toimport: - try: - obj = __import__(toimport) - break - except ImportError, err: - # print 'Import error on %s: %s' % (toimport, err) # debugging spam - toimport = '.'.join(toimport.split('.')[:-1]) - if obj is None: - raise ImportError('%s could not be imported' % (name, )) - for seg in name.split('.')[1:]: - try: - obj = getattr(obj, seg) - except AttributeError: - dirobj = dir(obj) - dirobj.sort() - raise AttributeError('attribute %r missing from %r (%r) %r' % ( - seg, obj, dirobj, name)) - return obj - diff --git a/eventlet/.svn/text-base/api_test.py.svn-base b/eventlet/.svn/text-base/api_test.py.svn-base deleted file mode 100644 index b2c371a..0000000 --- a/eventlet/.svn/text-base/api_test.py.svn-base +++ /dev/null @@ -1,164 +0,0 @@ -"""\ -@file api_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. -""" - -from eventlet import tests -from eventlet import api, wrappedfd, util -import socket - - -def check_hub(): - # Clear through the descriptor queue - api.sleep(0) - api.sleep(0) - assert not api.get_hub().descriptors, repr(api.get_hub().descriptors) - # Stop the runloop - api.get_hub().runloop.abort() - api.sleep(0) - assert not api.get_hub().runloop.running - - -class TestApi(tests.TestCase): - mode = 'static' - def test_tcp_listener(self): - socket = api.tcp_listener(('0.0.0.0', 0)) - assert socket.getsockname()[0] == '0.0.0.0' - socket.close() - - check_hub() - - def dont_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): - try: - conn, addr = listenfd.accept() - conn.write('hello\n') - conn.close() - finally: - listenfd.close() - - server = api.tcp_listener(('0.0.0.0', 0)) - api.spawn(accept_once, server) - - client = api.connect_tcp(('127.0.0.1', server.getsockname()[1])) - assert client.readline() == 'hello\n' - - assert client.read() == '' - client.close() - - check_hub() - - def test_server(self): - server = api.tcp_listener(('0.0.0.0', 0)) - bound_port = server.getsockname()[1] - connected = [] - - def accept_twice((conn, addr)): - print 'connected' - connected.append(True) - conn.close() - if len(connected) == 2: - server.close() - - api.call_after(0, api.connect_tcp, ('127.0.0.1', bound_port)) - api.call_after(0, api.connect_tcp, ('127.0.0.1', bound_port)) - api.tcp_server(server, accept_twice) - - assert len(connected) == 2 - - check_hub() - - def dont_test_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)) - bound_port = server.getsockname()[1] - - try: - desc = wrappedfd.wrapped_fd(util.tcp_socket()) - api.trampoline(desc, read=True, write=True, timeout=0.1) - except api.TimeoutError: - pass # test passed - else: - assert False, "Didn't timeout" - - check_hub() - - def test_timeout_cancel(self): - server = api.tcp_listener(('0.0.0.0', 0)) - bound_port = server.getsockname()[1] - - def client_connected((conn, addr)): - conn.close() - - def go(): - client = util.tcp_socket() - - desc = wrappedfd.wrapped_fd(client) - desc.connect(('127.0.0.1', bound_port)) - try: - api.trampoline(desc, read=True, write=True, timeout=0.1) - except api.TimeoutError: - assert False, "Timed out" - - server.close() - client.close() - - api.call_after(0, go) - - api.tcp_server(server, client_connected) - - check_hub() - - def dont_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) - assert isinstance(api.get_hub(), Foo), api.get_hub() - - api.use_hub(api.get_default_hub()) - - check_hub() - - def test_named(self): - named_foo = api.named('api_test.Foo') - self.assertEquals( - named_foo.__name__, - "Foo") - - def test_naming_missing_class(self): - self.assertRaises( - ImportError, api.named, 'this_name_should_hopefully_not_exist.Foo') - - -class Foo(object): - pass - - -if __name__ == '__main__': - tests.main() - diff --git a/eventlet/.svn/text-base/backdoor.py.svn-base b/eventlet/.svn/text-base/backdoor.py.svn-base deleted file mode 100644 index 8f792dd..0000000 --- a/eventlet/.svn/text-base/backdoor.py.svn-base +++ /dev/null @@ -1,85 +0,0 @@ -"""\ -@file backdoor.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 -from code import InteractiveConsole -from eventlet import greenlib - -try: - sys.ps1 -except AttributeError: - sys.ps1 = '>>> ' -try: - sys.ps2 -except AttributeError: - sys.ps2 = '... ' - -class SocketConsole(greenlib.GreenletContext): - def __init__(self, desc): - # mangle the socket - self.desc = desc - readline = desc.readline - self.old = {} - self.fixups = { - 'softspace': 0, - 'isatty': lambda: True, - 'flush': lambda: None, - 'readline': lambda *a: readline(*a).replace('\r\n', '\n'), - } - for key, value in self.fixups.iteritems(): - if hasattr(desc, key): - self.old[key] = getattr(desc, key) - setattr(desc, key, value) - - def finalize(self): - # restore the state of the socket - for key in self.fixups: - try: - value = self.old[key] - except KeyError: - delattr(self.desc, key) - else: - setattr(self.desc, key, value) - self.fixups.clear() - self.old.clear() - self.desc = None - - def swap_in(self): - self.saved = sys.stdin, sys.stderr, sys.stdout - sys.stdin = sys.stdout = sys.stderr = self.desc - - def swap_out(self): - sys.stdin, sys.stderr, sys.stdout = self.saved - -def backdoor((conn, addr), locals=None): - host, port = addr - print "backdoor to %s:%s" % (host, port) - ctx = SocketConsole(conn) - ctx.register() - try: - console = InteractiveConsole(locals) - console.interact() - finally: - ctx.unregister() diff --git a/eventlet/.svn/text-base/channel.py.svn-base b/eventlet/.svn/text-base/channel.py.svn-base deleted file mode 100644 index a24799a..0000000 --- a/eventlet/.svn/text-base/channel.py.svn-base +++ /dev/null @@ -1,98 +0,0 @@ -"""\ -@file channel.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 collections - -from eventlet import api, greenlib - -import greenlet - -__all__ = ['channel'] - -class channel(object): - """A channel is a control flow primitive for co-routines. It is a - "thread-like" queue for controlling flow between two (or more) co-routines. - The state model is: - - * If one co-routine calls send(), it is unscheduled until another - co-routine calls receive(). - * If one co-rounte calls receive(), it is unscheduled until another - co-routine calls send(). - * Once a paired send()/receive() have been called, both co-routeines - are rescheduled. - - This is similar to: http://stackless.com/wiki/Channels - """ - balance = 0 - - def _tasklet_loop(self): - deque = self.deque = collections.deque() - hub = api.get_hub() - switch = greenlib.switch - direction, caller, args = switch() - try: - while True: - if direction == -1: - # waiting to receive - if self.balance > 0: - sender, args = deque.popleft() - hub.schedule_call(0, switch, sender) - hub.schedule_call(0, switch, caller, *args) - else: - deque.append(caller) - else: - # waiting to send - if self.balance < 0: - receiver = deque.popleft() - hub.schedule_call(0, switch, receiver, *args) - hub.schedule_call(0, switch, caller) - else: - deque.append((caller, args)) - self.balance += direction - direction, caller, args = hub.switch() - finally: - deque.clear() - del self.deque - self.balance = 0 - - def _send_tasklet(self, *args): - try: - t = self._tasklet - except AttributeError: - t = self._tasklet = greenlib.tracked_greenlet() - greenlib.switch(t, (self._tasklet_loop,)) - if args: - return greenlib.switch(t, (1, greenlet.getcurrent(), args)) - else: - return greenlib.switch(t, (-1, greenlet.getcurrent(), args)) - - def receive(self): - return self._send_tasklet() - - def send(self, value): - return self._send_tasklet(value) - - def send_exception(self, exc): - return self._send_tasklet(None, exc) diff --git a/eventlet/.svn/text-base/coros.py.svn-base b/eventlet/.svn/text-base/coros.py.svn-base deleted file mode 100644 index c53f807..0000000 --- a/eventlet/.svn/text-base/coros.py.svn-base +++ /dev/null @@ -1,457 +0,0 @@ -"""\ -@file coros.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 collections -import sys -import time -import traceback - - -from eventlet import api -from eventlet import channel -from eventlet import pools -from eventlet import greenlib - - -try: - set -except NameError: # python 2.3 compatibility - from sets import Set as set - - -class Cancelled(RuntimeError): - pass - - -NOT_USED = object() - - -class event(object): - """An abstraction where an arbitrary number of coroutines - can wait for one event from another. - - Events differ from channels in two ways: - 1) calling send() does not unschedule the current coroutine - 2) send() can only be called once; use reset() to prepare the event for - another send() - They are ideal for communicating return values between coroutines. - - >>> from eventlet import coros, api - >>> evt = coros.event() - >>> def baz(b): - ... evt.send(b + 1) - ... - >>> _ = api.spawn(baz, 3) - >>> evt.wait() - 4 - """ - _result = None - def __init__(self): - self.reset() - - def reset(self): - """ Reset this event so it can be used to send again. - Can only be called after send has been called. - - >>> from eventlet import coros - >>> evt = coros.event() - >>> evt.send(1) - >>> evt.reset() - >>> evt.send(2) - >>> evt.wait() - 2 - - Calling reset multiple times in a row is an error. - - >>> evt.reset() - >>> evt.reset() - Traceback (most recent call last): - ... - AssertionError: Trying to re-reset() a fresh event. - - """ - assert self._result is not NOT_USED, 'Trying to re-reset() a fresh event.' - self.epoch = time.time() - self._result = NOT_USED - self._waiters = {} - - def wait(self): - """Wait until another coroutine calls send. - Returns the value the other coroutine passed to - send. - - >>> from eventlet import coros, api - >>> evt = coros.event() - >>> def wait_on(): - ... retval = evt.wait() - ... print "waited for", retval - >>> _ = api.spawn(wait_on) - >>> evt.send('result') - >>> api.sleep(0) - waited for result - - Returns immediately if the event has already - occured. - - >>> evt.wait() - 'result' - """ - if self._result is NOT_USED: - self._waiters[api.getcurrent()] = True - return api.get_hub().switch() - if self._exc is not None: - raise self._exc - return self._result - - def cancel(self, waiter): - """Raise an exception into a coroutine which called - wait() an this event instead of returning a value - from wait. Sends the eventlet.coros.Cancelled - exception - - waiter: The greenlet (greenlet.getcurrent()) of the - coroutine to cancel - - >>> from eventlet import coros, api - >>> evt = coros.event() - >>> def wait_on(): - ... try: - ... print "received " + evt.wait() - ... except coros.Cancelled, c: - ... print "Cancelled" - ... - >>> waiter = api.spawn(wait_on) - - The cancel call works on coroutines that are in the wait() call. - - >>> api.sleep(0) # enter the wait() - >>> evt.cancel(waiter) - >>> api.sleep(0) # receive the exception - Cancelled - - The cancel is invisible to coroutines that call wait() after cancel() - is called. This is different from send()'s behavior, where the result - is passed to any waiter regardless of the ordering of the calls. - - >>> waiter = api.spawn(wait_on) - >>> api.sleep(0) - - Cancels have no effect on the ability to send() to the event. - - >>> evt.send('stuff') - >>> api.sleep(0) - received stuff - """ - if waiter in self._waiters: - del self._waiters[waiter] - api.get_hub().schedule_call( - 0, greenlib.switch, waiter, None, Cancelled()) - - def send(self, result=None, exc=None): - """Makes arrangements for the waiters to be woken with the - result and then returns immediately to the parent. - - >>> from eventlet import coros, api - >>> evt = coros.event() - >>> def waiter(): - ... print 'about to wait' - ... result = evt.wait() - ... print 'waited for', result - >>> _ = api.spawn(waiter) - >>> api.sleep(0) - about to wait - >>> evt.send('a') - >>> api.sleep(0) - waited for a - - It is an error to call send() multiple times on the same event. - - >>> evt.send('whoops') - Traceback (most recent call last): - ... - AssertionError: Trying to re-send() an already-triggered event. - - Use reset() between send()s to reuse an event object. - """ - assert self._result is NOT_USED, 'Trying to re-send() an already-triggered event.' - self._result = result - self._exc = exc - hub = api.get_hub() - for waiter in self._waiters: - hub.schedule_call(0, greenlib.switch, waiter, self._result) - - -def execute(func, *args, **kw): - """ Executes an operation asynchronously in a new coroutine, returning - an event to retrieve the return value. - - This has the same api as the CoroutinePool.execute method; the only - difference is that this one creates a new coroutine instead of drawing - from a pool. - - >>> from eventlet import coros - >>> evt = coros.execute(lambda a: ('foo', a), 1) - >>> evt.wait() - ('foo', 1) - """ - evt = event() - def _really_execute(): - evt.send(func(*args, **kw)) - api.spawn(_really_execute) - return evt - - -class CoroutinePool(pools.Pool): - """ Like a thread pool, but with coroutines. - - Coroutine pools are useful for splitting up tasks or globally controlling - concurrency. You don't retrieve the coroutines directly with get() -- - instead use the execute() and execute_async() methods to run code. - - >>> from eventlet import coros, api - >>> p = coros.CoroutinePool(max_size=2) - >>> def foo(a): - ... print "foo", a - ... - >>> evt = p.execute(foo, 1) - >>> evt.wait() - foo 1 - - Once the pool is exhausted, calling an execute forces a yield. - - >>> p.execute_async(foo, 2) - >>> p.execute_async(foo, 3) - >>> p.free() - 0 - >>> p.execute_async(foo, 4) - foo 2 - foo 3 - - >>> api.sleep(0) - foo 4 - """ - - def __init__(self, min_size=0, max_size=4): - self._greenlets = set() - super(CoroutinePool, self).__init__(min_size, max_size) - - def _main_loop(self, sender): - """ Private, infinite loop run by a pooled coroutine. """ - while True: - recvd = sender.wait() - sender.reset() - (evt, func, args, kw) = recvd - self._safe_apply(evt, func, args, kw) - api.get_hub().runloop.cancel_timers(api.getcurrent()) - self.put(sender) - - def _safe_apply(self, evt, func, args, kw): - """ Private method that runs the function, catches exceptions, and - passes back the return value in the event.""" - try: - result = func(*args, **kw) - if evt is not None: - evt.send(result) - except api.GreenletExit, e: - # we're printing this out to see if it ever happens - # in practice - print "GreenletExit raised in coroutine pool", e - if evt is not None: - evt.send(e) # sent as a return value, not an exception - except KeyboardInterrupt: - raise # allow program to exit - except Exception, e: - traceback.print_exc() - if evt is not None: - evt.send(exc=e) - - def _execute(self, evt, func, args, kw): - """ Private implementation of the execute methods. - """ - # if reentering an empty pool, don't try to wait on a coroutine freeing - # itself -- instead, just execute in the current coroutine - if self.free() == 0 and api.getcurrent() in self._greenlets: - self._safe_apply(evt, func, args, kw) - else: - sender = self.get() - sender.send((evt, func, args, kw)) - - def create(self): - """Private implementation of eventlet.pools.Pool - interface. Creates an event and spawns the - _main_loop coroutine, passing the event. - The event is used to send a callable into the - new coroutine, to be executed. - """ - sender = event() - self._greenlets.add(api.spawn(self._main_loop, sender)) - return sender - - def execute(self, func, *args, **kw): - """Execute func in one of the coroutines maintained - by the pool, when one is free. - - Immediately returns an eventlet.coros.event object which - func's result will be sent to when it is available. - - >>> from eventlet import coros - >>> p = coros.CoroutinePool() - >>> evt = p.execute(lambda a: ('foo', a), 1) - >>> evt.wait() - ('foo', 1) - """ - receiver = event() - self._execute(receiver, func, args, kw) - return receiver - - def execute_async(self, func, *args, **kw): - """Execute func in one of the coroutines maintained - by the pool, when one is free. - - No return value is provided. - >>> from eventlet import coros, api - >>> p = coros.CoroutinePool() - >>> def foo(a): - ... print "foo", a - ... - >>> p.execute_async(foo, 1) - >>> api.sleep(0) - foo 1 - """ - self._execute(None, func, args, kw) - - -class pipe(object): - """ Implementation of pipe using events. Not tested! Not used, either.""" - def __init__(self): - self._event = event() - self._buffer = '' - - def send(self, txt): - self._buffer += txt - evt, self._event = self._event, event() - evt.send() - - def recv(self, num=16384): - if not self._buffer: - self._event.wait() - if num >= len(self._buffer): - buf, self._buffer = self._buffer, '' - else: - buf, self._buffer = self._buffer[:num], self._buffer[num:] - return buf - - -class Actor(object): - """ A free-running coroutine that accepts and processes messages. - - Kind of the equivalent of an Erlang process, really. It processes - a queue of messages in the order that they were sent. You must - subclass this and implement your own version of receive(). - - The actor's reference count will never drop to zero while the - coroutine exists; if you lose all references to the actor object - it will never be freed. - """ - def __init__(self, concurrency = 1): - """ Constructs an Actor, kicking off a new coroutine to process the messages. - - The concurrency argument specifies how many messages the actor will try - to process concurrently. If it is 1, the actor will process messages - serially. - """ - self._mailbox = collections.deque() - self._event = event() - self._killer = api.spawn(self.run_forever) - self._pool = CoroutinePool(min_size=0, max_size=concurrency) - - def run_forever(self): - """ Loops forever, continually checking the mailbox. """ - while True: - if not self._mailbox: - self._event.wait() - self._event = event() - else: - # leave the message in the mailbox until after it's - # been processed so the event doesn't get triggered - # while in the received method - self._pool.execute_async( - self.received, self._mailbox[0]) - self._mailbox.popleft() - - def cast(self, message): - """ Send a message to the actor. - - If the actor is busy, the message will be enqueued for later - consumption. There is no return value. - - >>> a = Actor() - >>> a.received = lambda msg: msg - >>> a.cast("hello") - """ - self._mailbox.append(message) - # if this is the only message, the coro could be waiting - if len(self._mailbox) == 1: - self._event.send() - - def received(self, message): - """ Called to process each incoming message. - - The default implementation just raises an exception, so - replace it with something useful! - - >>> class Greeter(Actor): - ... def received(self, (message, evt) ): - ... print "received", message - ... if evt: evt.send() - ... - >>> a = Greeter() - - This example uses events to synchronize between the actor and the main - coroutine in a predictable manner, but this kinda defeats the point of - the Actor, so don't do it in a real application. - - >>> evt = event() - >>> a.cast( ("message 1", evt) ) - >>> evt.wait() # force it to run at this exact moment - received message 1 - >>> evt.reset() - >>> a.cast( ("message 2", None) ) - >>> a.cast( ("message 3", evt) ) - >>> evt.wait() - received message 2 - received message 3 - - >>> api.kill(a._killer) # test cleanup - """ - raise NotImplementedError() - - -def _test(): - print "Running doctests. There will be no further output if they succeed." - import doctest - doctest.testmod() - -if __name__ == "__main__": - _test() diff --git a/eventlet/.svn/text-base/coros_test.py.svn-base b/eventlet/.svn/text-base/coros_test.py.svn-base deleted file mode 100644 index a72bc7a..0000000 --- a/eventlet/.svn/text-base/coros_test.py.svn-base +++ /dev/null @@ -1,300 +0,0 @@ -"""\ -@file coros_test.py -@author Donovan Preston, Ryan Williams - -Copyright (c) 2000-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 timer -from eventlet import coros, api - -class TestEvent(tests.TestCase): - mode = 'static' - def setUp(self): - # raise an exception if we're waiting forever - self._cancel_timeout = api.exc_after(1, RuntimeError()) - - def tearDown(self): - self._cancel_timeout.cancel() - - def test_waiting_for_event(self): - evt = coros.event() - value = 'some stuff' - def send_to_event(): - evt.send(value) - api.spawn(send_to_event) - self.assertEqual(evt.wait(), value) - - def test_multiple_waiters(self): - evt = coros.event() - value = 'some stuff' - results = [] - def wait_on_event(i_am_done): - evt.wait() - results.append(True) - i_am_done.send() - - waiters = [] - count = 5 - for i in range(count): - waiters.append(coros.event()) - api.spawn(wait_on_event, waiters[-1]) - evt.send() - - for w in waiters: - w.wait() - - self.assertEqual(len(results), count) - - def test_cancel(self): - evt = coros.event() - # close over the current coro so we can cancel it explicitly - current = api.getcurrent() - def cancel_event(): - evt.cancel(current) - api.spawn(cancel_event) - - self.assertRaises(coros.Cancelled, evt.wait) - - def test_reset(self): - evt = coros.event() - - # calling reset before send should throw - self.assertRaises(AssertionError, evt.reset) - - value = 'some stuff' - def send_to_event(): - evt.send(value) - api.spawn(send_to_event) - self.assertEqual(evt.wait(), value) - - # now try it again, and we should get the same exact value, - # and we shouldn't be allowed to resend without resetting - value2 = 'second stuff' - self.assertRaises(AssertionError, evt.send, value2) - self.assertEqual(evt.wait(), value) - - # reset and everything should be happy - evt.reset() - def send_to_event2(): - evt.send(value2) - api.spawn(send_to_event2) - self.assertEqual(evt.wait(), value2) - - def test_double_exception(self): - evt = coros.event() - # send an exception through the event - evt.send(exc=RuntimeError()) - self.assertRaises(RuntimeError, evt.wait) - evt.reset() - # shouldn't see the RuntimeError again - api.exc_after(0.001, api.TimeoutError) - self.assertRaises(api.TimeoutError, evt.wait) - -class TestCoroutinePool(tests.TestCase): - mode = 'static' - def setUp(self): - # raise an exception if we're waiting forever - self._cancel_timeout = api.exc_after(1, api.TimeoutError) - - def tearDown(self): - self._cancel_timeout.cancel() - - def test_execute_async(self): - done = coros.event() - def some_work(): - done.send() - pool = coros.CoroutinePool(0, 2) - pool.execute_async(some_work) - done.wait() - - def test_execute(self): - value = 'return value' - def some_work(): - return value - pool = coros.CoroutinePool(0, 2) - worker = pool.execute(some_work) - self.assertEqual(value, worker.wait()) - - def test_multiple_coros(self): - evt = coros.event() - results = [] - def producer(): - results.append('prod') - evt.send() - - def consumer(): - results.append('cons1') - evt.wait() - results.append('cons2') - - pool = coros.CoroutinePool(0, 2) - done = pool.execute(consumer) - pool.execute_async(producer) - done.wait() - self.assertEquals(['cons1', 'prod', 'cons2'], results) - - def test_timer_cancel(self): - def some_work(): - t = timer.Timer(5, lambda: None) - t.schedule() - return t - pool = coros.CoroutinePool(0, 2) - worker = pool.execute(some_work) - t = worker.wait() - api.sleep(0) - self.assertEquals(t.cancelled, True) - - def test_reentrant(self): - pool = coros.CoroutinePool(0,1) - def reenter(): - waiter = pool.execute(lambda a: a, 'reenter') - self.assertEqual('reenter', waiter.wait()) - - outer_waiter = pool.execute(reenter) - outer_waiter.wait() - - evt = coros.event() - def reenter_async(): - pool.execute_async(lambda a: a, 'reenter') - evt.send('done') - - pool.execute_async(reenter_async) - evt.wait() - - -class IncrActor(coros.Actor): - def received(self, evt): - self.value = getattr(self, 'value', 0) + 1 - if evt: evt.send() - -class TestActor(tests.TestCase): - mode = 'static' - def setUp(self): - # raise an exception if we're waiting forever - self._cancel_timeout = api.exc_after(1, api.TimeoutError()) - self.actor = IncrActor() - - def tearDown(self): - self._cancel_timeout.cancel() - api.kill(self.actor._killer) - - def test_cast(self): - evt = coros.event() - self.actor.cast(evt) - evt.wait() - evt.reset() - self.assertEqual(self.actor.value, 1) - self.actor.cast(evt) - evt.wait() - self.assertEqual(self.actor.value, 2) - - def test_cast_multi_1(self): - # make sure that both messages make it in there - evt = coros.event() - evt1 = coros.event() - self.actor.cast(evt) - self.actor.cast(evt1) - evt.wait() - evt1.wait() - self.assertEqual(self.actor.value, 2) - - def test_cast_multi_2(self): - # the actor goes through a slightly different code path if it - # is forced to enter its event loop prior to any cast()s - api.sleep(0) - self.test_cast_multi_1() - - def test_sleeping_during_received(self): - # ensure that even if the received method cooperatively - # yields, eventually all messages are delivered - msgs = [] - waiters = [] - def received( (message, evt) ): - api.sleep(0) - msgs.append(message) - evt.send() - self.actor.received = received - - waiters.append(coros.event()) - self.actor.cast( (1, waiters[-1])) - api.sleep(0) - waiters.append(coros.event()) - self.actor.cast( (2, waiters[-1]) ) - waiters.append(coros.event()) - self.actor.cast( (3, waiters[-1]) ) - api.sleep(0) - waiters.append(coros.event()) - self.actor.cast( (4, waiters[-1]) ) - waiters.append(coros.event()) - self.actor.cast( (5, waiters[-1]) ) - for evt in waiters: - evt.wait() - self.assertEqual(msgs, [1,2,3,4,5]) - - - def test_raising_received(self): - msgs = [] - def received( (message, evt) ): - evt.send() - if message == 'fail': - raise RuntimeError() - else: - msgs.append(message) - - self.actor.received = received - - evt = coros.event() - self.actor.cast( ('fail', evt) ) - evt.wait() - evt.reset() - self.actor.cast( ('should_appear', evt) ) - evt.wait() - self.assertEqual(['should_appear'], msgs) - - def test_multiple(self): - self.actor = IncrActor(concurrency=2) - total = [0] - def received( (func, ev, value) ): - func() - total[0] += value - ev.send() - self.actor.received = received - - def onemoment(): - api.sleep(0.1) - - evt = coros.event() - evt1 = coros.event() - - self.actor.cast( (onemoment, evt, 1) ) - self.actor.cast( (lambda: None, evt1, 2) ) - - evt1.wait() - self.assertEqual(total[0], 2) - # both coroutines should have been used - self.assertEqual(self.actor._pool.current_size, 2) - self.assertEqual(self.actor._pool.free(), 1) - evt.wait() - self.assertEqual(total[0], 3) - self.assertEqual(self.actor._pool.free(), 2) - -if __name__ == '__main__': - tests.main() diff --git a/eventlet/.svn/text-base/db_pool.py.svn-base b/eventlet/.svn/text-base/db_pool.py.svn-base deleted file mode 100644 index 3c91793..0000000 --- a/eventlet/.svn/text-base/db_pool.py.svn-base +++ /dev/null @@ -1,220 +0,0 @@ -"""\ -@file db_pool.py -@brief Uses saranwrap to implement a pool of nonblocking database connections to a db server. - -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 os, sys - -from eventlet.pools import Pool -from eventlet.processes import DeadProcess -from eventlet import saranwrap - -class DatabaseConnector(object): - """\ -@brief This is an object which will maintain a collection of database -connection pools keyed on host,databasename""" - def __init__(self, module, credentials, min_size = 0, max_size = 4, conn_pool=None, *args, **kwargs): - """\ - @brief constructor - @param min_size the minimum size of a child pool. - @param max_size the maximum size of a child pool.""" - assert(module) - self._conn_pool_class = conn_pool - if self._conn_pool_class is None: - self._conn_pool_class = ConnectionPool - self._module = module - self._min_size = min_size - self._max_size = max_size - self._args = args - self._kwargs = kwargs - self._credentials = credentials # this is a map of hostname to username/password - self._databases = {} - - def credentials_for(self, host): - if host in self._credentials: - return self._credentials[host] - else: - return self._credentials.get('default', None) - - def get(self, host, dbname): - key = (host, dbname) - if key not in self._databases: - new_kwargs = self._kwargs.copy() - new_kwargs['db'] = dbname - new_kwargs['host'] = host - new_kwargs.update(self.credentials_for(host)) - dbpool = self._conn_pool_class(self._module, min_size=self._min_size, max_size=self._max_size, - *self._args, **new_kwargs) - self._databases[key] = dbpool - - return self._databases[key] - -class BaseConnectionPool(Pool): - # *TODO: we need to expire and close connections if they've been - # idle for a while, so that system-wide connection count doesn't - # monotonically increase forever - def __init__(self, db_module, min_size = 0, max_size = 4, *args, **kwargs): - assert(db_module) - self._db_module = db_module - self._args = args - self._kwargs = kwargs - super(BaseConnectionPool, self).__init__(min_size, max_size) - - def get(self): - # wrap the connection for easier use - conn = super(BaseConnectionPool, self).get() - return PooledConnectionWrapper(conn, self) - - def put(self, conn): - # rollback any uncommitted changes, so that the next client - # has a clean slate. This also pokes the connection to see if - # it's dead or None - try: - conn.rollback() - except AttributeError, e: - # this means it's already been destroyed, so we don't need to print anything - conn = None - except: - # we don't care what the exception was, we just know the - # connection is dead - print "WARNING: connection.rollback raised: %s" % (sys.exc_info()[1]) - conn = None - - # unwrap the connection for storage - if isinstance(conn, GenericConnectionWrapper): - if conn: - base = conn._base - conn._destroy() - conn = base - else: - conn = None - - if conn is not None: - super(BaseConnectionPool, self).put(conn) - else: - self.current_size -= 1 - - -class SaranwrappedConnectionPool(BaseConnectionPool): - """A pool which gives out saranwrapped database connections from a pool - """ - def create(self): - return saranwrap.wrap(self._db_module).connect(*self._args, **self._kwargs) - -class TpooledConnectionPool(BaseConnectionPool): - """A pool which gives out tpool.Proxy-based database connections from a pool. - """ - def create(self): - from eventlet import tpool - try: - # *FIX: this is a huge hack that will probably only work for MySQLdb - autowrap = (self._db_module.cursors.DictCursor,) - except: - autowrap = () - return tpool.Proxy(self._db_module.connect(*self._args, **self._kwargs), - autowrap=autowrap) - -class RawConnectionPool(BaseConnectionPool): - """A pool which gives out plain database connections from a pool. - """ - def create(self): - return self._db_module.connect(*self._args, **self._kwargs) - -# default connection pool is the tpool one -ConnectionPool = TpooledConnectionPool - - -class GenericConnectionWrapper(object): - def __init__(self, baseconn): - self._base = baseconn - def __enter__(self): return self._base.__enter__() - def __exit__(self, exc, value, tb): return self._base.__exit__(exc, value, tb) - def __repr__(self): return self._base.__repr__() - def affected_rows(self): return self._base.affected_rows() - def autocommit(self,*args, **kwargs): return self._base.autocommit(*args, **kwargs) - def begin(self): return self._base.begin() - def change_user(self,*args, **kwargs): return self._base.change_user(*args, **kwargs) - def character_set_name(self,*args, **kwargs): return self._base.character_set_name(*args, **kwargs) - def close(self,*args, **kwargs): return self._base.close(*args, **kwargs) - def commit(self,*args, **kwargs): return self._base.commit(*args, **kwargs) - def cursor(self, cursorclass=None, **kwargs): return self._base.cursor(cursorclass, **kwargs) - def dump_debug_info(self,*args, **kwargs): return self._base.dump_debug_info(*args, **kwargs) - def errno(self,*args, **kwargs): return self._base.errno(*args, **kwargs) - def error(self,*args, **kwargs): return self._base.error(*args, **kwargs) - def errorhandler(self, conn, curs, errcls, errval): return self._base.errorhandler(conn, curs, errcls, errval) - def literal(self, o): return self._base.literal(o) - def set_character_set(self, charset): return self._base.set_character_set(charset) - def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) - def show_warnings(self): return self._base.show_warnings() - def warning_count(self): return self._base.warning_count() - def literal(self, o): return self._base.literal(o) - def ping(self,*args, **kwargs): return self._base.ping(*args, **kwargs) - def query(self,*args, **kwargs): return self._base.query(*args, **kwargs) - def rollback(self,*args, **kwargs): return self._base.rollback(*args, **kwargs) - def select_db(self,*args, **kwargs): return self._base.select_db(*args, **kwargs) - def set_server_option(self,*args, **kwargs): return self._base.set_server_option(*args, **kwargs) - def set_character_set(self, charset): return self._base.set_character_set(charset) - def set_sql_mode(self, sql_mode): return self._base.set_sql_mode(sql_mode) - def server_capabilities(self,*args, **kwargs): return self._base.server_capabilities(*args, **kwargs) - def show_warnings(self): return self._base.show_warnings() - def shutdown(self,*args, **kwargs): return self._base.shutdown(*args, **kwargs) - def sqlstate(self,*args, **kwargs): return self._base.sqlstate(*args, **kwargs) - def stat(self,*args, **kwargs): return self._base.stat(*args, **kwargs) - def store_result(self,*args, **kwargs): return self._base.store_result(*args, **kwargs) - def string_literal(self,*args, **kwargs): return self._base.string_literal(*args, **kwargs) - def thread_id(self,*args, **kwargs): return self._base.thread_id(*args, **kwargs) - def use_result(self,*args, **kwargs): return self._base.use_result(*args, **kwargs) - def warning_count(self): return self._base.warning_count() - - -class PooledConnectionWrapper(GenericConnectionWrapper): - """ A connection wrapper where: - - the close method returns the connection to the pool instead of closing it directly - - you can do if conn: - - returns itself to the pool if it gets garbage collected - """ - def __init__(self, baseconn, pool): - super(PooledConnectionWrapper, self).__init__(baseconn) - self._pool = pool - - def __nonzero__(self): - return (hasattr(self, '_base') and bool(self._base)) - - def _destroy(self): - self._pool = None - try: - del self._base - except AttributeError: - pass - - def close(self): - """ Return the connection to the pool, and remove the - reference to it so that you can't use it again through this - wrapper object. - """ - if self and self._pool: - self._pool.put(self) - self._destroy() - - def __del__(self): - self.close() diff --git a/eventlet/.svn/text-base/db_pool_test.py.svn-base b/eventlet/.svn/text-base/db_pool_test.py.svn-base deleted file mode 100644 index df1a043..0000000 --- a/eventlet/.svn/text-base/db_pool_test.py.svn-base +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/python -# @file test_mysql_pool.py -# @brief Test cases for mysql_pool -# -# 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 os.path - -from eventlet import api, coros, tests -from eventlet import db_pool - -class DBTester(object): - def setUp(self): - self.create_db() - self.connection = None - connection = self._dbmodule.connect(**self._auth) - cursor = connection.cursor() - cursor.execute("""CREATE TABLE gargleblatz - ( - a INTEGER - ) ENGINE = InnoDB;""") - connection.commit() - cursor.close() - - def tearDown(self): - if self.connection is not None: - self.connection.close() - self.drop_db() - - def set_up_test_table(self, connection = None): - if connection is None: - if self.connection is None: - self.connection = self._dbmodule.connect(**self._auth) - connection = self.connection - - cursor = connection.cursor() - cursor.execute("""CREATE TEMPORARY TABLE test_table - ( - row_id INTEGER PRIMARY KEY AUTO_INCREMENT, - value_int INTEGER, - value_float FLOAT, - value_string VARCHAR(200), - value_uuid CHAR(36), - value_binary BLOB, - value_binary_string VARCHAR(200) BINARY, - value_enum ENUM('Y','N'), - created TIMESTAMP - ) ENGINE = InnoDB;""") - connection.commit() - cursor.close() - -class TestDBConnectionPool(DBTester): - def setUp(self): - super(TestDBConnectionPool, self).setUp() - self.pool = self.create_pool() - self.connection = self.pool.get() - - def tearDown(self): - self.pool.put(self.connection) - super(TestDBConnectionPool, self).tearDown() - - def assert_cursor_works(self, cursor): - cursor.execute("show full processlist") - rows = cursor.fetchall() - self.assert_(rows) - - def test_connecting(self): - self.assert_(self.connection is not None) - - def test_create_cursor(self): - cursor = self.connection.cursor() - cursor.close() - - def test_run_query(self): - cursor = self.connection.cursor() - self.assert_cursor_works(cursor) - cursor.close() - - def test_run_bad_query(self): - cursor = self.connection.cursor() - try: - cursor.execute("garbage blah blah") - self.assert_(False) - except AssertionError: - raise - except Exception, e: - pass - cursor.close() - - def test_put_none(self): - # the pool is of size 1, and its only connection is out - self.assert_(self.pool.free() == 0) - self.pool.put(None) - # ha ha we fooled it into thinking that we had a dead process - self.assert_(self.pool.free() == 1) - conn2 = self.pool.get() - self.assert_(conn2 is not None) - self.assert_(conn2.cursor) - del conn2 - - def test_close_does_a_put(self): - self.assert_(self.pool.free() == 0) - self.connection.close() - self.assert_(self.pool.free() == 1) - self.assertRaises(AttributeError, self.connection.cursor) - - def test_deletion_does_a_put(self): - self.assert_(self.pool.free() == 0) - self.connection = None - self.assert_(self.pool.free() == 1) - - def test_put_doesnt_double_wrap(self): - self.pool.put(self.connection) - conn = self.pool.get() - self.assert_(not isinstance(conn._base, db_pool.PooledConnectionWrapper)) - - def test_bool(self): - self.assert_(self.connection) - self.connection.close() - self.assert_(not self.connection) - - def fill_test_table(self, conn): - curs = conn.cursor() - for i in range(1000): - curs.execute('insert into test_table (value_int) values (%s)' % i) - conn.commit() - - def test_returns_immediately(self): - self.pool = self.create_pool() - conn = self.pool.get() - self.set_up_test_table(conn) - self.fill_test_table(conn) - curs = conn.cursor() - results = [] - SHORT_QUERY = "select * from test_table" - evt = coros.event() - def a_query(): - self.assert_cursor_works(curs) - curs.execute(SHORT_QUERY) - results.append(2) - evt.send() - evt2 = coros.event() - api.spawn(a_query) - results.append(1) - self.assertEqual([1], results) - evt.wait() - self.assertEqual([1, 2], results) - - def test_connection_is_clean_after_put(self): - self.pool = self.create_pool() - conn = self.pool.get() - self.set_up_test_table(conn) - curs = conn.cursor() - for i in range(10): - curs.execute('insert into test_table (value_int) values (%s)' % i) - # do not commit :-) - self.pool.put(conn) - del conn - conn2 = self.pool.get() - curs2 = conn2.cursor() - for i in range(10): - curs2.execute('insert into test_table (value_int) values (%s)' % i) - conn2.commit() - rows = curs2.execute("select * from test_table") - # we should have only inserted them once - self.assertEqual(10, rows) - - def test_visibility_from_other_connections(self): - # *FIX: use some non-indra-specific table for testing (can't use a temp table) - self.pool = self.create_pool(3) - conn = self.pool.get() - conn2 = self.pool.get() - curs = conn.cursor() - try: - curs2 = conn2.cursor() - rows2 = curs2.execute("insert into gargleblatz (a) values (%s)" % (314159)) - self.assertEqual(rows2, 1) - conn2.commit() - selection_query = "select * from gargleblatz" - rows2 = curs2.execute(selection_query) - self.assertEqual(rows2, 1) - del curs2 - del conn2 - # create a new connection, it should see the addition - conn3 = self.pool.get() - curs3 = conn3.cursor() - rows3 = curs3.execute(selection_query) - self.assertEqual(rows3, 1) - # now, does the already-open connection see it? - rows = curs.execute(selection_query) - self.assertEqual(rows, 1) - finally: - # clean up my litter - curs.execute("delete from gargleblatz where a=314159") - conn.commit() - - - def test_two_simultaneous_connections(self): - self.pool = self.create_pool(2) - conn = self.pool.get() - self.set_up_test_table(conn) - self.fill_test_table(conn) - curs = conn.cursor() - conn2 = self.pool.get() - self.set_up_test_table(conn2) - self.fill_test_table(conn2) - curs2 = conn2.cursor() - results = [] - LONG_QUERY = "select * from test_table" - SHORT_QUERY = "select * from test_table where row_id <= 20" - - evt = coros.event() - def long_running_query(): - self.assert_cursor_works(curs) - curs.execute(LONG_QUERY) - results.append(1) - evt.send() - evt2 = coros.event() - def short_running_query(): - self.assert_cursor_works(curs2) - curs2.execute(SHORT_QUERY) - results.append(2) - evt2.send() - - api.spawn(long_running_query) - api.spawn(short_running_query) - evt.wait() - evt2.wait() - #print "results %s" % results - results.sort() - self.assertEqual([1, 2], results) - - -class TestTpoolConnectionPool(TestDBConnectionPool): - def create_pool(self, max_items = 1): - return db_pool.TpooledConnectionPool(self._dbmodule, 0, max_items, **self._auth) - - -class TestSaranwrapConnectionPool(TestDBConnectionPool): - def create_pool(self, max_items = 1): - return db_pool.SaranwrappedConnectionPool(self._dbmodule, 0, max_items, **self._auth) - -class TestMysqlConnectionPool(object): - def setUp(self): - import MySQLdb - self._dbmodule = MySQLdb - try: - import simplejson - import os.path - auth_utf8 = simplejson.load(open(os.path.join(os.path.dirname(__file__), 'auth.json'))) - # have to convert unicode objects to str objects because mysqldb is dum - self._auth = dict([(str(k), str(v)) - for k, v in auth_utf8.items()]) - except (IOError, ImportError), e: - self._auth = {'host': 'localhost','user': 'root','passwd': '','db': 'persist0'} - super(TestMysqlConnectionPool, self).setUp() - - def create_db(self): - auth = self._auth.copy() - try: - self.drop_db() - except Exception: - pass - dbname = auth.pop('db') - db = self._dbmodule.connect(**auth).cursor() - db.execute("create database "+dbname) - db.close() - del db - - def drop_db(self): - db = self._dbmodule.connect(**self._auth).cursor() - db.execute("drop database "+self._auth['db']) - db.close() - del db - -class TestMysqlTpool(TestMysqlConnectionPool, TestTpoolConnectionPool, tests.TestCase): - pass - -class TestMysqlSaranwrap(TestMysqlConnectionPool, TestSaranwrapConnectionPool, tests.TestCase): - pass - - -if __name__ == '__main__': - try: - import MySQLdb - except ImportError: - print "Unable to import MySQLdb, skipping db_pool_test." - else: - tests.main() -else: - import MySQLdb diff --git a/eventlet/.svn/text-base/greenlib.py.svn-base b/eventlet/.svn/text-base/greenlib.py.svn-base deleted file mode 100644 index 1dbf01e..0000000 --- a/eventlet/.svn/text-base/greenlib.py.svn-base +++ /dev/null @@ -1,331 +0,0 @@ -"""\ -@file greenlib.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 itertools - -import greenlet - -from eventlet import tls - -__all__ = [ - 'switch', 'kill', 'tracked_greenlets', - 'greenlet_id', 'greenlet_dict', 'GreenletContext', - 'tracked_greenlet', -] - -try: - reversed -except NameError: - def reversed(something): - for x in something[::-1]: - yield x - -_threadlocal = tls.local() - -def tracked_greenlet(): - """ - Returns a greenlet that has a greenlet-local dictionary and can be - used with GreenletContext and enumerated with tracked_greenlets - """ - return greenlet.greenlet(greenlet_body) - -class GreenletContextManager(object): - """ - Per-thread manager for GreenletContext. Created lazily on registration - """ - def __new__(cls, *args, **kw): - dct = greenlet_dict() - self = dct.get('greenlet_context', None) - if self is not None: - return self - self = super(GreenletContextManager, cls).__new__(cls, *args, **kw) - dct['greenlet_context'] = self - self.contexts = [] - return self - - def add_context(self, ctx): - fn = getattr(ctx, '_swap_in', None) - if fn is not None: - fn() - self.contexts.append(ctx) - - def remove_context(self, ctx): - try: - idx = self.contexts.index(ctx) - except ValueError: - return - else: - del self.contexts[idx] - fn = getattr(ctx, '_swap_out', None) - if fn is not None: - fn() - fn = getattr(ctx, '_finalize', None) - if fn is not None: - fn() - - def swap_in(self): - for ctx in self.contexts: - fn = getattr(ctx, '_swap_in', None) - if fn is not None: - fn() - - def swap_out(self): - for ctx in reversed(self.contexts): - fn = getattr(ctx, '_swap_out', None) - if fn is not None: - fn() - - def finalize(self): - for ctx in reversed(self.contexts): - fn = getattr(ctx, '_swap_out', None) - if fn is not None: - fn() - fn = getattr(ctx, '_finalize', None) - if fn is not None: - fn() - del self.contexts[:] - try: - del greenlet_dict()['greenlet_context'] - except KeyError: - pass - -class GreenletContext(object): - """ - A context manager to be triggered when a specific tracked greenlet is - swapped in, swapped out, or finalized. - - To use, subclass and override the swap_in, swap_out, and/or finalize - methods, for example:: - - import greenlib - from greenlib import greenlet_id, tracked_greenlet, switch - - class NotifyContext(greenlib.GreenletContext): - - def swap_in(self): - print "swap_in" - - def swap_out(self): - print "swap_out" - - def finalize(self): - print "finalize" - - def another_greenlet(): - print "another_greenlet" - - def notify_demo(): - print "starting" - NotifyContext().register() - switch(tracked_greenlet(), (another_greenlet,)) - print "finishing" - # we could have kept the NotifyContext object - # to unregister it here but finalization of all - # contexts is implicit when the greenlet returns - - t = tracked_greenlet() - switch(t, (notify_demo,)) - - The output should be: - - starting - swap_in - swap_out - another_greenlet - swap_in - finishing - swap_out - finalize - - """ - _balance = 0 - - def _swap_in(self): - if self._balance != 0: - raise RuntimeError("balance != 0: %r" % (self._balance,)) - self._balance = self._balance + 1 - fn = getattr(self, 'swap_in', None) - if fn is not None: - fn() - - def _swap_out(self): - if self._balance != 1: - raise RuntimeError("balance != 1: %r" % (self._balance,)) - self._balance = self._balance - 1 - fn = getattr(self, 'swap_out', None) - if fn is not None: - fn() - - def register(self): - GreenletContextManager().add_context(self) - - def unregister(self): - GreenletContextManager().remove_context(self) - - def _finalize(self): - fn = getattr(self, 'finalize', None) - if fn is not None: - fn() - - -def kill(g): - """ - Kill the given greenlet if it is alive by sending it a GreenletExit. - - Note that of any other exception is raised, it will pass-through! - """ - if not g: - return - kill_exc = greenlet.GreenletExit() - try: - try: - g.parent = greenlet.getcurrent() - except ValueError: - pass - try: - switch(g, exc=kill_exc) - except SwitchingToDeadGreenlet: - pass - except greenlet.GreenletExit, e: - if e is not kill_exc: - raise - -def tracked_greenlets(): - """ - Return a list of greenlets tracked in this thread. Tracked greenlets - use greenlet_body() to ensure that they have greenlet-local storage. - """ - try: - return _threadlocal.greenlets.keys() - except AttributeError: - return [] - -def greenlet_id(): - """ - Get the id of the current tracked greenlet, returns None if the - greenlet is not tracked. - """ - try: - d = greenlet_dict() - except RuntimeError: - return None - return d['greenlet_id'] - -def greenlet_dict(): - """ - Return the greenlet local storage for this greenlet. Raises RuntimeError - if this greenlet is not tracked. - """ - self = greenlet.getcurrent() - try: - return _threadlocal.greenlets[self] - except (AttributeError, KeyError): - raise RuntimeError("greenlet %r is not tracked" % (self,)) - -def _greenlet_context(dct=None): - if dct is None: - try: - dct = greenlet_dict() - except RuntimeError: - return None - return dct.get('greenlet_context', None) - -def _greenlet_context_call(name, dct=None): - ctx = _greenlet_context(dct) - fn = getattr(ctx, name, None) - if fn is not None: - fn() - -def greenlet_body(value, exc): - """ - Track the current greenlet during the execution of the given callback, - normally you would use tracked_greenlet() to get a greenlet that uses this. - - Greenlets using this body must be greenlib.switch()'ed to - """ - if exc is not None: - if isinstance(exc, tuple): - raise exc[0], exc[1], exc[2] - raise exc - cb, args = value[0], value[1:] - try: - greenlets = _threadlocal.greenlets - except AttributeError: - greenlets = _threadlocal.greenlets = {} - else: - if greenlet.getcurrent() in greenlets: - raise RuntimeError("greenlet_body can not be called recursively!") - try: - greenlet_id = _threadlocal.next_greenlet_id.next() - except AttributeError: - greenlet_id = 1 - _threadlocal.next_greenlet_id = itertools.count(2) - greenlets[greenlet.getcurrent()] = {'greenlet_id': greenlet_id} - try: - return cb(*args) - finally: - _greenlet_context_call('finalize') - greenlets.pop(greenlet.getcurrent(), None) - - -class SwitchingToDeadGreenlet(RuntimeError): - pass - - -def switch(other=None, value=None, exc=None): - """ - Switch to another greenlet, passing value or exception - """ - self = greenlet.getcurrent() - if other is None: - other = self.parent - if other is None: - other = self - if not (other or hasattr(other, 'run')): - raise SwitchingToDeadGreenlet("Switching to dead greenlet %r %r %r" % (other, value, exc)) - _greenlet_context_call('swap_out') - sys.exc_clear() # don't pass along exceptions to the other coroutine - try: - rval = other.switch(value, exc) - if not rval or not other: - res, exc = rval, None - else: - res, exc = rval - except: - res, exc = None, sys.exc_info() - _greenlet_context_call('swap_in') - # *NOTE: we don't restore exc_info, so don't switch inside an - # exception handler and then call sys.exc_info() or use bare - # raise. Instead, explicitly save off the exception before - # switching. We need an extension that allows us to restore the - # exception state at this point because vanilla Python doesn't - # allow that. - if isinstance(exc, tuple): - typ, exc, tb = exc - raise typ, exc, tb - elif exc is not None: - raise exc - - return res diff --git a/eventlet/.svn/text-base/httpc.py.svn-base b/eventlet/.svn/text-base/httpc.py.svn-base deleted file mode 100644 index 63c6caf..0000000 --- a/eventlet/.svn/text-base/httpc.py.svn-base +++ /dev/null @@ -1,597 +0,0 @@ -"""\ -@file httpc.py -@author Donovan Preston - -Copyright (c) 2005-2006, 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 datetime -import httplib -import os.path -import os -import time -import urlparse - - -_old_HTTPConnection = httplib.HTTPConnection -_old_HTTPSConnection = httplib.HTTPSConnection - - -HTTP_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT' -to_http_time = lambda t: time.strftime(HTTP_TIME_FORMAT, time.gmtime(t)) - -try: - - from mx import DateTime - def from_http_time(t, defaultdate=None): - return int(DateTime.Parser.DateTimeFromString( - t, defaultdate=defaultdate).gmticks()) - -except ImportError: - - import calendar - parse_formats = (HTTP_TIME_FORMAT, # RFC 1123 - '%A, %d-%b-%y %H:%M:%S GMT', # RFC 850 - '%a %b %d %H:%M:%S %Y') # asctime - def from_http_time(t, defaultdate=None): - for parser in parse_formats: - try: - return calendar.timegm(time.strptime(t, parser)) - except ValueError: - continue - return defaultdate - - -def host_and_port_from_url(url): - """@brief Simple function to get host and port from an http url. - @return Returns host, port and port may be None. - """ - host = None - port = None - parsed_url = urlparse.urlparse(url) - try: - host, port = parsed_url[1].split(':') - except ValueError: - host = parsed_url[1].split(':') - return host, port - - -def better_putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): - self.method = method - self.path = url - try: - # Python 2.4 and above - self.old_putrequest(method, url, skip_host, skip_accept_encoding) - except TypeError: - # Python 2.3 and below - self.old_putrequest(method, url, skip_host) - - -class HttpClient(httplib.HTTPConnection): - """A subclass of httplib.HTTPConnection that provides a better - putrequest that records the method and path on the request object. - """ - def __init__(self, host, port=None, strict=None): - _old_HTTPConnection.__init__(self, host, port, strict) - - old_putrequest = httplib.HTTPConnection.putrequest - putrequest = better_putrequest - -class HttpsClient(httplib.HTTPSConnection): - """A subclass of httplib.HTTPSConnection that provides a better - putrequest that records the method and path on the request object. - """ - old_putrequest = httplib.HTTPSConnection.putrequest - putrequest = better_putrequest - - -def wrap_httplib_with_httpc(): - """Replace httplib's implementations of these classes with our enhanced ones. - - Needed to work around code that uses httplib directly.""" - httplib.HTTP._connection_class = httplib.HTTPConnection = HttpClient - httplib.HTTPS._connection_class = httplib.HTTPSConnection = HttpsClient - - - -class FileScheme(object): - """Retarded scheme to local file wrapper.""" - host = '' - port = '' - reason = '' - - def __init__(self, location): - pass - - def request(self, method, fullpath, body='', headers=None): - self.status = 200 - self.msg = '' - self.path = fullpath.split('?')[0] - self.method = method = method.lower() - assert method in ('get', 'put', 'delete') - if method == 'delete': - try: - os.remove(self.path) - except OSError: - pass # don't complain if already deleted - elif method == 'put': - try: - f = file(self.path, 'w') - f.write(body) - f.close() - except IOError, e: - self.status = 500 - self.raise_connection_error() - elif method == 'get': - if not os.path.exists(self.path): - self.status = 404 - self.raise_connection_error(NotFound) - - def connect(self): - pass - - def getresponse(self): - return self - - def getheader(self, header): - if header == 'content-length': - try: - return os.path.getsize(self.path) - except OSError: - return 0 - - def read(self, howmuch=None): - if self.method == 'get': - try: - fl = file(self.path, 'r') - if howmuch is None: - return fl.read() - else: - return fl.read(howmuch) - except IOError: - self.status = 500 - self.raise_connection_error() - return '' - - def raise_connection_error(self, klass=None): - if klass is None: - klass=ConnectionError - raise klass(_Params('file://' + self.path, self.method)) - - def close(self): - """We're challenged here, and read the whole file rather than - integrating with this lib. file object already out of scope at this - point""" - pass - -class _Params(object): - def __init__(self, url, method, body='', headers=None, dumper=None, - loader=None, use_proxy=False, ok=(), aux=None): - ''' - @param connection The connection (as returned by make_connection) to use for the request. - @param method HTTP method - @param url Full url to make request on. - @param body HTTP body, if necessary for the method. Can be any object, assuming an appropriate dumper is also provided. - @param headers Dict of header name to header value - @param dumper Method that formats the body as a string. - @param loader Method that converts the response body into an object. - @param use_proxy Set to True if the connection is to a proxy. - @param ok Set of valid response statuses. If the returned status is not in this list, an exception is thrown. - ''' - self.instance = None - self.url = url - self.path = url - self.method = method - self.body = body - if headers is None: - self.headers = {} - else: - self.headers = headers - self.dumper = dumper - self.loader = loader - self.use_proxy = use_proxy - self.ok = ok or (200, 201, 204) - self.orig_body = body - self.aux = aux - - -class _LocalParams(_Params): - def __init__(self, params, **kwargs): - self._delegate = params - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - def __getattr__(self, key): - return getattr(self._delegate, key) - - -class ConnectionError(Exception): - """Detailed exception class for reporting on http connection problems. - - There are lots of subclasses so you can use closely-specified - exception clauses.""" - def __init__(self, params): - self.params = params - Exception.__init__(self) - - def location(self): - return self.params.response.msg.dict.get('location') - - def expired(self): - # 14.21 Expires - # - # HTTP/1.1 clients and caches MUST treat other invalid date - # formats, especially including the value "0", as in the past - # (i.e., "already expired"). - expires = from_http_time( - self.params.response_headers.get('expires', '0'), - defaultdate=DateTime.Epoch) - return time.time() > expires - - def __repr__(self): - response = self.params.response - return "%s(url=%r, method=%r, status=%r, reason=%r, body=%r)" % ( - self.__class__.__name__, self.params.url, self.params.method, - response.status, response.reason, self.params.body) - - __str__ = __repr__ - - -class UnparseableResponse(ConnectionError): - """Raised when a loader cannot parse the response from the server.""" - def __init__(self, content_type, response, url): - self.content_type = content_type - self.response = response - self.url = url - Exception.__init__(self) - - def __repr__(self): - return "Could not parse the data at the URL %r of content-type %r\nData:\n%r)" % ( - self.url, self.content_type, self.response) - - __str__ = __repr__ - - -class Accepted(ConnectionError): - """ 202 Accepted """ - pass - - -class Retriable(ConnectionError): - def retry_method(self): - return self.params.method - - def retry_url(self): - return self.location() or self.url() - - def retry_(self): - params = _LocalParams(self.params, - url=self.retry_url(), - method=self.retry_method()) - return self.params.instance.request_(params) - - def retry(self): - return self.retry_()[-1] - - -class MovedPermanently(Retriable): - """ 301 Moved Permanently """ - pass - - -class Found(Retriable): - """ 302 Found """ - - pass - - -class SeeOther(Retriable): - """ 303 See Other """ - - def retry_method(self): - return 'GET' - - -class NotModified(ConnectionError): - """ 304 Not Modified """ - pass - - -class TemporaryRedirect(Retriable): - """ 307 Temporary Redirect """ - pass - - -class BadRequest(ConnectionError): - """ 400 Bad Request """ - pass - - -class Forbidden(ConnectionError): - """ 403 Forbidden """ - pass - - -class NotFound(ConnectionError): - """ 404 Not Found """ - pass - - -class Gone(ConnectionError): - """ 410 Gone """ - pass - - -class ServiceUnavailable(Retriable): - """ 503 Service Unavailable """ - def url(self): - return self.params._delegate.url - - -class GatewayTimeout(Retriable): - """ 504 Gateway Timeout """ - def url(self): - return self.params._delegate.url - - -class InternalServerError(ConnectionError): - """ 500 Internal Server Error """ - def __repr__(self): - try: - import simplejson - traceback = simplejson.loads(self.params.response_body) - except: - try: - from indra.base import llsd - traceback = llsd.parse(self.params.response_body) - except: - traceback = self.params.response_body - if isinstance(traceback, dict): - body = traceback - traceback = "Traceback (most recent call last):\n" - for frame in body['stack-trace']: - traceback += ' File "%s", line %s, in %s\n' % ( - frame['filename'], frame['lineno'], frame['method']) - for line in frame['code']: - if line['lineno'] == frame['lineno']: - traceback += ' %s' % (line['line'].lstrip(), ) - break - traceback += body['description'] - return "The server raised an exception from our request:\n%s %s\n%s %s\n%s" % ( - self.params.method, self.params.url, self.params.response.status, self.params.response.reason, traceback) - __str__ = __repr__ - - - -status_to_error_map = { - 202: Accepted, - 301: MovedPermanently, - 302: Found, - 303: SeeOther, - 304: NotModified, - 307: TemporaryRedirect, - 400: BadRequest, - 403: Forbidden, - 404: NotFound, - 410: Gone, - 500: InternalServerError, - 503: ServiceUnavailable, - 504: GatewayTimeout, -} - -scheme_to_factory_map = { - 'http': HttpClient, - 'https': HttpsClient, - 'file': FileScheme, -} - - -def make_connection(scheme, location, use_proxy): - """ Create a connection object to a host:port. - - @param scheme Protocol, scheme, whatever you want to call it. http, file, https are currently supported. - @param location Hostname and port number, formatted as host:port or http://host:port if you're so inclined. - @param use_proxy Connect to a proxy instead of the actual location. Uses environment variables to decide where the proxy actually lives. - """ - if use_proxy: - if "http_proxy" in os.environ: - location = os.environ["http_proxy"] - elif "ALL_PROXY" in os.environ: - location = os.environ["ALL_PROXY"] - else: - location = "localhost:3128" #default to local squid - - # run a little heuristic to see if location is an url, and if so parse out the hostpart - if location.startswith('http'): - _scheme, location, path, parameters, query, fragment = urlparse.urlparse(location) - - result = scheme_to_factory_map[scheme](location) - result.connect() - return result - - -def connect(url, use_proxy=False): - """ Create a connection object to the host specified in a url. Convenience function for make_connection.""" - scheme, location = urlparse.urlparse(url)[:2] - return make_connection(scheme, location, use_proxy) - - -def make_safe_loader(loader): - if not callable(loader): - return loader - def safe_loader(what): - try: - return loader(what) - except Exception: - import traceback - traceback.print_exc() - return None - return safe_loader - - -class HttpSuite(object): - def __init__(self, dumper, loader, fallback_content_type): - self.dumper = dumper - self.loader = loader - self.fallback_content_type = fallback_content_type - - def request_(self, params): - '''Make an http request to a url, for internal use mostly.''' - - params = _LocalParams(params, instance=self) - - (scheme, location, path, parameters, query, - fragment) = urlparse.urlparse(params.url) - - if params.use_proxy: - if scheme == 'file': - params.use_proxy = False - else: - params.headers['host'] = location - - if not params.use_proxy: - params.path = path - if query: - params.path += '?' + query - - params.orig_body = params.body - - if params.method in ('PUT', 'POST'): - if self.dumper is not None: - params.body = self.dumper(params.body) - # don't set content-length header because httplib does it - # for us in _send_request - else: - params.body = '' - - params.response, params.response_body = self._get_response_body(params) - response, body = params.response, params.response_body - - if self.loader is not None: - try: - body = make_safe_loader(self.loader(body)) - except KeyboardInterrupt: - raise - except Exception, e: - raise UnparseableResponse(self.loader, body, params.url) - - return response.status, response.msg, body - - def _check_status(self, params): - response = params.response - if response.status not in params.ok: - klass = status_to_error_map.get(response.status, ConnectionError) - raise klass(params) - - def _get_response_body(self, params): - connection = connect(params.url, params.use_proxy) - connection.request(params.method, params.path, params.body, - params.headers) - params.response = connection.getresponse() - params.response_body = params.response.read() - connection.close() - self._check_status(params) - - return params.response, params.response_body - - def request(self, params): - return self.request_(params)[-1] - - def head_(self, url, headers=None, use_proxy=False, ok=None, aux=None): - return self.request_(_Params(url, 'HEAD', headers=headers, - loader=self.loader, dumper=self.dumper, - use_proxy=use_proxy, ok=ok, aux=aux)) - - def head(self, *args, **kwargs): - return self.head_(*args, **kwargs)[-1] - - def get_(self, url, headers=None, use_proxy=False, ok=None, aux=None): - if headers is None: - headers = {} - headers['accept'] = self.fallback_content_type+';q=1,*/*;q=0' - return self.request_(_Params(url, 'GET', headers=headers, - loader=self.loader, dumper=self.dumper, - use_proxy=use_proxy, ok=ok, aux=aux)) - - def get(self, *args, **kwargs): - return self.get_(*args, **kwargs)[-1] - - def put_(self, url, data, headers=None, content_type=None, ok=None, - aux=None): - if headers is None: - headers = {} - if 'content-type' not in headers: - if content_type is None: - headers['content-type'] = self.fallback_content_type - else: - headers['content-type'] = content_type - headers['accept'] = headers['content-type']+';q=1,*/*;q=0' - return self.request_(_Params(url, 'PUT', body=data, headers=headers, - loader=self.loader, dumper=self.dumper, - ok=ok, aux=aux)) - - def put(self, *args, **kwargs): - return self.put_(*args, **kwargs)[-1] - - def delete_(self, url, ok=None, aux=None): - return self.request_(_Params(url, 'DELETE', loader=self.loader, - dumper=self.dumper, ok=ok, aux=aux)) - - def delete(self, *args, **kwargs): - return self.delete_(*args, **kwargs)[-1] - - def post_(self, url, data='', headers=None, content_type=None, ok=None, - aux=None): - if headers is None: - headers = {} - if 'content-type' not in headers: - if content_type is None: - headers['content-type'] = self.fallback_content_type - else: - headers['content-type'] = content_type - headers['accept'] = headers['content-type']+';q=1,*/*;q=0' - return self.request_(_Params(url, 'POST', body=data, - headers=headers, loader=self.loader, - dumper=self.dumper, ok=ok, aux=aux)) - - def post(self, *args, **kwargs): - return self.post_(*args, **kwargs)[-1] - - -def make_suite(dumper, loader, fallback_content_type): - """ Return a tuple of methods for making http requests with automatic bidirectional formatting with a particular content-type.""" - suite = HttpSuite(dumper, loader, fallback_content_type) - return suite.get, suite.put, suite.delete, suite.post - - -suite = HttpSuite(str, None, 'text/plain') -delete = suite.delete -delete_ = suite.delete_ -get = suite.get -get_ = suite.get_ -head = suite.head -head_ = suite.head_ -post = suite.post -post_ = suite.post_ -put = suite.put -put_ = suite.put_ -request = suite.request -request_ = suite.request_ diff --git a/eventlet/.svn/text-base/httpc_test.py.svn-base b/eventlet/.svn/text-base/httpc_test.py.svn-base deleted file mode 100644 index b0e14f0..0000000 --- a/eventlet/.svn/text-base/httpc_test.py.svn-base +++ /dev/null @@ -1,396 +0,0 @@ -"""\ -@file httpc_test.py -@author Bryan O'Sullivan - -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 import api -from eventlet import httpc -from eventlet import httpd -from eventlet import processes -from eventlet import util -import time -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - - -util.wrap_socket_with_coroutine_socket() - - -from eventlet import tests - - -class Site(object): - def __init__(self): - self.stuff = {'hello': 'hello world'} - - def adapt(self, obj, req): - req.write(str(obj)) - - def handle_request(self, req): - return getattr(self, 'handle_%s' % req.method().lower())(req) - - -class BasicSite(Site): - def handle_get(self, req): - req.set_header('x-get', 'hello') - resp = StringIO() - path = req.path().lstrip('/') - try: - resp.write(self.stuff[path]) - except KeyError: - req.response(404, body='Not found') - return - for k,v in req.get_query_pairs(): - resp.write(k + '=' + v + '\n') - req.write(resp.getvalue()) - - def handle_head(self, req): - req.set_header('x-head', 'hello') - path = req.path().lstrip('/') - try: - req.write('') - except KeyError: - req.response(404, body='Not found') - - def handle_put(self, req): - req.set_header('x-put', 'hello') - path = req.path().lstrip('/') - if not path: - req.response(400, body='') - return - if path in self.stuff: - req.response(204) - else: - req.response(201) - self.stuff[path] = req.read_body() - req.write('') - - def handle_delete(self, req): - req.set_header('x-delete', 'hello') - path = req.path().lstrip('/') - if not path: - req.response(400, body='') - return - try: - del self.stuff[path] - req.response(204) - except KeyError: - req.response(404) - req.write('') - - def handle_post(self, req): - req.set_header('x-post', 'hello') - req.write(req.read_body()) - - -class TestBase(object): - site_class = BasicSite - - def base_url(self): - return 'http://localhost:31337/' - - def setUp(self): - self.logfile = StringIO() - self.victim = api.spawn(httpd.server, - api.tcp_listener(('0.0.0.0', 31337)), - self.site_class(), - log=self.logfile, - max_size=128) - - def tearDown(self): - api.kill(self.victim) - - -class TestHttpc(TestBase, tests.TestCase): - def test_get_bad_uri(self): - self.assertRaises(httpc.NotFound, - lambda: httpc.get(self.base_url() + 'b0gu5')) - - def test_get(self): - response = httpc.get(self.base_url() + 'hello') - self.assertEquals(response, 'hello world') - - def test_get_(self): - status, msg, body = httpc.get_(self.base_url() + 'hello') - self.assertEquals(status, 200) - self.assertEquals(msg.dict['x-get'], 'hello') - self.assertEquals(body, 'hello world') - - def test_get_query(self): - response = httpc.get(self.base_url() + 'hello?foo=bar&foo=quux') - self.assertEquals(response, 'hello worldfoo=bar\nfoo=quux\n') - - def test_head_(self): - status, msg, body = httpc.head_(self.base_url() + 'hello') - self.assertEquals(status, 200) - self.assertEquals(msg.dict['x-head'], 'hello') - self.assertEquals(body, '') - - def test_head(self): - self.assertEquals(httpc.head(self.base_url() + 'hello'), '') - - def test_post_(self): - data = 'qunge' - status, msg, body = httpc.post_(self.base_url() + '', data=data) - self.assertEquals(status, 200) - self.assertEquals(msg.dict['x-post'], 'hello') - self.assertEquals(body, data) - - def test_post(self): - data = 'qunge' - self.assertEquals(httpc.post(self.base_url() + '', data=data), - data) - - def test_put_bad_uri(self): - self.assertRaises( - httpc.BadRequest, - lambda: httpc.put(self.base_url() + '', data='')) - - def test_put_empty(self): - httpc.put(self.base_url() + 'empty', data='') - self.assertEquals(httpc.get(self.base_url() + 'empty'), '') - - def test_put_nonempty(self): - data = 'nonempty' - httpc.put(self.base_url() + 'nonempty', data=data) - self.assertEquals(httpc.get(self.base_url() + 'nonempty'), data) - - def test_put_01_create(self): - data = 'goodbye world' - status, msg, body = httpc.put_(self.base_url() + 'goodbye', - data=data) - self.assertEquals(status, 201) - self.assertEquals(msg.dict['x-put'], 'hello') - self.assertEquals(body, '') - self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) - - def test_put_02_modify(self): - self.test_put_01_create() - data = 'i really mean goodbye' - status = httpc.put_(self.base_url() + 'goodbye', data=data)[0] - self.assertEquals(status, 204) - self.assertEquals(httpc.get(self.base_url() + 'goodbye'), data) - - def test_delete_(self): - httpc.put(self.base_url() + 'killme', data='killme') - status, msg, body = httpc.delete_(self.base_url() + 'killme') - self.assertEquals(status, 204) - self.assertRaises( - httpc.NotFound, - lambda: httpc.get(self.base_url() + 'killme')) - - def test_delete(self): - httpc.put(self.base_url() + 'killme', data='killme') - self.assertEquals(httpc.delete(self.base_url() + 'killme'), '') - self.assertRaises( - httpc.NotFound, - lambda: httpc.get(self.base_url() + 'killme')) - - def test_delete_bad_uri(self): - self.assertRaises( - httpc.NotFound, - lambda: httpc.delete(self.base_url() + 'b0gu5')) - - -class RedirectSite(BasicSite): - response_code = 301 - - def handle_request(self, req): - if req.path().startswith('/redirect/'): - url = ('http://' + req.get_header('host') + - req.uri().replace('/redirect/', '/')) - req.response(self.response_code, headers={'location': url}, - body='') - return - return Site.handle_request(self, req) - -class Site301(RedirectSite): - pass - - -class Site302(BasicSite): - def handle_request(self, req): - if req.path().startswith('/expired/'): - url = ('http://' + req.get_header('host') + - req.uri().replace('/expired/', '/')) - headers = {'location': url, 'expires': '0'} - req.response(302, headers=headers, body='') - return - if req.path().startswith('/expires/'): - url = ('http://' + req.get_header('host') + - req.uri().replace('/expires/', '/')) - expires = time.time() + (100 * 24 * 60 * 60) - headers = {'location': url, 'expires': httpc.to_http_time(expires)} - req.response(302, headers=headers, body='') - return - return Site.handle_request(self, req) - - -class Site303(RedirectSite): - response_code = 303 - - -class Site307(RedirectSite): - response_code = 307 - - -class TestHttpc301(TestBase, tests.TestCase): - site_class = Site301 - - def base_url(self): - return 'http://localhost:31337/redirect/' - - def test_get(self): - try: - httpc.get(self.base_url() + 'hello') - self.assert_(False) - except httpc.MovedPermanently, err: - response = err.retry() - self.assertEquals(response, 'hello world') - - def test_post(self): - data = 'qunge' - try: - response = httpc.post(self.base_url() + '', data=data) - self.assert_(False) - except httpc.MovedPermanently, err: - response = err.retry() - self.assertEquals(response, data) - - -class TestHttpc302(TestBase, tests.TestCase): - site_class = Site302 - - def test_get_expired(self): - try: - httpc.get(self.base_url() + 'expired/hello') - self.assert_(False) - except httpc.Found, err: - response = err.retry() - self.assertEquals(response, 'hello world') - - def test_get_expires(self): - try: - httpc.get(self.base_url() + 'expires/hello') - self.assert_(False) - except httpc.Found, err: - response = err.retry() - self.assertEquals(response, 'hello world') - - -class TestHttpc303(TestBase, tests.TestCase): - site_class = Site303 - - def base_url(self): - return 'http://localhost:31337/redirect/' - - def test_post(self): - data = 'hello world' - try: - response = httpc.post(self.base_url() + 'hello', data=data) - self.assert_(False) - except httpc.SeeOther, err: - response = err.retry() - self.assertEquals(response, data) - - -class TestHttpc307(TestBase, tests.TestCase): - site_class = Site307 - - def base_url(self): - return 'http://localhost:31337/redirect/' - - def test_post(self): - data = 'hello world' - try: - response = httpc.post(self.base_url() + 'hello', data=data) - self.assert_(False) - except httpc.TemporaryRedirect, err: - response = err.retry() - self.assertEquals(response, data) - - -class Site500(BasicSite): - def handle_request(self, req): - req.response(500, body="screw you world") - return - - -class Site500(BasicSite): - def handle_request(self, req): - req.response(500, body="screw you world") - return - - -class TestHttpc500(TestBase, tests.TestCase): - site_class = Site500 - - def base_url(self): - return 'http://localhost:31337/' - - def test_get(self): - data = 'screw you world' - try: - response = httpc.get(self.base_url()) - self.fail() - except httpc.InternalServerError, e: - self.assertEquals(e.params.response_body, data) - self.assert_(str(e).count(data)) - self.assert_(repr(e).count(data)) - - -class Site504(BasicSite): - def handle_request(self, req): - req.response(504, body="screw you world") - - -class TestHttpc504(TestBase, tests.TestCase): - site_class = Site504 - - def base_url(self): - return 'http://localhost:31337/' - - def test_post(self): - # Simply ensure that a 504 status code results in a - # GatewayTimeout. Don't bother retrying. - data = 'hello world' - self.assertRaises(httpc.GatewayTimeout, - lambda: httpc.post(self.base_url(), data=data)) - - -class TestHttpTime(tests.TestCase): - rfc1123_time = 'Sun, 06 Nov 1994 08:49:37 GMT' - rfc850_time = 'Sunday, 06-Nov-94 08:49:37 GMT' - asctime_time = 'Sun Nov 6 08:49:37 1994' - secs_since_epoch = 784111777 - def test_to_http_time(self): - self.assertEqual(self.rfc1123_time, httpc.to_http_time(self.secs_since_epoch)) - - def test_from_http_time(self): - for formatted in (self.rfc1123_time, self.rfc850_time, self.asctime_time): - ticks = httpc.from_http_time(formatted, 0) - self.assertEqual(ticks, self.secs_since_epoch) - -if __name__ == '__main__': - tests.main() diff --git a/eventlet/.svn/text-base/httpd.py.svn-base b/eventlet/.svn/text-base/httpd.py.svn-base deleted file mode 100644 index 7d73756..0000000 --- a/eventlet/.svn/text-base/httpd.py.svn-base +++ /dev/null @@ -1,584 +0,0 @@ -"""\ -@file httpd.py -@author Donovan Preston - -Copyright (c) 2005-2006, 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 -import errno -import socket -import sys -import time -import urllib -import socket -import traceback -import BaseHTTPServer - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - -from eventlet import api -from eventlet import coros - - -DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' - -USE_ACCESS_LOG = True - - -CONNECTION_CLOSED = (errno.EPIPE, errno.ECONNRESET) - - -class ErrorResponse(Exception): - _responses = BaseHTTPServer.BaseHTTPRequestHandler.responses - - def __init__(self, code, reason_phrase=None, headers=None, body=None): - Exception.__init__(self, reason_phrase) - self.code = code - if reason_phrase is None: - self.reason = self._responses[code][0] - else: - self.reason = reason_phrase - self.headers = headers - if body is None: - self.body = self._responses[code][1] - else: - self.body = body - - -class Request(object): - _method = None - _path = None - _responsecode = 200 - _reason_phrase = None - _request_started = False - _chunked = False - _producer_adapters = {} - depth = 0 - def __init__(self, protocol, method, path, headers): - self.context = {} - self.request_start_time = time.time() - self.site = protocol.server.site - self.protocol = protocol - self._method = method - if '?' in path: - self._path, self._query = path.split('?', 1) - self._query = self._query.replace('&', '&') - else: - self._path, self._query = path, None - self._incoming_headers = headers - self._outgoing_headers = dict() - - def response(self, code, reason_phrase=None, headers=None, body=None): - """Change the response code. This will not be sent until some - data is written; last call to this method wins. Default is - 200 if this is not called. - """ - self._responsecode = code - self._reason_phrase = reason_phrase - self.protocol.set_response_code(self, code, reason_phrase) - if headers is not None: - try: - headers = headers.iteritems() - except AttributeError: - pass - for key, value in headers: - self.set_header(key, value) - if body is not None: - self.write(body) - - def is_okay(self): - return 200 <= self._responsecode <= 299 - - def full_url(self): - path = self.path() - query = self.query() - if query: - path = path + '?' + query - - via = self.get_header('via', '') - if via.strip(): - next_part = iter(via.split()).next - - received_protocol = next_part() - received_by = next_part() - if received_by.endswith(','): - received_by = received_by[:-1] - else: - comment = '' - while not comment.endswith(','): - try: - comment += next_part() - except StopIteration: - comment += ',' - break - comment = comment[:-1] - else: - received_by = self.get_header('host') - - return '%s://%s%s' % (self.request_protocol(), received_by, path) - - def begin_response(self, length="-"): - """Begin the response, and return the initial response text - """ - self._request_started = True - request_time = time.time() - self.request_start_time - - code = self._responsecode - proto = self.protocol - - if USE_ACCESS_LOG: - proto.server.write_access_log_line( - proto.client_address[0], - time.strftime("%d/%b/%Y %H:%M:%S"), - proto.requestline, - code, - length, - request_time) - - if self._reason_phrase is not None: - message = self._reason_phrase.split("\n")[0] - elif code in proto.responses: - message = proto.responses[code][0] - else: - message = '' - if proto.request_version == 'HTTP/0.9': - return [] - - response_lines = proto.generate_status_line() - - if not self._outgoing_headers.has_key('connection'): - con = self.get_header('connection') - if con is None and proto.request_version == 'HTTP/1.0': - con = 'close' - if con is not None: - self.set_header('connection', con) - - for key, value in self._outgoing_headers.items(): - key = '-'.join([x.capitalize() for x in key.split('-')]) - response_lines.append("%s: %s" % (key, value)) - - response_lines.append("") - return response_lines - - def write(self, obj): - """Writes an arbitrary object to the response, using - the sitemap's adapt method to convert it to bytes. - """ - if isinstance(obj, str): - self._write_bytes(obj) - elif isinstance(obj, unicode): - # use utf8 encoding for now, *TODO support charset negotiation - # Content-Type: text/html; charset=utf-8 - ctype = self._outgoing_headers.get('content-type', 'text/html') - ctype = ctype + '; charset=utf-8' - self._outgoing_headers['content-type'] = ctype - self._write_bytes(obj.encode('utf8')) - else: - self.site.adapt(obj, self) - - def _write_bytes(self, data): - """Write all the data of the response. - Can be called just once. - """ - if self._request_started: - print "Request has already written a response:" - traceback.print_stack() - return - - self._outgoing_headers['content-length'] = len(data) - - response_lines = self.begin_response(len(data)) - response_lines.append(data) - self.protocol.wfile.write("\r\n".join(response_lines)) - if hasattr(self.protocol.wfile, 'flush'): - self.protocol.wfile.flush() - - def method(self): - return self._method - - def path(self): - return self._path - - def path_segments(self): - return [urllib.unquote_plus(x) for x in self._path.split('/')[1:]] - - def query(self): - return self._query - - def uri(self): - if self._query: - return '%s?%s' % ( - self._path, self._query) - return self._path - - def get_headers(self): - return self._incoming_headers - - def get_header(self, header_name, default=None): - return self.get_headers().get(header_name.lower(), default) - - def get_query_pairs(self): - if not hasattr(self, '_split_query'): - if self._query is None: - self._split_query = () - else: - spl = self._query.split('&') - spl = [x.split('=', 1) for x in spl if x] - self._split_query = [] - for query in spl: - if len(query) == 1: - key = query[0] - value = '' - else: - key, value = query - self._split_query.append((urllib.unquote_plus(key), urllib.unquote_plus(value))) - - return self._split_query - - def get_queries_generator(self, name): - """Generate all query parameters matching the given name. - """ - for key, value in self.get_query_pairs(): - if key == name or not name: - yield value - - get_queries = lambda self, name: list(self.get_queries_generator) - - def get_query(self, name, default=None): - try: - return self.get_queries_generator(name).next() - except StopIteration: - return default - - def get_arg_list(self, name): - return self.get_field_storage().getlist(name) - - def get_arg(self, name, default=None): - return self.get_field_storage().getfirst(name, default) - - def get_field_storage(self): - if not hasattr(self, '_field_storage'): - if self.method() == 'GET': - data = '' - if self._query: - data = self._query - else: - data = self.read_body() - fl = StringIO(data) - ## Allow our resource to provide the FieldStorage instance for - ## customization purposes. - headers = self.get_headers() - environ = dict( - REQUEST_METHOD='POST', - QUERY_STRING=self._query or '') - - self._field_storage = cgi.FieldStorage(fl, headers, environ=environ) - - return self._field_storage - - def set_header(self, key, value): - if key.lower() == 'connection' and value.lower() == 'close': - self.protocol.close_connection = 1 - self._outgoing_headers[key.lower()] = value - __setitem__ = set_header - - def get_outgoing_header(self, key): - return self._outgoing_headers[key.lower()] - - def has_outgoing_header(self, key): - return self._outgoing_headers.has_key(key.lower()) - - def socket(self): - return self.protocol.socket - - def error(self, response=None, body=None, log_traceback=True): - if log_traceback: - traceback.print_exc(file=self.log) - if response is None: - response = 500 - if body is None: - typ, val, tb = sys.exc_info() - body = dict(type=str(typ), error=True, reason=str(val)) - self.response(response) - if(type(body) is str and not self.response_written()): - self.write(body) - return - try: - produce(body, self) - except Exception, e: - traceback.print_exc(file=self.log) - if not self.response_written(): - self.write('Internal Server Error') - - def not_found(self): - self.error(404, 'Not Found\n', log_traceback=False) - - def raw_body(self): - if not hasattr(self, '_cached_body'): - self.read_body() - return self._cached_body - - def read_body(self): - """ Returns the string body that was read off the request, or - the empty string if there was no request body. - - Requires a content-length header. Caches the body so multiple - calls to read_body() are free. - """ - if not hasattr(self, '_cached_body'): - length = self.get_header('content-length') - if length: - length = int(length) - if length: - self._cached_body = self.protocol.rfile.read(length) - else: - self._cached_body = '' - return self._cached_body - - def parsed_body(self): - """ Returns the parsed version of the body, using the - content-type header to select from the parsers on the site - object. - - If no parser is found, returns the string body from - read_body(). Caches the parsed body so multiple calls to - parsed_body() are free. - """ - if not hasattr(self, '_cached_parsed_body'): - body = self.read_body() - if hasattr(self.site, 'parsers'): - parser = self.site.parsers.get( - self.get_header('content-type')) - if parser is not None: - body = parser(body) - self._cached_parsed_body = body - return self._cached_parsed_body - - def override_body(self, body): - if not hasattr(self, '_cached_parsed_body'): - self.read_body() ## Read and discard body - self._cached_parsed_body = body - - def response_written(self): - ## TODO change badly named variable - return self._request_started - - def request_version(self): - return self.protocol.request_version - - def request_protocol(self): - if self.protocol.socket.is_secure: - return "https" - return "http" - - def server_address(self): - return self.protocol.server.address - - def __repr__(self): - return "" % ( - getattr(self, '_method'), getattr(self, '_path')) - -DEFAULT_TIMEOUT = 300 - -# This value was chosen because apache 2 has a default limit of 8190. -# I believe that slightly smaller number is because apache does not -# count the \r\n. -MAX_REQUEST_LINE = 8192 - -class Timeout(RuntimeError): - pass - -class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): - def __init__(self, request, client_address, server): - self.socket = self.request = self.rfile = self.wfile = request - self.client_address = client_address - self.server = server - self.set_response_code(None, 200, None) - self.protocol_version = server.max_http_version - - def set_response_code(self, request, code, message): - self._code = code - if message is not None: - self._message = message.split("\n")[0] - elif code in self.responses: - self._message = self.responses[code][0] - else: - self._message = '' - - def generate_status_line(self): - return [ - "%s %d %s" % ( - self.protocol_version, self._code, self._message)] - - def write_bad_request(self, status, reason): - self.set_response_code(self, status, reason) - self.wfile.write(''.join(self.generate_status_line())) - self.wfile.write('\r\nServer: %s\r\n' % self.version_string()) - self.wfile.write('Date: %s\r\n' % self.date_time_string()) - self.wfile.write('Content-Length: 0\r\n\r\n') - - def handle(self): - self.close_connection = 0 - - timeout = DEFAULT_TIMEOUT - while not self.close_connection: - if timeout == 0: - break - cancel = api.exc_after(timeout, Timeout) - try: - self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) - if self.raw_requestline is not None: - if len(self.raw_requestline) == MAX_REQUEST_LINE: - # Someone sent a request line which is too - # large. Be helpful and tell them. - self.write_bad_request(414, 'Request-URI Too Long') - self.close_connection = True - continue - except socket.error, e: - if e[0] in CONNECTION_CLOSED: - self.close_connection = True - cancel.cancel() - continue - except Timeout: - self.close_connection = True - continue - except Exception, e: - try: - if e[0][0][0].startswith('SSL'): - print "SSL Error:", e[0][0] - self.close_connection = True - cancel.cancel() - continue - except Exception, f: - print "Exception in ssl test:",f - pass - raise e - cancel.cancel() - - if not self.raw_requestline or not self.parse_request(): - self.close_connection = True - continue - - self.set_response_code(None, 200, None) - request = Request(self, self.command, self.path, self.headers) - request.set_header('Server', self.version_string()) - request.set_header('Date', self.date_time_string()) - try: - timeout = int(request.get_header('keep-alive', timeout)) - except TypeError, ValueError: - pass - - try: - try: - try: - self.server.site.handle_request(request) - except ErrorResponse, err: - request.response(code=err.code, - reason_phrase=err.reason, - headers=err.headers, - body=err.body) - finally: - # clean up any timers that might have been left around by the handling code - api.get_hub().runloop.cancel_timers(api.getcurrent()) - - # throw an exception if it failed to write a body - if not request.response_written(): - raise NotImplementedError("Handler failed to write response to request: %s" % request) - - if not hasattr(self, '_cached_body'): - try: - request.read_body() ## read & discard body - except: - pass - - except socket.error, e: - # Broken pipe, connection reset by peer - if e[0] in CONNECTION_CLOSED: - #print "Remote host closed connection before response could be sent" - pass - else: - raise - except Exception, e: - self.server.log_message("Exception caught in HttpRequest.handle():\n") - self.server.log_exception(*sys.exc_info()) - if not request.response_written(): - request.response(500) - request.write('Internal Server Error') - self.socket.close() - raise e # can't do a plain raise since exc_info might have been cleared - self.socket.close() - - -class Server(BaseHTTPServer.HTTPServer): - def __init__(self, socket, address, site, log, max_http_version=DEFAULT_MAX_HTTP_VERSION): - self.socket = socket - self.address = address - self.site = site - self.max_http_version = max_http_version - if log: - self.log = log - if hasattr(log, 'info'): - log.write = log.info - else: - self.log = self - - def write(self, something): - sys.stdout.write('%s' % (something, )); sys.stdout.flush() - - def log_message(self, message): - self.log.write(message) - - def log_exception(self, type, value, tb): - self.log.write(''.join(traceback.format_exception(type, value, tb))) - - def write_access_log_line(self, *args): - """Write a line to the access.log. Arguments: - client_address, date_time, requestline, code, size, request_time - """ - self.log.write( - '%s - - [%s] "%s" %s %s %.6f\n' % args) - - -def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): - pool = coros.CoroutinePool(max_size=max_size) - if serv is None: - serv = Server(sock, sock.getsockname(), site, log, max_http_version=max_http_version) - try: - serv.log.write("httpd starting up on %s\n" % (sock.getsockname(), )) - while True: - try: - new_sock, address = sock.accept() - proto = HttpProtocol(new_sock, address, serv) - pool.execute_async(proto.handle) - api.sleep(0) # sleep to allow other coros to run - except KeyboardInterrupt: - api.get_hub().remove_descriptor(sock.fileno()) - serv.log.write("httpd exiting\n") - break - finally: - try: - sock.close() - except socket.error: - pass diff --git a/eventlet/.svn/text-base/httpd_test.py.svn-base b/eventlet/.svn/text-base/httpd_test.py.svn-base deleted file mode 100644 index e7e0fbc..0000000 --- a/eventlet/.svn/text-base/httpd_test.py.svn-base +++ /dev/null @@ -1,207 +0,0 @@ -"""\ -@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. -""" - - -from eventlet import api -from eventlet import httpd -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 - - -class Site(object): - def handle_request(self, req): - path = req.path_segments() - if len(path) > 0 and path[0] == "notexist": - req.response(404, body='not found') - return - req.write('hello world') - - def adapt(self, obj, req): - req.write(str(obj)) - - -CONTENT_LENGTH = 'content-length' - - -""" -HTTP/1.1 200 OK -Date: foo -Content-length: 11 - -hello world -""" - -class ConnectionClosed(Exception): - pass - - -def read_http(sock): - response_line = sock.readline() - if not response_line: - raise ConnectionClosed - raw_headers = sock.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 = sock.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( - httpd.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)) - - sock.write('GET / HTTP/1.0\r\nHost: localhost\r\n\r\n') - result = sock.read() - sock.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)) - - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - read_http(sock) - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - read_http(sock) - sock.close() - - def test_003_passing_non_int_to_read(self): - # This should go in test_wrappedfd - sock = api.connect_tcp( - ('127.0.0.1', 12346)) - - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - cancel = api.exc_after(1, RuntimeError) - self.assertRaises(TypeError, sock.read, "This shouldn't work") - cancel.cancel() - sock.close() - - def test_004_close_keepalive(self): - sock = api.connect_tcp( - ('127.0.0.1', 12346)) - - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - read_http(sock) - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n') - read_http(sock) - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - self.assertRaises(ConnectionClosed, read_http, sock) - sock.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 - sock.write(request) - result = sock.readline() - status = result.split(' ')[1] - self.assertEqual(status, '414') - sock.close() - - def test_007_get_arg(self): - # define a new handler that does a get_arg as well as a read_body - def new_handle_request(req): - a = req.get_arg('a') - body = req.read_body() - req.write('a is %s, body is %s' % (a, body)) - self.site.handle_request = new_handle_request - - sock = api.connect_tcp( - ('127.0.0.1', 12346)) - request = '\r\n'.join(( - 'POST /%s HTTP/1.0', - 'Host: localhost', - 'Content-Length: 3', - '', - 'a=a')) - sock.write(request) - - # send some junk after the actual request - sock.write('01234567890123456789') - reqline, headers, body = read_http(sock) - self.assertEqual(body, 'a is a, body is a=a') - sock.close() - - def test_008_correctresponse(self): - sock = api.connect_tcp( - ('127.0.0.1', 12346)) - - sock.write('GET / HTTP/1.1\r\nHost: localhost\r\n\r\n') - response_line_200,_,_ = read_http(sock) - sock.write('GET /notexist HTTP/1.1\r\nHost: localhost\r\n\r\n') - response_line_404,_,_ = read_http(sock) - sock.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) - sock.close() - - -if __name__ == '__main__': - tests.main() diff --git a/eventlet/.svn/text-base/httpdate.py.svn-base b/eventlet/.svn/text-base/httpdate.py.svn-base deleted file mode 100644 index e1f81f3..0000000 --- a/eventlet/.svn/text-base/httpdate.py.svn-base +++ /dev/null @@ -1,39 +0,0 @@ -"""\ -@file httpdate.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 time - -__all__ = ['format_date_time'] - -# Weekday and month names for HTTP date/time formatting; always English! -_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] -_monthname = [None, # Dummy so we can use 1-based month numbers - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - -def format_date_time(timestamp): - year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp) - return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( - _weekdayname[wd], day, _monthname[month], year, hh, mm, ss - ) diff --git a/eventlet/.svn/text-base/jsonhttp.py.svn-base b/eventlet/.svn/text-base/jsonhttp.py.svn-base deleted file mode 100644 index 2fc0e54..0000000 --- a/eventlet/.svn/text-base/jsonhttp.py.svn-base +++ /dev/null @@ -1,40 +0,0 @@ -"""\ -@file jsonhttp.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. -""" - -from eventlet import httpc - -import simplejson - - -def safe_load(what): - if not what: - return None - return simplejson.loads(what) - - -suite = httpc.HttpSuite(simplejson.dumps, safe_load, 'application/json') -head, get, put, delete, post = ( - suite.head, suite.get, suite.put, suite.delete, suite.post) -head_, get_, put_, delete_, post_ = ( - suite.head_, suite.get_, suite.put_, suite.delete_, suite.post_) diff --git a/eventlet/.svn/text-base/kqueuehub.py.svn-base b/eventlet/.svn/text-base/kqueuehub.py.svn-base deleted file mode 100644 index 595e7b9..0000000 --- a/eventlet/.svn/text-base/kqueuehub.py.svn-base +++ /dev/null @@ -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()) diff --git a/eventlet/.svn/text-base/logutil.py.svn-base b/eventlet/.svn/text-base/logutil.py.svn-base deleted file mode 100644 index 0885042..0000000 --- a/eventlet/.svn/text-base/logutil.py.svn-base +++ /dev/null @@ -1,112 +0,0 @@ -"""\ -@file logutil.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 syslog -import logging - - -def file_logger(filename): - """Create a logger. This sucks, the logging module sucks, but - it'll do for now. - """ - handler = logging.FileHandler(filename) - formatter = logging.Formatter() - handler.setFormatter(formatter) - log = logging.getLogger(filename) - log.addHandler(handler) - log.setLevel(logging.DEBUG) - return log, handler - - -def stream_logger(stream): - """Create a logger. This sucks.""" - handler = logging.StreamHandler(stream) - formatter = logging.Formatter() - handler.setFormatter(formatter) - log = logging.getLogger() - log.addHandler(handler) - log.setLevel(logging.DEBUG) - return log, handler - - -class LineLogger(object): - towrite = '' - def __init__(self, emit=None): - if emit is not None: - self.emit = emit - - def write(self, stuff): - self.towrite += stuff - if '\n' in self.towrite: - self.flush() - - def flush(self): - try: - newline = self.towrite.index('\n') - except ValueError: - newline = len(self.towrite) - while True: - self.emit(self.towrite[:newline]) - self.towrite = self.towrite[newline+1:] - try: - newline = self.towrite.index('\n') - except ValueError: - break - - def close(self): - pass - - def emit(self, *args): - pass - - -class SysLogger(LineLogger): - """A file-like object which writes to syslog. Can be inserted - as sys.stdin and sys.stderr to have logging output redirected - to syslog. - """ - def __init__(self, priority): - self.priority = priority - - def emit(self, line): - syslog.syslog(self.priority, line) - - -class TeeLogger(LineLogger): - def __init__(self, one, two): - self.one, self.two = one, two - - def emit(self, line): - self.one.emit(line) - self.two.emit(line) - - -class FileLogger(LineLogger): - def __init__(self, file): - self.file = file - - def emit(self, line): - self.file.write(line + '\n') - self.file.flush() - diff --git a/eventlet/.svn/text-base/pollhub.py.svn-base b/eventlet/.svn/text-base/pollhub.py.svn-base deleted file mode 100644 index 0540837..0000000 --- a/eventlet/.svn/text-base/pollhub.py.svn-base +++ /dev/null @@ -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()) diff --git a/eventlet/.svn/text-base/pools.py.svn-base b/eventlet/.svn/text-base/pools.py.svn-base deleted file mode 100644 index 5e89dc4..0000000 --- a/eventlet/.svn/text-base/pools.py.svn-base +++ /dev/null @@ -1,184 +0,0 @@ -"""\ -@file pools.py -@author Donovan Preston, Aaron Brashears - -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 collections -import os -import socket - -from eventlet import api -from eventlet import channel -from eventlet import httpc - -class FanFailed(RuntimeError): - pass - - -class SomeFailed(FanFailed): - pass - - -class AllFailed(FanFailed): - pass - - -class Pool(object): - """ - When using the pool, if you do a get, you should ALWAYS do a put. - The pattern is: - - thing = self.pool.get() - try: - # do stuff - finally: - self.pool.put(thing) - """ - def __init__(self, min_size=0, max_size=4): - self.min_size = min_size - self.max_size = max_size - self.current_size = 0 - self.channel = channel.channel() - self.free_items = collections.deque() - for x in range(min_size): - self.current_size += 1 - self.free_items.append(self.create()) - - def get(self): - """Return an item from the pool, when one is available - """ - if self.free_items: - return self.free_items.popleft() - if self.current_size < self.max_size: - self.current_size += 1 - return self.create() - return self.channel.receive() - - def put(self, item): - """Put an item back into the pool, when done - """ - if self.current_size > self.max_size: - self.current_size -= 1 - return - - if self.channel.balance < 0: - self.channel.send(item) - else: - self.free_items.append(item) - - def resize(self, new_size): - """Resize the pool - """ - self.max_size = new_size - - def free(self): - """Return the number of free items in the pool. - """ - return len(self.free_items) + self.max_size - self.current_size - - def waiting(self): - """Return the number of routines waiting for a pool item. - """ - if self.channel.balance < 0: - return -self.channel.balance - return 0 - - def create(self): - """Generate a new pool item - """ - raise NotImplementedError("Implement in subclass") - - def fan(self, block, input_list): - chan = channel.channel() - results = [] - exceptional_results = 0 - for index, input_item in enumerate(input_list): - pool_item = self.get() - - ## Fan out - api.spawn( - self._invoke, block, pool_item, input_item, index, chan) - - ## Fan back in - for i in range(len(input_list)): - ## Wait for all guys to send to the queue - index, value = chan.receive() - if isinstance(value, Exception): - exceptional_results += 1 - results.append((index, value)) - - results.sort() - results = [value for index, value in results] - - if exceptional_results: - if exceptional_results == len(results): - raise AllFailed(results) - raise SomeFailed(results) - return results - - def _invoke(self, block, pool_item, input_item, index, chan): - try: - result = block(pool_item, input_item) - except Exception, e: - self.put(pool_item) - chan.send((index, e)) - return - self.put(pool_item) - chan.send((index, result)) - - -class Token(object): - pass - - -class TokenPool(Pool): - """A pool which gives out tokens, an object indicating that - the person who holds the token has a right to consume some - limited resource. - """ - def create(self): - return Token() - - -class ConnectionPool(Pool): - """A Pool which can limit the number of concurrent http operations - being made to a given server. - - *NOTE: *TODO: - - This does NOT currently keep sockets open. It discards the created - http object when it is put back in the pool. This is because we do - not yet have a combination of http clients and servers which can work - together to do HTTP keepalive sockets without errors. - """ - def __init__(self, proto, netloc, use_proxy, min_size=0, max_size=4): - self.proto = proto - self.netloc = netloc - self.use_proxy = use_proxy - Pool.__init__(self, min_size, max_size) - - def create(self): - return httpc.make_connection(self.proto, self.netloc, self.use_proxy) - - def put(self, item): - ## Discard item, create a new connection for the pool - Pool.put(self, self.create()) diff --git a/eventlet/.svn/text-base/pools_test.py.svn-base b/eventlet/.svn/text-base/pools_test.py.svn-base deleted file mode 100644 index e604bc1..0000000 --- a/eventlet/.svn/text-base/pools_test.py.svn-base +++ /dev/null @@ -1,179 +0,0 @@ -"""\ -@file test_pools.py -@author Donovan Preston, Aaron Brashears - -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 unittest - -from eventlet import api -from eventlet import channel -from eventlet import pools - - -class IntPool(pools.Pool): - def create(self): - self.current_integer = getattr(self, 'current_integer', 0) + 1 - return self.current_integer - - -class TestIntPool(unittest.TestCase): - mode = 'static' - def setUp(self): - self.pool = IntPool(min_size=0, max_size=4) - - def test_integers(self): - # Do not actually use this pattern in your code. The pool will be - # exhausted, and unrestoreable. - # If you do a get, you should ALWAYS do a put, probably like this: - # try: - # thing = self.pool.get() - # # do stuff - # finally: - # self.pool.put(thing) - - # with self.pool.some_api_name() as thing: - # # do stuff - self.assertEquals(self.pool.get(), 1) - self.assertEquals(self.pool.get(), 2) - self.assertEquals(self.pool.get(), 3) - self.assertEquals(self.pool.get(), 4) - - def test_free(self): - self.assertEquals(self.pool.free(), 4) - gotten = self.pool.get() - self.assertEquals(self.pool.free(), 3) - self.pool.put(gotten) - self.assertEquals(self.pool.free(), 4) - - def test_exhaustion(self): - waiter = channel.channel() - def consumer(): - gotten = None - try: - gotten = self.pool.get() - finally: - waiter.send(gotten) - - api.spawn(consumer) - - one, two, three, four = ( - self.pool.get(), self.pool.get(), self.pool.get(), self.pool.get()) - self.assertEquals(self.pool.free(), 0) - - # Let consumer run; nothing will be in the pool, so he will wait - api.sleep(0) - - # Wake consumer - self.pool.put(one) - - # wait for the consumer - self.assertEquals(waiter.receive(), one) - - def test_blocks_on_pool(self): - waiter = channel.channel() - def greedy(): - self.pool.get() - self.pool.get() - self.pool.get() - self.pool.get() - # No one should be waiting yet. - self.assertEquals(self.pool.waiting(), 0) - # The call to the next get will unschedule this routine. - self.pool.get() - # So this send should never be called. - waiter.send('Failed!') - - killable = api.spawn(greedy) - - # no one should be waiting yet. - self.assertEquals(self.pool.waiting(), 0) - - ## Wait for greedy - api.sleep(0) - - ## Greedy should be blocking on the last get - self.assertEquals(self.pool.waiting(), 1) - - ## Send will never be called, so balance should be 0. - self.assertEquals(waiter.balance, 0) - - api.kill(killable) - - -class TestAbstract(unittest.TestCase): - mode = 'static' - def test_abstract(self): - ## Going for 100% coverage here - ## A Pool cannot be used without overriding create() - pool = pools.Pool() - self.assertRaises(NotImplementedError, pool.get) - - -class TestIntPool2(unittest.TestCase): - mode = 'static' - def setUp(self): - self.pool = IntPool(min_size=3, max_size=3) - - def test_something(self): - self.assertEquals(len(self.pool.free_items), 3) - ## Cover the clause in get where we get from the free list instead of creating - ## an item on get - gotten = self.pool.get() - self.assertEquals(gotten, 1) - - -ALWAYS = RuntimeError('I always fail') -SOMETIMES = RuntimeError('I fail half the time') - - -class TestFan(unittest.TestCase): - mode = 'static' - def setUp(self): - self.pool = IntPool(max_size=2) - - def test_with_list(self): - list_of_input = ['agent-one', 'agent-two', 'agent-three'] - - def my_callable(pool_item, next_thing): - ## Do some "blocking" (yielding) thing - api.sleep(0.01) - return next_thing - - output = self.pool.fan(my_callable, list_of_input) - self.assertEquals(list_of_input, output) - - def test_all_fail(self): - def my_failure(pool_item, next_thing): - raise ALWAYS - self.assertRaises(pools.AllFailed, self.pool.fan, my_failure, range(4)) - - def test_some_fail(self): - def my_failing_callable(pool_item, next_thing): - if next_thing % 2: - raise SOMETIMES - return next_thing - self.assertRaises(pools.SomeFailed, self.pool.fan, my_failing_callable, range(4)) - - -if __name__ == '__main__': - unittest.main() - diff --git a/eventlet/.svn/text-base/processes.py.svn-base b/eventlet/.svn/text-base/processes.py.svn-base deleted file mode 100644 index c51d1e7..0000000 --- a/eventlet/.svn/text-base/processes.py.svn-base +++ /dev/null @@ -1,141 +0,0 @@ -"""\ -@file processes.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. -""" - - -import os -import popen2 -import signal - - -from eventlet import util, pools -from eventlet import wrappedfd - -class DeadProcess(RuntimeError): - pass - - -class Process(object): - process_number = 0 - def __init__(self, command, args, dead_callback=lambda:None): - self.process_number = self.process_number + 1 - Process.process_number = self.process_number - self.command = command - self.args = args - self._dead_callback = dead_callback - self.run() - - def run(self): - self.dead = False - self.started = False - self.popen4 = None - - ## We use popen4 so that read() will read from either stdout or stderr - self.popen4 = popen2.Popen4([self.command] + self.args) - child_stdout_stderr = self.popen4.fromchild - child_stdin = self.popen4.tochild - util.set_nonblocking(child_stdout_stderr) - util.set_nonblocking(child_stdin) - self.child_stdout_stderr = wrappedfd.wrapped_file(child_stdout_stderr) - 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.newlines = '\n' - - self.sendall = self.child_stdin.write - self.send = self.child_stdin.write - self.recv = self.child_stdout_stderr.read - self.readline = self.child_stdout_stderr.readline - - def dead_callback(self): - self.dead = True - if self._dead_callback: - self._dead_callback() - - def makefile(self, mode, *arg): - if mode.startswith('r'): - return self.child_stdout_stderr - if mode.startswith('w'): - return self.child_stdin - raise RuntimeError("Unknown mode", mode) - - def read(self, amount=None): - result = self.child_stdout_stderr.read(amount) - if result == '': - # This process is dead. - self.dead_callback() - raise DeadProcess - return result - - def write(self, stuff): - written = self.child_stdin.send(stuff) - try: - self.child_stdin.flush() - except ValueError, e: - ## File was closed - assert str(e) == 'I/O operation on closed file' - if written == 0: - self.dead_callback() - raise DeadProcess - - def flush(self): - self.child_stdin.flush() - - def close(self): - self.child_stdout_stderr.close() - self.child_stdin.close() - self.dead_callback() - - def close_stdin(self): - self.child_stdin.close() - - def kill(self, sig=None): - if sig == None: - sig = signal.SIGTERM - os.kill(self.popen4.pid, sig) - - def getpid(self): - return self.popen4.pid - - -class ProcessPool(pools.Pool): - def __init__(self, command, args=None, min_size=0, max_size=4): - """@param command the command to run - """ - self.command = command - if args is None: - args = [] - self.args = args - pools.Pool.__init__(self, min_size, max_size) - - def create(self): - """Generate a process - """ - def dead_callback(): - self.current_size -= 1 - return Process(self.command, self.args, dead_callback) - - def put(self, item): - if not item.dead: - if item.popen4.poll() != -1: - item.dead_callback() - else: - pools.Pool.put(self, item) diff --git a/eventlet/.svn/text-base/processes_test.py.svn-base b/eventlet/.svn/text-base/processes_test.py.svn-base deleted file mode 100644 index 2cbab7d..0000000 --- a/eventlet/.svn/text-base/processes_test.py.svn-base +++ /dev/null @@ -1,134 +0,0 @@ -"""\ -@file processes_test.py -@author Donovan Preston, Aaron Brashears - -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 -from eventlet import channel -from eventlet import processes - -class TestEchoPool(tests.TestCase): - mode = 'static' - def setUp(self): - self.pool = processes.ProcessPool('echo', ["hello"]) - - def test_echo(self): - result = None - - proc = self.pool.get() - try: - result = proc.read() - finally: - self.pool.put(proc) - self.assertEquals(result, 'hello\n') - - def test_read_eof(self): - proc = self.pool.get() - try: - proc.read() - self.assertRaises(processes.DeadProcess, proc.read) - finally: - self.pool.put(proc) - - -class TestCatPool(tests.TestCase): - mode = 'static' - def setUp(self): - self.pool = processes.ProcessPool('cat') - - def test_cat(self): - result = None - - proc = self.pool.get() - try: - proc.write('goodbye') - proc.close_stdin() - result = proc.read() - finally: - self.pool.put(proc) - - self.assertEquals(result, 'goodbye') - - def test_write_to_dead(self): - result = None - - proc = self.pool.get() - try: - proc.write('goodbye') - proc.close_stdin() - result = proc.read() - self.assertRaises(processes.DeadProcess, proc.write, 'foo') - finally: - self.pool.put(proc) - - def test_close(self): - result = None - - proc = self.pool.get() - try: - proc.write('hello') - proc.close() - self.assertRaises(processes.DeadProcess, proc.write, 'goodbye') - finally: - self.pool.put(proc) - - -class TestDyingProcessesLeavePool(tests.TestCase): - mode = 'static' - def setUp(self): - self.pool = processes.ProcessPool('echo', ['hello'], max_size=1) - - def test_dead_process_not_inserted_into_pool(self): - proc = self.pool.get() - try: - result = proc.read() - self.assertEquals(result, 'hello\n') - finally: - self.pool.put(proc) - proc2 = self.pool.get() - self.assert_(proc is not proc2) - - -class TestProcessLivesForever(tests.TestCase): - mode = 'static' - def setUp(self): - self.pool = processes.ProcessPool('yes', max_size=1) - - def test_reading_twice_from_same_process(self): - proc = self.pool.get() - try: - result = proc.read(2) - self.assertEquals(result, 'y\n') - finally: - self.pool.put(proc) - - proc2 = self.pool.get() - self.assert_(proc is proc2) - try: - result = proc2.read(2) - self.assertEquals(result, 'y\n') - finally: - self.pool.put(proc2) - - -if __name__ == '__main__': - tests.main() diff --git a/eventlet/.svn/text-base/pylibsupport.py.svn-base b/eventlet/.svn/text-base/pylibsupport.py.svn-base deleted file mode 100644 index 1378e7a..0000000 --- a/eventlet/.svn/text-base/pylibsupport.py.svn-base +++ /dev/null @@ -1,42 +0,0 @@ -"""\ -@file pylibsupport.py -@author Donovan Preston - -Copyright (c) 2005-2006, 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. -""" - - -from py.magic 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 - - - diff --git a/eventlet/.svn/text-base/runloop.py.svn-base b/eventlet/.svn/text-base/runloop.py.svn-base deleted file mode 100644 index 3fde1a4..0000000 --- a/eventlet/.svn/text-base/runloop.py.svn-base +++ /dev/null @@ -1,228 +0,0 @@ -"""\ -@file runloop.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. -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 time -import bisect -import sys -import traceback - -import greenlet - -from eventlet.timer import Timer - - -class RunLoop(object): - SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) - - def __init__(self, wait=None, clock=None): - if clock is None: - clock = self.default_clock() - self.clock = clock - if wait is None: - wait = self.default_wait - self.wait = wait - self.stopping = False - self.running = False - self.timers = [] - self.timers_by_greenlet = {} - self.next_timers = [] - self.observers = {} - self.observer_modes = { - 'entry': [], - 'before_timers': [], - 'before_waiting': [], - 'after_waiting': [], - 'exit': [], - } - - def default_wait(self, time): - return None - - def default_clock(self): - return time.time - - def default_sleep(self): - return 60.0 - - def sleep_until(self): - t = self.timers - if not t: - return None - return t[0][0] - - def run(self): - """Run the runloop until abort is called. - """ - if self.running: - raise RuntimeError("Already running!") - try: - self.running = True - self.stopping = False - self.fire_observers('entry') - while not self.stopping: - self.prepare_timers() - self.fire_observers('before_timers') - self.fire_timers(self.clock()) - self.prepare_timers() - wakeup_when = self.sleep_until() - if wakeup_when is None: - sleep_time = self.default_sleep() - else: - sleep_time = wakeup_when - self.clock() - if sleep_time > 0: - self.fire_observers('before_waiting') - self.wait(sleep_time) - self.fire_observers('after_waiting') - else: - self.wait(0) - else: - del self.timers[:] - del self.next_timers[:] - self.fire_observers('exit') - finally: - self.running = False - self.stopping = False - - def abort(self): - """Stop the runloop. If run is executing, it will exit after completing - the next runloop iteration. - """ - if self.running: - self.stopping = True - - def add_observer(self, observer, *modes): - """Add an event observer to this runloop with the given modes. - Valid modes are: - entry: The runloop is being entered. - before_timers: Before the expired timers for this iteration are executed. - before_waiting: Before waiting for the calculated wait_time - where nothing will happen. - after_waiting: After waiting, immediately before starting the top of the - runloop again. - exit: The runloop is exiting. - - If no mode is passed or mode is all, the observer will be fired for every - event type. - """ - if not modes or modes == ('all',): - modes = tuple(self.observer_modes) - self.observers[observer] = modes - for mode in modes: - self.observer_modes[mode].append(observer) - - def remove_observer(self, observer): - """Remove a previously registered observer from all event types. - """ - for mode in self.observers.pop(observer, ()): - self.observer_modes[mode].remove(observer) - - def squelch_observer_exception(self, observer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Removing observer: %r" % (observer,) - self.remove_observer(observer) - - def fire_observers(self, activity): - for observer in self.observer_modes[activity]: - try: - observer(self, activity) - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_observer_exception(observer, sys.exc_info()) - - def squelch_timer_exception(self, timer, exc_info): - traceback.print_exception(*exc_info) - print >>sys.stderr, "Timer raised: %r" % (timer,) - - def _add_absolute_timer(self, when, info): - # the 0 placeholder makes it easy to bisect_right using (now, 1) - self.next_timers.append((when, 0, info)) - - def add_timer(self, timer): - scheduled_time = self.clock() + timer.seconds - self._add_absolute_timer(scheduled_time, timer) - current_greenlet = greenlet.getcurrent() - 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 - - - def prepare_timers(self): - ins = bisect.insort_right - t = self.timers - for item in self.next_timers: - ins(t, item) - del self.next_timers[:] - - def schedule_call(self, seconds, cb, *args, **kw): - """Schedule a callable to be called after 'seconds' seconds have - elapsed. - seconds: The number of seconds to wait. - cb: The callable to call after the given time. - *args: Arguments to pass to the callable when called. - **kw: Keyword arguments to pass to the callable when called. - """ - t = Timer(seconds, cb, *args, **kw) - self.add_timer(t) - return t - - def fire_timers(self, when): - t = self.timers - last = bisect.bisect_right(t, (when, 1)) - i = 0 - for i in xrange(last): - timer = t[i][2] - try: - try: - timer() - except self.SYSTEM_EXCEPTIONS: - raise - except: - self.squelch_timer_exception(timer, sys.exc_info()) - finally: - try: - del self.timers_by_greenlet[timer.greenlet][timer] - except KeyError: - pass - del t[:last] - - def cancel_timers(self, greenlet): - if greenlet not in self.timers_by_greenlet: - return - for timer in self.timers_by_greenlet[greenlet]: - if not timer.cancelled and timer.seconds: - ## If timer.seconds is 0, this isn't a timer, it's - ## actually eventlet's silly way of specifying whether - ## a coroutine is "ready to run" or not. - timer.cancel() - print 'Runloop cancelling left-over timer %s' % timer - del self.timers_by_greenlet[greenlet] - diff --git a/eventlet/.svn/text-base/runloop_test.py.svn-base b/eventlet/.svn/text-base/runloop_test.py.svn-base deleted file mode 100644 index 4827c32..0000000 --- a/eventlet/.svn/text-base/runloop_test.py.svn-base +++ /dev/null @@ -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() - diff --git a/eventlet/.svn/text-base/saranwrap.py.svn-base b/eventlet/.svn/text-base/saranwrap.py.svn-base deleted file mode 100644 index 43e5fe3..0000000 --- a/eventlet/.svn/text-base/saranwrap.py.svn-base +++ /dev/null @@ -1,685 +0,0 @@ -"""\ -@file saranwrap.py -@author Phoenix -@date 2007-07-13 -@brief A simple, pickle based rpc mechanism which reflects python -objects and callables. - -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. - -This file provides classes and exceptions used for simple python level -remote procedure calls. This is achieved by intercepting the basic -getattr and setattr calls in a client proxy, which commnicates those -down to the server which will dispatch them to objects in it's process -space. - -The basic protocol to get and set attributes is for the client proxy -to issue the command: - -getattr $id $name -setattr $id $name $value - -getitem $id $item -setitem $id $item $value -eq $id $rhs -del $id - -When the get returns a callable, the client proxy will provide a -callable proxy which will invoke a remote procedure call. The command -issued from the callable proxy to server is: - -call $id $name $args $kwargs - -If the client supplies an id of None, then the get/set/call is applied -to the object(s) exported from the server. - -The server will parse the get/set/call, take the action indicated, and -return back to the caller one of: - -value $val -callable -object $id -exception $excp - -To handle object expiration, the proxy will instruct the rpc server to -discard objects which are no longer in use. This is handled by -catching proxy deletion and sending the command: - -del $id - -The server will handle this by removing clearing it's own internal -references. This does not mean that the object will necessarily be -cleaned from the server, but no artificial references will remain -after successfully completing. On completion, the server will return -one of: - -value None -exception $excp - -The server also accepts a special command for debugging purposes: - -status - -Which will be intercepted by the server to write back: - -status {...} - -The wire protocol is to pickle the Request class in this file. The -request class is basically an action and a map of parameters' -""" - -import os -from cPickle import dumps, loads -import struct -import sys - -try: - set = set - frozenset = frozenset -except NameError: - from sets import Set as set, ImmutableSet as frozenset - -from eventlet.processes import Process, DeadProcess -from eventlet import api, pools - -# debugging hooks -_g_debug_mode = False -if _g_debug_mode: - import traceback - import tempfile - -def pythonpath_sync(): - """ -@brief apply the current sys.path to the environment variable PYTHONPATH, so that child processes have the same paths as the caller does. -""" - pypath = os.pathsep.join(sys.path) - os.environ['PYTHONPATH'] = pypath - -def wrap(obj, dead_callback = None): - """ -@brief wrap in object in another process through a saranwrap proxy -@param object The object to wrap. -@param dead_callback A callable to invoke if the process exits.""" - - if type(obj).__name__ == 'module': - return wrap_module(obj.__name__, dead_callback) - pythonpath_sync() - if _g_debug_mode: - p = Process('python', [__file__, '--child', '--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')], dead_callback) - else: - p = Process('python', [__file__, '--child'], dead_callback) - prox = Proxy(ChildProcess(p, p)) - prox.obj = obj - return prox.obj - -def wrap_module(fqname, dead_callback = None): - """ -@brief wrap a module in another process through a saranwrap proxy -@param fqname The fully qualified name of the module. -@param dead_callback A callable to invoke if the process exits.""" - pythonpath_sync() - global _g_debug_mode - if _g_debug_mode: - p = Process('python', [__file__, '--module', fqname, '--logfile', os.path.join(tempfile.gettempdir(), 'saranwrap.log')], dead_callback) - else: - p = Process('python', [__file__, '--module', fqname,], dead_callback) - prox = Proxy(ChildProcess(p,p)) - return prox - -def status(proxy): - """ -@brief get the status from the server through a proxy -@param proxy a saranwrap.Proxy object connected to a server.""" - return proxy.__local_dict['_cp'].make_request(Request('status', {})) - -class BadResponse(Exception): - """"This exception is raised by an saranwrap client when it could - parse but cannot understand the response from the server.""" - pass - -class BadRequest(Exception): - """"This exception is raised by a saranwrap server when it could parse - but cannot understand the response from the server.""" - pass - -class UnrecoverableError(Exception): - pass - -class Request(object): - "@brief A wrapper class for proxy requests to the server." - def __init__(self, action, param): - self._action = action - self._param = param - def __str__(self): - return "Request `"+self._action+"` "+str(self._param) - def __getitem__(self, name): - return self._param[name] - def get(self, name, default = None): - try: - return self[name] - except KeyError: - return default - def action(self): - return self._action - -def _read_lp_hunk(stream): - len_bytes = stream.read(4) - length = struct.unpack('I', len_bytes)[0] - body = stream.read(length) - return body - -def _read_response(id, attribute, input, cp): - """@brief local helper method to read respones from the rpc server.""" - try: - str = _read_lp_hunk(input) - _prnt(`str`) - response = loads(str) - except (AttributeError, DeadProcess), e: - raise UnrecoverableError(e) - _prnt("response: %s" % response) - if response[0] == 'value': - return response[1] - elif response[0] == 'callable': - return CallableProxy(id, attribute, cp) - elif response[0] == 'object': - return ObjectProxy(cp, response[1]) - elif response[0] == 'exception': - exp = response[1] - raise exp - else: - raise BadResponse(response[0]) - -def _write_lp_hunk(stream, hunk): - write_length = struct.pack('I', len(hunk)) - stream.write(write_length + hunk) - if hasattr(stream, 'flush'): - stream.flush() - -def _write_request(param, output): - _prnt("request: %s" % param) - str = dumps(param) - _write_lp_hunk(output, str) - -def _is_local(attribute): - "Return true if the attribute should be handled locally" -# return attribute in ('_in', '_out', '_id', '__getattribute__', '__setattr__', '__dict__') - # good enough for now. :) - if '__local_dict' in attribute: - return True - return False - -def _prnt(message): - global _g_debug_mode - if _g_debug_mode: - print message - -_g_logfile = None -def _log(message): - global _g_logfile - if _g_logfile: - _g_logfile.write(str(os.getpid()) + ' ' + message) - _g_logfile.write('\n') - _g_logfile.flush() - -def _unmunge_attr_name(name): - """ Sometimes attribute names come in with classname prepended, not sure why. - This function removes said classname, because we're huge hackers and we didn't - find out what the true right thing to do is. *TODO: find out. """ - if(name.startswith('_Proxy')): - name = name[len('_Proxy'):] - if(name.startswith('_ObjectProxy')): - name = name[len('_ObjectProxy'):] - return name - -class ChildProcess(object): - """\ - This class wraps a remote python process, presumably available - in an instance of an Server. - """ - def __init__(self, instr, outstr, dead_list = None): - """ - @param instr a file-like object which supports read(). - @param outstr a file-like object which supports write() and flush(). - @param dead_list a list of ids of remote objects that are dead - """ - # default dead_list inside the function because all objects in method - # argument lists are init-ed only once globally - _prnt("ChildProcess::__init__") - if dead_list is None: - dead_list = set() - self._dead_list = dead_list - self._in = instr - self._out = outstr - self._lock = pools.TokenPool(max_size=1) - - def make_request(self, request, attribute=None): - _id = request.get('id') - - t = self._lock.get() - try: - _write_request(request, self._out) - retval = _read_response(_id, attribute, self._in, self) - finally: - self._lock.put(t) - - return retval - - -class Proxy(object): - """\ -@class Proxy -@brief This is the class you will typically use as a client to a child -process. - -Simply instantiate one around a file-like interface and start -calling methods on the thing that is exported. The dir() builtin is -not supported, so you have to know what has been exported. -""" - def __init__(self, cp): - """@param A ChildProcess instance that wraps the i/o to the child process. - """ - #_prnt("Proxy::__init__") - self.__local_dict = dict( - _cp = cp, - _id = None) - - def __getattribute__(self, attribute): - #_prnt("Proxy::__getattr__: %s" % attribute) - if _is_local(attribute): - # call base class getattribute so we actually get the local variable - attribute = _unmunge_attr_name(attribute) - return super(Proxy, self).__getattribute__(attribute) - elif attribute in ('__deepcopy__', '__copy__'): - # redirect copy function calls to our own versions instead of - # to the proxied object - return super(Proxy, self).__getattribute__('__deepcopy__') - else: - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - - _dead_list = my_cp._dead_list - for dead_object in _dead_list.copy(): - request = Request('del', {'id':dead_object}) - - my_cp.make_request(request) - _dead_list.remove(dead_object) - - # Pass all public attributes across to find out if it is - # callable or a simple attribute. - request = Request('getattr', {'id':my_id, 'attribute':attribute}) - return my_cp.make_request(request, attribute=attribute) - - def __setattr__(self, attribute, value): - #_prnt("Proxy::__setattr__: %s" % attribute) - if _is_local(attribute): - # It must be local to this actual object, so we have to apply - # it to the dict in a roundabout way - attribute = _unmunge_attr_name(attribute) - super(Proxy, self).__getattribute__('__dict__')[attribute]=value - else: - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - # Pass the set attribute across - request = Request('setattr', {'id':my_id, 'attribute':attribute, 'value':value}) - return my_cp.make_request(request, attribute=attribute) - -class ObjectProxy(Proxy): - """\ -@class ObjectProxy -@brief This class wraps a remote object in the Server - -This class will be created during normal operation, and users should -not need to deal with this class directly.""" - - def __init__(self, cp, _id): - """\ -@param cp A ChildProcess object that wraps the i/o of a child process. -@param _id an identifier for the remote object. humans do not provide this. -""" - Proxy.__init__(self, cp) - self.__local_dict['_id'] = _id - #_prnt("ObjectProxy::__init__ %s" % _id) - - def __del__(self): - my_id = self.__local_dict['_id'] - #_prnt("ObjectProxy::__del__ %s" % my_id) - self.__local_dict['_cp']._dead_list.add(my_id) - - def __getitem__(self, key): - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('getitem', {'id':my_id, 'key':key}) - return my_cp.make_request(request, attribute=key) - - def __setitem__(self, key, value): - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('setitem', {'id':my_id, 'key':key, 'value':value}) - return my_cp.make_request(request, attribute=key) - - def __eq__(self, rhs): - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('eq', {'id':my_id, 'rhs':rhs.__local_dict['_id']}) - return my_cp.make_request(request) - - def __repr__(self): - # apparently repr(obj) skips the whole getattribute thing and just calls __repr__ - # directly. Therefore we just pass it through the normal call pipeline, and - # tack on a little header so that you can tell it's an object proxy. - val = self.__repr__() - return "saran:%s" % val - - def __str__(self): - # see description for __repr__, because str(obj) works the same. We don't - # tack anything on to the return value here because str values are used as data. - return self.__str__() - - def __nonzero__(self): - # bool(obj) is another method that skips __getattribute__. There's no good way to just pass - # the method on, so we use a special message. - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('nonzero', {'id':my_id}) - return my_cp.make_request(request) - - def __len__(self): - # see description for __repr__, len(obj) is the same. - return self.__len__() - - def __deepcopy__(self, memo=None): - """Copies the entire external object and returns its - value. Will only work if the remote object is pickleable.""" - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('copy', {'id':my_id}) - return my_cp.make_request(request) - - # since the remote object is being serialized whole anyway, - # there's no semantic difference between copy and deepcopy - __copy__ = __deepcopy__ - - -def proxied_type(self): - if type(self) is not ObjectProxy: - return type(self) - - my_cp = self.__local_dict['_cp'] - my_id = self.__local_dict['_id'] - request = Request('type', {'id':my_id}) - return my_cp.make_request(request) - -class CallableProxy(object): - """\ -@class CallableProxy -@brief This class wraps a remote function in the Server - -This class will be created by an Proxy during normal operation, -and users should not need to deal with this class directly.""" - - def __init__(self, object_id, name, cp): - #_prnt("CallableProxy::__init__: %s, %s" % (object_id, name)) - self._object_id = object_id - self._name = name - self._cp = cp - - def __call__(self, *args, **kwargs): - #_prnt("CallableProxy::__call__: %s, %s" % (args, kwargs)) - - # Pass the call across. We never build a callable without - # having already checked if the method starts with '_' so we - # can safely pass this one to the remote object. - #_prnt("calling %s %s" % (self._object_id, self._name) - request = Request('call', {'id':self._object_id, 'name':self._name, 'args':args, 'kwargs':kwargs}) - return self._cp.make_request(request, attribute=self._name) - -class Server(object): - def __init__(self, input, output, export): - """\ -@param input a file-like object which supports read(). -@param output a file-like object which supports write() and flush(). -@param export an object, function, or map which is exported to clients -when the id is None.""" - #_log("Server::__init__") - self._in = input - self._out = output - self._export = export - self._next_id = 1 - self._objects = {} - - def handle_status(self, obj, req): - return { - 'object_count':len(self._objects), - 'next_id':self._next_id, - 'pid':os.getpid()} - - def handle_getattr(self, obj, req): - try: - return getattr(obj, req['attribute']) - except AttributeError, e: - if hasattr(obj, "__getitem__"): - return obj[req['attribute']] - else: - raise e - #_log('getattr: %s' % str(response)) - - def handle_setattr(self, obj, req): - try: - return setattr(obj, req['attribute'], req['value']) - except AttributeError, e: - if hasattr(obj, "__setitem__"): - return obj.__setitem__(req['attribute'], req['value']) - else: - raise e - - def handle_getitem(self, obj, req): - return obj[req['key']] - - def handle_setitem(self, obj, req): - obj[req['key']] = req['value'] - return None # *TODO figure out what the actual return value of __setitem__ should be - - def handle_eq(self, obj, req): - #_log("__eq__ %s %s" % (obj, req)) - rhs = None - try: - rhs = self._objects[req['rhs']] - except KeyError, e: - return False - return (obj == rhs) - - def handle_call(self, obj, req): - #_log("calling %s " % (req['name'])) - try: - fn = getattr(obj, req['name']) - except AttributeError, e: - if hasattr(obj, "__setitem__"): - fn = obj[req['name']] - else: - raise e - - return fn(*req['args'],**req['kwargs']) - - def handle_del(self, obj, req): - id = req['id'] - _log("del %s from %s" % (id, self._objects)) - - # *TODO what does __del__ actually return? - del self._objects[id] - return None - - def handle_type(self, obj, req): - return type(obj) - - def handle_nonzero(self, obj, req): - return bool(obj) - - def handle_copy(self, obj, req): - return obj - - def loop(self): - """@brief Loop forever and respond to all requests.""" - _log("Server::loop") - while True: - try: - try: - str = _read_lp_hunk(self._in) - except EOFError: - sys.exit(0) # normal exit - request = loads(str) - _log("request: %s (%s)" % (request, self._objects)) - req = request - id = None - obj = None - try: - id = req['id'] - if id: - id = int(id) - obj = self._objects[id] - #_log("id, object: %d %s" % (id, obj)) - except Exception, e: - #_log("Exception %s" % str(e)) - pass - if obj is None or id is None: - id = None - obj = self._export - #_log("found object %s" % str(obj)) - - # Handle the request via a method with a special name on the server - handler_name = 'handle_%s' % request.action() - - try: - handler = getattr(self, handler_name) - except AttributeError: - raise BadRequest, request.action() - - response = handler(obj, request) - - # figure out what to do with the response, and respond - # apprpriately. - if request.action() in ['status', 'type', 'copy']: - # have to handle these specially since we want to - # pickle up the actual value and not return a proxy - self.respond(['value', response]) - elif callable(response): - #_log("callable %s" % response) - self.respond(['callable']) - elif self.is_value(response): - self.respond(['value', response]) - else: - self._objects[self._next_id] = response - #_log("objects: %s" % str(self._objects)) - self.respond(['object', self._next_id]) - self._next_id += 1 - except SystemExit, e: - raise e - except Exception, e: - self.write_exception(e) - except: - self.write_exception(sys.exc_info()[0]) - - def is_value(self, value): - """\ -@brief Test if value should be serialized as a simple dataset. -@param value The value to test. -@return Returns true if value is a simple serializeable set of data. -""" - return type(value) in (str,unicode,int,float,long,bool,type(None)) - - def respond(self, body): - _log("responding with: %s" % body) - #_log("objects: %s" % self._objects) - s = dumps(body) - _log(`s`) - str = _write_lp_hunk(self._out, s) - - def write_exception(self, e): - """@brief Helper method to respond with an exception.""" - #_log("exception: %s" % sys.exc_info()[0]) - # TODO: serialize traceback using generalization of code from mulib.htmlexception - self.respond(['exception', e]) - global _g_debug_mode - if _g_debug_mode: - _log("traceback: %s" % traceback.format_tb(sys.exc_info()[2])) - - -# test function used for testing that final except clause -def raise_a_weird_error(): - raise "oh noes you can raise a string" - -# test function used for testing return of unpicklable exceptions -def raise_an_unpicklable_error(): - class Unpicklable(Exception): - pass - raise Unpicklable() - -# test function used for testing return of picklable exceptions -def raise_standard_error(): - raise FloatingPointError() - -# test function to make sure print doesn't break the wrapper -def print_string(str): - print str - -# test function to make sure printing on stdout doesn't break the -# wrapper -def err_string(str): - print >>sys.stderr, str - -def main(): - import optparse - parser = optparse.OptionParser( - usage="usage: %prog [options]", - description="Simple saranwrap.Server wrapper") - parser.add_option( - '-c', '--child', default=False, action='store_true', - help='Wrap an object serialized via setattr.') - parser.add_option( - '-m', '--module', type='string', dest='module', default=None, - help='a module to load and export.') - parser.add_option( - '-l', '--logfile', type='string', dest='logfile', default=None, - help='file to log to.') - options, args = parser.parse_args() - global _g_logfile - if options.logfile: - _g_logfile = open(options.logfile, 'a') - if options.module: - export = api.named(options.module) - server = Server(sys.stdin, sys.stdout, export) - elif options.child: - server = Server(sys.stdin, sys.stdout, {}) - - # *HACK: some modules may emit on stderr, which breaks everything. - class NullSTDOut(object): - def write(a, b): - pass - sys.stderr = NullSTDOut() - sys.stdout = NullSTDOut() - - # Loop until EOF - server.loop() - if _g_logfile: - _g_logfile.close() - - -if __name__ == "__main__": - main() diff --git a/eventlet/.svn/text-base/saranwrap_test.py.svn-base b/eventlet/.svn/text-base/saranwrap_test.py.svn-base deleted file mode 100644 index df0878d..0000000 --- a/eventlet/.svn/text-base/saranwrap_test.py.svn-base +++ /dev/null @@ -1,316 +0,0 @@ -#!/usr/bin/python -# @file test_saranwrap.py -# @brief Test cases for saranwrap. -# -# 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 import saranwrap, coros - -import os -import sys -import tempfile -import time -import unittest -import uuid - -# random test stuff -def list_maker(): - return [0,1,2] - -one = 1 -two = 2 -three = 3 - -class TestSaranwrap(unittest.TestCase): - def assert_server_exists(self, prox): - self.assert_(saranwrap.status(prox)) - prox.foo = 0 - self.assertEqual(0, prox.foo) - - def test_wrap_tuple(self): - my_tuple = (1, 2) - prox = saranwrap.wrap(my_tuple) - self.assertEqual(prox[0], 1) - self.assertEqual(prox[1], 2) - self.assertEqual(len(my_tuple), 2) - - def test_wrap_string(self): - my_object = "whatever" - prox = saranwrap.wrap(my_object) - self.assertEqual(str(my_object), str(prox)) - self.assertEqual(len(my_object), len(prox)) - self.assertEqual(my_object.join(['a', 'b']), prox.join(['a', 'b'])) - - def test_wrap_uniterable(self): - # here we're treating the exception as just a normal class - prox = saranwrap.wrap(FloatingPointError()) - def index(): - prox[0] - def key(): - prox['a'] - - self.assertRaises(IndexError, index) - self.assertRaises(TypeError, key) - - def test_wrap_dict(self): - my_object = {'a':1} - prox = saranwrap.wrap(my_object) - self.assertEqual('a', prox.keys()[0]) - self.assertEqual(1, prox['a']) - self.assertEqual(str(my_object), str(prox)) - self.assertEqual('saran:' + repr(my_object), repr(prox)) - self.assertEqual('saran:' + `my_object`, `prox`) - - def test_wrap_module_class(self): - prox = saranwrap.wrap(uuid) - self.assertEqual(saranwrap.Proxy, type(prox)) - id = prox.uuid4() - self.assertEqual(id.get_version(), uuid.uuid4().get_version()) - self.assert_(repr(prox.uuid4)) - - def test_wrap_eq(self): - prox = saranwrap.wrap(uuid) - id1 = prox.uuid4() - id2 = prox.UUID(str(id1)) - self.assertEqual(id1, id2) - id3 = prox.uuid4() - self.assert_(id1 != id3) - - def test_wrap_nonzero(self): - prox = saranwrap.wrap(uuid) - id1 = prox.uuid4() - self.assert_(bool(id1)) - prox2 = saranwrap.wrap([1, 2, 3]) - self.assert_(bool(prox2)) - - def test_multiple_wraps(self): - prox1 = saranwrap.wrap(uuid) - prox2 = saranwrap.wrap(uuid) - x1 = prox1.uuid4() - x2 = prox1.uuid4() - del x2 - x3 = prox2.uuid4() - - def test_dict_passthru(self): - prox = saranwrap.wrap(uuid) - x = prox.uuid4() - self.assertEqual(type(x.__dict__), saranwrap.ObjectProxy) - # try it all on one line just for the sake of it - self.assertEqual(type(saranwrap.wrap(uuid).uuid4().__dict__), saranwrap.ObjectProxy) - - def test_is_value(self): - server = saranwrap.Server(None, None, None) - self.assert_(server.is_value(None)) - - def test_wrap_getitem(self): - prox = saranwrap.wrap([0,1,2]) - self.assertEqual(prox[0], 0) - - def test_wrap_setitem(self): - prox = saranwrap.wrap([0,1,2]) - prox[1] = 2 - self.assertEqual(prox[1], 2) - - def test_raising_exceptions(self): - prox = saranwrap.wrap(uuid) - def nofunc(): - prox.never_name_a_function_like_this() - self.assertRaises(AttributeError, nofunc) - - def test_raising_weird_exceptions(self): - # the recursion is killing me! - prox = saranwrap.wrap(saranwrap) - try: - prox.raise_a_weird_error() - self.assert_(False) - except: - import sys - ex = sys.exc_info()[0] - self.assertEqual(ex, "oh noes you can raise a string") - self.assert_server_exists(prox) - - def test_unpicklable_server_exception(self): - prox = saranwrap.wrap(saranwrap) - def unpickle(): - prox.raise_an_unpicklable_error() - - self.assertRaises(saranwrap.UnrecoverableError, unpickle) - - # It's basically dead - #self.assert_server_exists(prox) - - def test_pickleable_server_exception(self): - prox = saranwrap.wrap(saranwrap) - def fperror(): - prox.raise_standard_error() - - self.assertRaises(FloatingPointError, fperror) - self.assert_server_exists(prox) - - def test_print_does_not_break_wrapper(self): - prox = saranwrap.wrap(saranwrap) - prox.print_string('hello') - self.assert_server_exists(prox) - - def test_stderr_does_not_break_wrapper(self): - prox = saranwrap.wrap(saranwrap) - prox.err_string('goodbye') - self.assert_server_exists(prox) - - def assertLessThan(self, a, b): - self.assert_(a < b, "%s is not less than %s" % (a, b)) - - def test_status(self): - prox = saranwrap.wrap(time) - a = prox.gmtime(0) - status = saranwrap.status(prox) - self.assertEqual(status['object_count'], 1) - self.assertEqual(status['next_id'], 2) - self.assert_(status['pid']) # can't guess what it will be - # status of an object should be the same as the module - self.assertEqual(saranwrap.status(a), status) - # create a new one then immediately delete it - prox.gmtime(1) - is_id = prox.ctime(1) # sync up deletes - status = saranwrap.status(prox) - self.assertEqual(status['object_count'], 1) - self.assertEqual(status['next_id'], 3) - prox2 = saranwrap.wrap(uuid) - self.assert_(status['pid'] != saranwrap.status(prox2)['pid']) - - def test_del(self): - prox = saranwrap.wrap(time) - delme = prox.gmtime(0) - status_before = saranwrap.status(prox) - #print status_before['objects'] - del delme - # need to do an access that doesn't create an object - # in order to sync up the deleted objects - prox.ctime(1) - status_after = saranwrap.status(prox) - #print status_after['objects'] - self.assertLessThan(status_after['object_count'], status_before['object_count']) - - def test_variable_and_keyword_arguments_with_function_calls(self): - import optparse - prox = saranwrap.wrap(optparse) - parser = prox.OptionParser() - z = parser.add_option('-n', action='store', type='string', dest='n') - opts,args = parser.parse_args(["-nfoo"]) - self.assertEqual(opts.n, 'foo') - - def test_original_proxy_going_out_of_scope(self): - def make_uuid(): - prox = saranwrap.wrap(uuid) - # after this function returns, prox should fall out of scope - return prox.uuid4() - tid = make_uuid() - self.assertEqual(tid.get_version(), uuid.uuid4().get_version()) - def make_list(): - from eventlet import saranwrap_test - prox = saranwrap.wrap(saranwrap_test.list_maker) - # after this function returns, prox should fall out of scope - return prox() - proxl = make_list() - self.assertEqual(proxl[2], 2) - - def test_status_of_none(self): - try: - saranwrap.status(None) - self.assert_(False) - except AttributeError, e: - pass - - def test_not_inheriting_pythonpath(self): - # construct a fake module in the temp directory - temp_dir = tempfile.mkdtemp("saranwrap_test") - fp = open(os.path.join(temp_dir, "jitar_hero.py"), "w") - fp.write("""import os, sys -pypath = os.environ['PYTHONPATH'] -sys_path = sys.path""") - fp.close() - - # this should fail because we haven't stuck the temp_dir in our path yet - prox = saranwrap.wrap_module('jitar_hero') - import cPickle - try: - prox.pypath - self.fail() - except cPickle.UnpicklingError: - pass - - # now try to saranwrap it - sys.path.append(temp_dir) - try: - import jitar_hero - prox = saranwrap.wrap(jitar_hero) - self.assert_(prox.pypath.count(temp_dir)) - self.assert_(prox.sys_path.count(temp_dir)) - finally: - import shutil - shutil.rmtree(temp_dir) - sys.path.remove(temp_dir) - - def test_contention(self): - from eventlet import saranwrap_test - prox = saranwrap.wrap(saranwrap_test) - - pool = coros.CoroutinePool(max_size=4) - waiters = [] - waiters.append(pool.execute(lambda: self.assertEquals(prox.one, 1))) - waiters.append(pool.execute(lambda: self.assertEquals(prox.two, 2))) - waiters.append(pool.execute(lambda: self.assertEquals(prox.three, 3))) - for waiter in waiters: - waiter.wait() - - def test_copy(self): - import copy - compound_object = {'a':[1,2,3]} - prox = saranwrap.wrap(compound_object) - def make_assertions(copied): - self.assert_(isinstance(copied, dict)) - self.assert_(isinstance(copied['a'], list)) - self.assertEquals(copied, compound_object) - self.assertNotEqual(id(compound_object), id(copied)) - - make_assertions(copy.copy(prox)) - make_assertions(copy.deepcopy(prox)) - - def test_list_of_functions(self): - return # this test is known to fail, we can implement it sometime in the future if we wish - from eventlet import saranwrap_test - prox = saranwrap.wrap([saranwrap_test.list_maker]) - self.assertEquals(list_maker(), prox[0]()) - - def test_detection_of_server_crash(self): - # make the server crash here - pass - - def test_equality_with_local_object(self): - # we'll implement this if there's a use case for it - pass - - def test_non_blocking(self): - # here we test whether it's nonblocking - pass - -if __name__ == '__main__': - unittest.main() diff --git a/eventlet/.svn/text-base/selecthub.py.svn-base b/eventlet/.svn/text-base/selecthub.py.svn-base deleted file mode 100644 index 60e2bb6..0000000 --- a/eventlet/.svn/text-base/selecthub.py.svn-base +++ /dev/null @@ -1,173 +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.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 = select.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()) diff --git a/eventlet/.svn/text-base/stacklesssupport.py.svn-base b/eventlet/.svn/text-base/stacklesssupport.py.svn-base deleted file mode 100644 index 8e97555..0000000 --- a/eventlet/.svn/text-base/stacklesssupport.py.svn-base +++ /dev/null @@ -1,110 +0,0 @@ -"""\ -@file __init__.py -@brief Support for using stackless python. Broken and riddled with -print statements at the moment. Please fix it! - -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 types - -import stackless -import traceback - - -caller = None - - -coro_args = {} - - -tasklet_to_greenlet = {} - - -def getcurrent(): - return tasklet_to_greenlet[stackless.getcurrent()] - - -class FirstSwitch(object): - def __init__(self, gr): - self.gr = gr - - def __call__(self, *args, **kw): - print "first call", args, kw - gr = self.gr - del gr.switch - run, gr.run = gr.run, None - t = stackless.tasklet(run) - gr.t = t - tasklet_to_greenlet[t] = gr - t.setup(*args, **kw) - result = t.run() - - -class greenlet(object): - def __init__(self, run=None, parent=None): - self.dead = False - if parent is None: - parent = getcurrent() - - self.parent = parent - if run is not None: - self.run = run - - self.switch = FirstSwitch(self) - - def switch(self, *args): - print "switch", args - global caller - caller = stackless.getcurrent() - coro_args[self] = args - self.t.insert() - stackless.schedule() - if caller is not self.t: - caller.remove() - rval = coro_args[self] - return rval - - def run(self): - pass - - def __bool__(self): - return self.run is None and not self.dead - - -class GreenletExit(Exception): - pass - - -def emulate(): - module = types.ModuleType('greenlet') - sys.modules['greenlet'] = module - module.greenlet = greenlet - module.getcurrent = getcurrent - module.GreenletExit = GreenletExit - - caller = t = stackless.getcurrent() - tasklet_to_greenlet[t] = None - main_coro = greenlet() - tasklet_to_greenlet[t] = main_coro - main_coro.t = t - del main_coro.switch ## It's already running - coro_args[main_coro] = None diff --git a/eventlet/.svn/text-base/tests.py.svn-base b/eventlet/.svn/text-base/tests.py.svn-base deleted file mode 100644 index 41968c2..0000000 --- a/eventlet/.svn/text-base/tests.py.svn-base +++ /dev/null @@ -1,89 +0,0 @@ -"""\ -@file tests.py -@author Donovan Preston -@brief Indirection layer for tests in case we want to fix unittest. - -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 errno -import os -import sys -import unittest, doctest - - -TestCase = unittest.TestCase - - -name = getattr(sys.modules['__main__'], '__name__', None) -main = unittest.main - -# listing of files containing doctests -doc_test_files = ['coros'] - -def find_command(command): - for dir in os.getenv('PATH', '/usr/bin:/usr/sbin').split(os.pathsep): - p = os.path.join(dir, command) - if os.access(p, os.X_OK): - return p - raise IOError(errno.ENOENT, 'Command not found: %r' % command) - -def run_all_tests(test_files = doc_test_files): - """ Runs all the unit tests, returning immediately after the - first failed test. - - Returns true if the tests all succeeded. This method is really much longer - than it ought to be. - """ - eventlet_dir = os.path.realpath(os.path.dirname(__file__)) - if eventlet_dir not in sys.path: - sys.path.append(eventlet_dir) - - # add all _test files as a policy - import glob - test_files += [os.path.splitext(os.path.basename(x))[0] - for x in glob.glob(os.path.join(eventlet_dir, "*_test.py"))] - test_files.sort() - - for test_file in test_files: - print "-=", test_file, "=-" - try: - test_module = __import__(test_file) - except ImportError: - print "Unable to import %s, skipping" % test_file - continue - - if test_file.endswith('_test'): - # gawd, unittest, why you make it so difficult to just run some tests! - suite = unittest.findTestCases(test_module) - result = unittest.TextTestRunner().run(suite) - if not result.wasSuccessful(): - return False - else: - failures, tests = doctest.testmod(test_module) - if failures: - return False - else: - print "OK" - - return True - -if __name__ == '__main__': - run_all_tests() diff --git a/eventlet/.svn/text-base/timer.py.svn-base b/eventlet/.svn/text-base/timer.py.svn-base deleted file mode 100644 index a91390a..0000000 --- a/eventlet/.svn/text-base/timer.py.svn-base +++ /dev/null @@ -1,83 +0,0 @@ -"""\ -@file timer.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 get_hub - -""" If true, captures a stack trace for each timer when constructed. This is -useful for debugging leaking timers, to find out where the timer was set up. """ -_g_debug = False - -class Timer(object): - __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback'] - def __init__(self, seconds, cb, *args, **kw): - """Create a timer. - seconds: The minimum number of seconds to wait before calling - cb: The callback to call when the timer has expired - *args: The arguments to pass to cb - **kw: The keyword arguments to pass to cb - - This timer will not be run unless it is scheduled in a runloop by - calling timer.schedule() or runloop.add_timer(timer). - """ - self.cancelled = False - self.seconds = seconds - self.tpl = cb, args, kw - self.called = False - if _g_debug: - import traceback, cStringIO - self.traceback = cStringIO.StringIO() - traceback.print_stack(file=self.traceback) - - def __repr__(self): - secs = getattr(self, 'seconds', None) - cb, args, kw = getattr(self, 'tpl', (None, None, None)) - retval = "Timer(%s, %s, *%s, **%s)" % ( - secs, cb, args, kw) - if _g_debug and hasattr(self, 'traceback'): - retval += '\n' + self.traceback.getvalue() - return retval - - def copy(self): - cb, args, kw = self.tpl - return self.__class__(self.seconds, cb, *args, **kw) - - def schedule(self): - """Schedule this timer to run in the current runloop. - """ - self.called = False - self.scheduled_time = get_hub().runloop.add_timer(self) - return self - - def __call__(self): - if not self.called: - self.called = True - cb, args, kw = self.tpl - cb(*args, **kw) - - def cancel(self): - """Prevent this timer from being called. If the timer has already - been called, has no effect. - """ - self.cancelled = True - self.called = True diff --git a/eventlet/.svn/text-base/timer_test.py.svn-base b/eventlet/.svn/text-base/timer_test.py.svn-base deleted file mode 100644 index 496a884..0000000 --- a/eventlet/.svn/text-base/timer_test.py.svn-base +++ /dev/null @@ -1,66 +0,0 @@ -"""\ -@file timer_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 unittest - -from eventlet import api, runloop, tests, timer - -class TestTimer(tests.TestCase): - mode = 'static' - def test_copy(self): - t = timer.Timer(0, lambda: None) - t2 = t.copy() - assert t.seconds == t2.seconds - assert t.tpl == t2.tpl - assert t.called == t2.called - - def test_cancel(self): - r = runloop.RunLoop() - called = [] - t = timer.Timer(0, lambda: called.append(True)) - t.cancel() - r.add_timer(t) - r.add_observer(lambda r, activity: r.abort(), 'after_waiting') - r.run() - assert not called - assert not r.running - - def test_schedule(self): - hub = api.get_hub() - r = hub.runloop - # clean up the runloop, preventing side effects from previous tests - # on this thread - if r.running: - r.abort() - api.sleep(0) - called = [] - t = timer.Timer(0, lambda: (called.append(True), hub.runloop.abort())) - t.schedule() - r.default_sleep = lambda: 0.0 - r.run() - assert called - assert not r.running - -if __name__ == '__main__': - unittest.main() diff --git a/eventlet/.svn/text-base/tls.py.svn-base b/eventlet/.svn/text-base/tls.py.svn-base deleted file mode 100644 index 3cb3921..0000000 --- a/eventlet/.svn/text-base/tls.py.svn-base +++ /dev/null @@ -1,57 +0,0 @@ -"""\ -@file tls.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 threading -import weakref - -__all__ = ['local'] - -class _local(object): - """ - Crappy Python 2.3 compatible implementation of thread-local storage - """ - - __slots__ = ('__thread_dict__',) - - def __init__(self): - object.__setattr__(self, '__thread_dict__', weakref.WeakKeyDictionary()) - - def __getattr__(self, attr): - try: - return self.__thread_dict__[threading.currentThread()][attr] - except KeyError: - raise AttributeError(attr) - - def __setattr__(self, attr, value): - t = threading.currentThread() - try: - d = self.__thread_dict__[t] - except KeyError: - d = self.__thread_dict__[t] = {} - d[attr] = value - -try: - local = threading.local -except AttributeError: - local = _local diff --git a/eventlet/.svn/text-base/tpool.py.svn-base b/eventlet/.svn/text-base/tpool.py.svn-base deleted file mode 100644 index 6ca505d..0000000 --- a/eventlet/.svn/text-base/tpool.py.svn-base +++ /dev/null @@ -1,123 +0,0 @@ -"""\ -@file tpool.py - -Copyright (c) 2007, Linden Research, Inc. -Copyright (c) 2007, IBM Corp. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os, socket, time, threading -import Queue - -from sys import stdout -from Queue import Empty, Queue - -from eventlet import api, coros, httpc, httpd, util, wrappedfd -from eventlet.api import trampoline, get_hub - -_rpipe, _wpipe = os.pipe() -_rfile = os.fdopen(_rpipe,"r",0) -_wrap_rfile = wrappedfd.wrapped_file(_rfile) -util.set_nonblocking(_rfile) - -def _signal_t2e(): - nwritten = os.write(_wpipe,' ') - -_reqq = Queue(maxsize=-1) -_rspq = Queue(maxsize=-1) - -def tpool_trampoline(): - global _reqq, _rspq - while(True): - _c = _wrap_rfile.recv(1) - assert(_c != "") - while not _rspq.empty(): - try: - (e,rv) = _rspq.get(block=False) - e.send(rv) - except Empty: - pass - -def esend(meth,*args, **kwargs): - global _reqq, _rspq - e = coros.event() - _reqq.put((e,meth,args,kwargs)) - return e - -def tworker(): - global _reqq, _rspq - while(True): - (e,meth,args,kwargs) = _reqq.get() - rv = None - try: - rv = meth(*args,**kwargs) - except Exception,exn: - import sys, traceback - (a,b,tb) = sys.exc_info() - rv = (exn,a,b,tb) - _rspq.put((e,rv)) - _signal_t2e() - -def erecv(e): - rv = e.wait() - if isinstance(rv,tuple) and len(rv) == 4 and isinstance(rv[0],Exception): - import sys, traceback - (e,a,b,tb) = rv - traceback.print_exception(Exception,e,tb) - traceback.print_stack() - raise e - return rv - -def erpc(meth,*args, **kwargs): - e = esend(meth,*args,**kwargs) - rv = erecv(e) - return rv - -class Proxy(object): - """ 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. - A key restriction is that the object's methods cannot call into eventlets, since the - eventlet dispatcher runs on a different native thread. This is for running native-threaded - code only. """ - def __init__(self, obj,autowrap=()): - self._obj = obj - self._autowrap = autowrap - - def __getattr__(self,attr_name): - f = getattr(self._obj,attr_name) - if not callable(f): - return f - def doit(*args, **kwargs): - if kwargs.pop('nonblocking',False): - rv = f(*args, **kwargs) - else: - rv = erpc(f,*args,**kwargs) - if type(rv) in self._autowrap: - return Proxy(rv) - else: - return rv - return doit - -_nthreads = 20 -_threads = {} -def setup(): - global _threads - for i in range(0,_nthreads): - _threads[i] = threading.Thread(target=tworker) - _threads[i].setDaemon(True) - _threads[i].start() - - api.spawn(tpool_trampoline) - -setup() diff --git a/eventlet/.svn/text-base/tpool_test.py.svn-base b/eventlet/.svn/text-base/tpool_test.py.svn-base deleted file mode 100644 index 19442bc..0000000 --- a/eventlet/.svn/text-base/tpool_test.py.svn-base +++ /dev/null @@ -1,70 +0,0 @@ -"""\ -@file tpool_test.py - -Copyright (c) 2007, Linden Research, Inc. -Copyright (c) 2007, IBM Corp. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os, socket, time, threading -from eventlet import coros, api, tpool, tests - -from eventlet.tpool import erpc -from sys import stdout - -import random -r = random.WichmannHill() - -_g_debug = False - -def prnt(msg): - if _g_debug: - print msg - -class yadda(object): - def __init__(self): - pass - - def foo(self,when,n=None): - assert(n is not None) - prnt("foo: %s, %s" % (when,n)) - time.sleep(r.random()) - return n - -def sender_loop(pfx): - n = 0 - obj = tpool.Proxy(yadda()) - while n < 10: - api.sleep(0) - now = time.time() - prnt("%s: send (%s,%s)" % (pfx,now,n)) - rv = obj.foo(now,n=n) - prnt("%s: recv %s" % (pfx, rv)) - assert(n == rv) - api.sleep(0) - n += 1 - - -class TestTpool(tests.TestCase): - def test1(self): - pool = coros.CoroutinePool(max_size=10) - waiters = [] - for i in range(0,9): - waiters.append(pool.execute(sender_loop,i)) - for waiter in waiters: - waiter.wait() - - -if __name__ == '__main__': - tests.main() diff --git a/eventlet/.svn/text-base/twistedsupport.py.svn-base b/eventlet/.svn/text-base/twistedsupport.py.svn-base deleted file mode 100644 index 5bbbc8b..0000000 --- a/eventlet/.svn/text-base/twistedsupport.py.svn-base +++ /dev/null @@ -1,134 +0,0 @@ -"""\ -@file twistedsupport.py -@author Donovan Preston - -Copyright (c) 2005-2006, 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 sys -import traceback - -from eventlet import api -from eventlet import timer - -from twisted.internet import posixbase -from twisted.internet.interfaces import IReactorFDSet - -try: - from zope.interface import implements - _working = True -except ImportError: - _working = False - def implements(*args, **kw): - pass - - -class TwistedTimer(object): - def __init__(self, timer): - self.timer = timer - - def cancel(self): - self.timer.cancel() - - def getTime(self): - return self.timer.scheduled_time - - def delay(self, seconds): - hub = api.get_hub() - new_time = hub.clock() - self.timer_scheduled_time + seconds - self.timer.cancel() - cb, args, kw = self.timer.tpl - self.timer = hub.schedule_call(new_time, cb, *args, **kw) - - def reset(self, new_time): - self.timer.cancel() - cb, args, kw = self.timer.tpl - self.timer = api.get_hub().schedule_call(new_time, cb, *args, **kw) - - def active(self): - return not self.timer.called - - -class EventletReactor(posixbase.PosixReactorBase): - implements(IReactorFDSet) - - def __init__(self, *args, **kw): - self._readers = {} - self._writers = {} - posixbase.PosixReactorBase.__init__(self, *args, **kw) - - def callLater(self, func, *args, **kw): - return TwistedTimer(api.call_after(func, *args, **kw)) - - def run(self): - self.running = True - self._stopper = api.call_after(sys.maxint / 1000.0, lambda: None) - ## schedule a call way in the future, and cancel it in stop? - api.get_hub().runloop.run() - - def stop(self): - self._stopper.cancel() - posixbase.PosixReactorBase.stop(self) - api.get_hub().remove_descriptor(self._readers.keys()[0]) - api.get_hub().runloop.abort() - - def addReader(self, reader): - fileno = reader.fileno() - self._readers[fileno] = reader - api.get_hub().add_descriptor(fileno, read=self._got_read) - - def _got_read(self, fileno): - self._readers[fileno].doRead() - - def addWriter(self, writer): - fileno = writer.fileno() - self._writers[fileno] = writer - api.get_hub().add_descriptor(fileno, write=self._got_write) - - def _got_write(self, fileno): - self._writers[fileno].doWrite() - - def removeReader(self, reader): - fileno = reader.fileno() - if fileno in self._readers: - self._readers.pop(fileno) - api.get_hub().remove_descriptor(fileno) - - def removeWriter(self, writer): - fileno = writer.fileno() - if fileno in self._writers: - self._writers.pop(fileno) - api.get_hub().remove_descriptor(fileno) - - def removeAll(self): - return self._removeAll(self._readers.values(), self._writers.values()) - - -def emulate(): - if not _working: - raise RuntimeError, "Can't use twistedsupport because zope.interface is not installed." - reactor = EventletReactor() - from twisted.internet.main import installReactor - installReactor(reactor) - - -__all__ = ['emulate'] - diff --git a/eventlet/.svn/text-base/util.py.svn-base b/eventlet/.svn/text-base/util.py.svn-base deleted file mode 100644 index 657716c..0000000 --- a/eventlet/.svn/text-base/util.py.svn-base +++ /dev/null @@ -1,214 +0,0 @@ -"""\ -@file util.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 os -import fcntl -import socket -import errno -from errno import EWOULDBLOCK, EAGAIN - -try: - from OpenSSL import SSL -except ImportError: - class SSL(object): - class WantWriteError(object): - pass - - class WantReadError(object): - pass - - class ZeroReturnError(object): - pass - - class SysCallError(object): - pass - - -def g_log(*args): - import sys - import greenlet - from eventlet.greenlib import greenlet_id - g_id = greenlet_id() - if g_id is None: - if greenlet.getcurrent().parent is None: - ident = 'greenlet-main' - else: - g_id = id(greenlet.getcurrent()) - if g_id < 0: - g_id += 1 + ((sys.maxint + 1) << 1) - ident = '%08X' % (g_id,) - else: - ident = 'greenlet-%d' % (g_id,) - print >>sys.stderr, '[%s] %s' % (ident, ' '.join(map(str, args))) - -CONNECT_ERR = (errno.EINPROGRESS, errno.EALREADY, 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 - -def tcp_socket(): - s = __original_socket__(socket.AF_INET, socket.SOCK_STREAM) - set_nonblocking(s) - return s - - -__original_ssl__ = socket.ssl - - -def wrap_ssl(sock, certificate=None, private_key=None): - from OpenSSL import SSL - from eventlet import wrappedfd, util - context = SSL.Context(SSL.SSLv23_METHOD) - print certificate, private_key - if certificate is not None: - context.use_certificate_file(certificate) - if private_key is not None: - context.use_privatekey_file(private_key) - context.set_verify(SSL.VERIFY_NONE, lambda *x: True) - - ## TODO only do this on client sockets? how? - connection = SSL.Connection(context, sock) - connection.set_connect_state() - return wrappedfd.wrapped_fd(connection) - - -def wrap_socket_with_coroutine_socket(): - def new_socket(*args, **kw): - from eventlet import wrappedfd - s = __original_socket__(*args, **kw) - set_nonblocking(s) - return wrappedfd.wrapped_fd(s) - socket.socket = new_socket - - socket.ssl = wrap_ssl - - -def socket_bind_and_listen(descriptor, addr=('', 0), backlog=50): - set_reuse_addr(descriptor) - descriptor.bind(addr) - descriptor.listen(backlog) - return descriptor - -def socket_accept(descriptor): - try: - return descriptor.accept() - except socket.error, e: - if e[0] == EWOULDBLOCK: - return None - raise - -def socket_send(descriptor, data): - try: - return descriptor.send(data) - except socket.error, e: - if e[0] == EWOULDBLOCK: - return 0 - raise - except SSL.WantWriteError: - return 0 - except SSL.WantReadError: - return 0 - - -# 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] == EWOULDBLOCK: - return None - if e[0] in SOCKET_CLOSED: - return '' - raise - except SSL.WantReadError: - return None - except SSL.ZeroReturnError: - return '' - except SSL.SysCallError, e: - if e[0] == -1 or e[0] > 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] == 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_reuse_addr(descriptor): - try: - descriptor.setsockopt( - socket.SOL_SOCKET, - socket.SO_REUSEADDR, - descriptor.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1, - ) - except socket.error: - 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 - diff --git a/eventlet/.svn/text-base/wrappedfd.py.svn-base b/eventlet/.svn/text-base/wrappedfd.py.svn-base deleted file mode 100644 index 21265fb..0000000 --- a/eventlet/.svn/text-base/wrappedfd.py.svn-base +++ /dev/null @@ -1,284 +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): - self.fd.flush() diff --git a/eventlet/.svn/text-base/wsgi.py.svn-base b/eventlet/.svn/text-base/wsgi.py.svn-base deleted file mode 100644 index 2fa765a..0000000 --- a/eventlet/.svn/text-base/wsgi.py.svn-base +++ /dev/null @@ -1,219 +0,0 @@ -"""\ -@file wsgi.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 time -import urllib -import socket -import cStringIO -import SocketServer -import BaseHTTPServer - -from eventlet import api -from eventlet.httpdate import format_date_time - -class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): - - def log_message(self, format, *args): - self.server.log_message("%s - - [%s] %s" % ( - self.address_string(), - self.log_date_time_string(), - format % args)) - - def handle_one_request(self): - self.raw_requestline = self.rfile.readline() - - if not self.raw_requestline: - self.close_connection = 1 - return - - if not self.parse_request(): - return - - self.environ = self.get_environ() - try: - self.handle_one_response() - except socket.error, e: - # Broken pipe, connection reset by peer - if e[0] in (32, 54): - pass - else: - raise - - def handle_one_response(self): - headers_set = [] - headers_sent = [] - # set of lowercase header names that were sent - header_dict = {} - - wfile = self.wfile - num_blocks = None - - def write(data, _write=wfile.write): - if not headers_set: - raise AssertionError("write() before start_response()") - elif not headers_sent: - status, response_headers = headers_set - headers_sent.append(1) - for k, v in response_headers: - header_dict[k.lower()] = k - _write('HTTP/1.0 %s\r\n' % status) - # send Date header? - if 'date' not in header_dict: - _write('Date: %s\r\n' % (format_date_time(time.time()),)) - if 'content-length' not in header_dict and num_blocks == 1: - _write('Content-Length: %s\r\n' % (len(data),)) - for header in response_headers: - _write('%s: %s\r\n' % header) - _write('\r\n') - _write(data) - - def start_request(status, response_headers, exc_info=None): - if exc_info: - try: - if headers_sent: - # Re-raise original exception if headers sent - raise exc_info[0], exc_info[1], exc_info[2] - finally: - # Avoid dangling circular ref - exc_info = None - elif headers_set: - raise AssertionError("Headers already set!") - - headers_set[:] = [status, response_headers] - return write - - result = self.server.app(self.environ, start_request) - try: - num_blocks = len(result) - except (TypeError, AttributeError, NotImplementedError): - pass - - try: - for data in result: - if data: - write(data) - if not headers_sent: - write('') - finally: - if hasattr(result, 'close'): - result.close() - - def get_environ(self): - env = self.server.get_environ() - env['REQUEST_METHOD'] = self.command - env['SCRIPT_NAME'] = '' - - if '?' in self.path: - path, query = self.path.split('?', 1) - else: - path, query = self.path, '' - env['PATH_INFO'] = urllib.unquote(path) - env['QUERY_STRING'] = query - - if self.headers.typeheader is None: - env['CONTENT_TYPE'] = self.headers.type - else: - env['CONTENT_TYPE'] = self.headers.typeheader - - length = self.headers.getheader('content-length') - if length: - env['CONTENT_LENGTH'] = length - env['SERVER_PROTOCOL'] = 'HTTP/1.0' - - host, port = self.request.getsockname() - env['SERVER_NAME'] = host - env['SERVER_PORT'] = str(port) - env['REMOTE_ADDR'] = self.client_address[0] - env['GATEWAY_INTERFACE'] = 'CGI/1.1' - - for h in self.headers.headers: - k, v = h.split(':', 1) - k = k.replace('-', '_').upper() - v = v.strip() - if k in env: - continue - envk = 'HTTP_' + k - if envk in env: - env[envk] += ',' + v - else: - env[envk] = v - - return env - - def finish(self): - # Override SocketServer.StreamRequestHandler.finish because - # we only need to call close on the socket, not the makefile'd things - - self.request.close() - - -class Server(BaseHTTPServer.HTTPServer): - def __init__(self, socket, address, app, log, environ=None): - self.socket = socket - self.address = address - if log: - self.log = log - log.write = log.info - else: - self.log = sys.stderr - self.app = app - self.environ = environ - - def get_environ(self): - socket = self.socket - d = { - 'wsgi.input': socket, - 'wsgi.errors': sys.stderr, - 'wsgi.version': (1, 0), - 'wsgi.multithread': True, - 'wsgi.multiprocess': False, - 'wsgi.run_once': False, - 'wsgi.url_scheme': 'http', - } - if self.environ is not None: - d.update(self.environ) - return d - - def process_request(self, (socket, address)): - proto = HttpProtocol(socket, address, self) - - def log_message(self, message): - self.log.write(message + '\n') - - -def server(socket, site, log=None, environ=None): - serv = Server(socket, socket.getsockname(), site, log, environ=None) - try: - print "wsgi starting up on", socket.getsockname() - while True: - try: - api.spawn(serv.process_request, socket.accept()) - except KeyboardInterrupt: - api.get_hub().remove_descriptor(socket.fileno()) - print "wsgi exiting" - break - finally: - socket.close() diff --git a/eventlet/.svn/tmp/tempfile.2.tmp b/eventlet/.svn/tmp/tempfile.2.tmp deleted file mode 100644 index 7d73756..0000000 --- a/eventlet/.svn/tmp/tempfile.2.tmp +++ /dev/null @@ -1,584 +0,0 @@ -"""\ -@file httpd.py -@author Donovan Preston - -Copyright (c) 2005-2006, 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 -import errno -import socket -import sys -import time -import urllib -import socket -import traceback -import BaseHTTPServer - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - -from eventlet import api -from eventlet import coros - - -DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' - -USE_ACCESS_LOG = True - - -CONNECTION_CLOSED = (errno.EPIPE, errno.ECONNRESET) - - -class ErrorResponse(Exception): - _responses = BaseHTTPServer.BaseHTTPRequestHandler.responses - - def __init__(self, code, reason_phrase=None, headers=None, body=None): - Exception.__init__(self, reason_phrase) - self.code = code - if reason_phrase is None: - self.reason = self._responses[code][0] - else: - self.reason = reason_phrase - self.headers = headers - if body is None: - self.body = self._responses[code][1] - else: - self.body = body - - -class Request(object): - _method = None - _path = None - _responsecode = 200 - _reason_phrase = None - _request_started = False - _chunked = False - _producer_adapters = {} - depth = 0 - def __init__(self, protocol, method, path, headers): - self.context = {} - self.request_start_time = time.time() - self.site = protocol.server.site - self.protocol = protocol - self._method = method - if '?' in path: - self._path, self._query = path.split('?', 1) - self._query = self._query.replace('&', '&') - else: - self._path, self._query = path, None - self._incoming_headers = headers - self._outgoing_headers = dict() - - def response(self, code, reason_phrase=None, headers=None, body=None): - """Change the response code. This will not be sent until some - data is written; last call to this method wins. Default is - 200 if this is not called. - """ - self._responsecode = code - self._reason_phrase = reason_phrase - self.protocol.set_response_code(self, code, reason_phrase) - if headers is not None: - try: - headers = headers.iteritems() - except AttributeError: - pass - for key, value in headers: - self.set_header(key, value) - if body is not None: - self.write(body) - - def is_okay(self): - return 200 <= self._responsecode <= 299 - - def full_url(self): - path = self.path() - query = self.query() - if query: - path = path + '?' + query - - via = self.get_header('via', '') - if via.strip(): - next_part = iter(via.split()).next - - received_protocol = next_part() - received_by = next_part() - if received_by.endswith(','): - received_by = received_by[:-1] - else: - comment = '' - while not comment.endswith(','): - try: - comment += next_part() - except StopIteration: - comment += ',' - break - comment = comment[:-1] - else: - received_by = self.get_header('host') - - return '%s://%s%s' % (self.request_protocol(), received_by, path) - - def begin_response(self, length="-"): - """Begin the response, and return the initial response text - """ - self._request_started = True - request_time = time.time() - self.request_start_time - - code = self._responsecode - proto = self.protocol - - if USE_ACCESS_LOG: - proto.server.write_access_log_line( - proto.client_address[0], - time.strftime("%d/%b/%Y %H:%M:%S"), - proto.requestline, - code, - length, - request_time) - - if self._reason_phrase is not None: - message = self._reason_phrase.split("\n")[0] - elif code in proto.responses: - message = proto.responses[code][0] - else: - message = '' - if proto.request_version == 'HTTP/0.9': - return [] - - response_lines = proto.generate_status_line() - - if not self._outgoing_headers.has_key('connection'): - con = self.get_header('connection') - if con is None and proto.request_version == 'HTTP/1.0': - con = 'close' - if con is not None: - self.set_header('connection', con) - - for key, value in self._outgoing_headers.items(): - key = '-'.join([x.capitalize() for x in key.split('-')]) - response_lines.append("%s: %s" % (key, value)) - - response_lines.append("") - return response_lines - - def write(self, obj): - """Writes an arbitrary object to the response, using - the sitemap's adapt method to convert it to bytes. - """ - if isinstance(obj, str): - self._write_bytes(obj) - elif isinstance(obj, unicode): - # use utf8 encoding for now, *TODO support charset negotiation - # Content-Type: text/html; charset=utf-8 - ctype = self._outgoing_headers.get('content-type', 'text/html') - ctype = ctype + '; charset=utf-8' - self._outgoing_headers['content-type'] = ctype - self._write_bytes(obj.encode('utf8')) - else: - self.site.adapt(obj, self) - - def _write_bytes(self, data): - """Write all the data of the response. - Can be called just once. - """ - if self._request_started: - print "Request has already written a response:" - traceback.print_stack() - return - - self._outgoing_headers['content-length'] = len(data) - - response_lines = self.begin_response(len(data)) - response_lines.append(data) - self.protocol.wfile.write("\r\n".join(response_lines)) - if hasattr(self.protocol.wfile, 'flush'): - self.protocol.wfile.flush() - - def method(self): - return self._method - - def path(self): - return self._path - - def path_segments(self): - return [urllib.unquote_plus(x) for x in self._path.split('/')[1:]] - - def query(self): - return self._query - - def uri(self): - if self._query: - return '%s?%s' % ( - self._path, self._query) - return self._path - - def get_headers(self): - return self._incoming_headers - - def get_header(self, header_name, default=None): - return self.get_headers().get(header_name.lower(), default) - - def get_query_pairs(self): - if not hasattr(self, '_split_query'): - if self._query is None: - self._split_query = () - else: - spl = self._query.split('&') - spl = [x.split('=', 1) for x in spl if x] - self._split_query = [] - for query in spl: - if len(query) == 1: - key = query[0] - value = '' - else: - key, value = query - self._split_query.append((urllib.unquote_plus(key), urllib.unquote_plus(value))) - - return self._split_query - - def get_queries_generator(self, name): - """Generate all query parameters matching the given name. - """ - for key, value in self.get_query_pairs(): - if key == name or not name: - yield value - - get_queries = lambda self, name: list(self.get_queries_generator) - - def get_query(self, name, default=None): - try: - return self.get_queries_generator(name).next() - except StopIteration: - return default - - def get_arg_list(self, name): - return self.get_field_storage().getlist(name) - - def get_arg(self, name, default=None): - return self.get_field_storage().getfirst(name, default) - - def get_field_storage(self): - if not hasattr(self, '_field_storage'): - if self.method() == 'GET': - data = '' - if self._query: - data = self._query - else: - data = self.read_body() - fl = StringIO(data) - ## Allow our resource to provide the FieldStorage instance for - ## customization purposes. - headers = self.get_headers() - environ = dict( - REQUEST_METHOD='POST', - QUERY_STRING=self._query or '') - - self._field_storage = cgi.FieldStorage(fl, headers, environ=environ) - - return self._field_storage - - def set_header(self, key, value): - if key.lower() == 'connection' and value.lower() == 'close': - self.protocol.close_connection = 1 - self._outgoing_headers[key.lower()] = value - __setitem__ = set_header - - def get_outgoing_header(self, key): - return self._outgoing_headers[key.lower()] - - def has_outgoing_header(self, key): - return self._outgoing_headers.has_key(key.lower()) - - def socket(self): - return self.protocol.socket - - def error(self, response=None, body=None, log_traceback=True): - if log_traceback: - traceback.print_exc(file=self.log) - if response is None: - response = 500 - if body is None: - typ, val, tb = sys.exc_info() - body = dict(type=str(typ), error=True, reason=str(val)) - self.response(response) - if(type(body) is str and not self.response_written()): - self.write(body) - return - try: - produce(body, self) - except Exception, e: - traceback.print_exc(file=self.log) - if not self.response_written(): - self.write('Internal Server Error') - - def not_found(self): - self.error(404, 'Not Found\n', log_traceback=False) - - def raw_body(self): - if not hasattr(self, '_cached_body'): - self.read_body() - return self._cached_body - - def read_body(self): - """ Returns the string body that was read off the request, or - the empty string if there was no request body. - - Requires a content-length header. Caches the body so multiple - calls to read_body() are free. - """ - if not hasattr(self, '_cached_body'): - length = self.get_header('content-length') - if length: - length = int(length) - if length: - self._cached_body = self.protocol.rfile.read(length) - else: - self._cached_body = '' - return self._cached_body - - def parsed_body(self): - """ Returns the parsed version of the body, using the - content-type header to select from the parsers on the site - object. - - If no parser is found, returns the string body from - read_body(). Caches the parsed body so multiple calls to - parsed_body() are free. - """ - if not hasattr(self, '_cached_parsed_body'): - body = self.read_body() - if hasattr(self.site, 'parsers'): - parser = self.site.parsers.get( - self.get_header('content-type')) - if parser is not None: - body = parser(body) - self._cached_parsed_body = body - return self._cached_parsed_body - - def override_body(self, body): - if not hasattr(self, '_cached_parsed_body'): - self.read_body() ## Read and discard body - self._cached_parsed_body = body - - def response_written(self): - ## TODO change badly named variable - return self._request_started - - def request_version(self): - return self.protocol.request_version - - def request_protocol(self): - if self.protocol.socket.is_secure: - return "https" - return "http" - - def server_address(self): - return self.protocol.server.address - - def __repr__(self): - return "" % ( - getattr(self, '_method'), getattr(self, '_path')) - -DEFAULT_TIMEOUT = 300 - -# This value was chosen because apache 2 has a default limit of 8190. -# I believe that slightly smaller number is because apache does not -# count the \r\n. -MAX_REQUEST_LINE = 8192 - -class Timeout(RuntimeError): - pass - -class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): - def __init__(self, request, client_address, server): - self.socket = self.request = self.rfile = self.wfile = request - self.client_address = client_address - self.server = server - self.set_response_code(None, 200, None) - self.protocol_version = server.max_http_version - - def set_response_code(self, request, code, message): - self._code = code - if message is not None: - self._message = message.split("\n")[0] - elif code in self.responses: - self._message = self.responses[code][0] - else: - self._message = '' - - def generate_status_line(self): - return [ - "%s %d %s" % ( - self.protocol_version, self._code, self._message)] - - def write_bad_request(self, status, reason): - self.set_response_code(self, status, reason) - self.wfile.write(''.join(self.generate_status_line())) - self.wfile.write('\r\nServer: %s\r\n' % self.version_string()) - self.wfile.write('Date: %s\r\n' % self.date_time_string()) - self.wfile.write('Content-Length: 0\r\n\r\n') - - def handle(self): - self.close_connection = 0 - - timeout = DEFAULT_TIMEOUT - while not self.close_connection: - if timeout == 0: - break - cancel = api.exc_after(timeout, Timeout) - try: - self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) - if self.raw_requestline is not None: - if len(self.raw_requestline) == MAX_REQUEST_LINE: - # Someone sent a request line which is too - # large. Be helpful and tell them. - self.write_bad_request(414, 'Request-URI Too Long') - self.close_connection = True - continue - except socket.error, e: - if e[0] in CONNECTION_CLOSED: - self.close_connection = True - cancel.cancel() - continue - except Timeout: - self.close_connection = True - continue - except Exception, e: - try: - if e[0][0][0].startswith('SSL'): - print "SSL Error:", e[0][0] - self.close_connection = True - cancel.cancel() - continue - except Exception, f: - print "Exception in ssl test:",f - pass - raise e - cancel.cancel() - - if not self.raw_requestline or not self.parse_request(): - self.close_connection = True - continue - - self.set_response_code(None, 200, None) - request = Request(self, self.command, self.path, self.headers) - request.set_header('Server', self.version_string()) - request.set_header('Date', self.date_time_string()) - try: - timeout = int(request.get_header('keep-alive', timeout)) - except TypeError, ValueError: - pass - - try: - try: - try: - self.server.site.handle_request(request) - except ErrorResponse, err: - request.response(code=err.code, - reason_phrase=err.reason, - headers=err.headers, - body=err.body) - finally: - # clean up any timers that might have been left around by the handling code - api.get_hub().runloop.cancel_timers(api.getcurrent()) - - # throw an exception if it failed to write a body - if not request.response_written(): - raise NotImplementedError("Handler failed to write response to request: %s" % request) - - if not hasattr(self, '_cached_body'): - try: - request.read_body() ## read & discard body - except: - pass - - except socket.error, e: - # Broken pipe, connection reset by peer - if e[0] in CONNECTION_CLOSED: - #print "Remote host closed connection before response could be sent" - pass - else: - raise - except Exception, e: - self.server.log_message("Exception caught in HttpRequest.handle():\n") - self.server.log_exception(*sys.exc_info()) - if not request.response_written(): - request.response(500) - request.write('Internal Server Error') - self.socket.close() - raise e # can't do a plain raise since exc_info might have been cleared - self.socket.close() - - -class Server(BaseHTTPServer.HTTPServer): - def __init__(self, socket, address, site, log, max_http_version=DEFAULT_MAX_HTTP_VERSION): - self.socket = socket - self.address = address - self.site = site - self.max_http_version = max_http_version - if log: - self.log = log - if hasattr(log, 'info'): - log.write = log.info - else: - self.log = self - - def write(self, something): - sys.stdout.write('%s' % (something, )); sys.stdout.flush() - - def log_message(self, message): - self.log.write(message) - - def log_exception(self, type, value, tb): - self.log.write(''.join(traceback.format_exception(type, value, tb))) - - def write_access_log_line(self, *args): - """Write a line to the access.log. Arguments: - client_address, date_time, requestline, code, size, request_time - """ - self.log.write( - '%s - - [%s] "%s" %s %s %.6f\n' % args) - - -def server(sock, site, log=None, max_size=512, serv=None, max_http_version=DEFAULT_MAX_HTTP_VERSION): - pool = coros.CoroutinePool(max_size=max_size) - if serv is None: - serv = Server(sock, sock.getsockname(), site, log, max_http_version=max_http_version) - try: - serv.log.write("httpd starting up on %s\n" % (sock.getsockname(), )) - while True: - try: - new_sock, address = sock.accept() - proto = HttpProtocol(new_sock, address, serv) - pool.execute_async(proto.handle) - api.sleep(0) # sleep to allow other coros to run - except KeyboardInterrupt: - api.get_hub().remove_descriptor(sock.fileno()) - serv.log.write("httpd exiting\n") - break - finally: - try: - sock.close() - except socket.error: - pass diff --git a/examples/.svn/all-wcprops b/examples/.svn/all-wcprops deleted file mode 100644 index 8bd82e5..0000000 --- a/examples/.svn/all-wcprops +++ /dev/null @@ -1,17 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 39 -/svn/eventlet/!svn/ver/3/trunk/examples -END -webcrawler.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/3/trunk/examples/webcrawler.py -END -echoserver.py -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/eventlet/!svn/ver/3/trunk/examples/echoserver.py -END diff --git a/examples/.svn/entries b/examples/.svn/entries deleted file mode 100644 index b1283ae..0000000 --- a/examples/.svn/entries +++ /dev/null @@ -1,54 +0,0 @@ -8 - -dir -100 -https://svn.secondlife.com/svn/eventlet/trunk/examples -https://svn.secondlife.com/svn/eventlet - - - -2007-08-23T23:57:41.609100Z -3 -which.linden - - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -cde37729-5338-0410-b9e2-c56166a61366 - -webcrawler.py -file - - - - -2008-02-20T19:57:15.000000Z -d97f9983eb0eb98eddc66259f2abb82d -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - -echoserver.py -file - - - - -2008-02-20T19:57:15.000000Z -ee5d6fb335a4a28b2b9af8ae3fdca660 -2007-08-23T23:51:23.835123Z -1 -which.linden -has-props - diff --git a/examples/.svn/format b/examples/.svn/format deleted file mode 100644 index 45a4fb7..0000000 --- a/examples/.svn/format +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/examples/.svn/prop-base/echoserver.py.svn-base b/examples/.svn/prop-base/echoserver.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/examples/.svn/prop-base/echoserver.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/examples/.svn/prop-base/webcrawler.py.svn-base b/examples/.svn/prop-base/webcrawler.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/examples/.svn/prop-base/webcrawler.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/examples/.svn/text-base/echoserver.py.svn-base b/examples/.svn/text-base/echoserver.py.svn-base deleted file mode 100644 index 3fe0191..0000000 --- a/examples/.svn/text-base/echoserver.py.svn-base +++ /dev/null @@ -1,52 +0,0 @@ -"""\ -@file echoserver.py - -Simple server that listens on port 6000 and echos back every input to -the client. To try out the server, start it up by running this file. - -Connect to it with: - telnet localhost 6000 - -You terminate your connection by terminating telnet (typically Ctrl-] -and then 'quit') - -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 import api - -def handle_socket(client): - print "client connected" - while True: - # pass through every non-eof line - x = client.readline() - if not x: break - client.write(x) - print "echoed", x - print "client disconnected" - -# server socket listening on port 6000 -server = api.tcp_listener(('0.0.0.0', 6000)) -while True: - new_sock, address = server.accept() - # handle every new connection with a new coroutine - api.spawn(handle_socket, new_sock) - -server.close() diff --git a/examples/.svn/text-base/webcrawler.py.svn-base b/examples/.svn/text-base/webcrawler.py.svn-base deleted file mode 100644 index 27c9c40..0000000 --- a/examples/.svn/text-base/webcrawler.py.svn-base +++ /dev/null @@ -1,55 +0,0 @@ -"""\ -@file webcrawler.py - -This is a simple web "crawler" that fetches a bunch of urls using a coroutine pool. It fetches as - many urls at time as coroutines in the pool. - -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. -""" - -urls = ["http://www.google.com/intl/en_ALL/images/logo.gif", - "http://wiki.secondlife.com/w/images/secondlife.jpg", - "http://us.i1.yimg.com/us.yimg.com/i/ww/beta/y3.gif"] - -import time -from eventlet import coros, httpc, util - -# replace socket with a cooperative coroutine socket because httpc -# uses httplib, which uses socket. Removing this serializes the http -# requests, because the standard socket is blocking. -util.wrap_socket_with_coroutine_socket() - -def fetch(url): - # we could do something interesting with the result, but this is - # example code, so we'll just report that we did it - print "%s fetching %s" % (time.asctime(), url) - httpc.get(url) - print "%s fetched %s" % (time.asctime(), url) - -pool = coros.CoroutinePool(max_size=4) -waiters = [] -for url in urls: - waiters.append(pool.execute(fetch, url)) - -# wait for all the coroutines to come back before exiting the process -for waiter in waiters: - waiter.wait() - - From 71c2219dfbcff4ae0983c109352318f0c25d4a94 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:17:25 -0700 Subject: [PATCH 084/118] Fix the echoserver example --- examples/echoserver.py | 14 ++++++++------ examples/webcrawler.py | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/echoserver.py b/examples/echoserver.py index 8c1813a..15e5c10 100644 --- a/examples/echoserver.py +++ b/examples/echoserver.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python """\ @file echoserver.py @@ -36,17 +37,18 @@ def handle_socket(reader, writer): print "client connected" while True: # pass through every non-eof line - x = client.readline() + x = reader.readline() if not x: break - client.write(x) + writer.write(x) print "echoed", x 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)) while True: - new_sock, address = server.accept() + try: + new_sock, address = server.accept() + except KeyboardInterrupt: + break # handle every new connection with a new coroutine api.spawn(handle_socket, new_sock.makefile('r'), new_sock.makefile('w')) - -server.close() diff --git a/examples/webcrawler.py b/examples/webcrawler.py index 27c9c40..809c37c 100644 --- a/examples/webcrawler.py +++ b/examples/webcrawler.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python """\ @file webcrawler.py From 27980fcc2b32efd2a2461713b4e32f0a1ea5083d Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:17:43 -0700 Subject: [PATCH 085/118] Cleanup in the api module --- eventlet/api.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index d446cdd..1d28248 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -54,9 +54,10 @@ except ImportError: from eventlet import greenlib, tls __all__ = [ - 'use_hub', 'get_hub', 'sleep', 'spawn', 'kill', - 'call_after', 'exc_after', 'trampoline', 'tcp_listener', 'tcp_server', - 'with_timeout', + 'call_after', 'exc_after', 'getcurrent', 'get_default_hub', 'get_hub', + 'GreenletExit', 'kill', 'sleep', 'spawn', 'spew', 'switch', + 'ssl_listener', 'tcp_listener', 'tcp_server', 'trampoline', + 'unspew', 'use_hub', 'with_timeout', ] @@ -204,8 +205,10 @@ def spawn(function, *args, **kwds): greenlib.switch(g, (_spawn_startup, function, args, kwds, t.cancel)) return g + kill = greenlib.kill + def call_after(seconds, function, *args, **kwds): """Schedule *function* to be called after *seconds* have elapsed. @@ -296,7 +299,8 @@ def exc_after(seconds, exception_object): def get_default_hub(): - """ + """Select the default hub implementation based on what multiplexing + libraries are installed. Tries libevent first, then poll, then select. """ try: import eventlet.hubs.libevent @@ -314,7 +318,8 @@ def get_default_hub(): 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: mod = get_default_hub() @@ -326,7 +331,7 @@ def use_hub(mod=None): _threadlocal.Hub = mod def get_hub(): - """ + """Get the current event hub singleton object. """ try: hub = _threadlocal.hub @@ -403,13 +408,14 @@ class Spew(object): def spew(trace_names=None, show_values=False): - """ + """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(): - """ + """Remove the trace hook installed by spew. """ sys.settrace(None) From 96f02a61a7a343c3b843ee66aac6a954e3cfcf31 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:17:56 -0700 Subject: [PATCH 086/118] Use the correct waitpid --- eventlet/processes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eventlet/processes.py b/eventlet/processes.py index f00b2ae..a439af1 100644 --- a/eventlet/processes.py +++ b/eventlet/processes.py @@ -30,6 +30,7 @@ import sys from eventlet import coros from eventlet import pools from eventlet import greenio +from eventlet import util class DeadProcess(RuntimeError): @@ -44,7 +45,7 @@ CHILD_EVENTS = {} def sig_child(signal, frame): for child_pid in CHILD_PIDS: try: - pid, code = os.waitpid(child_pid, os.WNOHANG) + pid, code = util.__original_waitpid__(child_pid, os.WNOHANG) if not pid: continue ## Wasn't this one that died elif pid == -1: From 6cdd46d0c65094d5340df10b53828ada6041a074 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:18:24 -0700 Subject: [PATCH 087/118] Don't have a KeyError if we're not tracking a pid; not sure if this is correct behavior. --- eventlet/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eventlet/util.py b/eventlet/util.py index 6547c8d..6400584 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -158,7 +158,9 @@ def wrap_pipes_with_coroutine_pipes(): return pid def new_waitpid(pid, options): from eventlet import processes - evt = processes.CHILD_EVENTS[pid] + evt = processes.CHILD_EVENTS.get(pid) + if not evt: + return 0, 0 if options == os.WNOHANG: if evt.ready(): return pid, evt.wait() From ae9022385543e03e731b1356121bbe459e3adb67 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:18:34 -0700 Subject: [PATCH 088/118] Update version number in prep for release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 25f340d..b166432 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.2', + version='0.5', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From ea932f6ccccbb8b6d1ea703d031dde8477f13754 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:19:47 -0700 Subject: [PATCH 089/118] Update hgignore --- .hgignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.hgignore b/.hgignore index 0b29f33..50b2ac8 100644 --- a/.hgignore +++ b/.hgignore @@ -1 +1,4 @@ -glob:*.pyc \ No newline at end of file +syntax: glob +*.pyc +dist +eventlet.egg-info From 9f50a46f3766500a2486e55518d498fd936ddc8e Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:19:59 -0700 Subject: [PATCH 090/118] add corolocal module --- eventlet/corolocal.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 eventlet/corolocal.py diff --git a/eventlet/corolocal.py b/eventlet/corolocal.py new file mode 100644 index 0000000..7998459 --- /dev/null +++ b/eventlet/corolocal.py @@ -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())) From bc10f2887dfb85f2de77648788fd62abc26b3999 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 13:35:48 -0700 Subject: [PATCH 091/118] Update hgignore to ignore build directory --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index 50b2ac8..f4913ee 100644 --- a/.hgignore +++ b/.hgignore @@ -2,3 +2,4 @@ syntax: glob *.pyc dist eventlet.egg-info +build From 70df105cfc2076fa1ab5da4ca9494f2086a339ad Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 11 Jun 2008 15:01:22 -0700 Subject: [PATCH 092/118] Fix bug in stackless support, remove prints, ad update the operating systems in setup.py --- eventlet/api.py | 6 +++--- eventlet/support/stacklesss.py | 4 ++-- setup.py | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index 1d28248..265cb8c 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -44,10 +44,10 @@ except ImportError: greenlet = sys.modules['greenlet'] except ImportError: try: - import support.stackless - support.stackless.emulate() + import support.stacklesss + support.stacklesss.emulate() greenlet = sys.modules['greenlet'] - except ImportError: + except ImportError, e: raise ImportError("Unable to find an implementation of greenlet.") diff --git a/eventlet/support/stacklesss.py b/eventlet/support/stacklesss.py index 8e97555..5220cb6 100644 --- a/eventlet/support/stacklesss.py +++ b/eventlet/support/stacklesss.py @@ -48,7 +48,7 @@ class FirstSwitch(object): self.gr = gr def __call__(self, *args, **kw): - print "first call", args, kw + #print "first call", args, kw gr = self.gr del gr.switch run, gr.run = gr.run, None @@ -72,7 +72,7 @@ class greenlet(object): self.switch = FirstSwitch(self) def switch(self, *args): - print "switch", args + #print "switch", args global caller caller = stackless.getcurrent() coro_args[self] = args diff --git a/setup.py b/setup.py index b166432..bd3f020 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,8 @@ setup( classifiers=[ "License :: OSI Approved :: MIT License", "Programming Language :: Python", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", "Intended Audience :: Developers", From ed5bc90261917156912e3aed54736c7af7bb78c2 Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 12 Jun 2008 11:10:15 -0700 Subject: [PATCH 093/118] Update the version number to 0.6pre --- README | 3 +-- setup.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README b/README index 18094ae..3b73486 100644 --- a/README +++ b/README @@ -13,12 +13,11 @@ https://lists.secondlife.com/cgi-bin/mailman/listinfo/eventletdev == requirements === 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 == limitations == -* Sorely lacking in documentation * Not enough test coverage -- the goal is 100%, but we are not there yet. * Eventlet does not currently run on stackless using tasklets, though it is a goal to do so in the future. diff --git a/setup.py b/setup.py index bd3f020..0d6d54a 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.5', + version='0.6pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From 3a764271bbeb7a5a939b0b4f8982075847a50bff Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 17 Jun 2008 16:17:37 -0700 Subject: [PATCH 094/118] Fix a bug in generating Transfer-coding: chunked in the case of no response body. Fix a bug where '200 OK' was written to the access log instead of just '200'. Use the sum builtin --- eventlet/wsgi.py | 11 ++++------- setup.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 3b0110a..4acf8d8 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -167,7 +167,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): _write(joined) def start_response(status, response_headers, exc_info=None): - status_code[0] = status + status_code[0] = status.split()[0] if exc_info: try: if headers_sent: @@ -202,10 +202,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): for data in result: if data: towrite.append(data) - if reduce( - lambda x, y: x + y, - map( - lambda x: len(x), towrite)) > 4096: + if sum([len(x) for x in towrite]) > 4096: write(''.join(towrite)) del towrite[:] except Exception, e: @@ -218,10 +215,10 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): if towrite: write(''.join(towrite)) - if use_chunked: - wfile.write('0\r\n\r\n') if not headers_sent: write('') + if use_chunked: + wfile.write('0\r\n\r\n') except Exception, e: traceback.print_exc() finally: diff --git a/setup.py b/setup.py index 0d6d54a..ce3893f 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( author_email='eventletdev@lists.secondlife.com', url='http://wiki.secondlife.com/wiki/Eventlet', packages=find_packages(), - install_requires=['greenlet'], + install_requires=['greenlet', 'pyOpenSSL'], long_description=""" Eventlet is a networking library written in Python. It achieves high scalability by using non-blocking io while at the same time From 0b6f65b69f2b6d13989fd9bb8b5ef25cecbbbdf3 Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 20 Jun 2008 12:35:25 -0700 Subject: [PATCH 095/118] Port http_test.py to wsgi_test.py and add a check to wsgi.py for request lines that are too long in the process --- eventlet/wsgi.py | 11 ++- eventlet/wsgi_test.py | 222 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 eventlet/wsgi_test.py diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 4acf8d8..36549f2 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -23,6 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import cgi import errno import os import sys @@ -75,6 +76,9 @@ class Input(object): return read +MAX_REQUEST_LINE = 8192 + + class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' def log_message(self, format, *args): @@ -89,7 +93,12 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): self.protocol_version = self.server.max_http_version try: - self.raw_requestline = self.rfile.readline() + 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 diff --git a/eventlet/wsgi_test.py b/eventlet/wsgi_test.py new file mode 100644 index 0000000..c8a6158 --- /dev/null +++ b/eventlet/wsgi_test.py @@ -0,0 +1,222 @@ +"""\ +@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"] + + +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() + + +if __name__ == '__main__': + tests.main() From de9bc61f897f75952f7a370de646645a2d62a97d Mon Sep 17 00:00:00 2001 From: donovan Date: Fri, 20 Jun 2008 13:09:14 -0700 Subject: [PATCH 096/118] Fix a bug where eventlet.wsgi was sending chunked encoding to http/1.0 clients, which isn't legal. Also, tests for chunked encoding, and the absence of chunked encoding in http/1.0 --- eventlet/wsgi.py | 5 ++--- eventlet/wsgi_test.py | 49 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 36549f2..23bc0b2 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -23,7 +23,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import cgi import errno import os import sys @@ -202,7 +201,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): try: num_blocks = len(result) except (TypeError, AttributeError, NotImplementedError): - if self.protocol_version == 'HTTP/1.1': + if self.request_version == 'HTTP/1.1': use_chunked = True try: try: @@ -211,7 +210,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): for data in result: if data: towrite.append(data) - if sum([len(x) for x in towrite]) > 4096: + if use_chunked and sum(map(len, towrite)) > 4096: write(''.join(towrite)) del towrite[:] except Exception, e: diff --git a/eventlet/wsgi_test.py b/eventlet/wsgi_test.py index c8a6158..c066392 100644 --- a/eventlet/wsgi_test.py +++ b/eventlet/wsgi_test.py @@ -50,6 +50,20 @@ def hello_world(env, start_response): 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 @@ -217,6 +231,41 @@ class TestHttpd(tests.TestCase): 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() diff --git a/setup.py b/setup.py index ce3893f..c5ff7af 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.6pre', + version='0.5.2', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From 5619bcf00352aee0fd1dfe02b19a2e9a8ebcf082 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 24 Jun 2008 10:02:15 -0700 Subject: [PATCH 097/118] Allow customization of the threadpool size using an environment variable (gross, but it'll have to do for now) --- eventlet/tpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 574ee5c..95fd1e0 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -123,7 +123,7 @@ class Proxy(object): return rv return doit -_nthreads = 20 +_nthreads = int(os.environ.get('EVENTLET_THREADPOOL_SIZE', 20)) _threads = {} def setup(): global _threads From 8925cbb13a69a51bef6c0e245df9f09f4fccc1bb Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 24 Jun 2008 17:55:58 -0700 Subject: [PATCH 098/118] bump to 0.5.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c5ff7af..00edfd2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.5.2', + version='0.5.3', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From 0df670fd3ed305c9ad6689179c6be6deb0433005 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 24 Jun 2008 18:07:57 -0700 Subject: [PATCH 099/118] Add patch to squelch exceptions properly when running with libevent. Thanks, 'the grugq' --- eventlet/hubs/libevent.py | 28 +++++++++++++++++----------- eventlet/hubs/poll.py | 4 ++-- setup.py | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 46f8d3e..a5b4afd 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -46,7 +46,7 @@ try: except ImportError: # don't have rel, but might still have libevent pass - + import event @@ -55,7 +55,7 @@ class Hub(hub.BaseHub): super(Hub, self).__init__(clock) self.interrupted = False event.init() - + sig = event.signal(signal.SIGINT, self.signal_received, signal.SIGINT) sig.add() @@ -70,40 +70,46 @@ class Hub(hub.BaseHub): evt = event.write(fileno, write, fileno) evt.add() self.writers[fileno] = evt, write - + if exc: self.excs[fileno] = exc - + 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) - + 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() - status = event.dispatch() + 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 @@ -115,7 +121,7 @@ class Hub(hub.BaseHub): timer.impltimer = eventtimer eventtimer.add() self.track_timer(timer) - + def timer_canceled(self, timer): """ Cancels the underlying libevent timer. """ try: diff --git a/eventlet/hubs/poll.py b/eventlet/hubs/poll.py index 8d34ff0..bbebf40 100644 --- a/eventlet/hubs/poll.py +++ b/eventlet/hubs/poll.py @@ -64,12 +64,12 @@ class Hub(hub.BaseHub): 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) diff --git a/setup.py b/setup.py index 00edfd2..1361dbf 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.5.3', + version='0.5.4pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From afba654348c08d9e0bd78ac903d55b162b86de50 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 25 Jun 2008 11:00:23 -0700 Subject: [PATCH 100/118] Fix for some SwitchingToDeadGreenlet or TypeError: '_socketobject' object is not iterable exceptions: Passing both read=True and write=True to trampoline would cause the fd to select as both readable and writable in some error conditions. This would cause the greenlet to be spuriously resumed a second time. --- eventlet/greenio.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index fe20a57..f214f75 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -203,7 +203,7 @@ class GreenSocket(object): client, addr = res set_nonblocking(client) return type(self)(client), addr - trampoline(fd, read=True, write=True) + trampoline(fd, read=True) def bind(self, *args, **kw): fn = self.bind = self.fd.bind @@ -226,7 +226,7 @@ class GreenSocket(object): fd = self.fd connect = socket_connect while not connect(fd, address): - trampoline(fd, read=True, write=True) + trampoline(fd, write=True) def connect_ex(self, *args, **kw): fn = self.connect_ex = self.fd.connect_ex diff --git a/setup.py b/setup.py index 1361dbf..ce3893f 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.5.4pre', + version='0.6pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From f56c764b78177d41739936f166fef39464fa9613 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 25 Jun 2008 13:04:19 -0700 Subject: [PATCH 101/118] Make the wsgi.input provided by eventlet.wsgi conform to the spec by implementing readline, readlines, and __iter__. --- eventlet/wsgi.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 23bc0b2..5ce1bb6 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -57,7 +57,7 @@ class Input(object): self.position = 0 - def read(self, length=None): + def _do_read(self, reader, length=None): if self.wfile is not None: ## 100 Continue self.wfile.write(self.wfile_line) @@ -70,10 +70,22 @@ class Input(object): length = self.content_length - self.position if not length: return '' - read = self.rfile.read(length) + 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 From 135d2ad865f1f6f905b7390d6e379bc5580669cb Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 25 Jun 2008 15:26:00 -0700 Subject: [PATCH 102/118] Fix another common cause of SwitchingToDeadGreenlet or _socketobject is not iterable. Using exc_after to cancel a socket operation would leave the socket's fd in the hub, causing a spurious resumption of that greenlet when the socket operation completed in the future. --- eventlet/api.py | 3 ++- eventlet/hubs/hub.py | 9 +++++++++ eventlet/hubs/libevent.py | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index 265cb8c..e365d0f 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -295,7 +295,8 @@ def exc_after(seconds, exception_object): else: timer.cancel() """ - return call_after(seconds, switch, getcurrent(), None, exception_object) + hub = get_hub() + return call_after(seconds, hub.exc_greenlet, getcurrent(), exception_object) def get_default_hub(): diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index f9637bf..d36a248 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -45,6 +45,7 @@ class BaseHub(object): self.readers = {} self.writers = {} self.excs = {} + self.waiters_by_greenlet = {} self.clock = clock self.greenlet = None @@ -80,11 +81,19 @@ class BaseHub(object): self.readers[fileno] = read or self.readers.get(fileno) self.writers[fileno] = write or self.writers.get(fileno) self.excs[fileno] = exc or self.excs.get(fileno) + 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) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index a5b4afd..4e5c643 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -59,7 +59,6 @@ class Hub(hub.BaseHub): 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) @@ -74,12 +73,15 @@ class Hub(hub.BaseHub): 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() From 9b07c4a5e82f595b2a89ba65d7e2b7619d17420f Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 25 Jun 2008 17:50:05 -0700 Subject: [PATCH 103/118] Call cancel timers on the way out of the main greenlet_body, since that timer can't possibly resume the greenlet since it's going to be dead once that function returns. --- eventlet/greenlib.py | 7 +++++-- eventlet/hubs/hub.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/eventlet/greenlib.py b/eventlet/greenlib.py index 1dbf01e..646820a 100644 --- a/eventlet/greenlib.py +++ b/eventlet/greenlib.py @@ -265,6 +265,7 @@ def greenlet_body(value, exc): Greenlets using this body must be greenlib.switch()'ed to """ + from eventlet import api if exc is not None: if isinstance(exc, tuple): raise exc[0], exc[1], exc[2] @@ -282,12 +283,14 @@ def greenlet_body(value, exc): except AttributeError: greenlet_id = 1 _threadlocal.next_greenlet_id = itertools.count(2) - greenlets[greenlet.getcurrent()] = {'greenlet_id': greenlet_id} + cur = greenlet.getcurrent() + greenlets[cur] = {'greenlet_id': greenlet_id} try: return cb(*args) finally: _greenlet_context_call('finalize') - greenlets.pop(greenlet.getcurrent(), None) + greenlets.pop(cur, None) + api.get_hub().cancel_timers(cur, quiet=True) class SwitchingToDeadGreenlet(RuntimeError): diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index d36a248..fabd864 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -283,7 +283,7 @@ class BaseHub(object): pass del t[:last] - def cancel_timers(self, greenlet): + def cancel_timers(self, greenlet, quiet=False): if greenlet not in self.timers_by_greenlet: return for timer in self.timers_by_greenlet[greenlet]: @@ -292,6 +292,6 @@ class BaseHub(object): ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. timer.cancel() - if _g_debug: + if _g_debug and not quiet: print 'Hub cancelling left-over timer %s' % timer del self.timers_by_greenlet[greenlet] From 63410e9de224857d927242f9bf08d837941dd442 Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 26 Jun 2008 09:29:45 -0700 Subject: [PATCH 104/118] Prepare for 0.6 release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ce3893f..3d87d22 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.6pre', + version='0.6', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From f6ae4e494e116024cfbd81030e817426e5138999 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 30 Jun 2008 11:10:57 -0700 Subject: [PATCH 105/118] Add a patch from the grugq to allow binding to a specific interface in api.connect_tcp. --- eventlet/api.py | 5 ++++- setup.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/eventlet/api.py b/eventlet/api.py index e365d0f..35a4517 100644 --- a/eventlet/api.py +++ b/eventlet/api.py @@ -102,12 +102,15 @@ def ssl_listener(address, certificate, private_key): socket.is_secure = True return socket -def connect_tcp(address): +def connect_tcp(address, localaddr=None): """ Create a TCP connection to address (host, port) and return the socket. + Optionally, bind to localaddr (host, port) first. """ from eventlet import greenio, util desc = greenio.GreenSocket(util.tcp_socket()) + if localaddr is not None: + desc.bind(localaddr) desc.connect(address) return desc diff --git a/setup.py b/setup.py index 3d87d22..971084c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.6', + version='0.7pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From 387503156dad38209ce11d8ad9528654bed9c175 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 30 Jun 2008 11:12:28 -0700 Subject: [PATCH 106/118] add a patch from the grugq, a libev hub. eventlet won't automatically choose this hub yet, changes needed in api.py to detect if the libev module is available. --- eventlet/hubs/libev.py | 125 +++++++++++++++++++++++++++++++++++++++++ eventlet/timer.py | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 eventlet/hubs/libev.py diff --git a/eventlet/hubs/libev.py b/eventlet/hubs/libev.py new file mode 100644 index 0000000..9a5e7f7 --- /dev/null +++ b/eventlet/hubs/libev.py @@ -0,0 +1,125 @@ +"""\ +@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() + +import ev + + +class Hub(hub.BaseHub): + def __init__(self, clock=time.time): + super(Hub, self).__init__(clock) + self.interrupted = False + self._evloop = ev.default_loop() + #event.init() + + sig = ev.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 = event.Io(fileno, ev.EV_READ, self._evloop, read, fileno) + evt.start() + self.readers[fileno] = evt, read + + if write: + evt = ev.Io(fileno, ev.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 = ev.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 = ev.Timer(timer.seconds, 0, self._evloop, timer) + timer.impltimer = eventtimer + eventtimer.start() + self.track_timer(timer) + + def timer_canceled(self, timer): + """ Cancels the underlying libevent timer. """ + try: + timer.impltimer.stop() + except AttributeError: + pass + diff --git a/eventlet/timer.py b/eventlet/timer.py index 51a8eda..6e41a18 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -69,7 +69,7 @@ class Timer(object): self.scheduled_time = get_hub().add_timer(self) return self - def __call__(self): + def __call__(self, *args): if not self.called: self.called = True cb, args, kw = self.tpl From b0e4872d1221d673b37b0d5e4e2c1258c97fe92a Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 7 Jul 2008 11:31:59 -0700 Subject: [PATCH 107/118] Add tpool.killall function which can be used to cleanly exit the threadpool. --- eventlet/tpool.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/eventlet/tpool.py b/eventlet/tpool.py index 95fd1e0..9fce38b 100644 --- a/eventlet/tpool.py +++ b/eventlet/tpool.py @@ -61,7 +61,10 @@ def esend(meth,*args, **kwargs): def tworker(): global _reqq, _rspq while(True): - (e,meth,args,kwargs) = _reqq.get() + msg = _reqq.get() + if msg is None: + return + (e,meth,args,kwargs) = msg rv = None try: rv = meth(*args,**kwargs) @@ -96,6 +99,7 @@ def execute(meth,*args, **kwargs): erpc = execute + class Proxy(object): """ 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. @@ -123,6 +127,7 @@ class Proxy(object): return rv return doit + _nthreads = int(os.environ.get('EVENTLET_THREADPOOL_SIZE', 20)) _threads = {} def setup(): @@ -135,3 +140,11 @@ def setup(): api.spawn(tpool_trampoline) setup() + + +def killall(): + for i in _threads: + _reqq.put(None) + for thr in _threads.values(): + thr.join() + From cd1b86488ea27bed98deb9832ca53f54b986eb42 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 7 Jul 2008 13:17:39 -0700 Subject: [PATCH 108/118] Add a NEWS file and an example of using the wsgi server along with a link to Spawning; Set version number to 0.6.1 for a release. --- NEWS | 18 ++++++++++++++++++ examples/wsgi.py | 20 ++++++++++++++++++++ setup.py | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 NEWS create mode 100644 examples/wsgi.py diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..39c67ac --- /dev/null +++ b/NEWS @@ -0,0 +1,18 @@ +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. diff --git a/examples/wsgi.py b/examples/wsgi.py new file mode 100644 index 0000000..150fead --- /dev/null +++ b/examples/wsgi.py @@ -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) + diff --git a/setup.py b/setup.py index 971084c..0bf1313 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.7pre', + version='0.6.1', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From c167061759dad09b3ad90296aebf9a7e40d48943 Mon Sep 17 00:00:00 2001 From: donovan Date: Tue, 8 Jul 2008 10:55:23 -0700 Subject: [PATCH 109/118] Change the formatting of the 'wsgi starting up' message to look a little nicer --- eventlet/wsgi.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index 5ce1bb6..230401f 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -353,7 +353,18 @@ def server(sock, site, log=None, environ=None, max_size=None, max_http_version=D max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS pool = coros.CoroutinePool(max_size=max_size) try: - print "(%s) wsgi starting up on %s" % (os.getpid(), sock.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: try: client_socket = sock.accept() From 9f31852ad4512f391e5e89ee7d97f218a00505fe Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 14 Jul 2008 12:42:41 -0700 Subject: [PATCH 110/118] 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). --- NEWS | 2 ++ eventlet/timer.py | 4 ++++ setup.py | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 39c67ac..bffaada 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ Fixes some long-standing bugs where sometimes failures in accept() or connect() 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.6.2: 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). + 0.5.x ===== diff --git a/eventlet/timer.py b/eventlet/timer.py index 6e41a18..1018320 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -40,6 +40,7 @@ class Timer(object): This timer will not be run unless it is scheduled in a runloop by calling timer.schedule() or runloop.add_timer(timer). """ + self.impltimer = None self.cancelled = False self.seconds = seconds self.tpl = cb, args, kw @@ -72,6 +73,9 @@ class Timer(object): def __call__(self, *args): if not self.called: self.called = True + if self.impltimer is not None: + del get_hub().timers_by_greenlet[self.greenlet][self] + cb, args, kw = self.tpl cb(*args, **kw) diff --git a/setup.py b/setup.py index 0bf1313..3e82c55 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.6.1', + version='0.6.2pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From dd926e62e8470f0b70059a4a46ec4fe05ba666d6 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 14 Jul 2008 12:47:34 -0700 Subject: [PATCH 111/118] Minor tweaks to the libev hub. (Patch from the grugq, thanks) --- eventlet/hubs/libev.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/eventlet/hubs/libev.py b/eventlet/hubs/libev.py index 9a5e7f7..1064f3e 100644 --- a/eventlet/hubs/libev.py +++ b/eventlet/hubs/libev.py @@ -1,5 +1,5 @@ """\ -@file libevent.py +@file libev.py Copyright (c) 2007, Linden Research, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -38,27 +38,26 @@ import greenlet # XXX for debugging only #raise ImportError() -import ev +import ev as libev class Hub(hub.BaseHub): def __init__(self, clock=time.time): super(Hub, self).__init__(clock) self.interrupted = False - self._evloop = ev.default_loop() - #event.init() + self._evloop = libev.default_loop() - sig = ev.Signal(signal.SIGINT, self._evloop, self.signal_received, signal.SIGINT) + 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 = event.Io(fileno, ev.EV_READ, self._evloop, read, fileno) + evt = libev.Io(fileno, libev.EV_READ, self._evloop, read, fileno) evt.start() self.readers[fileno] = evt, read if write: - evt = ev.Io(fileno, ev.EV_WRITE, self._evloop, write, fileno) + evt = libev.Io(fileno, libev.EV_WRITE, self._evloop, write, fileno) evt.start() self.writers[fileno] = evt, write @@ -89,7 +88,7 @@ class Hub(hub.BaseHub): def wait(self, seconds=None): # this timeout will cause us to return from the dispatch() call # when we want to - timer = ev.Timer(seconds, 0, self._evloop, lambda *args: None) + timer = libev.Timer(seconds, 0, self._evloop, lambda *args: None) timer.start() try: @@ -111,7 +110,7 @@ class Hub(hub.BaseHub): def add_timer(self, timer): # store the pyevent timer object so that we can cancel later - eventtimer = ev.Timer(timer.seconds, 0, self._evloop, timer) + eventtimer = libev.Timer(timer.seconds, 0, self._evloop, timer) timer.impltimer = eventtimer eventtimer.start() self.track_timer(timer) From 5b0389ee919033121b0bce0c6f4bbe4276468880 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 14 Jul 2008 12:48:35 -0700 Subject: [PATCH 112/118] Make using the threadpool for dns operations optional when calling wrap_socket_with_coroutine_socket. Patch by the grugq, thanks --- eventlet/util.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/eventlet/util.py b/eventlet/util.py index 6400584..9bad6d9 100644 --- a/eventlet/util.py +++ b/eventlet/util.py @@ -81,7 +81,7 @@ def wrap_ssl(sock, certificate=None, private_key=None): from OpenSSL import SSL from eventlet import greenio, util context = SSL.Context(SSL.SSLv23_METHOD) - print certificate, private_key + #print certificate, private_key if certificate is not None: context.use_certificate_file(certificate) if private_key is not None: @@ -95,7 +95,7 @@ def wrap_ssl(sock, certificate=None, private_key=None): socket_already_wrapped = False -def wrap_socket_with_coroutine_socket(): +def wrap_socket_with_coroutine_socket(use_thread_pool=True): global socket_already_wrapped if socket_already_wrapped: return @@ -107,16 +107,17 @@ def wrap_socket_with_coroutine_socket(): socket.ssl = wrap_ssl - from eventlet import tpool - def new_gethostbyname(*args, **kw): - return tpool.execute( - __original_gethostbyname__, *args, **kw) - socket.gethostbyname = new_gethostbyname + 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_getaddrinfo(*args, **kw): + return tpool.execute( + __original_getaddrinfo__, *args, **kw) + socket.getaddrinfo = new_getaddrinfo def new_fromfd(*args, **kw): from eventlet import greenio From 5c0de2e192d86b5bc74903f48a82f7dac10928aa Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 14 Jul 2008 12:51:22 -0700 Subject: [PATCH 113/118] news for the last commit --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index bffaada..acb70b3 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,7 @@ Fixes some long-standing bugs where sometimes failures in accept() or connect() 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.6.2: 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). +0.6.2: 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). Also, make it possible to call wrap_socket_with_coroutine_socket without using the threadpool to make dns operations non-blocking (Thanks the grugq). 0.5.x ===== From 63eb7e78f523e5303f0b3d553d239649c3aa8a26 Mon Sep 17 00:00:00 2001 From: donovan Date: Mon, 14 Jul 2008 15:01:12 -0700 Subject: [PATCH 114/118] Clean up timers_by_greenlet in the timer_canceled case as well. Patch by the grugq --- eventlet/hubs/hub.py | 4 +++- eventlet/hubs/libev.py | 3 ++- eventlet/hubs/libevent.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index fabd864..9ae5165 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -242,7 +242,9 @@ class BaseHub(object): self.timers_by_greenlet[current_greenlet][timer] = True def timer_canceled(self, timer): - pass + del self.timers_by_greenlet[timer.greenlet][timer] + if not self.timers_by_greenlet[timer.greenlet]: + del self.timers_by_greenlet[timer.greenlet] def prepare_timers(self): ins = bisect.insort_right diff --git a/eventlet/hubs/libev.py b/eventlet/hubs/libev.py index 1064f3e..6e5d421 100644 --- a/eventlet/hubs/libev.py +++ b/eventlet/hubs/libev.py @@ -121,4 +121,5 @@ class Hub(hub.BaseHub): timer.impltimer.stop() except AttributeError: pass - + finally: + super(Hub, self).timer_canceled(timer) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 4e5c643..7492158 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -130,4 +130,6 @@ class Hub(hub.BaseHub): timer.impltimer.delete() except AttributeError: pass + finally: + super(Hub, self).timer_canceled(timer) From a6b7e58671bda5d354f3b72421a5edd3b7edb4fc Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 16 Jul 2008 10:43:05 -0700 Subject: [PATCH 115/118] Combination of patches from the grugq and Augusto Becciu; try really hard not to leak timers as garbage. --- eventlet/hubs/hub.py | 38 +++++++++++++++++++++++++------------- eventlet/hubs/libev.py | 13 ++++++++++++- eventlet/hubs/libevent.py | 13 ++++++++++++- eventlet/timer.py | 19 +++++++++---------- 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index 9ae5165..ca52c48 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -22,6 +22,7 @@ THE SOFTWARE. """ import bisect +import weakref import sys import socket import errno @@ -237,14 +238,20 @@ class BaseHub(object): def track_timer(self, timer): current_greenlet = greenlet.getcurrent() timer.greenlet = current_greenlet - if current_greenlet not in self.timers_by_greenlet: - self.timers_by_greenlet[current_greenlet] = {} - self.timers_by_greenlet[current_greenlet][timer] = True + 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): - del self.timers_by_greenlet[timer.greenlet][timer] - if not self.timers_by_greenlet[timer.greenlet]: - del self.timers_by_greenlet[timer.greenlet] + self.timer_finished(timer) def prepare_timers(self): ins = bisect.insort_right @@ -279,21 +286,26 @@ class BaseHub(object): except: self.squelch_timer_exception(timer, sys.exc_info()) finally: - try: - del self.timers_by_greenlet[timer.greenlet][timer] - except KeyError: - pass + self.timer_finished(timer) del t[:last] def cancel_timers(self, greenlet, quiet=False): if greenlet not in self.timers_by_greenlet: return - for timer in self.timers_by_greenlet[greenlet]: + for timer in self.timers_by_greenlet[greenlet].keys(): if not timer.cancelled and not timer.called and timer.seconds: ## If timer.seconds is 0, this isn't a timer, it's ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. - timer.cancel() + try: + # this might be None due to weirdness with weakrefs + timer.cancel() + except TypeError: + pass if _g_debug and not quiet: print 'Hub cancelling left-over timer %s' % timer - del self.timers_by_greenlet[greenlet] + try: + del self.timers_by_greenlet[greenlet] + except KeyError: + pass + diff --git a/eventlet/hubs/libev.py b/eventlet/hubs/libev.py index 6e5d421..9899336 100644 --- a/eventlet/hubs/libev.py +++ b/eventlet/hubs/libev.py @@ -115,11 +115,22 @@ class Hub(hub.BaseHub): 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() - except AttributeError: + del timer.impltimer + except (AttributeError, TypeError): pass finally: super(Hub, self).timer_canceled(timer) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 7492158..bb5ab6a 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -124,11 +124,22 @@ class Hub(hub.BaseHub): 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() - except AttributeError: + del timer.impltimer + except (AttributeError, TypeError): pass finally: super(Hub, self).timer_canceled(timer) diff --git a/eventlet/timer.py b/eventlet/timer.py index 1018320..abeb868 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -29,7 +29,7 @@ useful for debugging leaking timers, to find out where the timer was set up. """ _g_debug = False class Timer(object): - __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback', 'impltimer'] + #__slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback', 'impltimer'] def __init__(self, seconds, cb, *args, **kw): """Create a timer. seconds: The minimum number of seconds to wait before calling @@ -40,7 +40,6 @@ class Timer(object): This timer will not be run unless it is scheduled in a runloop by calling timer.schedule() or runloop.add_timer(timer). """ - self.impltimer = None self.cancelled = False self.seconds = seconds self.tpl = cb, args, kw @@ -57,28 +56,28 @@ class Timer(object): secs, cb, args, kw) if _g_debug and hasattr(self, 'traceback'): retval += '\n' + self.traceback.getvalue() - return retval + return retval def copy(self): cb, args, kw = self.tpl return self.__class__(self.seconds, cb, *args, **kw) - + def schedule(self): """Schedule this timer to run in the current runloop. """ self.called = False self.scheduled_time = get_hub().add_timer(self) return self - + def __call__(self, *args): if not self.called: self.called = True - if self.impltimer is not None: - del get_hub().timers_by_greenlet[self.greenlet][self] - cb, args, kw = self.tpl - cb(*args, **kw) - + try: + cb(*args, **kw) + finally: + get_hub().timer_finished(self) + def cancel(self): """Prevent this timer from being called. If the timer has already been called, has no effect. From a6f6f001da7418a3c6deada9ff4bd90807d78941 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 16 Jul 2008 11:20:13 -0700 Subject: [PATCH 116/118] Fix socket_recv to return '' instead of raising socket.error(errno.ECONNRESET) explicitly when using an ssl socket. This allows an eventlet ssl client to talk to an eventlet ssl server properly! Thanks, Ryan Williams. --- NEWS | 8 ++++++-- README | 3 --- eventlet/api_test.py | 36 ++++++++++++++++++++++++++++++++++-- eventlet/greenio.py | 13 +++++++++++-- setup.py | 2 +- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index acb70b3..ae64b3a 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,9 @@ +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) + 0.6.x ===== @@ -5,8 +11,6 @@ Fixes some long-standing bugs where sometimes failures in accept() or connect() 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.6.2: 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). Also, make it possible to call wrap_socket_with_coroutine_socket without using the threadpool to make dns operations non-blocking (Thanks the grugq). - 0.5.x ===== diff --git a/README b/README index 3b73486..0d05178 100644 --- a/README +++ b/README @@ -21,9 +21,6 @@ Eventlet runs on Python version 2.3 or greater, with the following dependenceis: * Not enough test coverage -- the goal is 100%, but we are not there yet. * Eventlet does not currently run on stackless using tasklets, though 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 == diff --git a/eventlet/api_test.py b/eventlet/api_test.py index 53c5501..0e45f8f 100644 --- a/eventlet/api_test.py +++ b/eventlet/api_test.py @@ -22,10 +22,14 @@ 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 os import socket +from eventlet import api +from eventlet import greenio +from eventlet import tests +from eventlet import util + def check_hub(): # Clear through the descriptor queue @@ -43,6 +47,10 @@ def check_hub(): class TestApi(tests.TestCase): 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): socket = api.tcp_listener(('0.0.0.0', 0)) assert socket.getsockname()[0] == '0.0.0.0' @@ -74,6 +82,30 @@ class TestApi(tests.TestCase): 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): connected = [] server = api.tcp_listener(('0.0.0.0', 0)) diff --git a/eventlet/greenio.py b/eventlet/greenio.py index f214f75..c2faca6 100644 --- a/eventlet/greenio.py +++ b/eventlet/greenio.py @@ -137,7 +137,7 @@ def socket_recv(descriptor, buflen): return '' except util.SSL.SysCallError, e: if e[0] == -1 or e[0] > 0: - raise socket.error(errno.ECONNRESET, errno.errorcode[errno.ECONNRESET]) + return '' raise finally: if cancel: @@ -213,6 +213,12 @@ class GreenSocket(object): 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) @@ -296,7 +302,10 @@ class GreenSocket(object): return fn(*args, **kw) def shutdown(self, *args, **kw): - fn = self.shutdown = self.fd.shutdown + 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): diff --git a/setup.py b/setup.py index 3e82c55..971084c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup setup( name='eventlet', - version='0.6.2pre', + version='0.7pre', description='Coroutine-based networking library', author='Linden Lab', author_email='eventletdev@lists.secondlife.com', From 09c5a8e1660a48b470ddeaa6ae5ed06a30e4cfe3 Mon Sep 17 00:00:00 2001 From: donovan Date: Wed, 16 Jul 2008 11:23:24 -0700 Subject: [PATCH 117/118] Add test certificates --- eventlet/test_server.crt | 15 +++++++++++++++ eventlet/test_server.key | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 eventlet/test_server.crt create mode 100644 eventlet/test_server.key diff --git a/eventlet/test_server.crt b/eventlet/test_server.crt new file mode 100644 index 0000000..1379e1d --- /dev/null +++ b/eventlet/test_server.crt @@ -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----- diff --git a/eventlet/test_server.key b/eventlet/test_server.key new file mode 100644 index 0000000..24cd8e5 --- /dev/null +++ b/eventlet/test_server.key @@ -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----- From 283270075a4675a671618b30f016d04b0aeab884 Mon Sep 17 00:00:00 2001 From: donovan Date: Thu, 17 Jul 2008 15:30:39 -0700 Subject: [PATCH 118/118] 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. --- NEWS | 2 ++ eventlet/hubs/hub.py | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index ae64b3a..86efb0c 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Fix a major memory leak when using the libevent or libev hubs. Timers were not b 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 ===== diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index ca52c48..a5a3eb9 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -78,10 +78,21 @@ class BaseHub(object): so the exc callback happens instead of the respective read or write callback. """ - - self.readers[fileno] = read or self.readers.get(fileno) - self.writers[fileno] = write or self.writers.get(fileno) - self.excs[fileno] = exc or self.excs.get(fileno) + 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):