-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3231 from bdarnell/wsgi-executor
wsgi: Support ThreadPoolExecutor
- Loading branch information
Showing
2 changed files
with
186 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,116 @@ | ||
import asyncio | ||
import concurrent.futures | ||
import threading | ||
|
||
from wsgiref.validate import validator | ||
|
||
from tornado.testing import AsyncHTTPTestCase | ||
from tornado.routing import RuleRouter | ||
from tornado.testing import AsyncHTTPTestCase, gen_test | ||
from tornado.wsgi import WSGIContainer | ||
|
||
|
||
class WSGIContainerTest(AsyncHTTPTestCase): | ||
class WSGIAppMixin: | ||
# TODO: Now that WSGIAdapter is gone, this is a pretty weak test. | ||
def wsgi_app(self, environ, start_response): | ||
def get_executor(self): | ||
raise NotImplementedError() | ||
|
||
def get_app(self): | ||
executor = self.get_executor() | ||
# The barrier test in DummyExecutorTest will always wait the full | ||
# value of this timeout, so we don't want it to be too high. | ||
self.barrier = threading.Barrier(2, timeout=0.3) | ||
|
||
def make_container(app): | ||
return WSGIContainer(validator(app), executor=executor) | ||
|
||
return RuleRouter( | ||
[ | ||
("/simple", make_container(self.simple_wsgi_app)), | ||
("/barrier", make_container(self.barrier_wsgi_app)), | ||
("/streaming_barrier", make_container(self.streaming_barrier_wsgi_app)), | ||
] | ||
) | ||
|
||
def respond_plain(self, start_response): | ||
status = "200 OK" | ||
response_headers = [("Content-Type", "text/plain")] | ||
start_response(status, response_headers) | ||
|
||
def simple_wsgi_app(self, environ, start_response): | ||
self.respond_plain(start_response) | ||
return [b"Hello world!"] | ||
|
||
def get_app(self): | ||
return WSGIContainer(validator(self.wsgi_app)) | ||
def barrier_wsgi_app(self, environ, start_response): | ||
self.respond_plain(start_response) | ||
try: | ||
n = self.barrier.wait() | ||
except threading.BrokenBarrierError: | ||
return [b"broken barrier"] | ||
else: | ||
return [b"ok %d" % n] | ||
|
||
def streaming_barrier_wsgi_app(self, environ, start_response): | ||
self.respond_plain(start_response) | ||
yield b"ok " | ||
try: | ||
n = self.barrier.wait() | ||
except threading.BrokenBarrierError: | ||
yield b"broken barrier" | ||
else: | ||
yield b"%d" % n | ||
|
||
|
||
class WSGIContainerDummyExecutorTest(WSGIAppMixin, AsyncHTTPTestCase): | ||
def get_executor(self): | ||
return None | ||
|
||
def test_simple(self): | ||
response = self.fetch("/simple") | ||
self.assertEqual(response.body, b"Hello world!") | ||
|
||
@gen_test | ||
async def test_concurrent_barrier(self): | ||
self.barrier.reset() | ||
resps = await asyncio.gather( | ||
self.http_client.fetch(self.get_url("/barrier")), | ||
self.http_client.fetch(self.get_url("/barrier")), | ||
) | ||
for resp in resps: | ||
self.assertEqual(resp.body, b"broken barrier") | ||
|
||
@gen_test | ||
async def test_concurrent_streaming_barrier(self): | ||
self.barrier.reset() | ||
resps = await asyncio.gather( | ||
self.http_client.fetch(self.get_url("/streaming_barrier")), | ||
self.http_client.fetch(self.get_url("/streaming_barrier")), | ||
) | ||
for resp in resps: | ||
self.assertEqual(resp.body, b"ok broken barrier") | ||
|
||
|
||
class WSGIContainerThreadPoolTest(WSGIAppMixin, AsyncHTTPTestCase): | ||
def get_executor(self): | ||
return concurrent.futures.ThreadPoolExecutor() | ||
|
||
def test_simple(self): | ||
response = self.fetch("/") | ||
response = self.fetch("/simple") | ||
self.assertEqual(response.body, b"Hello world!") | ||
|
||
@gen_test | ||
async def test_concurrent_barrier(self): | ||
self.barrier.reset() | ||
resps = await asyncio.gather( | ||
self.http_client.fetch(self.get_url("/barrier")), | ||
self.http_client.fetch(self.get_url("/barrier")), | ||
) | ||
self.assertEqual([b"ok 0", b"ok 1"], sorted([resp.body for resp in resps])) | ||
|
||
@gen_test | ||
async def test_concurrent_streaming_barrier(self): | ||
self.barrier.reset() | ||
resps = await asyncio.gather( | ||
self.http_client.fetch(self.get_url("/streaming_barrier")), | ||
self.http_client.fetch(self.get_url("/streaming_barrier")), | ||
) | ||
self.assertEqual([b"ok 0", b"ok 1"], sorted([resp.body for resp in resps])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters