Spawn a thread and communicate between the thread and tornado app?

Open
#2,802 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
python
Domain
backend

Research direction

Start with tornado/queues.py and the IOLoop.add_callback behavior linked in the issue, then compare it with run_in_executor and the standard queue API. Determine whether a Tornado-native synchronous-to-asynchronous communication pattern is feasible and what interface it should expose; done requires a decided approach, documented behavior, and tests for bidirectional communication.

Written by the indexing model from the issue text.

Description

docs

My Tornado app needs to have bidirectional communicate with the worker thread spawned by .run_in_executor. Starting a worker thread takes time. It needs to initialise an expensive network connection before it can do the work.

Is there a native Tornado approach to solve this problem?

Approach 1: tornado.queue

I've looked into tornado.queues module, but I couldn't figure out how to use it to communicate between sync and async worlds. Documentation suggests wrapping operations with IOLoop.add_callback:

https://github.com/tornadoweb/tornado/blob/18b653cf93fe870cbc630fb48595e1b92fc1e06c/tornado/queues.py#L21-L24

However, .add_callback is useful only for .put and .put_nowait methods, and not for .get and .get_nowait. There's no way to get the result back from .add_callback.

Approach 2: standard queue library

Next option is queue from Python standard library. It should work, but all queue operation needs to be wrapped with .run_in_executor. Seems excessive and not pretty.

Approach 3: janus library

There's janus - library for mixed sync/async queue. Looks good, but it's not small and it's an extra dependency to maintain.

Approach 4: Tornado native approach?

Is there Tornado-native approach? For example something similar to trio.from_thread.run. It's the same as IOLoop.add_callback, but Trio's run also sends back the result of the operation. Thus run can safely wrap Trio queue methods (called "memory channel" in Trio). Example from Trio documentation:

def thread_fn(receive_from_trio, send_to_trio):
    while True:
        # Since we're in a thread, we can't call methods on Trio
        # objects directly -- so we use trio.from_thread to call them.
        try:
            request = trio.from_thread.run(receive_from_trio.receive)
        except trio.EndOfChannel:
            trio.from_thread.run(send_to_trio.aclose)
            return
        else:
            response = request + 1
            trio.from_thread.run(send_to_trio.send, response)
Dominant language
Python
Stars
22.2k
Forks
5.6k
Avg merge
3h 42m
Merged PRs (30d)
16

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tornadoweb/tornado

All issues in tornadoweb/tornado

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.