Repository: benoitc/gunicorn

Issue:
tornado 5.0+ not compatible with gunicorn
gunicorn version: 19.7.1
tornado: 5.0

failed with:

```
$ ./bin/gunicorn -w 1 -t 300 -k tornado web:app -b 0.0.0.0:9003
[2018-03-20 14:17:29 +0000] [28600] [INFO] Starting gunicorn 19.7.1
[2018-03-20 14:17:29 +0000] [28600] [INFO] Listening at: http://0.0.0.0:9003 (28600)
[2018-03-20 14:17:29 +0000] [28600] [INFO] Using worker: tornado
[2018-03-20 14:17:29 +0000] [28605] [INFO] Booting worker with pid: 28605
[2018-03-20 14:17:29 +0000] [28605] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/arbiter.py", line 578, in spawn_worker
    worker.init_process()
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/workers/base.py", line 131, in init_process
    self.run()
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/workers/gtornado.py", line 70, in run
    PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start()
TypeError: __init__() got an unexpected keyword argument 'io_loop'
[2018-03-20 14:17:29 +0000] [28605] [INFO] Worker exiting (pid: 28605)
[2018-03-20 14:17:29 +0000] [28608] [INFO] Booting worker with pid: 28608
[2018-03-20 14:17:29 +0000] [28608] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/arbiter.py", line 578, in spawn_worker
    worker.init_process()
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/workers/base.py", line 131, in init_process
    self.run()
  File "/data/data/pkgs/nginxgw/nginxgw-45-838b8953/.buildout/eggs/gunicorn-19.7.1-py2.py3-none-any.ovo/gunicorn/workers/gtornado.py", line 70, in run
    PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start()
TypeError: __init__() got an unexpected keyword argument 'io_loop'
```
according to the release note of Tornado 5.0:

```
io_loop arguments to many Tornado functions have been removed. Use IOLoop.current() instead of passing IOLoop objects explicitly.
```
http://www.tornadoweb.org/en/stable/releases/v5.0.0.html#backwards-compatibility-notes


Discussion:
I'm seeing this too.
If anyone can make a PR I'd be happy to review it.

Resolving patch:
diff --git a/gunicorn/workers/gtornado.py b/gunicorn/workers/gtornado.py
--- a/gunicorn/workers/gtornado.py
+++ b/gunicorn/workers/gtornado.py
@@ -8,9 +8,10 @@
 import sys
 
 try:
-    import tornado.web
+    import tornado
 except ImportError:
     raise RuntimeError("You need tornado installed to use this worker.")
+import tornado.web
 import tornado.httpserver
 from tornado.ioloop import IOLoop, PeriodicCallback
 from tornado.wsgi import WSGIContainer
@@ -18,6 +19,11 @@
 from gunicorn import __version__ as gversion
 
 
+# `io_loop` arguments to many Tornado functions have been removed in Tornado 5.0
+# <http://www.tornadoweb.org/en/stable/releases/v5.0.0.html#backwards-compatibility-notes>
+IOLOOP_PARAMETER_REMOVED = tornado.version_info >= (5, 0, 0)
+
+
 class TornadoWorker(Worker):
 
     @classmethod
@@ -67,8 +73,12 @@ def run(self):
         self.ioloop = IOLoop.instance()
         self.alive = True
         self.server_alive = False
-        PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start()
-        PeriodicCallback(self.heartbeat, 1000, io_loop=self.ioloop).start()
+        if IOLOOP_PARAMETER_REMOVED:
+            PeriodicCallback(self.watchdog, 1000).start()
+            PeriodicCallback(self.heartbeat, 1000).start()
+        else:
+            PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start()
+            PeriodicCallback(self.heartbeat, 1000, io_loop=self.ioloop).start()
 
         # Assume the app is a WSGI callable if its not an
         # instance of tornado.web.Application or is an
@@ -109,10 +119,16 @@ def on_close(instance, server_conn):
             # options
             del _ssl_opt["do_handshake_on_connect"]
             del _ssl_opt["suppress_ragged_eofs"]
-            server = server_class(app, io_loop=self.ioloop,
-                                  ssl_options=_ssl_opt)
+            if IOLOOP_PARAMETER_REMOVED:
+                server = server_class(app, ssl_options=_ssl_opt)
+            else:
+                server = server_class(app, io_loop=self.ioloop,
+                                      ssl_options=_ssl_opt)
         else:
-            server = server_class(app, io_loop=self.ioloop)
+            if IOLOOP_PARAMETER_REMOVED:
+                server = server_class(app)
+            else:
+                server = server_class(app, io_loop=self.ioloop)
 
         self.server = server
         self.server_alive = True


Test patch: