From b909d37e5496a043d115341974eb121e462d9018 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Fri, 20 May 2022 13:04:24 -0400 Subject: [PATCH] docs,demos: Update for asyncio.run pattern Adapt for Python 3.10 deprecation changes by using asyncio.run instead of IOLoop.start throughout (except for TCPServer multi-process docs). Demos have all been changed but I haven't tested all of them. --- demos/blog/blog.py | 3 +- demos/chat/chatdemo.py | 7 ++-- demos/facebook/facebook.py | 8 ++--- demos/file_upload/file_receiver.py | 11 +++--- demos/file_upload/file_uploader.py | 5 +-- demos/helloworld/helloworld.py | 8 ++--- demos/s3server/s3server.py | 8 ++--- demos/tcpecho/client.py | 14 ++++---- demos/tcpecho/server.py | 12 ++++--- demos/twitter/twitterdemo.py | 8 ++--- demos/websocket/chatdemo.py | 8 ++--- demos/webspider/webspider.py | 6 ++-- docs/guide/coroutines.rst | 4 +-- docs/guide/intro.rst | 7 +--- docs/guide/queues.rst | 7 ++-- docs/guide/running.rst | 17 +++++---- docs/guide/structure.rst | 21 +++++++++++- docs/index.rst | 8 ++--- tornado/httpserver.py | 5 +++ tornado/ioloop.py | 55 +++++++++++++----------------- tornado/iostream.py | 6 +--- tornado/locks.py | 29 ++++++++-------- tornado/queues.py | 42 ++++++++++++++--------- tornado/web.py | 24 +++++++------ tornado/wsgi.py | 11 +++--- 25 files changed, 181 insertions(+), 153 deletions(-) diff --git a/demos/blog/blog.py b/demos/blog/blog.py index a16ddf3e7..66e42b69a 100755 --- a/demos/blog/blog.py +++ b/demos/blog/blog.py @@ -15,6 +15,7 @@ # under the License. import aiopg +import asyncio import bcrypt import markdown import os.path @@ -313,4 +314,4 @@ async def main(): if __name__ == "__main__": - tornado.ioloop.IOLoop.current().run_sync(main) + asyncio.run(main()) diff --git a/demos/chat/chatdemo.py b/demos/chat/chatdemo.py index c109b222a..f96ac0515 100755 --- a/demos/chat/chatdemo.py +++ b/demos/chat/chatdemo.py @@ -16,7 +16,6 @@ import asyncio import tornado.escape -import tornado.ioloop import tornado.locks import tornado.web import os.path @@ -107,7 +106,7 @@ class MessageUpdatesHandler(tornado.web.RequestHandler): self.wait_future.cancel() -def main(): +async def main(): parse_command_line() app = tornado.web.Application( [ @@ -122,8 +121,8 @@ def main(): debug=options.debug, ) app.listen(options.port) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/demos/facebook/facebook.py b/demos/facebook/facebook.py index dc054b9a9..c23c02474 100755 --- a/demos/facebook/facebook.py +++ b/demos/facebook/facebook.py @@ -14,11 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. +import asyncio import os.path import tornado.auth import tornado.escape import tornado.httpserver -import tornado.ioloop import tornado.options import tornado.web @@ -109,15 +109,15 @@ class PostModule(tornado.web.UIModule): return self.render_string("modules/post.html", post=post) -def main(): +async def main(): tornado.options.parse_command_line() if not (options.facebook_api_key and options.facebook_secret): print("--facebook_api_key and --facebook_secret must be set") return http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/demos/file_upload/file_receiver.py b/demos/file_upload/file_receiver.py index 4f8e721e7..360c8aa58 100755 --- a/demos/file_upload/file_receiver.py +++ b/demos/file_upload/file_receiver.py @@ -8,10 +8,10 @@ HTTP POST, or streams in the raw data of a single file in an HTTP PUT. See file_uploader.py in this directory for code that uploads files in this format. """ +import asyncio import logging from urllib.parse import unquote -import tornado.ioloop import tornado.web from tornado import options @@ -48,9 +48,12 @@ def make_app(): return tornado.web.Application([(r"/post", POSTHandler), (r"/(.*)", PUTHandler)]) -if __name__ == "__main__": - # Tornado configures logging. +async def main(): options.parse_command_line() app = make_app() app.listen(8888) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/demos/file_upload/file_uploader.py b/demos/file_upload/file_uploader.py index c63ec1891..67fce7ed0 100755 --- a/demos/file_upload/file_uploader.py +++ b/demos/file_upload/file_uploader.py @@ -9,6 +9,7 @@ single file without encoding. See also file_receiver.py in this directory, a server that receives uploads. """ +import asyncio import mimetypes import os import sys @@ -16,7 +17,7 @@ from functools import partial from urllib.parse import quote from uuid import uuid4 -from tornado import gen, httpclient, ioloop +from tornado import gen, httpclient from tornado.options import define, options @@ -110,4 +111,4 @@ if __name__ == "__main__": sys.exit(1) method = put if options.put else post - ioloop.IOLoop.current().run_sync(lambda: method(filenames)) + asyncio.run(method(filenames)) diff --git a/demos/helloworld/helloworld.py b/demos/helloworld/helloworld.py index 7f64e8240..e19b61bb1 100755 --- a/demos/helloworld/helloworld.py +++ b/demos/helloworld/helloworld.py @@ -14,8 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import asyncio import tornado.httpserver -import tornado.ioloop import tornado.options import tornado.web @@ -29,13 +29,13 @@ class MainHandler(tornado.web.RequestHandler): self.write("Hello, world") -def main(): +async def main(): tornado.options.parse_command_line() application = tornado.web.Application([(r"/", MainHandler)]) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(options.port) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/demos/s3server/s3server.py b/demos/s3server/s3server.py index 11be1c2c2..197dc092c 100644 --- a/demos/s3server/s3server.py +++ b/demos/s3server/s3server.py @@ -30,6 +30,7 @@ S3 client with this module: """ +import asyncio import bisect import datetime import hashlib @@ -39,7 +40,6 @@ import urllib from tornado import escape from tornado import httpserver -from tornado import ioloop from tornado import web from tornado.util import unicode_type from tornado.options import options, define @@ -54,12 +54,12 @@ define("root_directory", default="/tmp/s3", help="Root storage directory") define("bucket_depth", default=0, help="Bucket file system depth limit") -def start(port, root_directory, bucket_depth): +async def start(port, root_directory, bucket_depth): """Starts the mock S3 server on the given port at the given path.""" application = S3Application(root_directory, bucket_depth) http_server = httpserver.HTTPServer(application) http_server.listen(port) - ioloop.IOLoop.current().start() + await asyncio.Event().wait() class S3Application(web.Application): @@ -265,4 +265,4 @@ class ObjectHandler(BaseRequestHandler): if __name__ == "__main__": options.parse_command_line() - start(options.port, options.root_directory, options.bucket_depth) + asyncio.run(start(options.port, options.root_directory, options.bucket_depth)) diff --git a/demos/tcpecho/client.py b/demos/tcpecho/client.py index a2ead08bc..e39b5e7e9 100755 --- a/demos/tcpecho/client.py +++ b/demos/tcpecho/client.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -from tornado.ioloop import IOLoop -from tornado import gen +import asyncio from tornado.tcpclient import TCPClient from tornado.options import options, define @@ -10,15 +9,14 @@ define("port", default=9888, help="TCP port to connect to") define("message", default="ping", help="Message to send") -@gen.coroutine -def send_message(): - stream = yield TCPClient().connect(options.host, options.port) - yield stream.write((options.message + "\n").encode()) +async def send_message(): + stream = await TCPClient().connect(options.host, options.port) + await stream.write((options.message + "\n").encode()) print("Sent to server:", options.message) - reply = yield stream.read_until(b"\n") + reply = await stream.read_until(b"\n") print("Response from server:", reply.decode().strip()) if __name__ == "__main__": options.parse_command_line() - IOLoop.current().run_sync(send_message) + asyncio.run(send_message()) diff --git a/demos/tcpecho/server.py b/demos/tcpecho/server.py index ebe683cf0..e7da4bff3 100755 --- a/demos/tcpecho/server.py +++ b/demos/tcpecho/server.py @@ -1,7 +1,7 @@ #!/usr/bin/env python +import asyncio import logging -from tornado.ioloop import IOLoop from tornado import gen from tornado.iostream import StreamClosedError from tornado.tcpserver import TCPServer @@ -28,9 +28,13 @@ class EchoServer(TCPServer): print(e) -if __name__ == "__main__": +async def main(): options.parse_command_line() + logger.info("Listening on TCP port %d", options.port) server = EchoServer() server.listen(options.port) - logger.info("Listening on TCP port %d", options.port) - IOLoop.current().start() + await asyncio.Event().wait() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/demos/twitter/twitterdemo.py b/demos/twitter/twitterdemo.py index 4bd302253..c459df3a3 100755 --- a/demos/twitter/twitterdemo.py +++ b/demos/twitter/twitterdemo.py @@ -17,11 +17,11 @@ To run this app, you must first register an application with Twitter: browser. """ +import asyncio import logging from tornado.auth import TwitterMixin from tornado.escape import json_decode, json_encode -from tornado.ioloop import IOLoop from tornado import gen from tornado.options import define, options, parse_command_line, parse_config_file from tornado.web import Application, RequestHandler, authenticated @@ -86,7 +86,7 @@ class LogoutHandler(BaseHandler): self.clear_cookie(self.COOKIE_NAME) -def main(): +async def main(): parse_command_line(final=False) parse_config_file(options.config_file) @@ -98,8 +98,8 @@ def main(): app.listen(options.port) logging.info("Listening on http://localhost:%d" % options.port) - IOLoop.current().start() + await asyncio.Event().wait() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/demos/websocket/chatdemo.py b/demos/websocket/chatdemo.py index 1a7a3042c..594a7c5e0 100755 --- a/demos/websocket/chatdemo.py +++ b/demos/websocket/chatdemo.py @@ -18,9 +18,9 @@ Authentication, error handling, etc are left as an exercise for the reader :) """ +import asyncio import logging import tornado.escape -import tornado.ioloop import tornado.options import tornado.web import tornado.websocket @@ -91,12 +91,12 @@ class ChatSocketHandler(tornado.websocket.WebSocketHandler): ChatSocketHandler.send_updates(chat) -def main(): +async def main(): tornado.options.parse_command_line() app = Application() app.listen(options.port) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() if __name__ == "__main__": - main() + asyncio.run(main()) diff --git a/demos/webspider/webspider.py b/demos/webspider/webspider.py index 16c3840fa..dcc0afa1e 100755 --- a/demos/webspider/webspider.py +++ b/demos/webspider/webspider.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 +import asyncio import time from datetime import timedelta from html.parser import HTMLParser from urllib.parse import urljoin, urldefrag -from tornado import gen, httpclient, ioloop, queues +from tornado import gen, httpclient, queues base_url = "http://www.tornadoweb.org/en/stable/" concurrency = 10 @@ -94,5 +95,4 @@ async def main(): if __name__ == "__main__": - io_loop = ioloop.IOLoop.current() - io_loop.run_sync(main) + asyncio.run(main()) diff --git a/docs/guide/coroutines.rst b/docs/guide/coroutines.rst index 795631bf7..811c084f1 100644 --- a/docs/guide/coroutines.rst +++ b/docs/guide/coroutines.rst @@ -6,7 +6,7 @@ Coroutines from tornado import gen **Coroutines** are the recommended way to write asynchronous code in -Tornado. Coroutines use the Python ``await`` or ``yield`` keyword to +Tornado. Coroutines use the Python ``await`` keyword to suspend and resume execution instead of a chain of callbacks (cooperative lightweight threads as seen in frameworks like `gevent `_ are sometimes called coroutines as well, but @@ -281,7 +281,7 @@ loop condition from accessing the results, as in this example from Running in the background ^^^^^^^^^^^^^^^^^^^^^^^^^ -`.PeriodicCallback` is not normally used with coroutines. Instead, a +As an alternative to `.PeriodicCallback`, a coroutine can contain a ``while True:`` loop and use `tornado.gen.sleep`:: diff --git a/docs/guide/intro.rst b/docs/guide/intro.rst index 8d87ba62b..2684c3890 100644 --- a/docs/guide/intro.rst +++ b/docs/guide/intro.rst @@ -9,7 +9,7 @@ can scale to tens of thousands of open connections, making it ideal for `WebSockets `_, and other applications that require a long-lived connection to each user. -Tornado can be roughly divided into four major components: +Tornado can be roughly divided into three major components: * A web framework (including `.RequestHandler` which is subclassed to create web applications, and various supporting classes). @@ -18,11 +18,6 @@ Tornado can be roughly divided into four major components: * An asynchronous networking library including the classes `.IOLoop` and `.IOStream`, which serve as the building blocks for the HTTP components and can also be used to implement other protocols. -* A coroutine library (`tornado.gen`) which allows asynchronous - code to be written in a more straightforward way than chaining - callbacks. This is similar to the native coroutine feature introduced - in Python 3.5 (``async def``). Native coroutines are recommended - in place of the `tornado.gen` module when available. The Tornado web framework and HTTP server together offer a full-stack alternative to `WSGI `_. diff --git a/docs/guide/queues.rst b/docs/guide/queues.rst index eb2f73f74..c8684e500 100644 --- a/docs/guide/queues.rst +++ b/docs/guide/queues.rst @@ -3,9 +3,10 @@ .. currentmodule:: tornado.queues -Tornado's `tornado.queues` module implements an asynchronous producer / -consumer pattern for coroutines, analogous to the pattern implemented for -threads by the Python standard library's `queue` module. +Tornado's `tornado.queues` module (and the very similar ``Queue`` classes in +`asyncio`) implements an asynchronous producer / consumer pattern for +coroutines, analogous to the pattern implemented for threads by the Python +standard library's `queue` module. A coroutine that yields `Queue.get` pauses until there is an item in the queue. If the queue has a maximum size set, a coroutine that yields `Queue.put` pauses diff --git a/docs/guide/running.rst b/docs/guide/running.rst index f7fd26443..47db460ad 100644 --- a/docs/guide/running.rst +++ b/docs/guide/running.rst @@ -10,17 +10,13 @@ configuring a WSGI container to find your application, you write a import asyncio - async def amain(): + async def main(): app = make_app() app.listen(8888) await asyncio.Event().wait() - - def main(): - asyncio.run(amain()) - if __name__ == '__main__': - main() + asyncio.run(main()) .. testoutput:: :hide: @@ -39,6 +35,15 @@ Due to the Python GIL (Global Interpreter Lock), it is necessary to run multiple Python processes to take full advantage of multi-CPU machines. Typically it is best to run one process per CPU. +.. note:: + + This section is somewhat out of date; the built-in multi-process mode + produces deprecation warnings on Python 3.10 (in addition to its other + limitations). Updated guidance is still in development; tentative + recommendations include running independent processes as described + in the paragraph beginning "For more sophisticated deployments", or + using ``SO_REUSEPORT`` instead of forking. + Tornado includes a built-in multi-process mode to start several processes at once (note that multi-process mode does not work on Windows). This requires a slight alteration to the standard main diff --git a/docs/guide/structure.rst b/docs/guide/structure.rst index 1c9a012a4..42a8e63b4 100644 --- a/docs/guide/structure.rst +++ b/docs/guide/structure.rst @@ -32,7 +32,8 @@ A minimal "hello world" example looks something like this: async def main(): app = make_app() app.listen(8888) - await asyncio.Event().wait() + shutdown_event = asyncio.Event() + await shutdown_event.wait() if __name__ == "__main__": asyncio.run(main()) @@ -40,6 +41,24 @@ A minimal "hello world" example looks something like this: .. testoutput:: :hide: +The ``main`` coroutine +~~~~~~~~~~~~~~~~~~~~~~ + +Beginning with Tornado 6.2 and Python 3.10, the recommended pattern for starting +a Tornado application is to create a ``main`` coroutine to be run with +`asyncio.run`. (In older versions, it was common to do initialization in a +regular function and then start the event loop with +``IOLoop.current().start()``. However, this pattern produces deprecation +warnings starting in Python 3.10 and will break in some future version of +Python.) + +When the ``main`` function returns, the program exits, so most of the time for a +web server ``main`` should run forever. Waiting on an `asyncio.Event` whose +``set()`` method is never called is a convenient way to make an asynchronus +function run forever. (and if you wish to have ``main`` exit early as a part of +a graceful shutdown procedure, you can call ``shutdown_event.set()`` to make it +exit). + The ``Application`` object ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/index.rst b/docs/index.rst index e8e50e8d9..f8d60b9ff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -100,15 +100,11 @@ installed in this way, so you may wish to download a copy of the source tarball or clone the `git repository `_ as well. -**Prerequisites**: Tornado 6.0 requires Python 3.7 or newer (See -`Tornado 5.1 `_ if -compatibility with Python 2.7 is required). The following optional -packages may be useful: +**Prerequisites**: Tornado 6.2 requires Python 3.7 or newer. The following +optional packages may be useful: * `pycurl `_ is used by the optional ``tornado.curl_httpclient``. Libcurl version 7.22 or higher is required. -* `Twisted `_ may be used with the classes in - `tornado.platform.twisted`. * `pycares `_ is an alternative non-blocking DNS resolver that can be used when threads are not appropriate. diff --git a/tornado/httpserver.py b/tornado/httpserver.py index cd4a46812..b3e034b9e 100644 --- a/tornado/httpserver.py +++ b/tornado/httpserver.py @@ -84,6 +84,11 @@ class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate) `HTTPServer` initialization follows one of three patterns (the initialization methods are defined on `tornado.tcpserver.TCPServer`): + .. note:: + + The multi-process examples here produce deprecation warnings in + Python 3.10; updated guidance is still in development. + 1. `~tornado.tcpserver.TCPServer.listen`: simple single-process:: server = HTTPServer(app) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 47b4804ad..6bd63a0f3 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -15,18 +15,11 @@ """An I/O event loop for non-blocking sockets. -In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event -loop, with a slightly different interface for historical reasons. -Applications can use either the `.IOLoop` interface or the underlying -`asyncio` event loop directly (unless compatibility with older -versions of Tornado is desired, in which case `.IOLoop` must be used). - -Typical applications will use a single `IOLoop` object, accessed via -`IOLoop.current` class method. The `IOLoop.start` method (or -equivalently, `asyncio.AbstractEventLoop.run_forever`) should usually -be called at the end of the ``main()`` function. Atypical applications -may use more than one `IOLoop`, such as one `IOLoop` per thread, or -per `unittest` case. +In Tornado 6.0, `.IOLoop` is a wrapper around the `asyncio` event loop, with a +slightly different interface. The `.IOLoop` interface is now provided primarily +for backwards compatibility; new code should generally use the `asyncio` event +loop interface directly. The `IOLoop.current` class method provides the +`IOLoop` instance corresponding to the running `asyncio` event loop. """ @@ -79,8 +72,7 @@ _S = TypeVar("_S", bound=_Selectable) class IOLoop(Configurable): """An I/O event loop. - As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event - loop. + As of Tornado 6.0, `IOLoop` is a wrapper around the `asyncio` event loop. Example usage for a simple TCP server: @@ -127,20 +119,19 @@ class IOLoop(Configurable): .. testoutput:: :hide: - Do not attempt to construct an `IOLoop` directly; this is deprecated - since Tornado 6.2. Instead, initialize the `asyncio` event loop and - use `IOLoop.current()` to access an `IOLoop` wrapper around the - current event loop. - - In general, an `IOLoop` cannot survive a fork or be shared across - processes in any way. When multiple processes are being used, each - process should create its own `IOLoop`, which also implies that - any objects which depend on the `IOLoop` (such as - `.AsyncHTTPClient`) must also be created in the child processes. - As a guideline, anything that starts processes (including the - `tornado.process` and `multiprocessing` modules) should do so as - early as possible, ideally the first thing the application does - after loading its configuration in ``main()``. + Do not attempt to construct an `IOLoop` directly; this is deprecated since + Tornado 6.2. Instead, initialize the `asyncio` event loop and use + `IOLoop.current()` to access an `IOLoop` wrapper around the current event + loop. + + In general, an `IOLoop` cannot survive a fork or be shared across processes + in any way. When multiple processes are being used, each process should + create its own `IOLoop`, which also implies that any objects which depend on + the `IOLoop` (such as `.AsyncHTTPClient`) must also be created in the child + processes. As a guideline, anything that starts processes (including the + `tornado.process` and `multiprocessing` modules) should do so as early as + possible, ideally the first thing the application does after loading its + configuration, and *before* any calls to `.IOLoop.start` or `asyncio.run`. .. versionchanged:: 4.2 Added the ``make_current`` keyword argument to the `IOLoop` @@ -148,13 +139,13 @@ class IOLoop(Configurable): .. versionchanged:: 5.0 - Uses the `asyncio` event loop by default. The - ``IOLoop.configure`` method cannot be used on Python 3 except - to redundantly specify the `asyncio` event loop. + Uses the `asyncio` event loop by default. The ``IOLoop.configure`` method + cannot be used on Python 3 except to redundantly specify the `asyncio` + event loop. .. deprecated:: 6.2 It is deprecated to create an event loop that is "current" but not - currently running. This means it is deprecated to pass + running. This means it is deprecated to pass ``make_current=True`` to the ``IOLoop`` constructor, or to create an ``IOLoop`` while no asyncio event loop is running unless ``make_current=False`` is used. diff --git a/tornado/iostream.py b/tornado/iostream.py index 7fee16dc3..333d5366f 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -1110,11 +1110,7 @@ class IOStream(BaseIOStream): stream.close() if __name__ == '__main__': - tornado.ioloop.IOLoop.current().run_sync(main) - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - stream = tornado.iostream.IOStream(s) - stream.connect(("friendfeed.com", 80), send_request) - tornado.ioloop.IOLoop.current().start() + asyncio.run(main()) .. testoutput:: :hide: diff --git a/tornado/locks.py b/tornado/locks.py index 29b6b4129..1bcec1b3a 100644 --- a/tornado/locks.py +++ b/tornado/locks.py @@ -60,8 +60,8 @@ class Condition(_TimeoutGarbageCollector): .. testcode:: + import asyncio from tornado import gen - from tornado.ioloop import IOLoop from tornado.locks import Condition condition = Condition() @@ -80,7 +80,7 @@ class Condition(_TimeoutGarbageCollector): # Wait for waiter() and notifier() in parallel await gen.multi([waiter(), notifier()]) - IOLoop.current().run_sync(runner) + asyncio.run(runner()) .. testoutput:: @@ -110,10 +110,6 @@ class Condition(_TimeoutGarbageCollector): next iteration of the `.IOLoop`. """ - def __init__(self) -> None: - super().__init__() - self.io_loop = ioloop.IOLoop.current() - def __repr__(self) -> str: result = "<%s" % (self.__class__.__name__,) if self._waiters: @@ -169,8 +165,8 @@ class Event(object): .. testcode:: + import asyncio from tornado import gen - from tornado.ioloop import IOLoop from tornado.locks import Event event = Event() @@ -189,7 +185,7 @@ class Event(object): async def runner(): await gen.multi([waiter(), setter()]) - IOLoop.current().run_sync(runner) + asyncio.run(runner()) .. testoutput:: @@ -302,8 +298,7 @@ class Semaphore(_TimeoutGarbageCollector): from tornado.ioloop import IOLoop from tornado.concurrent import Future - # Ensure reliable doctest output: resolve Futures one at a time. - futures_q = deque([Future() for _ in range(3)]) + inited = False async def simulator(futures): for f in futures: @@ -312,15 +307,21 @@ class Semaphore(_TimeoutGarbageCollector): await gen.sleep(0) f.set_result(None) - IOLoop.current().add_callback(simulator, list(futures_q)) - def use_some_resource(): + global inited + global futures_q + if not inited: + inited = True + # Ensure reliable doctest output: resolve Futures one at a time. + futures_q = deque([Future() for _ in range(3)]) + IOLoop.current().add_callback(simulator, list(futures_q)) + return futures_q.popleft() .. testcode:: semaphore + import asyncio from tornado import gen - from tornado.ioloop import IOLoop from tornado.locks import Semaphore sem = Semaphore(2) @@ -338,7 +339,7 @@ class Semaphore(_TimeoutGarbageCollector): # Join all workers. await gen.multi([worker(i) for i in range(3)]) - IOLoop.current().run_sync(runner) + asyncio.run(runner()) .. testoutput:: semaphore diff --git a/tornado/queues.py b/tornado/queues.py index 1e87f62e0..32132e16f 100644 --- a/tornado/queues.py +++ b/tornado/queues.py @@ -85,7 +85,7 @@ class Queue(Generic[_T]): .. testcode:: - from tornado import gen + import asyncio from tornado.ioloop import IOLoop from tornado.queues import Queue @@ -95,7 +95,7 @@ class Queue(Generic[_T]): async for item in q: try: print('Doing work on %s' % item) - await gen.sleep(0.01) + await asyncio.sleep(0.01) finally: q.task_done() @@ -111,7 +111,7 @@ class Queue(Generic[_T]): await q.join() # Wait for consumer to finish all tasks. print('Done') - IOLoop.current().run_sync(main) + asyncio.run(main()) .. testoutput:: @@ -353,16 +353,20 @@ class PriorityQueue(Queue): .. testcode:: + import asyncio from tornado.queues import PriorityQueue - q = PriorityQueue() - q.put((1, 'medium-priority item')) - q.put((0, 'high-priority item')) - q.put((10, 'low-priority item')) + async def main(): + q = PriorityQueue() + q.put((1, 'medium-priority item')) + q.put((0, 'high-priority item')) + q.put((10, 'low-priority item')) + + print(await q.get()) + print(await q.get()) + print(await q.get()) - print(q.get_nowait()) - print(q.get_nowait()) - print(q.get_nowait()) + asyncio.run(main()) .. testoutput:: @@ -386,16 +390,20 @@ class LifoQueue(Queue): .. testcode:: + import asyncio from tornado.queues import LifoQueue - q = LifoQueue() - q.put(3) - q.put(2) - q.put(1) + async def main(): + q = LifoQueue() + q.put(3) + q.put(2) + q.put(1) + + print(await q.get()) + print(await q.get()) + print(await q.get()) - print(q.get_nowait()) - print(q.get_nowait()) - print(q.get_nowait()) + asyncio.run(main()) .. testoutput:: diff --git a/tornado/web.py b/tornado/web.py index 2dab485a4..0604053ff 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -22,19 +22,22 @@ Here is a simple "Hello, world" example app: .. testcode:: - import tornado.ioloop + import asyncio import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") - if __name__ == "__main__": + async def main(): application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(8888) - tornado.ioloop.IOLoop.current().start() + await asyncio.Event().wait() + + if __name__ == "__main__": + asyncio.run(main()) .. testoutput:: :hide: @@ -1964,7 +1967,6 @@ class Application(ReversibleRouter): ]) http_server = httpserver.HTTPServer(application) http_server.listen(8080) - ioloop.IOLoop.current().start() The constructor for this class takes in a list of `~.routing.Rule` objects or tuples of values corresponding to the arguments of @@ -2096,16 +2098,16 @@ class Application(ReversibleRouter): def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer: """Starts an HTTP server for this application on the given port. - This is a convenience alias for creating an `.HTTPServer` - object and calling its listen method. Keyword arguments not - supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the - `.HTTPServer` constructor. For advanced uses - (e.g. multi-process mode), do not use this method; create an - `.HTTPServer` and call its + This is a convenience alias for creating an `.HTTPServer` object and + calling its listen method. Keyword arguments not supported by + `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer` + constructor. For advanced uses (e.g. multi-process mode), do not use + this method; create an `.HTTPServer` and call its `.TCPServer.bind`/`.TCPServer.start` methods directly. Note that after calling this method you still need to call - ``IOLoop.current().start()`` to start the server. + ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start + the server. Returns the `.HTTPServer` object. diff --git a/tornado/wsgi.py b/tornado/wsgi.py index f9b37db2c..c60f152d4 100644 --- a/tornado/wsgi.py +++ b/tornado/wsgi.py @@ -75,10 +75,13 @@ class WSGIContainer(object): start_response(status, response_headers) return [b"Hello world!\n"] - container = tornado.wsgi.WSGIContainer(simple_app) - http_server = tornado.httpserver.HTTPServer(container) - http_server.listen(8888) - tornado.ioloop.IOLoop.current().start() + async def main(): + container = tornado.wsgi.WSGIContainer(simple_app) + http_server = tornado.httpserver.HTTPServer(container) + http_server.listen(8888) + await asyncio.Event().wait() + + asyncio.run(main()) This class is intended to let other frameworks (Django, web.py, etc) run on the Tornado HTTP server and I/O loop. -- 2.47.2