]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
docs,demos: Update for asyncio.run pattern 3147/head
authorBen Darnell <ben@bendarnell.com>
Fri, 20 May 2022 17:04:24 +0000 (13:04 -0400)
committerBen Darnell <ben@bendarnell.com>
Fri, 3 Jun 2022 18:50:20 +0000 (14:50 -0400)
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.

25 files changed:
demos/blog/blog.py
demos/chat/chatdemo.py
demos/facebook/facebook.py
demos/file_upload/file_receiver.py
demos/file_upload/file_uploader.py
demos/helloworld/helloworld.py
demos/s3server/s3server.py
demos/tcpecho/client.py
demos/tcpecho/server.py
demos/twitter/twitterdemo.py
demos/websocket/chatdemo.py
demos/webspider/webspider.py
docs/guide/coroutines.rst
docs/guide/intro.rst
docs/guide/queues.rst
docs/guide/running.rst
docs/guide/structure.rst
docs/index.rst
tornado/httpserver.py
tornado/ioloop.py
tornado/iostream.py
tornado/locks.py
tornado/queues.py
tornado/web.py
tornado/wsgi.py

index a16ddf3e7f7cdc262b8895ac8422302c791d2728..66e42b69a9db406b0e98a266ee5ff81f866d1266 100755 (executable)
@@ -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())
index c109b222a299639d2444eb7daee5598db21d326c..f96ac0515b9344ee4bccef2e1a54b93eb7bd10d6 100755 (executable)
@@ -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())
index dc054b9a9dddc366dff8e95c6f74833ffc8053d2..c23c024740df840a1ea89263f40d3da6d27eb3be 100755 (executable)
 # 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())
index 4f8e721e75979609a8c72f0dd26e7d018aeb76fc..360c8aa58df03eee4870a5e2e50b708b3ddb68c7 100755 (executable)
@@ -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())
index c63ec18911baebe7c48f43dbefe8cf819d13037a..67fce7ed047f0fef7b766a82aa9358c72ad2135b 100755 (executable)
@@ -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))
index 7f64e824051b6fb39c3d41cd035597dae0472857..e19b61bb164e52836a0fdda6255290f1acc9a81c 100755 (executable)
@@ -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())
index 11be1c2c2474a4c7ee9102c0113b74cd671cb179..197dc092cf70df5b5607ee7f9b44595278137065 100644 (file)
@@ -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))
index a2ead08bc82eb6c7493589e7d42048325b446e09..e39b5e7e953cab65459701caeac0edda6d7b189b 100755 (executable)
@@ -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())
index ebe683cf0fe5b0f20fda38ba74782b44dfdcdf8b..e7da4bff30aa297b616d4b3d8093df9468ed24d8 100755 (executable)
@@ -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())
index 4bd30225317dda3e79675f5f847c2f6f8a34df79..c459df3a35e6b9e9479bf6ceb5907d1a1b42080e 100755 (executable)
@@ -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())
index 1a7a3042c33b2f9907f4b74fc7eea33f591fd00f..594a7c5e0988b3214db570a7c1afd9aa5123f974 100755 (executable)
@@ -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())
index 16c3840fa7a9930515bf5cc4d7d8faf97e0f2b16..dcc0afa1eaa43d3d504e9a0262b2ab098ae9620d 100755 (executable)
@@ -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())
index 795631bf74a855f0144a6fe76a7c80c68932024d..811c084f1a3177a672b67012f9db79ab4e0972eb 100644 (file)
@@ -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
 <http://www.gevent.org>`_ 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`::
 
index 8d87ba62b2b9dfec95f1b3a554ad003afe9012f9..2684c3890e08b47db3e47f207e4e97d3013dd2e2 100644 (file)
@@ -9,7 +9,7 @@ can scale to tens of thousands of open connections, making it ideal for
 `WebSockets <http://en.wikipedia.org/wiki/WebSocket>`_, 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 <http://www.python.org/dev/peps/pep-3333/>`_.
index eb2f73f749874780db676b7688007325b82640eb..c8684e500a15a0263450141b2f9419cf7957b76f 100644 (file)
@@ -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
index f7fd26443b42af4995d678d5ea663fdce847f96c..47db460ad9911de4f32ea76e82e207ba39e0bf5a 100644 (file)
@@ -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
index 1c9a012a44ac54e01473a89aeac7250666bbdf09..42a8e63b43ee2c5080657f99d2a10068488224f1 100644 (file)
@@ -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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
index e8e50e8d942a33c38352d8f6225452a846ba30f1..f8d60b9ff97e65db0d1dfb01aa4f29c50097b43c 100644 (file)
@@ -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
 <https://github.com/tornadoweb/tornado>`_ as well.
 
-**Prerequisites**: Tornado 6.0 requires Python 3.7 or newer (See
-`Tornado 5.1 <https://www.tornadoweb.org/en/branch5.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 <http://pycurl.io/>`_ is used by the optional
   ``tornado.curl_httpclient``.  Libcurl version 7.22 or higher is required.
-* `Twisted <https://www.twistedmatrix.com/>`_ may be used with the classes in
-  `tornado.platform.twisted`.
 * `pycares <https://pypi.org/project/pycares/>`_ is an alternative
   non-blocking DNS resolver that can be used when threads are not
   appropriate.
index cd4a468120140e8f39a6ec256a18ce7a2ee27a50..b3e034b9ef3927d685af564216356b8ec6e11cd9 100644 (file)
@@ -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)
index 47b4804ad29b6d7ad7efc7263abcdbe410fffc02..6bd63a0f30c8c6e4a1558d63cc454020023d948e 100644 (file)
 
 """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.
index 7fee16dc34b3cf681ca0aa81cfec0a28ffcd3ae3..333d5366fc811e87a85cf8762cb4d3fb036d3f0f 100644 (file)
@@ -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:
index 29b6b41299d08fc7c68e08815bd8b49e89877eca..1bcec1b3af3dd0a6a552b6e066c8c7547c8a5ac4 100644 (file)
@@ -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
 
index 1e87f62e090c716cb6ac04009950dafe2b9ab535..32132e16fab33f1a148ba84a76541fcc80fbcbe6 100644 (file)
@@ -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::
 
index 2dab485a480007e972d8b8795dc9ecb50231c9f9..0604053ff4fd8628a8b75ddff135603dc242986c 100644 (file)
@@ -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.
 
index f9b37db2cdbc1cbe5909faf536982e7758bed0f0..c60f152d473e7a93c60a4f02c91d0d0c7d91e1d7 100644 (file)
@@ -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.