Repository: tornadoweb/tornado

Issue:
Autoreload with asyncio event loop doesn't work
When running Tornado server in debug mode with autoreload and asyncio event loop the following exception raises: `RuntimeError: Cannot close a running event loop`.

Seems like autoreload hook tries to close event loop, but asyncio loop can't be closed without calling `stop()`. Code to reproduce and traceback are below.

Steps to reproduce:
1. run the app server
2. modify script with app server code to trigger auto reload

``` python
# save this to server.py and run as "python server.py"
import os
import asyncio
import tornado.ioloop
import tornado.httpserver
import tornado.web

from tornado.ioloop import IOLoop
IOLoop.configure('tornado.platform.asyncio.AsyncIOLoop')


class HomePage(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello!")

if __name__ == '__main__':
    port = 8000
    host = '127.0.0.1'
    app = tornado.web.Application(
        [
            (r'/', HomePage),
        ],
        debug=True)

    print("Running server at http://%s:%s" % (host, port))

    server = tornado.httpserver.HTTPServer(app)
    server.bind(port, host)
    server.start()
    tornado.ioloop.IOLoop.current().start()
```

The full traceback:

```
Traceback (most recent call last):
  File "/Users/user/.virtualenvs/project/src/tornado/tornado/ioloop.py", line 1039, in _run
    return self.callback()
  File "/Users/user/.virtualenvs/project/src/tornado/tornado/autoreload.py", line 190, in _reload_on_update
    _check_file(modify_times, path)
  File "/Users/user/.virtualenvs/project/src/tornado/tornado/autoreload.py", line 205, in _check_file
    _reload()
  File "/Users/user/.virtualenvs/project/src/tornado/tornado/autoreload.py", line 212, in _reload
    fn()
  File "/Users/user/.virtualenvs/project/src/tornado/tornado/platform/asyncio.py", line 63, in close
    self.asyncio_loop.close()
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/unix_events.py", line 55, in close
    super().close()
  File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/selector_events.py", line 94, in close
    raise RuntimeError("Cannot close a running event loop")
RuntimeError: Cannot close a running event loop 
```



Discussion:
And as I can see `stop()` method of asyncio loop doesn't stop immidiatelly, but places callback on event loop instead via `call_soon()`:
https://github.com/python/cpython/blob/master/Lib/asyncio/base_events.py#L345

So we can't just add `stop()` call line before `close()`.

I think the right thing to do here is to just swallow any exceptions from `IOLoop.close()`. It's not really necessary; it's just there to guard against file descriptors that did not have the `cloexec` flag set. Whether the `close` succeeds or not, it's better to proceed to the `execv` than to abort the autoreload process.

Hi Ben! Thank you for the idea. Well, may be I'm trying to simplify things, but shouldn't we just swallow all exceptions on auto-reload? I've created a pull request implementing such simple logic, it really worked perfectly. Is it ok? :)

Hi @rudyryk, @bdarnell. It still happening in tornado 4.4.2. Any workaround?
The snippet bellow seems to work, however. (python 3.5.2, tornado 4.4.2)

The only difference is that instead of initializing the event loop using this form: http://www.tornadoweb.org/en/stable/asyncio.html#tornado.platform.asyncio.AsyncIOLoop

It uses this form:
http://www.tornadoweb.org/en/stable/asyncio.html#tornado.platform.asyncio.AsyncIOMainLoop

Anyway, it's kinda hard to tell only from the docs in which circumstances each form is usefull, as the examples seem to be trying to achieve the exact same result.

```python
# save this to server.py and run as "python server.py"
import os
import asyncio
import tornado.ioloop
import tornado.httpserver
import tornado.platform.asyncio
import tornado.web

tornado.platform.asyncio.AsyncIOMainLoop().install()


class HomePage(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello!")

if __name__ == '__main__':
    port = 8000
    host = '127.0.0.1'
    app = tornado.web.Application(
        [
            (r'/', HomePage),
        ],
        debug=True)

    print("Running server at http://%s:%s" % (host, port))

    server = tornado.httpserver.HTTPServer(app)
    server.bind(port, host)
    server.start()
    asyncio.get_event_loop().run_forever()
```



Resolving patch:
diff --git a/tornado/autoreload.py b/tornado/autoreload.py
--- a/tornado/autoreload.py
+++ b/tornado/autoreload.py
@@ -103,10 +103,6 @@
 # os.execv is broken on Windows and can't properly parse command line
 # arguments and executable name if they contain whitespaces. subprocess
 # fixes that behavior.
-# This distinction is also important because when we use execv, we want to
-# close the IOLoop and all its file descriptors, to guard against any
-# file descriptors that were not set CLOEXEC. When execv is not available,
-# we must not close the IOLoop because we want the process to exit cleanly.
 _has_execv = sys.platform != 'win32'
 
 _watched_files = set()
@@ -127,8 +123,6 @@ def start(io_loop=None, check_time=500):
     _io_loops[io_loop] = True
     if len(_io_loops) > 1:
         gen_log.warning("tornado.autoreload started more than once in the same process")
-    if _has_execv:
-        add_reload_hook(functools.partial(io_loop.close, all_fds=True))
     modify_times = {}
     callback = functools.partial(_reload_on_update, modify_times)
     scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)


Test patch: