From aec9f79fc2bae16e0ba27e848343ae19824061a8 Mon Sep 17 00:00:00 2001 From: rdw Date: Sun, 16 Mar 2008 21:45:23 -0500 Subject: [PATCH] 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 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.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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