Search code examples
websockettornado

infinite loop cannot be connected websocket server


A client connect websocket and calls tail_log method, and new client can't connect

How to solve this problem

def on_message(self, message):

    def tail_log(user,ip,port,cmd,log_path,url):
        cmd = "/usr/bin/ssh -p {port} {user}@{ipaddr} {command} {logpath}" \
            .format(user=user, ipaddr=ip, port=port, command=cmd, logpath=log_path)
        f = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)

        while True:
            line = f.stdout.readline().strip()
            if line == '':
                self.write_message('failed')
                break
            self.write_message(line)

    tail_log(user=SSH_USER,ip=IP_ADDR,cmd=CMD,port=SSH_PORT,log_path=LOG_PATH,url=SOCKET_URL)

Solution

  • Your infinite loop must yield control back to Tornado's event loop, either by executing a yield, await, or by returning from the tail_log function. Since your infinite loop does not yield control to the event loop, the event loop can never process any more events, including new websocket connections.

    Try using Tornado's own process module to read from your subprocess's stdout asynchronously. Something like this:

    import tornado.ioloop
    import tornado.process
    import tornado.web
    import tornado.websocket
    
    
    class TailHandler(tornado.websocket.WebSocketHandler):
        def open(self):
            self.write_message(u"Tailing....")
            self.p = tornado.process.Subprocess(
                "tail -f log.log",
                stdout=tornado.process.Subprocess.STREAM,
                stderr=tornado.process.Subprocess.STREAM,
                shell=True)
    
            tornado.ioloop.IOLoop.current().add_callback(
                lambda: self.tail(self.p.stdout))
    
            tornado.ioloop.IOLoop.current().add_callback(
                lambda: self.tail(self.p.stderr))
    
            self.p.set_exit_callback(self.close)
    
        async def tail(self, stream):
            try:
                while True:
                    line = await stream.read_until(b'\n')
                    if line:
                        self.write_message(line.decode('utf-8'))
                    else:
                        # "tail" exited.
                        return
            except tornado.iostream.StreamClosedError:
                # Subprocess killed.
                pass
            finally:
                self.close()
    
        def on_close(self):
            # Client disconnected, kill the subprocess.
            self.p.proc.kill()
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("""<html><head><script>
            var ws = new WebSocket("ws://localhost:8888/tail");
    ws.onmessage = function (evt) {
       document.write('<p>' + evt.data + '</p>');
    };</script></head></html>""")
    
    
    def make_app():
        return tornado.web.Application([
            (r"/", MainHandler),
            (r"/tail", TailHandler),
        ])
    
    
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
    

    If you're not on Python 3.5 yet, substitute @gen.coroutine for "async def", substitute "yield" for "await", and substitute "break" for "return".