-
Notifications
You must be signed in to change notification settings - Fork 542
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
flask.copy_current_request_context
+gevent
+FlaskInstrumentor
cause improper span lifecycles
#1551
Comments
Even though I have 0.38b0 version, I see a similar error:
It happens during the teardown of the request, and it crashes it with a 500 status code. At least it would be nicer not to crash and log the error, since instrumentation is something that should not prevent the final user from getting the request data. This is not consistent, most of the requests return 200. Like the original post, I am using |
high-level problem
To create a new request-aware greenlet within a flask request, we copy the existing flask-req-ctx and push a new req-ctx onto the req-ctx-stack. When this greenlet+req-ctx ends, it tries to call the patched
_teardown_request
withinflask-instrumentor
, which throws errors because the greenlet doesn't "own" the span, and therefore shouldn't be allowed to end it.Describe your environment
I created a test repo with all of the source code for reproduction: https://github.com/matthewgrossman/otel-flask-repro. The important file is the app.py.
From trying a few different versions, it doesn't seem that the pinned deps matter too much, but here's a
requirements.txt
on macOS 13.0.1:If you'd prefer, I could turn this env into a docker image to make reproing easier for others. They key deps are
flask
,gevent
, andopentelemetry-instrumentation-flask
, and I've noticed this problem in every variation of those deps that I've tried.Steps to reproduce
$ python3.8 -m venv venv $ source venv/bin/activate $ pip install -r requirements.txt $ python -m flask run -p 8080
in other shell:
curl localhost:8080/test -H 'x-test-header: val`
In the running shell with the flask service, you should see an error like:
What is the expected behavior?
I wouldn't expect to see this error message.
What is the actual behavior?
This error message is caused by the greenlet's flask-req-ctx trying to call
_teardown_request
for the overall parent flask-req-ctx. This causes issues withcontextvars
and span ownership.When a new request comes into the flask application,
FlaskInstrumentor
set the current span+ctxmanager on theflask.request.environ
, seen here: flask-instrumentorWhen we create a new greenlet, we copy in the current
contextvars
context, as well as create a new flask-req-context based upon the existing flask-req-context. When this greenlet ends, the teardown gets called and attempts to callactivation.__exit__
. This causes the error seen in the above reproduction steps.Just a few lines above, there already is a guard clause meant to stop this exact problem: https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py#L434-L435.
However, this only ends up protecting us in situations where the ending-req-ctx has a fresh
flask.request.environ
, like with atest_request_context
(noted in the comments of the guard clause). When creating greenlets based on the existing flask request context, this guard clause doesn't sufficiently realize that this ending-req-ctx should also escape early, and not attempt to end the spanAdditional context
In the reproduction source code, I provided a workaround. When you create a new greenlet based on the current request context, first pop off the
_ENVIRON_ACTIVATION_KEY
to ensure the code does go through the guard clause (workaround source code). Commenting out the existingwrap_fn_in_req_context__broken
and commenting-in thewrap_fn_in_req_context__workaround
removes the error.However, I'm hoping there's some way to fix such that my code shouldn't need to pop a specific key from an environ before spawning greenlets. A couple options:
_teardown_request
, escape early if the currentflask-req-ctx
doesn't match the original req-ctx that created the span in the first placeThe text was updated successfully, but these errors were encountered: