the very basic multi instances code fails on python 3.5 env. It works with python2.7 and 3.6.4.
Python 3.5.2
tornado 5.0.1
Flask 0.12.2
Flask-Cors 3.0.3
gevent 1.2.2
greenlet 0.4.13
gunicorn 19.7.1
lxml 4.2.0
from flask import Flask, request, jsonify
from flask_cors import CORS, cross_origin
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
app = Flask(__name__)
PROD = True
CORS(app)
@app.route('/api/check/alive', methods=['GET'])
def handle_api_alive():
resp = jsonify(dict(status="OK"))
return resp
if __name__ == '__main__':
# start HTTP server
http_server = HTTPServer(WSGIContainer(app))
http_server.bind(9999)
http_server.start(num_processes=2)
IOLoop.instance().start()
part of error log:
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test_server.py", line 32, in <module>
http_server.start(num_processes=2)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 223, in start
self.add_sockets(sockets)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 159, in add_sockets
sock, self._handle_connection)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/netutil.py", line 258, in add_accept_handler
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/platform/asyncio.py", line 60, in add_handler
fd, self._handle_events, fd, IOLoop.READ)
File "/usr/lib/python3.5/asyncio/selector_events.py", line 237, in add_reader
(handle, None))
File "/usr/lib/python3.5/selectors.py", line 411, in register
self._epoll.register(key.fd, epoll_events)
FileExistsError: [Errno 17] File exists
child 1 (pid 23316) exited with status 1, restarting
Traceback (most recent call last):
File "/usr/lib/python3.5/asyncio/selector_events.py", line 234, in add_reader
key = self._selector.get_key(fd)
File "/usr/lib/python3.5/selectors.py", line 191, in get_key
raise KeyError("{!r} is not registered".format(fileobj)) from None
KeyError: '6 is not registered'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test_server.py", line 32, in <module>
http_server.start(num_processes=2)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 223, in start
self.add_sockets(sockets)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 159, in add_sockets
sock, self._handle_connection)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/netutil.py", line 258, in add_accept_handler
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/platform/asyncio.py", line 60, in add_handler
fd, self._handle_events, fd, IOLoop.READ)
File "/usr/lib/python3.5/asyncio/selector_events.py", line 237, in add_reader
(handle, None))
File "/usr/lib/python3.5/selectors.py", line 411, in register
self._epoll.register(key.fd, epoll_events)
FileExistsError: [Errno 17] File exists
child 1 (pid 23317) exited with status 1, restarting
Traceback (most recent call last):
File "/usr/lib/python3.5/asyncio/selector_events.py", line 234, in add_reader
key = self._selector.get_key(fd)
File "/usr/lib/python3.5/selectors.py", line 191, in get_key
raise KeyError("{!r} is not registered".format(fileobj)) from None
KeyError: '6 is not registered'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test_server.py", line 32, in <module>
http_server.start(num_processes=2)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 223, in start
self.add_sockets(sockets)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/tcpserver.py", line 159, in add_sockets
sock, self._handle_connection)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/netutil.py", line 258, in add_accept_handler
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
File "/home/jin/pyenv/py3DL/lib/python3.5/site-packages/tornado/platform/asyncio.py", line 60, in add_handler
fd, self._handle_events, fd, IOLoop.READ)
File "/usr/lib/python3.5/asyncio/selector_events.py", line 237, in add_reader
(handle, None))
File "/usr/lib/python3.5/selectors.py", line 411, in register
self._epoll.register(key.fd, epoll_events)
FileExistsError: [Errno 17] File exists
child 1 (pid 23318) exited with status 1, restarting
Traceback (most recent call last):
File "/usr/lib/python3.5/asyncio/selector_events.py", line 234, in add_reader
key = self._selector.get_key(fd)
File "/usr/lib/python3.5/selectors.py", line 191, in get_key
raise KeyError("{!r} is not registered".format(fileobj)) from None
KeyError: '6 is not registered'
During handling of the above exception, another exception occurred:
more specifically, the example code works under the following env:
flask==0.12.2
flask-cors==3.0.3
tornado==4.5.3
Same problem here, using Tornado鈥檚 web framework and HTTP server, tornado==5.0.2 and Python 3.6.4
def start_application(app, loop, port):
"""Start a tornado application."""
parse_command_line()
_app = app()
_server = HTTPServer(_app)
_server.bind(port)
_server.start(2)
logging.info(f"Running on port {port}")
tornado.ioloop.IOLoop.current().start()
[I 180620 15:40:08 process:128] Starting 2 processes
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 264, in _add_reader
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 264, in _add_reader
key = self._selector.get_key(fd)
key = self._selector.get_key(fd)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 191, in get_key
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 191, in get_key
raise KeyError("{!r} is not registered".format(fileobj)) from None
raise KeyError("{!r} is not registered".format(fileobj)) from None
KeyError: '8 is not registered'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/mio/Work/h3c/face_service/web/app.py", line 35, in <module>
KeyError: '8 is not registered'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/mio/Work/h3c/face_service/web/app.py", line 35, in <module>
run()
File "/Users/mio/Work/h3c/face_service/web/app.py", line 31, in run
run()
File "/Users/mio/Work/h3c/face_service/web/app.py", line 31, in run
start_application(Configurer, loop, port=APP_PORT)
File "/Users/mio/Work/h3c/face_service/web/utils/helpers.py", line 41, in start_application
start_application(Configurer, loop, port=APP_PORT)
File "/Users/mio/Work/h3c/face_service/web/utils/helpers.py", line 41, in start_application
_server.start(2)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/tcpserver.py", line 223, in start
_server.start(2)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/tcpserver.py", line 223, in start
self.add_sockets(sockets)
self.add_sockets(sockets)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/tcpserver.py", line 159, in add_sockets
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/tcpserver.py", line 159, in add_sockets
sock, self._handle_connection)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/netutil.py", line 258, in add_accept_handler
sock, self._handle_connection)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/netutil.py", line 258, in add_accept_handler
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/platform/asyncio.py", line 75, in add_handler
io_loop.add_handler(sock, accept_handler, IOLoop.READ)
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/platform/asyncio.py", line 75, in add_handler
fd, self._handle_events, fd, IOLoop.READ)
fd, self._handle_events, fd, IOLoop.READ)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 337, in add_reader
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 337, in add_reader
return self._add_reader(fd, callback, *args)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 267, in _add_reader
return self._add_reader(fd, callback, *args)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py", line 267, in _add_reader
(handle, None))
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 541, in register
(handle, None))
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 541, in register
self._kqueue.control([kev], 0, 0)
OSError: [Errno 9] Bad file descriptor
self._kqueue.control([kev], 0, 0)
OSError: [Errno 9] Bad file descriptor
[W 180620 15:40:09 process:163] child 1 (pid 3255) exited with status 1, restarting
I think the issue in these line of code:
http_server.bind(9999)
http_server.start(num_processes=2)
Both the process you create are binding on the same port.
http_server.listen(9999)
http_server.start(num_processes=2)
@ajayuranakar http://www.tornadoweb.org/en/stable/guide/running.html#processes-and-ports
def main():
app = make_app()
server = tornado.httpserver.HTTPServer(app)
server.bind(8888)
server.start(0) # forks one process per cpu
IOLoop.current().start()
BTW, if use listen:
[I 180626 13:38:54 helpers:45] Running on port 8888
IOLoop.current().start()
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/platform/asyncio.py", line 127, in start
self.asyncio_loop.run_forever()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 421, in run_forever
self._run_once()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 1395, in _run_once
event_list = self._selector.select(timeout)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 577, in select
kev_list = self._kqueue.control(None, max_ev, timeout)
OSError: [Errno 9] Bad file descriptor
Traceback (most recent call last):
File "app.py", line 35, in <module>
run()
File "app.py", line 31, in run
start_application(Configurer, loop, port=APP_PORT)
File "/Users/mio/Work/h3c/face_service/web/utils/helpers.py", line 52, in start_application
IOLoop.current().start()
File "/Users/mio/Work/h3c/face_service/env/lib/python3.6/site-packages/tornado/platform/asyncio.py", line 127, in start
self.asyncio_loop.run_forever()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 421, in run_forever
self._run_once()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 1395, in _run_once
event_list = self._selector.select(timeout)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/selectors.py", line 577, in select
kev_list = self._kqueue.control(None, max_ev, timeout)
OSError: [Errno 9] Bad file descriptor
[W 180626 13:38:54 process:163] child 1 (pid 5396) exited with status 1, restarting
[W 180626 13:38:54 process:163] child 0 (pid 5395) exited with status 1, restarting
@ajayuranakar on Linux, listen works, i tried on my centos7 server and debian docker container on macOS ; on macOS(10.13.5), listen don't work.
Thank you for your help.
The original bind() way is pretty much as is suggested in the docs. I think this is a tricky low-level macOS kqueue related bug.
actually I tried above in ubuntu 16.04, not mac OS
@chrjxj yeah, the problem is that the bind() way which is suggested in the docs does not work on Linux or macOS; the listen way works on Linux, does not work on macOS.
This is a result of the move to asyncio. asyncio is not as multiprocess-friendly as the old Tornado implementation was. I highly recommend using a process manager like supervisord and a separate load balancer instead of Tornado's multi-process mode.
If you want to continue using Tornado's multi-process mode, you must be very careful not to do anything that might initialize the IOLoop before the fork. In particular, this may mean using the "advanced" multi-process instructions so that the only thing that happens before the fork is tornado.netutil.bind_sockets.
It may not be possible to use the non-advanced versions of multi-process mode any more in Python 3. If so, maybe these modes should be removed in Tornado 6.0.
I see what happened, at least in @chrjxj 's case. In Tornado 5.0, simply importing tornado.wsgi is enough to initialize the IOLoop and break bind/start.
@MioYvo, I'm not sure if you're seeing the same thing. If you're seeing this error without importing tornado.wsgi, you're probably doing something else that initializes the IOLoop too soon. The most common things that do this are creating an Application with debug=True and creating an AsyncHTTPClient. This is not allowed in older versions of Tornado either, although the move to asyncio in Tornado 5.0 has made it harder to give good error messages.
@bdarnell Thanks for helping. As you said, yes I created an unused IOLoop instance using unused = IOLoop.current(), and an Application with debug=True.
In the source code about Application.__init__, debug=True will set autoreload=True if you don't set autoreload=False in the application kwargs. And the autoreload=True call tornado.autoreload.start(), this method initializes the IOLoop as you said "too soon".
So I remove the unused IOLoop instance, and create an Application with debug=True and autoreload=False, it works fine. I've used nginx and docker for load balancing even though it's a bit more complicated than this multi-process mode.
So autoreload cannot be used with mullti process? Is that a bug or should the server start not ignore this option if it's not supported? Can it be supported?
It is documented that autoreload is not compatible with multiple processes. That's probably a good thing since during development you typically do not need more than a single process and autoreload is a debugging feature that is best not to use in production.
It would be a lot of work to support autoreload with multiple processes. It would be nice to at least give an error if you try to combine the two modes but even that is a little tricky because there are so many permutations (sometimes you create the app first, sometimes you fork first).
I do not expect to invest any time into improving multiprocess mode. Now that we have SO_REUSEPORT I think I'd recommend that over multiprocess mode in all cases.
Most helpful comment
This is a result of the move to
asyncio.asynciois not as multiprocess-friendly as the old Tornado implementation was. I highly recommend using a process manager like supervisord and a separate load balancer instead of Tornado's multi-process mode.If you want to continue using Tornado's multi-process mode, you must be very careful not to do anything that might initialize the IOLoop before the fork. In particular, this may mean using the "advanced" multi-process instructions so that the only thing that happens before the fork is
tornado.netutil.bind_sockets.It may not be possible to use the non-advanced versions of multi-process mode any more in Python 3. If so, maybe these modes should be removed in Tornado 6.0.