[Repoze-checkins] r1095 - in repoze.retry/trunk: . repoze/retry

Chris McDonough chrism at agendaless.com
Sun Jun 15 23:53:56 EDT 2008


Author: Chris McDonough <chrism at agendaless.com>
Date: Sun Jun 15 23:53:55 2008
New Revision: 1095

Log:
 Fixed concurrency bug whereby a response from one request might be
 returned as result of a different request.

 Initial PyPI release.



Modified:
   repoze.retry/trunk/CHANGES.txt
   repoze.retry/trunk/README.txt
   repoze.retry/trunk/repoze/retry/__init__.py
   repoze.retry/trunk/repoze/retry/tests.py
   repoze.retry/trunk/setup.py

Modified: repoze.retry/trunk/CHANGES.txt
==============================================================================
--- repoze.retry/trunk/CHANGES.txt	(original)
+++ repoze.retry/trunk/CHANGES.txt	Sun Jun 15 23:53:55 2008
@@ -1,3 +1,10 @@
+0.9 (2008-06-15)
+
+ Fixed concurrency bug whereby a response from one request might be
+ returned as result of a different request.
+
+ Initial PyPI release.
+
 0.8
 
  Added WSGI conformance testing for the middleware.

Modified: repoze.retry/trunk/README.txt
==============================================================================
--- repoze.retry/trunk/README.txt	(original)
+++ repoze.retry/trunk/README.txt	Sun Jun 15 23:53:55 2008
@@ -1,27 +1,38 @@
 repoze.retry README
+=====================
 
-  Overview
+This package implements a WSGI middleware filter which intercepts
+"retryable" exceptions and retries the WSGI request a configurable
+number of times.  If the request cannot be satisfied via retries, the
+exception is reraised.
 
-    This package implements a WSGI Middleware filter which intercepts
-    "retryable" exceptions and retries the WSGI request a configurable number
-    of times.
+Installation
+------------
 
-  Installation
+Install using setuptools, e.g. (within a virtualenv)::
 
-    The simple way::
+ $ easy_install repoze.retry
 
-      $ bin/easy_install --find-links=http://dist.repoze.org/ repoze.retry
+Configuration via Python
+------------------------
 
-  Configuraiton
-    
-    By default, the retryable exception is 'ZODB.POSException.ConflictError';
-    the 'tries' count defaults to 3 times.
+Wire up the middleware in your application::
+
+  from repoze.retry import Retry
+  mw = Retry(app, tries=3, retryable=(ValueError, IndexError))
 
-    If you want to use the default configuration, you can just include the
-    filter in your application's pipeline.  Note that the filter should come
-    before (to the "left") of the 'repoze.tm' filter, your pipeline includes
-    it, so that retried requests are first aborted and then restarted in a
-    new transaction::
+By default, the retryable exception is ``repoze.retry.ConflictError``
+(or if ZODB is installed, it's ``ZODB.POSException.ConflictError``);
+the ``tries`` count defaults to 3 times.
+
+Configuration via Paste
+-----------------------
+    
+If you want to use the default configuration, you can just include the
+filter in your application's pipeline.  Note that the filter should
+come before (to the "left") of the ``repoze.tm`` filter, your pipeline
+includes it, so that retried requests are first aborted and then
+restarted in a new transaction::
 
         [pipeline:main]
         pipeline = egg:Paste#cgitb
@@ -31,19 +42,26 @@
                    egg:repoze.vhm#vhm_xheaders
                    zope2
 
-    If you want to override the defaults, e.g. to change the number of retries,
-    or the exceptions which will be retried, you need to make a separate section
-    for the filter::
+If you want to override the defaults, e.g. to change the number of retries,
+or the exceptions which will be retried, you need to make a separate section
+for the filter::
 
         [filter:retry]
         use = egg:repoze.retry
         tries = 2
         retryable = egg:mypackage.exceptions:SomeRetryableException
 
-    and then use it in the pipeline::
+and then use it in the pipeline::
 
         [pipeline:main]
         pipeline = egg:Paste#cgitb
                    egg:Paste#httpexceptions
                    retry
                    myapp
+
+Reporting Bugs / Development Versions
+-------------------------------------
+
+Visit http://bugs.repoze.org to report bugs.  Visit
+http://svn.repoze.org to download development or tagged versions.
+

Modified: repoze.retry/trunk/repoze/retry/__init__.py
==============================================================================
--- repoze.retry/trunk/repoze/retry/__init__.py	(original)
+++ repoze.retry/trunk/repoze/retry/__init__.py	Sun Jun 15 23:53:55 2008
@@ -1,5 +1,6 @@
 # repoze retry-on-conflict-error behavior
 import traceback
+import itertools
 
 try:
     from ZODB.POSException import ConflictError
@@ -20,7 +21,6 @@
         """
         self.application = application
         self.tries = tries
-        self.start_response_result = None
 
         if retryable is None:
             retryable = ConflictError
@@ -30,22 +30,18 @@
 
         self.retryable = tuple(retryable)
 
-    def buffer_start_response(self, *arg, **kw):
-        # we can't successfully retry if a downstream application has already
-        # called start_response, so we buffer the result and call the
-        # original start_response if we don't
-        self.start_response_result = (arg, kw)
-
-    def call_start_response(self, start_response):
-        if self.start_response_result is not None:
-            arg, kw = self.start_response_result
-            start_response(*arg, **kw)
-
     def __call__(self, environ, start_response):
+        catch_response = []
+        written = []
+
+        def replace_start_response(status, headers, exc_info=None):
+            catch_response[:] = [status, headers, exc_info]
+            return written.append
+
         i = 0
         while 1:
             try:
-                result = self.application(environ, self.buffer_start_response)
+                app_iter = self.application(environ, replace_start_response)
             except self.retryable, e:
                 i += 1
                 if environ.get('wsgi.errors'):
@@ -54,12 +50,13 @@
                     traceback.print_exc(environ['wsgi.errors'])
                 if i < self.tries:
                     continue
-                self.call_start_response(start_response)
+                if catch_response:
+                    start_response(*catch_response)
                 raise
             else:
-                self.call_start_response(start_response)
-                return result
-
+                if catch_response:
+                    start_response(*catch_response)
+                return itertools.chain(written, app_iter)
 
 def make_retry(app, global_conf, **local_conf):
     from pkg_resources import EntryPoint

Modified: repoze.retry/trunk/repoze/retry/tests.py
==============================================================================
--- repoze.retry/trunk/repoze/retry/tests.py	(original)
+++ repoze.retry/trunk/repoze/retry/tests.py	Sun Jun 15 23:53:55 2008
@@ -10,7 +10,7 @@
 
 _MINIMAL_HEADERS = [('Content-Type', 'text/plain')]
 
-def _faux_start_response(result, headers):
+def _faux_start_response(result, headers, exc_info=None):
     pass
 
 class RetryTests(unittest.TestCase, CEBase):
@@ -39,8 +39,9 @@
         application = DummyApplication(conflicts=5)
         retry = self._makeOne(application, tries=4,
                               retryable=(self.ConflictError,))
+        env = self._makeEnv()
         self.failUnlessRaises(self.ConflictError,
-                              retry, self._makeEnv(), _faux_start_response)
+                              retry, env, _faux_start_response)
         self.assertEqual(application.called, 4)
 
     def _dummy_start_response(self, *arg):
@@ -54,7 +55,7 @@
                               self._dummy_start_response)
         self.assertEqual(application.called, 4)
         self.assertEqual(self._dummy_start_response_result,
-                         ('200 OK', _MINIMAL_HEADERS))
+                         ('200 OK', _MINIMAL_HEADERS, None))
 
     def test_conflict_not_raised_start_response_called(self):
         application = DummyApplication(conflicts=1, call_start_response=True)
@@ -63,7 +64,7 @@
         result = retry(self._makeEnv(), self._dummy_start_response)
         self.assertEqual(application.called, 1)
         self.assertEqual(self._dummy_start_response_result,
-                         ('200 OK', _MINIMAL_HEADERS))
+                         ('200 OK', _MINIMAL_HEADERS, None))
         self.assertEqual(list(result), ['hello'])
 
     def test_alternate_retryble_exception(self):

Modified: repoze.retry/trunk/setup.py
==============================================================================
--- repoze.retry/trunk/setup.py	(original)
+++ repoze.retry/trunk/setup.py	Sun Jun 15 23:53:55 2008
@@ -1,15 +1,16 @@
-__version__ = '0.8dev'
+__version__ = '0.9'
 
 import os
 from setuptools import setup, find_packages
 
 here = os.path.abspath(os.path.dirname(__file__))
 README = open(os.path.join(here, 'README.txt')).read()
+CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
 
 setup(name='repoze.retry',
       version=__version__,
       description='Middleware which implements a retryable exceptions',
-      long_description=README,
+      long_description=README + '\n\n' + CHANGES,
       classifiers=[
         "Development Status :: 3 - Alpha",
         "Intended Audience :: Developers",
@@ -18,7 +19,7 @@
         "Topic :: Internet :: WWW/HTTP :: WSGI",
         "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware",
         ],
-      keywords='web application server wsgi zope',
+      keywords='wsgi middleware retry',
       author="Agendaless Consulting",
       author_email="repoze-dev at lists.repoze.org",
       url="http://www.repoze.org",


More information about the Repoze-checkins mailing list