Library / SDK
tornadoweb/tornado avatar
tornadoweb/tornado

Tornado's Hello world example blocks forever and skips the asynchronous features

Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.

22,164 stars5,607 forksPythonApache-2.0

At a glance

What is it?
Tornado is a Python web framework and asynchronous networking library from FriendFeed, aimed at long polling and WebSocket work where each user holds a connection open. The repository is a small, carefully conditioned build: the C extension is CPython only, the wheel matrix starts at cp311, and there are no GitHub releases to pin against.
Who is it for?
Pick Tornado when every user is expected to hold a connection open, since that is the case the project claims for tens of thousands of open connections through non-blocking I/O, and pick something else when you only need request and response handlers.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • tornadoweb/tornado22,164 ★ · -16
Star history
Repository2026-09-212026-11-13
tornadoweb/tornado22.2k22.2k

-16 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The one complete program in the README uses none of the asynchronous features

The example is a RequestHandler subclass with a get method that writes a string, an Application constructed from a single regex route pair, an async main that calls app.listen(8888), and asyncio.run to start the loop. The text then says the example does not use any of Tornado's asynchronous features and points to the simple chat room under the stable branch for that. So what you copy is the handler and routing skeleton, not the concurrency model.

What the example cannot show is why the project claims it scales to tens of thousands of open connections, because nothing in it overlaps any I/O. And the process holds itself open on a wait for an asyncio Event with no shutdown branch anywhere in the snippet. For a reader that means the starting point does not tell you how to stop the server, how many connections it serves at once, or where blocking work is meant to go, so all three answers have to come from the documentation site rather than from the code you were handed.

python
import asyncio
import tornado

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

async def main():
    app = make_app()
    app.listen(8888)
    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

app.listen takes a bare port and the Application takes only a route list

app.listen(8888) is the whole server configuration on display. No bind address, no certificate, no worker count, no static file root and no settings object appear in the snippet, because the Application is built from a single argument, the route table. That minimalism does reveal something real about the shape of the API: the handler table and the server object are separate, so the listener is its own call rather than something buried inside the application constructor.

What it cannot do is hand you a production configuration. A reader who copies app.listen(8888) into a container gets one port, defaults chosen for you, and a process lifetime tied to an event loop that nothing in the example ever stops. The documentation site is where the settings for TLS, proxy headers and static files live, and the README does not document them. The same gap applies to routing: exactly one pattern is demonstrated, with no second example of named groups, subdomains or version prefixes, so the parts of the routing layer you will actually need are the parts you must look up.

TORNADO_EXTENSION=0 is the only value that drops the C extension

setup.py compiles an extension module only when two conditions hold together. The interpreter must report itself as CPython, and the environment variable TORNADO_EXTENSION must not equal the string "0". Any other value, including an empty string or a 1, keeps the extension, so the switch looks boolean but is a single string comparison. When the limited API is usable the build defines Py_LIMITED_API as 0x030b0000 and sets bdist_wheel_options to py_limited_api cp311, which is the mechanism that lets one wheel serve several interpreter versions.

What this cannot do is give PyPy users the same acceleration, and the source states the reason in a comment: the extension builds and works on pypy as well, although pypy's jit produces equivalent performance. The consequence for a reader is asymmetric. On CPython there is an off switch in setup.py, so a build that misbehaves in native code has a way out. On PyPy the variable buys nothing at all, because the extension is only built on CPython.

Free-threaded builds take a different macro path and produce a different wheel

One line decides the extension's ABI. can_use_limited_api is the negation of the Py_GIL_DISABLED build configuration variable, so on a free-threaded build that variable is set, can_use_limited_api is false, and both define_macros and bdist_wheel_options stay empty. On every other CPython build the limited API macro is applied instead.

So there is no single wheel shape covering both kinds of interpreter, and an install into a free-threaded environment receives the extension without the limited API contract attached to it. What this cannot do is quietly fall back to pure Python: on CPython the extension is built either way, unless the environment variable stops it first. For a reader the consequence is practical. A lockfile resolved from a free-threaded interpreter does not describe the same artifact as one resolved from a regular interpreter, and the version list carries a free-threaded target of its own, so the split is planned rather than an oversight.

The wheel matrix is one generated line running cp311 through cp315

The build section of pyproject.toml holds the whole matrix in a single value, cp311* cp312* cp313* cp314* cp315* cp314t*, with the test command `python -m tornado.test` applied to each, and the macOS section narrowing archs to universal2. Those strings are produced by cog blocks rather than typed by hand, and the same generator writes the black target version, so the supported interpreter range is defined once and pushed into more than one file.

What the matrix cannot do is cover an interpreter older than 3.11 or newer than the list, and the README offers no source distribution route for anyone outside it. A reader on an older interpreter finds nothing in this file pointing at an answer, and the practical consequence is that 3.11 is a floor rather than a preference. Anyone doing their own builds still needs to know that the file they must satisfy is pyproject.toml with setuptools as the backend, while setup.py holds the version lookup and the C extension logic.

On emulated architectures the test command shrinks to one module

cibuildwheel carries overrides for riscv64, ppc64le and s390x that replace the test command with `python -m tornado.test tornado.test.websocket_test`. The comment beside it gives the reasoning: on emulated platforms only the websocket tests run, because those are the ones that exercise the C extension and are the ones most prone to architecture related problems, and because the full suite is very slow under emulation. A TODO in the same block says it would be better to run the full suite on a single Python version per architecture and only the websocket tests on the others.

What that override cannot do is catch a regression in the HTTP or template paths on those three machines, so architecture coverage for this project is narrower than the package on purpose. The consequence for a reader on one of them is that passing a build tells you much less than it would on x86, and running `python -m tornado.test` locally is the only way to see the full result before you trust an install.

There are no GitHub releases, and the version is read out of the package source

The repository publishes no GitHub releases at all, and the default branch is master. Version discovery happens at build time instead: setup.py opens tornado/__init__.py, executes the file and pulls the version attribute out of the resulting namespace, which is how the number reaches the package metadata. The file also carries a 2009 Facebook copyright header, consistent with the project's origin at FriendFeed, and reads its long description straight from README.rst with an rst content type. Meanwhile the chat demo link in the README points at a different branch, /tree/stable/demos/chat, so the branch serving demos is not the default branch.

What this cannot do is give you a page of versions to pin against from the hosting service, and there is no changelog file at the top level next to CONTRIBUTING.md and SECURITY.md. For a reader the consequence is that a version pin has to come from your own resolver or from the package index, and the last push to master on 2026-10-26 tells you the branch is active without telling you which release your install contains.

Black and flake8 both gate the same tree, and tox.ini is the real recipe

The root holds a .flake8 file, a black section in pyproject.toml targeting py311, .coveragerc, codecov.yml, tox.ini, runtests.sh, setup.cfg and MANIFEST.in, alongside a demos directory, a docs directory and a maint directory for maintainer scripts. requirements.txt pins the whole toolchain: black, flake8, mypy, sphinx, tox and pip-tools, and the header records that the file was autogenerated by pip-compile with Python 3.11.

What that arrangement cannot do is settle a disagreement between the two style tools for you, and the README names no single command a new contributor should run before opening a change. The consequence for a reader who wants to work on the project is that the local recipe has to be recovered from tox.ini or runtests.sh rather than from the front page, and the pinned dev set is tied to the interpreter that ran pip-compile, so a contributor on a newer Python regenerates a different one before running anything.

Editorial conclusion

Pick Tornado when every user is expected to hold a connection open, since that is the case the project claims for tens of thousands of open connections through non-blocking I/O, and pick something else when you only need request and response handlers. Before you commit, read your version out of tornado/__init__.py yourself, because the repository publishes no GitHub releases to pin against, and run the full `python -m tornado.test` if you build on ppc64le, riscv64 or s390x, where the project's own gate drops to the websocket module.

Frequently asked questions

What is called a tornado in the context of this repository?

In this repository a tornado is the Python web framework and asynchronous networking library originally developed at FriendFeed, licensed under Apache-2.0. Nothing in the source tree relates to the weather phenomenon, and the project documentation lives at tornadoweb.org.

how to use tornado mop

Nothing in this repository relates to a mop. What the project ships is a Hello world web application built on tornado.web.RequestHandler, plus a simple chat room demo under the stable branch that demonstrates its asynchronous features.

how to install tornado mod minecraft

There is no Minecraft mod here. This is a Python package built through setuptools and setup.py, its documentation is at tornadoweb.org, and the README itself contains no installation command.

how to use tornado archive

The framework has no archive function. What it targets is long lived connections: it is described as ideal for long polling, WebSockets and other applications that require a long-lived connection to each user.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. Project website
  4. README
  5. tornadoweb/tornado on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tornadoweb-tornado.svg)](https://hysenlabs.com/projects/tornadoweb-tornado)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/tornadoweb-tornado.svg)](https://hysenlabs.com/projects/tornadoweb-tornado)

Was this useful?