Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

only monkey patch required modules/functions (fix errors such as 'module' object has no attribute 'epoll') #413

Closed
cs01 opened this issue Feb 22, 2017 · 8 comments
Assignees
Labels

Comments

@cs01
Copy link

cs01 commented Feb 22, 2017

I am working on a project that uses Flask-SocketIO and runs the select.epoll() function in a websocket callback. When debug=False, there is no issue and everything works using both eventlet and gevent. Also, when debug=True and mode=eventlet there is no issue.

However, when debug=True and mode=gevent I get the exception (at the bottom) from the following psuedo code. I'm not sure if this is a Flask-SocketIO issue or a gevent issue, so feel free to redirect me if I'm in the wrong place.

from flask import Flask
from flask_socketio import SocketIO
import select

app = Flask('name')
socketio = SocketIO(async_mode='gevent')
socketio.run(app, debug=True)

@socketio.on('connect')
def client_connected():
  select.epoll()

The actual code in question is here for the flask code and here for the select.epoll() call.

  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/gevent/pywsgi.py", line 936, in handle_one_response
    self.run_application()
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/gevent/pywsgi.py", line 909, in run_application
    self.result = self.application(self.environ, self.start_response)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/flask/app.py", line 1994, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/flask_socketio/__init__.py", line 42, in __call__
    start_response)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/engineio/middleware.py", line 47, in __call__
    return self.engineio_app.handle_request(environ, start_response)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/socketio/server.py", line 353, in handle_request
    return self.eio.handle_request(environ, start_response)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/engineio/server.py", line 278, in handle_request
    socket.handle_post_request(environ)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/engineio/socket.py", line 103, in handle_post_request
    self.receive(pkt)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/engineio/socket.py", line 55, in receive
    async=self.server.async_handlers)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/engineio/server.py", line 378, in _trigger_event
    return self.handlers[event](*args)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/socketio/server.py", line 509, in _handle_eio_message
    self._handle_connect(sid, pkt.namespace)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/socketio/server.py", line 414, in _handle_connect
    self.environ[sid]) is False:
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/socketio/server.py", line 481, in _trigger_event
    return self.handlers[namespace][event](*args)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/flask_socketio/__init__.py", line 224, in _handler
    *args)
  File "/home/.virtualenvs/p3/lib/python3.4/site-packages/flask_socketio/__init__.py", line 589, in _handle_event
    ret = handler()
  File "gdbgui/backend.py", line 64, in client_connected
    _gdb[request.sid] = GdbController(gdb_path=GDB_PATH, gdb_args=['-nx', '--interpreter=mi2'])
  File "/home/git/pygdbmi/pygdbmi/gdbcontroller.py", line 33, in __init__
    self.epoll = select.epoll()
AttributeError: 'module' object has no attribute 'epoll'
@miguelgrinberg
Copy link
Owner

My understanding is that select.epoll() is not a greenlet friendly function at this time. Neither gevent nor eventlet implement it. In the cases that you don't get the crash, you are simply using Python's original epoll function, which is blocking, so it won't allow other greenlets in the system to run concurrently.

The case in which you get the error is caused by the monkey patching of gevent. In that case, gevent replaces the select package with its own greenlet aware version, and the replacement lacks the epoll function.

@cs01
Copy link
Author

cs01 commented Feb 22, 2017

Cool thanks for the quick reply.

I found why this is a problem in debug mode. If debug is true, Flask-SocketIO uses the reloader, which calls this code in SocketIO.__init__()

if use_reloader:
    # monkey patching is required by the reloader
    from gevent import monkey
    monkey.patch_all()

    def run_server():
        self.wsgi_server.serve_forever()

    run_with_reloader(run_server, extra_files=extra_files)
else:
    self.wsgi_server.serve_forever()

The patch_all method has aggressive set to True by default which removes a bunch of functions in select. If it were false, it wouldn't remove select.epoll. Is there any way some options to the patch_all() function can be passed to SocketIO or is that just a bad idea in general? Should I just be using gevent.select.poll instead?

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Feb 22, 2017

@cs01 sorry if I wasn't 100% clear before. The epoll function is incompatible with eventlet and gevent, it cannot be used. Theselect.epoll.poll() call is blocking, which means that it will prevent multi-tasking. The patch_all() call that you found replaces all the incompatible functions in the Python standard library with compatible versions. Neither gevent nor eventlet implemented an epoll replacement, so unfortunately you don't have an immediate solution. You will either need to stop using epoll, or stop using eventlet/gevent.

If you use one of the other alternatives, make sure you apply the patching for your application, or else import from the eventlet or gevent package instead of the Python standard library.

It looks like gevent has an implementation of select.poll(). With eventlet, the best you can do is select.select().

@cs01
Copy link
Author

cs01 commented Feb 22, 2017

I guess I was a little confused because I am currently using Python's select.epoll with no problems whatsoever unless I'm using gevent in debug mode. Perhaps it's because I have a timeout of 0 that it's not blocking anything.

I'll add a check to see if select.epoll has been removed and use one of the functions in gevent.select. Thanks again.

@miguelgrinberg
Copy link
Owner

Perhaps it's because I have a timeout of 0 that it's not blocking anything.

Yes. Maybe if you restrict yourself to never calling blocking functions you'll be okay, I did not consider that.

@cs01
Copy link
Author

cs01 commented Feb 22, 2017

In that case would you be open to adding an argument to SocketIO.__init__() to turn off the aggressive monkey patching?

@miguelgrinberg
Copy link
Owner

Yes, I'll look into it. I need to find out exactly what function(s) the reloader needs, and only patch those.

@miguelgrinberg miguelgrinberg self-assigned this Feb 22, 2017
@cs01 cs01 changed the title 'module' object has no attribute 'epoll' with gevent, but works fine with eventlet only monkey patch required modules/functions (fix errors such as 'module' object has no attribute 'epoll') Mar 9, 2017
@miguelgrinberg
Copy link
Owner

Fixed in 55d02d9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants