Search code examples
pythontornado

How to listen for event of closed stdin in a tornado loop?


This is a follow-up question on handling the data in the socket. However, I am unable to capture the "stdin closed" event. Here's what I have now:

import sys
import tornado

from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler

class MainHandler(RequestHandler):
    def get(self):
        self.finish("foo")
application = Application([ (r"/", MainHandler), ])

@tornado.gen.coroutine
def close_callback(*args, **kwargs):
    print args, kwargs

if __name__ == "__main__":
    application.listen(8888)

    stdin = tornado.iostream.PipeIOStream(sys.stdin.fileno())
    stdin.set_close_callback(close_callback)

    IOLoop.instance().start()

And a test:

$ ./tornado_sockets.py   # expect to close stdin
<C-d>    # nothing happens

Another test:

$ echo expect_stdin_to_be_closed | ./tornado_sockets.py
# nothing happens

How can I listen for closing of stdin?


Solution

  • Quote sys.stdin does not close on ctrl-d:

    Ctrl+D has a strange effect. It doesn't close the input stream, but only causes a C-level fread() to return an empty result.

    So basically you need to assert read line with empty string. Some example without PipeIOStream:

    from tornado.ioloop import IOLoop
    import sys
    
    def on_stdin(fd, events):
        line = fd.readline()
        print("received: %s" % repr(line))
        if line == '':
            print('stdin ctr+d')
            sys.exit()
    
    if __name__ == "__main__":
        IOLoop.instance().add_handler(sys.stdin, on_stdin, IOLoop.READ)
        IOLoop.instance().start()
    

    With PipeIOStream it is pretty straightforward using read_until_close. Callback will be called on close or on Ctrl+D.

    import sys
    import tornado
    import tornado.iostream
    from tornado.ioloop import IOLoop
    from functools import partial
    
    def read(stdin, data):
        print(data)
        # below lines are unnecessary they are only for testing purposes
        stdin.close()
        IOLoop.instance().stop()
    
    if __name__ == "__main__":
        stdin = tornado.iostream.PipeIOStream(sys.stdin.fileno())
        stdin.read_until_close(callback=partial(read, stdin))
        IOLoop.instance().start()