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

Gunicorn RecursionError with gevent and requests in python 3.6.2 #1559

Closed
jltchiu opened this issue Jul 31, 2017 · 15 comments
Closed

Gunicorn RecursionError with gevent and requests in python 3.6.2 #1559

jltchiu opened this issue Jul 31, 2017 · 15 comments

Comments

@jltchiu
Copy link

jltchiu commented Jul 31, 2017

I have a simple flask script that uses requests to make http requests to third party web service. The way I run the script in gunicorn is

gunicorn abc:APP -b 0.0.0.0:8080 -w 4 -k gevent --timeout 30 --preload

However, after I upgrade the code to python 3.6.2, I can still run the server, but whenever the webserver received a request, it shows

RecursionError: maximum recursion depth exceeded while calling a Python object

on every worker, and the server seems are still running. When I change the running command to

gunicorn abc:APP -b 0.0.0.0:8080 -w 4 --timeout 30 --preload

It all works again. So is there any issue with gunicorn's async worker and requests in python 3.6.2? Is there a way to fix this?

@berkerpeksag
Copy link
Collaborator

Thank you for the report. Unfortunately, we need to see the full traceback and a minimal reproducer in order to track down the problem.

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

[2017-07-31 16:38:13 -0400] [3120] [INFO] Booting worker with pid: 3120
[2017-07-31 16:38:18,448] ERROR in app: Exception on /structQA [POST]
Traceback (most recent call last):
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/framework_flask.py", line 96, in _nr_wrapper_Flask_handle_exception_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/framework_flask.py", line 35, in _nr_wrapper_handler_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/gqa/gqa.py", line 114, in nlqa
    bing_output = gqa.bing.call(text, ipaddress, latitude, longitude, timezone)
  File "/Users/justinchiu/Work/srv-gqa-ws/gqa/bing.py", line 129, in call
    params=data, headers=headers)  # change to v6
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/api.py", line 72, in get
    return request('get', url, params=params, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/api/external_trace.py", line 37, in dynamic_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/sessions.py", line 502, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/api/external_trace.py", line 37, in dynamic_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/sessions.py", line 612, in send
    r = adapter.send(request, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
    timeout=timeout
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 601, in urlopen
    chunked=chunked)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_urllib3.py", line 16, in _nr_wrapper_make_request_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_urllib3.py", line 16, in _nr_wrapper_make_request_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 346, in _make_request
    self._validate_conn(conn)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 850, in _validate_conn
    conn.connect()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_httplib.py", line 13, in httplib_connect_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connection.py", line 314, in connect
    cert_reqs=resolve_cert_reqs(self.cert_reqs),
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 269, in create_urllib3_context
    context.options |= options
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  [Previous line repeated 299 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
[2017-07-31 16:38:18 -0400] [3119] [ERROR] Exception on /structQA [POST]
Traceback (most recent call last):
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/framework_flask.py", line 96, in _nr_wrapper_Flask_handle_exception_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/framework_flask.py", line 35, in _nr_wrapper_handler_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/gqa/gqa.py", line 114, in nlqa
    bing_output = gqa.bing.call(text, ipaddress, latitude, longitude, timezone)
  File "/Users/justinchiu/Work/srv-gqa-ws/gqa/bing.py", line 129, in call
    params=data, headers=headers)  # change to v6
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/api.py", line 72, in get
    return request('get', url, params=params, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/api/external_trace.py", line 37, in dynamic_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/sessions.py", line 502, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/api/external_trace.py", line 37, in dynamic_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/sessions.py", line 612, in send
    r = adapter.send(request, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
    timeout=timeout
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 601, in urlopen
    chunked=chunked)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_urllib3.py", line 16, in _nr_wrapper_make_request_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_urllib3.py", line 16, in _nr_wrapper_make_request_
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 346, in _make_request
    self._validate_conn(conn)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connectionpool.py", line 850, in _validate_conn
    conn.connect()
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/newrelic/hooks/external_httplib.py", line 13, in httplib_connect_wrapper
    return wrapped(*args, **kwargs)
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/connection.py", line 314, in connect
    cert_reqs=resolve_cert_reqs(self.cert_reqs),
  File "/Users/justinchiu/Work/srv-gqa-ws/venv/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 269, in create_urllib3_context
    context.options |= options
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 459, in options
    super(SSLContext, SSLContext).options.__set__(self, value)
  [Previous line repeated 299 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

Is this enough? This happens when gunicorn is running, and I use another python script to make a request to the server. My command of running gunicorn is NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program gunicorn gqa.gqa:APP -b 0.0.0.0:8080 -w 4 -k gevent --timeout 30 --preload

@jamadden
Copy link
Collaborator

jamadden commented Aug 1, 2017

That looks like gevent/gevent#941, ie, incorrect monkey-patching order.

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

Is there a way to change the monkey patch order in the gunicorn setup? Right now in my code I've never type any "monkey-patch-all", I just use -k gevent to do all the gevent related operation.

@jamadden
Copy link
Collaborator

jamadden commented Aug 1, 2017

Speaking only for myself, I have found the order in which gunicorn preload and monkey-patches to be incompatible. Instead, we use a wrapper script; in principle it's something like:

import gevent.monkey; gevent.monkey.patch_all()
import gunicorn
gunicorn.main()

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

It seems like I develop in a very different (and probably worse) way than how you did it. In my code, my code looks like a standard flask app, and in my code there's no import gevent.monkey;, gevent.monkey.patch_all(), import gunicorn or gunicorn.main(). I use gunicorn to load the app for the server to run which is probably the gqa.gqa:APP part in my gunicorn execution command. Will adding the import line you shared fix this issue?

@jamadden
Copy link
Collaborator

jamadden commented Aug 1, 2017

The way you develop has nothing to do with this. This is simply about deployment. I deploy with (roughly) the script I shared because gunicorn and --preload do not implement my use case correctly in production. You can develop how you like; you might (or might not) find that this approach fixes your deployment issues. (Again this is nothing about development or app frameworks or the like; neither gunicorn nor gevent express an opinion about that so long as the app is WSGI.)

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

I see, would you mind suggest me a deployment script that I can use to run my code that can fix this monkey patch order issue? Originally I thought the deployment script would be a bash script that's similar to the line I share above but it seems like you are also using python for this, then I don't know how can you feed in those parameters (or even simulate the New relic line...?). I just have a gqa.py in the gqa folder that I want to use gunicorn to run it.

@jamadden
Copy link
Collaborator

jamadden commented Aug 1, 2017

It's both trivial and depends on your app. Our app uses Pyramid, so we simply have a setuptools console_script that we invoke instead of pyramid. Roughly like this:

# Note that we must not import *anything* before the patch
from nti.monkey import patch_nti_pserve_on_import
patch_nti_pserve_on_import.patch() # gevent.monkey.patch_all()

import sys
from pkg_resources import load_entry_point, get_distribution

def main():
    sys.exit(
        load_entry_point('pyramid', 'console_scripts', 'pserve')()
    )


if __name__ == '__main__':
    sys.exit(main())

@jamadden
Copy link
Collaborator

jamadden commented Aug 1, 2017

I should add, we absolutely depend on preload, so we had no alternative.

@jltchiu
Copy link
Author

jltchiu commented Aug 1, 2017

Thanks, your suggestions are extremely helpful (Although I am still not sure how to use my own deployment script as I have no experience on that). But will probably looking into load_entry_point as that seems to be similar to the gunicorn command.

@jltchiu jltchiu closed this as completed Aug 1, 2017
@jltchiu jltchiu reopened this Aug 1, 2017
@berkerpeksag
Copy link
Collaborator

Thanks for the details. I think this is more or less a duplicate of #1056.

@berkerpeksag berkerpeksag marked this as a duplicate of #1056 Aug 1, 2017
@tilgovi
Copy link
Collaborator

tilgovi commented Aug 7, 2017

I also opened #1566 is the place to discuss changing this behavior. @jamadden I would love your thoughts and examples in there!

Do we still need this issue open?

@berkerpeksag
Copy link
Collaborator

We can close this one now.

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

No branches or pull requests

4 participants