Permalink
Browse files

- A brown-bag bug prevented request concurrency. A slow request would…

… block

  subsequent the responses of subsequent requests until the slow request's
  response was fully generated.  This was due to a "task lock" being declared
  as a class attribute rather than as an instance attribute on HTTPChannel.
  ALso took the opportunity to move another lock named "outbuf lock" to the
  channel instance rather than the class.
  • Loading branch information...
1 parent 7734339 commit 7fa8ef4d9410b83237dfd11f0700d3117eeb9b27 @mcdonc mcdonc committed Feb 13, 2012
View
@@ -1,3 +1,16 @@
+Next release
+-------------
+
+Bug Fixes
+---------
+
+- A brown-bag bug prevented request concurrency. A slow request would block
+ subsequent the responses of subsequent requests until the slow request's
+ response was fully generated. This was due to a "task lock" being declared
+ as a class attribute rather than as an instance attribute on HTTPChannel.
+ ALso took the opportunity to move another lock named "outbuf lock" to the
+ channel instance rather than the class.
+
0.8 (2012-01-31)
----------------
View
@@ -52,8 +52,6 @@ class HTTPChannel(logging_dispatcher, object):
close_when_flushed = False # set to True to close the socket when flushed
requests = () # currently pending requests
sent_continue = False # used as a latch after sending 100 continue
- task_lock = thread.allocate_lock() # lock used to push/pop requests
- outbuf_lock = thread.allocate_lock() # lock used to access any outbuf
force_flush = False # indicates a need to flush the outbuf
#
@@ -73,6 +71,12 @@ def __init__(
self.adj = adj
self.outbufs = [OverflowableBuffer(adj.outbuf_overflow)]
self.creation_time = self.last_activity = time.time()
+
+ # task_lock used to push/pop requests
+ self.task_lock = thread.allocate_lock()
+ # outbuf_lock used to access any outbuf
+ self.outbuf_lock = thread.allocate_lock()
+
asyncore.dispatcher.__init__(self, sock, map=map)
def any_outbuf_has_data(self):
@@ -0,0 +1,14 @@
+import sys
+
+if __name__ == '__main__':
+ try:
+ from urllib.request import Request, urlopen
+ except ImportError:
+ from urllib2 import Request, urlopen
+
+ url = sys.argv[1]
+ headers = {'Content-Type':'text/plain; charset=utf-8'}
+ resp = urlopen(url)
+ line = resp.readline().decode('ascii') # py3
+ print(line)
+
@@ -0,0 +1,26 @@
+import time
+
+def app(environ, start_response):
+ if environ['PATH_INFO'] == '/sleepy':
+ time.sleep(2)
+ body = b'sleepy returned'
+ else:
+ body = b'notsleepy returned'
+ cl = str(len(body))
+ start_response(
+ '200 OK',
+ [('Content-Length', cl), ('Content-Type', 'text/plain')]
+ )
+ return [body]
+
+if __name__ == '__main__':
+ import logging
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+ h = NullHandler()
+ logging.getLogger('waitress').addHandler(h)
+ from waitress import serve
+ serve(app, port=61523, _quiet=True)
+
+
@@ -19,16 +19,12 @@ class SubprocessTests(object):
host = 'localhost'
def start_subprocess(self, cmd):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- cwd = os.getcwd()
- os.chdir(dn(dn(here)))
self.proc = subprocess.Popen(cmd)
- os.chdir(cwd)
time.sleep(.2)
if self.proc.returncode is not None: # pragma: no cover
raise RuntimeError('%s didnt start' % str(cmd))
def stop_subprocess(self):
- time.sleep(.2)
if self.proc.returncode is None:
#self.conn.close()
self.proc.terminate()
@@ -40,6 +36,32 @@ def assertline(self, line, status, reason, version):
self.assertEqual(r, tobytes(reason))
self.assertEqual(v, tobytes(version))
+class SleepyThreadTests(SubprocessTests, unittest.TestCase):
+ # test that sleepy thread doesnt block other requests
+ def setUp(self):
+ echo = os.path.join(here, 'fixtureapps', 'sleepy.py')
+ self.start_subprocess([self.exe, echo])
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_it(self):
+ sleepycmd = [self.exe, os.path.join(here, 'fixtureapps', 'getline.py'),
+ 'http://127.0.0.1:%s/sleepy' % self.port]
+ notsleepycmd = [self.exe,os.path.join(here, 'fixtureapps','getline.py'),
+ 'http://127.0.0.1:%s/' % self.port]
+ r, w = os.pipe()
+ sleepyproc = subprocess.Popen(sleepycmd, stdout=w)
+ notsleepyproc = subprocess.Popen(notsleepycmd, stdout=w)
+ time.sleep(3)
+ sleepyproc.terminate()
+ notsleepyproc.terminate()
+ # the notsleepy response should always be first returned (it sleeps
+ # for 2 seconds, then returns; the notsleepy response should be
+ # processed in the meantime)
+ result = os.read(r, 10000)
+ self.assertEqual(result, b'notsleepy returned\nsleepy returned\n')
+
class EchoTests(SubprocessTests, unittest.TestCase):
def setUp(self):
echo = os.path.join(here, 'fixtureapps', 'echo.py')

0 comments on commit 7fa8ef4

Please sign in to comment.