flasky: db.session.commit() is not performed at the end of requests (prev "Confirmation of users")
Update - begins
db.session.commit() is not performed automatically at the end of each request, despite having SQLALCHEMY_COMMIT_ON_TEARDOWN = True in config.py.
This issue was noted after analysing why user confirmation was not working properly.
The app is hosted on heroku (can be reached here), is using Heroku Postgres and is based on this repository, which is forked from the original 17d.
Update - ends
The confirmation link does not update the current_user.confirmed to True and new registered users thus remain unconfirmed.
If the model column is changed by hand via shell, then the user is recognised as confirmed and the app behaves as expected.
Here are the results of my analysis. By examining the logs, the app behaviour is correct. I have the following lines from registration to confirmation of a new user:
- REGISTER, GET, with status 200
2015-08-07T08:50:09.059305+00:00 heroku[router]: at=info method=GET path="/auth/register" - REGISTER, POST, with status 302
2015-08-07T08:50:23.954973+00:00 heroku[router]: at=info method=POST path="/auth/register" - LOGIN, GET, with status 200
2015-08-07T08:50:24.181845+00:00 heroku[router]: at=info method=GET path="/auth/login" - (after clicking on the email confirmation link) CONFIRM, GET, with status 302
2015-08-07T08:50:35.040888+00:00 heroku[router]: at=info method=GET path="/auth/confirm/eyJleHAiOjE0Mzg5NDEwMjMsImlhdCI6MTQzODkzNzQyMywiYWxnIjoiSFMyNTYifQ.eyJjb25maXJtIjoxfQ.7hMYjfDmmwE_8c3kkw7jSdIRLWV-bPSJCCuYsy7bPeY" - (login is required here) LOGIN GET, with status 200
2015-08-07T08:50:35.259303+00:00 heroku[router]: at=info method=GET path="/auth/login?next=%2Fauth%2Fconfirm%2FeyJleHAiOjE0Mzg5NDEwMjMsImlhdCI6MTQzODkzNzQyMywiYWxnIjoiSFMyNTYifQ.eyJjb25maXJtIjoxfQ.7hMYjfDmmwE_8c3kkw7jSdIRLWV-bPSJCCuYsy7bPeY" - LOGIN POST, with status 302
2015-08-07T08:51:04.677048+00:00 heroku[router]: at=info method=POST path="/auth/login?next=%2Fauth%2Fconfirm%2FeyJleHAiOjE0Mzg5NDEwMjMsImlhdCI6MTQzODkzNzQyMywiYWxnIjoiSFMyNTYifQ.eyJjb25maXJtIjoxfQ.7hMYjfDmmwE_8c3kkw7jSdIRLWV-bPSJCCuYsy7bPeY" - CONFIRM GET, with status 302
2015-08-07T08:51:04.947112+00:00 heroku[router]: at=info method=GET path="/auth/confirm/eyJleHAiOjE0Mzg5NDEwMjMsImlhdCI6MTQzODkzNzQyMywiYWxnIjoiSFMyNTYifQ.eyJjb25maXJtIjoxfQ.7hMYjfDmmwE_8c3kkw7jSdIRLWV-bPSJCCuYsy7bPeY" - ROOT GET, with status 302
2015-08-07T08:51:05.156445+00:00 heroku[router]: at=info method=GET path="/" - UNCONFIRMED , with status 200
2015-08-07T08:51:05.868130+00:00 heroku[router]: at=info method=GET path="/auth/unconfirmed"
From what I understand, the auth.before_app_request is redirecting the user to the unconfirmed page.
However the flash message "You have confirmed your account. Thanks!" is displayed (see https://github.com/lucanaso/flasky/blob/master/app/auth/views.py#L72), therefore it looks like current_user.confirm returns True but it does not update the db (see https://github.com/lucanaso/flasky/blob/master/app/models.py#L147).
I have forked the project at commit 17d and after that I have committed only some files for deploying on heroku and requirements.
The app is running on heroku at: https://flasky-forky.herokuapp.com/ you can try it yourself by registering and trying to confirm.
What can be the cause of the user not being confirmed?
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 2
- Comments: 29 (9 by maintainers)
Okay, I think I have figured this thing out, turns out this is a Flask bug.
When the app context is popped at the end of the request, Flask calls all the
@teardown_appcontextdecorated callbacks. The auto commit feature of Flask-SQLAlchemy is implemented in one of these callbacks (code here). The teardown callbacks get one argument, which is an exception object if there was an error during the handling of the request.Unfortunately, the logic that the current version of Flask uses to determine if there was an exception is flawed. This has been fixed in master (see the PR) and will be included in the 1.0 release.
Now, why this happens with gunicorn v19 but not v18? The sync worker in version 19 throws an exception before it passes control to the Flask application. This exception is handled, so it does not cause any problem other than exposing the Flask bug, which thinks this gunicorn exception was thrown during the request. So the Flask-SQLAlchemy teardown callback is called with the gunicorn exception as an argument, and because of that Flask-SQLAlchemy thinks there was an error and does not commit the session.