Search code examples
pythonmultithreadingwebsockettornadohttpserver

Why creating a thread inside Tornado server Post method gives Runtime error?


I have written a Tornado HTTP server and trying to start a thread as soon as the API request http://localhost:8889/img is hit. I am getting a RuntimeError exception after some time or immediately. Not able to understand this behavior. Please explain me.

import tornado.ioloop
import tornado.web
import threading
import time
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
class img(tornado.web.RequestHandler):
    def post(self):
        t_img = threading.Thread(target=self.sendimg,)
        t_img.start()

    def sendimg(self):
        while True:
            self.write({"name":"vinay"})
            print("sent")
            time.sleep(0.5)

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

if __name__ == "__main__":
    app = make_app()
    app = tornado.httpserver.HTTPServer(app)
    app.listen(8889)
    tornado.ioloop.IOLoop.current().start()

Error:

  self.write({"name":"vinay"})
    File "/Users/vinaykp/anaconda3/lib/python3.6/site-
    packages/tornado/web.py", line 708, in write
      raise RuntimeError("Cannot write() after finish()")
    RuntimeError: Cannot write() after finish()

Solution

  • I'm not an expert in concurrency and Tornado, but it seems that main problem is with lifecycle of request object. See, when you're passing img class to tornado.web.Application:

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

    Tornado receives reference to the class, create its object and executes proper method. As request is done and client get response from the server - it's nothing more to do. You cannot write to client once request is done and in "finished" state, because it's completely pointless, as at the moment client doesn't expect nothing from you.

    If you think of HTTP design, it's how this protocol works. It's not ongoing connection, but rather stateless, request-response communication that has its start and end, which from the other hand determines request lifecycle.

    You can of course start new Thread when your server was hit with request, like:

    def get(self):
        self.write("Hello, world!")
        tf = threading.Thread(target=self.thread_func)
        tf.start()
    
    def thread_func(self):
        while True:
            print("ok\n")
            time.sleep(0.5)
    

    And new Thread will be created every time you hit proper endpoint with request, but you cannot really write to self anymore after request is done.