I want to know how to make my Tornado video streaming code block less so that I don't impact other handers in my web app.
I have some legacy code that I use in Tornado to stream incoming jpegs and show them as video on an html page. My handler looks like this. It's worked well enough for a few years, but lately I've noticed that it is actually a big resource hog. Whenever something calls this handler all other requests slow down dramatically.
class VideoAPI(tornado.web.RequestHandler):
'''
Serves a MJPEG of the images posted from the vehicle.
'''
@tornado.gen.coroutine
def get(self):
host = self.get_argument("host")
port = int(self.get_argument("port"))
ioloop = tornado.ioloop.IOLoop.current()
self.set_header("Content-type", "multipart/x-mixed-replace;boundary=--frame")
self.served_image_timestamp = time.time()
my_boundary = "--frame"
for frame in live_video_stream(host,port=port):
interval = .1
if self.served_image_timestamp + interval < time.time():
img = cv2.imencode('.jpg', frame)[1].tostring()
self.write(my_boundary)
self.write("Content-type: image/jpeg\r\n")
self.write("Content-length: %s\r\n\r\n" % len(img))
# Serve the image
self.write(img)
self.served_image_timestamp = time.time()
yield tornado.gen.Task(self.flush)
else:
yield tornado.gen.Task(ioloop.add_timeout, ioloop.time() + interval)
Elsewhere in my app I have code that's somewhat computationally expensive that I can run without hogging the CPU too much. It follows this pattern:
class Example(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(3)
@tornado.concurrent.run_on_executor
def do_some_stuff(self):
"""Do something that's not asyncio-friendly, and computationally slow"""
return something
@tornado.gen.coroutine
def get(self):
result = yield self.do_some_stuff()
self.write(result)
Assuming this second example is the most efficient pattern for streaming video, how can I convert the video handler to look more like this? I inherited the video handler code from someone else, and I don't really know how to replicate the loop part. For example, should I leave the loop in the function that runs on the executor? Should I keep it in the @tornado.gen.coroutine decorated method? I should do something entirely different?