Description
I noticed a concerning potential race condition today while using flask-dance via the Flask local dev server. Here's the setup:
- I have a script polling my app's "get session" endpoint, using a session that is not logged in. This endpoint invokes
flask_dance.contrib.github.authorized
in order to check if the session should be pulled from GitHub. - While that script is running, I log in to my app via GitHub on a web browser.
- 1 in 3 times, the web browser login session will be duplicated to the script's session. This is bad because the script has done nothing to activate the session.
I tried to trace the code paths within flask-dance and I think that the use of @cached_property
on either the blueprint's session.token
or on the blueprint's session
itself is part of the problem. Even though flask_dance.contrib.github
is a LocalProxy to g.flask_dance_github
(which is equivalent to github_bp.session
) that isn't sufficient for separation between threads -- @cached_property
's cache is associated with the blueprint, which is global across all threads/requests.
Basically, what I think is happening in my case is:
- The valid login process begins to handle the
authorized()
blueprint endpoint - The OAuth access token is saved to
github_bp.session.token
- Before the endpoint handler returns, my script calls my "get session" endpoint
- A new thread begins to handle that parallel request, which calls
github.authorized
- This checks for
github_bp.session.token
, which is cached by@cached_property
- It returns successfully, allowing a subsequent call to
github.get("/user")
to fetch the full session info - The script then gets the transferred session
- A new thread begins to handle that parallel request, which calls
- Finally, the original
authorized()
blueprint endpoint concludes, and the session is set as expected to the original request
I've done a little testing of my app that is deployed in a test environment using gunicorn (which separates requests into processes). I haven't been able to reproduce the problem there yet, but because it's stochastic, I haven't fully ruled it out yet. Regardless, though, this seems like a concern as login sessions should never leak across threads.
Lastly, I tried a quick patch to take out the use of @cached_property
for session
and session.token
, and it seemed to fix the issue -- I was unable to trigger the bad behavior even when the logs showed the possibility of a race condition.
Should we consider removing @cached_property
or replace it with something that uses a thread-local cache?