[Checkins] SVN: zc.resumelb/trunk/src/zc/resumelb/ Fixed: Poorly-behaved WSGI applications that fail to catch errors

jim cvs-admin at zope.org
Mon May 7 18:07:19 UTC 2012


Log message for revision 125693:
  Fixed: Poorly-behaved WSGI applications that fail to catch errors
  caused requests to hang rather than return 500 responses.
  
  Moved helper functions from worker.test into tests module so they can
  be used in other tests.
  

Changed:
  U   zc.resumelb/trunk/src/zc/resumelb/README.txt
  U   zc.resumelb/trunk/src/zc/resumelb/tests.py
  U   zc.resumelb/trunk/src/zc/resumelb/worker.py
  U   zc.resumelb/trunk/src/zc/resumelb/worker.test

-=-
Modified: zc.resumelb/trunk/src/zc/resumelb/README.txt
===================================================================
--- zc.resumelb/trunk/src/zc/resumelb/README.txt	2012-05-07 15:53:29 UTC (rev 125692)
+++ zc.resumelb/trunk/src/zc/resumelb/README.txt	2012-05-07 18:07:15 UTC (rev 125693)
@@ -252,6 +252,9 @@
 - Fixed: worker errors were written to standard out rather than being
   logged.
 
+- Fixed: Poorly-behaved WSGI applications that fail to catch errors
+  caused requests to hang rather than return 500 responses.
+
 0.5.0 (2012-05-03)
 ------------------
 

Modified: zc.resumelb/trunk/src/zc/resumelb/tests.py
===================================================================
--- zc.resumelb/trunk/src/zc/resumelb/tests.py	2012-05-07 15:53:29 UTC (rev 125692)
+++ zc.resumelb/trunk/src/zc/resumelb/tests.py	2012-05-07 18:07:15 UTC (rev 125693)
@@ -14,6 +14,7 @@
 import bobo
 import doctest
 import gevent
+import gevent.socket
 import hashlib
 import manuel.capture
 import manuel.doctest
@@ -24,11 +25,13 @@
 import pprint
 import re
 import time
+import traceback
 import unittest
 import webob
 import zc.resumelb.util
 import zc.resumelb.worker
 import zc.zk.testing
+import zope.testing.loggingsupport
 import zope.testing.setupstack
 import zope.testing.wait
 import zope.testing.renormalizing
@@ -86,6 +89,42 @@
 #
 ###############################################################################
 
+def newenv(rclass, *a, **kw):
+    r = webob.Request.blank(*a, **kw)
+    env = r.environ.copy()
+    inp = env.pop('wsgi.input')
+    del env['wsgi.errors']
+    env['zc.resumelb.request_class'] = rclass
+    return env
+
+def print_response(worker_socket, rno, size_only=False):
+    d = zc.resumelb.util.read_message(worker_socket)
+    try: rn, (status, headers) = d
+    except:
+      print 'wtf', `d`
+      return
+    #rn, (status, headers) = read_message(worker_socket)
+    if rn != rno:
+       raise AssertionError("Bad request numbers", rno, rn)
+    print rno, status
+    for h in sorted(headers):
+        print "%s: %s" % h
+    print
+    size = 0
+    while 1:
+        rn, data = zc.resumelb.util.read_message(worker_socket)
+        if rn != rno:
+           raise AssertionError("Bad request numbers", rno, rn)
+        if data:
+            if size_only:
+                size += len(data)
+            else:
+                print data,
+        else:
+            break
+    if size_only:
+       print size
+
 def test_loading_recipes_with_no_history_argument():
     """A bug as introduced that caused resumes to be loaded
     incorrectly when no history was given to the constructor.  It
@@ -100,9 +139,50 @@
 
     >>> pprint.pprint(worker.perf_data)
     {'a': (0, 1.0, 9999), 'b': (0, 0.5, 9999)}
+
+    >>> worker.stop()
     """
 
+def workers_generate_500s_for_bad_apps():
+    """If an app is poorly behaved and raises exceptions, a worker
+    will generate a 500.
 
+    >>> def baddapp(*args):
+    ...     raise Exception("I'm a bad-app")
+
+    >>> worker = zc.resumelb.worker.Worker(baddapp, ('127.0.0.1', 0))
+    >>> worker_socket = gevent.socket.create_connection(worker.addr)
+    >>> zc.resumelb.util.read_message(worker_socket)
+    (0, {})
+
+    >>> env = newenv('', '/hi.html')
+    >>> handler = zope.testing.loggingsupport.InstalledHandler(
+    ...     'zc.resumelb.worker')
+    >>> zc.resumelb.util.write_message(worker_socket, 1, env, '')
+    >>> print_response(worker_socket, 1)
+    1 500 Internal Server Error
+    Content-Length: 23
+    Content-Type: text/html; charset=UTF-8
+    <BLANKLINE>
+    A system error occurred
+
+    >>> for record in handler.records:
+    ...     print record.name, record.levelname
+    ...     print record.getMessage()
+    ...     if record.exc_info:
+    ...         traceback.print_exception(*record.exc_info)
+    ... # doctest: +ELLIPSIS
+    zc.resumelb.worker ERROR
+    Uncaught application exception for 1
+    Traceback (most recent call last):
+    ...
+    Exception: I'm a bad-app
+
+    >>> handler.uninstall()
+    >>> worker.stop()
+    """
+
+
 def test_classifier(env):
     return "yup, it's a test"
 
@@ -121,6 +201,8 @@
     zope.testing.setupstack.register(
         test, setattr, zc.resumelb.util, 'queue_size_bytes', old)
     zc.resumelb.util.queue_size_bytes = 999
+    test.globs['newenv'] = newenv
+    test.globs['print_response'] = print_response
 
 def zkSetUp(test):
     setUp(test)

Modified: zc.resumelb/trunk/src/zc/resumelb/worker.py
===================================================================
--- zc.resumelb/trunk/src/zc/resumelb/worker.py	2012-05-07 15:53:29 UTC (rev 125692)
+++ zc.resumelb/trunk/src/zc/resumelb/worker.py	2012-05-07 18:07:15 UTC (rev 125693)
@@ -66,7 +66,15 @@
             def start_response(status, headers, exc_info=None):
                 assert not exc_info # XXX
                 response[0] = (status, headers)
-            body = app(env, start_response)
+
+            try:
+                body = app(env, start_response)
+            except Exception:
+                error("Uncaught application exception for %s" % trno)
+                import webob
+                body = webob.Response(
+                    'A system error occurred', status=500)(env, start_response)
+
             return response[0], body
 
         if tracelog:
@@ -200,7 +208,7 @@
                     url += '?' + query_string
                 self.tracelog(trno, 'B', '%s %s' % (env['REQUEST_METHOD'], url))
             else:
-                trno = 0
+                trno = rno
 
             env['wsgi.errors'] = sys.stderr
 

Modified: zc.resumelb/trunk/src/zc/resumelb/worker.test
===================================================================
--- zc.resumelb/trunk/src/zc/resumelb/worker.test	2012-05-07 15:53:29 UTC (rev 125692)
+++ zc.resumelb/trunk/src/zc/resumelb/worker.test	2012-05-07 18:07:15 UTC (rev 125693)
@@ -44,17 +44,8 @@
 When the worker sends its resume, it sends 0 as the request number.
 
 Now, let's send a request to the worker.  Requests are based on wsgi
-environments. We'll use webob to help us set this up.
+environments. We have a helper, newenv, that helps us create one.
 
-    >>> import webob
-    >>> def newenv(rclass, *a, **kw):
-    ...     r = webob.Request.blank(*a, **kw)
-    ...     env = r.environ.copy()
-    ...     inp = env.pop('wsgi.input')
-    ...     del env['wsgi.errors']
-    ...     env['zc.resumelb.request_class'] = rclass
-    ...     return env
-
     >>> env = newenv('', '/hi.html')
 
 The newenv helper:
@@ -76,33 +67,7 @@
 - response body, and
 - an empty end-of-body message.
 
-    >>> def print_response(worker_socket, rno, size_only=False):
-    ...     d = read_message(worker_socket)
-    ...     try: rn, (status, headers) = d
-    ...     except:
-    ...       print 'wtf', `d`
-    ...       return
-    ...     #rn, (status, headers) = read_message(worker_socket)
-    ...     if rn != rno:
-    ...        raise AssertionError("Bad request numbers", rno, rn)
-    ...     print rno, status
-    ...     for h in sorted(headers):
-    ...         print "%s: %s" % h
-    ...     print
-    ...     size = 0
-    ...     while 1:
-    ...         rn, data = read_message(worker_socket)
-    ...         if rn != rno:
-    ...            raise AssertionError("Bad request numbers", rno, rn)
-    ...         if data:
-    ...             if size_only:
-    ...                 size += len(data)
-    ...             else:
-    ...                 print data,
-    ...         else:
-    ...             break
-    ...     if size_only:
-    ...        print size
+We also have a helper that consumes and prints a response:
 
     >>> print_response(worker_socket, 1)
     1 200 OK



More information about the checkins mailing list