Tornado error: cannot switch to a different thread - python

A request comes to tornado's GET handler of a web app.
From the GET function, a blocking_task function is called. This blocking_task function has #run_on_executor decorator.
But this execution fails.
Could you please help on this. It seems that motor db is not able to execute the thread.
import time
from concurrent.futures import ThreadPoolExecutor
from tornado import gen, web
from tornado.concurrent import run_on_executor
from tornado.ioloop import IOLoop
import argparse
from common.config import APIConfig
import sys
import os
import motor
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config-file", dest='config_file',
help="Config file location")
args = parser.parse_args()
CONF = APIConfig().parse(args.config_file)
client = motor.MotorClient(CONF.mongo_url)
db = client[CONF.mongo_dbname]
class Handler(web.RequestHandler):
executor = ThreadPoolExecutor(10)
def initialize(self):
""" Prepares the database for the entire class """
self.db = self.settings["db"]
#gen.coroutine
def get(self):
self.blocking_task()
#run_on_executor
def blocking_task(self):
mongo_dict = self.db.test_cases.find_one({"name": "Ping"})
if __name__ == "__main__":
app = web.Application([
(r"/", Handler),
],
db=db,
debug=CONF.api_debug_on,
)
app.listen(8888)
IOLoop.current().start()
> ERROR:tornado.application:Exception in callback <functools.partial
> object at 0x7f72dfbe48e8> Traceback (most recent call last): File
> "/usr/local/lib/python2.7/dist-packages/tornado-4.3-py2.7-linux-x86_64.egg/tornado/ioloop.py",
> line 600, in _run_callback
> ret = callback() File "/usr/local/lib/python2.7/dist-packages/tornado-4.3-py2.7-linux-x86_64.egg/tornado/stack_context.py",
> line 275, in null_wrapper
> return fn(*args, **kwargs) File "/usr/local/lib/python2.7/dist-packages/motor-0.5-py2.7.egg/motor/frameworks/tornado.py",
> line 231, in callback
> child_gr.switch(future.result()) error: cannot switch to a different thread
Could you please help on this.

From docs
IOLoop and executor to be used are determined by the io_loop and
executor attributes of self. To use different attributes, pass keyword
arguments to the decorator
You have to provide a init threadpoolexecutor:
import time
from concurrent.futures import ThreadPoolExecutor
from tornado import gen, web
from tornado.concurrent import run_on_executor
from tornado.ioloop import IOLoop
class Handler(web.RequestHandler):
executor = ThreadPoolExecutor(10)
#gen.coroutine
def get(self):
self.blocking_task()
#run_on_executor
def blocking_task(self):
time.sleep(10)
if __name__ == "__main__":
app = web.Application([
(r"/", Handler),
])
app.listen(8888)
IOLoop.current().start()
By default run_on_executor search for threadpool in executor attribute, unless you pass other explicitly, e.g.
_thread_pool = ThreadPoolExecutor(10)
#run_on_executor(executor='_thread_pool')
def blocking_task(self):
pass
edit
Basically IOLoop should be used in single-threaded env (you can run separate IOLoop on each thread, but it is not your case). To communicate with IOLoop you should use add_callback, that is the only thread safe function.
You can use like:
#run_on_executor
def blocking_task(self):
IOLoop.instance().add_callback(some_update)
#gen.coroutine
def some_update():
db.test_cases.update({ "name": "abc" }, { "$set": { "status" : "xyz" } } )
But do you really need threading at all. What is the purpose of the separate thread if you schedule update on main - IOLoop's thread.

Finally following code works, Thank you #kwarunek
Also added parameters to the callback function.
import time
from concurrent.futures import ThreadPoolExecutor
from tornado import gen, web
from tornado.concurrent import run_on_executor
from tornado.ioloop import IOLoop
import argparse
from common.config import APIConfig
import sys
import os
import motor
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config-file", dest='config_file',
help="Config file location")
args = parser.parse_args()
CONF = APIConfig().parse(args.config_file)
client = motor.MotorClient(CONF.mongo_url)
db = client[CONF.mongo_dbname]
class Handler(web.RequestHandler):
executor = ThreadPoolExecutor(10)
def initialize(self):
""" Prepares the database for the entire class """
self.db = self.settings["db"]
#gen.coroutine
def get(self):
self.blocking_task("Ping", "Void-R")
#run_on_executor
def blocking_task(self, name, status):
IOLoop.instance().add_callback(callback=lambda: self.some_update(name, status))
#gen.coroutine
def some_update(self, name, status):
mongo_dict = yield self.db.test_cases.find_one({"name": name})
self.db.test_cases.update({ "name": name }, { "$set": { "status" : status } } )
if __name__ == "__main__":
app = web.Application([
(r"/", Handler),
],
db=db,
debug=CONF.api_debug_on,
)
app.listen(8888)
IOLoop.current().start()

Motor is a non-blocking library, designed to be used from the single IOLoop thread. You would use a ThreadPoolExecutor with a blocking library like PyMongo, but you must not use other threads with Motor.
Instead, you should call the Motor methods with yield directly:
#gen.coroutine
def get(self):
yield self.non_blocking_task()
#gen.coroutine
def non_blocking_task(self):
motor_dict = yield self.db.test_cases.find_one({"name": "Ping"})
Also note that if you do use #run_on_executor with a blocking library like PyMongo, the decorator makes blocking functions non-blocking, so the decorated function must be called with yield.

Related

Tornado web server returns "There is no current event loop in thread" when started in a thread

I want to start a web server in Tornado and then carry on with other business. I have not been able to discover how to start the web server in a thread. Here is what I tried:
import asyncio
import sys
from time import sleep
import tornado.ioloop
import tornado.web
port = 28130
context = 'tornado'
statemgr_url = 'http://localhost:%d/%s' %(port,context)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return
if __name__ == "__main__":
# tornado.ioloop.IOLoop.current().start()
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
def asyncListen():
app = tornado.web.Application([(r"/%s" %context, MainHandler),])
app.listen(port) # THROWS exception
AsyncIOMainLoop().install()
asyncio.get_event_loop().run_forever()
# asyncio.set_event_loop(asyncio.new_event_loop())
from threading import Thread
t = Thread(target=asyncListen) # , args=(statemgr_url, handler))
t.start()
sleep(1)
At the line marked THROWS Exception the following happens:
File "/usr/local/Cellar/python/3.7.4_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
So how can the web server be started / run in the non-main thread?

Tornado timeout block all services

I create a simple test app to check timeout in tornado
import tornado.ioloop
import tornado.web
class LoopHandler(tornado.web.RequestHandler):
def get(self):
while (True):
print ("in loop")
self.write("Loop, Handler")
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/loop", LoopHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Then I call http://localhost:8888/loop the endpoint never response because the infinite loop the problems is that http://localhost:8888/ not responding either. the question is why this happened and how can solve this?
EDIT
Update code that solve the problemn
import tornado.ioloop
import tornado.web
#unblock
class LoopHandler(tornado.web.RequestHandler):
def get(self):
while (True):
print ("in loop")
return "Loop, Handler"
#unblock
class MainHandler(tornado.web.RequestHandler):
def get(self):
return "Hello, world"
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/loop", LoopHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
#unblock.py
import tornado.web
import tornado.ioloop
from concurrent.futures import ThreadPoolExecutor
from functools import partial, wraps
EXECUTOR = ThreadPoolExecutor(max_workers=4)
def unblock(f):
#tornado.web.asynchronous
#wraps(f)
def wrapper(*args, **kwargs):
self = args[0]
def callback(future):
self.write(future.result())
self.finish()
EXECUTOR.submit(
partial(f, *args, **kwargs)
).add_done_callback(
lambda future: tornado.ioloop.IOLoop.instance().add_callback(
partial(callback, future)))
return wrapper
These are basics of async programming. To point you in the right direction take a look at the reactor pattern and especially at the event loop.
The reactor pattern is one implementation technique of event-driven
architecture. In simple terms, it uses a single threaded event loop
blocking on resource-emitting events and dispatches them to
corresponding handlers and callbacks.
Both functions LoopHandler and MainHandler are processed in the same event loop, therefore MainHandler gets queued but never executed since the event loop is busy executing LoopHandler.
One of the challenges (at least for me) in async programming is to be careful about blocking calls like database operations with for example SQLAlchemy, file operations, expensive calculations, etc. There are some interesting approaches using thread pools to solve this but you won't need them to get you started.
Ah, and in case you stumbling over the first sentence of the wiki article I have linked take a look here to understand the difference between parallel and concurrent. It helped me a lot.

python tornado - how to return real-time data

I am using the tornado library in python. I have a queue where data gets added in. I have to keep connection open so that when client requests I send out items from queue. Here is a simple implementation of mine. The problem I face is when I add new elements to queue, I don't see it being it returned. Infact, I don't see any code executed below IOLoop.current().start() line.
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url,asynchronous
from Queue import Queue
import json
q=Queue()
q.put("one")
q.put("two")
class HelloHandler(RequestHandler):
def get(self):
data=q.get()
self.write(data)
def make_app():
return Application([
url(r"/", HelloHandler),
])
def main():
app = make_app()
app.listen(8888)
IOLoop.current().start() # stops here
time.sleep(2)
q.put("three")
print q
if __name__=='__main__':
main()
first time on this :
http://localhost:8888/
returns "one"
second time on this:
http://localhost:8888/
return "two"
Third time on this"
http://localhost:8888/
blocking
The problem you have is that calling IOLoop.current().start() transfers control to Tornado. It loops until IOLoop.stop() is called.
If you need to do something after the IOLoop has started, then you can use one of the callbacks. For example, here is your code modified to use IOLoop.call_later(). You could also use IOLoop.add_timeout() if you are using an earlier (<4.0) version of Tornado.
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url,asynchronous
from Queue import Queue
import json
q=Queue()
q.put("one")
q.put("two")
class HelloHandler(RequestHandler):
def get(self):
data=q.get()
self.write(data)
def make_app():
return Application([
url(r"/", HelloHandler),
])
def main():
app = make_app()
app.listen(8888)
IOLoop.current().call_later(2, q.put, "three")
IOLoop.current().start()
if __name__=='__main__':
main()

Why tornado doesn't go concurrently?

The following is my test code. I am using Python2.7, with futures installed using:
pip install futures
The following is my demo code:
import tornado.ioloop
import tornado.web
from tornado.gen import coroutine, Task
from tornado.concurrent import Future
import time
class MainHandler(tornado.web.RequestHandler):
#coroutine
def get(self):
print "in"
res = yield Task(self._work)
self.write(res)
def _work(self, callback):
time.sleep(10)
callback("hello world!")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
This code should go concurrently, shouldn't it? However, it just doesn't.
I tested with Firefox and IE. I think I made some mistakes. It would be nice for you to point out my error.
only one request at a time(http://localhost:8888/:
import tornado.ioloop
import tornado.web
from tornado.gen import coroutine, Return, Task
from tornado.process import Subprocess
from tornado.concurrent import Future
from threading import Thread
import time
#coroutine
def async_sleep(timeout):
""" Sleep without blocking the IOLoop. """
yield Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + timeout)
class MainHandler(tornado.web.RequestHandler):
#coroutine
def get(self):
print "in"
res = yield self._work()
self.write(res)
#coroutine
def _work(self):
yield async_sleep(5)
raise Return("hello world!")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
ioloop=tornado.ioloop.IOLoop.instance()
Thread(target=ioloop.start).start()
Since you indicated in the comments you want to run a subprocess via tornado, here's an example illustrating how to do that. I also fixed a logic error where you were creating a Task when calling _work, which wasn't going to work the way you intended:
import tornado.ioloop
import tornado.web
from tornado.gen import coroutine, Return
from tornado.process import Subprocess
from tornado.concurrent import Future
class MainHandler(tornado.web.RequestHandler):
#coroutine
def get(self):
print "in"
res = yield self._work()
self.write(res)
#coroutine
def _work(self):
p = Subprocess(['sleep', '10'])
f = Future()
p.set_exit_callback(f.set_result)
yield f
raise Return("hello world!")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
As you can see, I made _work a coroutine, and then used tornado's built-in Subprocess class to execute a command. I created a Future object, and instructed the Subprocess to call Future.set_result(return_code_of_subprocess) when it completed. Then I called yield on the Future instance. That makes the code wait until the subprocess completes, without blocking the I/O loop.
The time.sleep in your code is a blocking method.
You need tornado.gen.sleep(a non-blocking method, new in tornado 4.1) here to instead.

How to make tornado call itself asynchronously

Below is the simple hello world app. How can I have tornado call '/' in two threads with a 1 sec sleep. Thus the page will be called 2 times per second. I will need later to extend to a redis call but want to start simple now since I am new to this logic. I need to build a web page that is called repeatably asynchronously.
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8880)
tornado.ioloop.IOLoop.instance().start()
If you are new to tornado, check out tornadogist. They have a lot of useful code snippets. Here is one adapted to your need:
from time import sleep
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, asynchronous, RequestHandler
from multiprocessing.pool import ThreadPool
# 2 threads
_workers = ThreadPool(2)
def run_background(func, callback, args=(), kwds={}):
def _callback(result):
IOLoop.instance().add_callback(lambda: callback(result))
_workers.apply_async(func, args, kwds, _callback)
# blocking task like querying to MySQL
def blocking_task(n):
sleep(n)
return n
class MainHandler(RequestHandler):
#asynchronous
def get(self):
run_background(blocking_task, self.on_complete, (1,))
def on_complete(self, res):
self.write("Test {0}<br/>".format(res))
self.finish()
HTTPServer(Application([("/", MainHandler)],debug=True)).listen(8888)
IOLoop.instance().start()

Categories

Resources