Ответ 1
Изменить 2016-06-05:
PR, который решает эту проблему, был объединен 26 мая 2016 года.
Изменить 2015-04-13:
Тайна решена!
TL; DR: Будьте абсолютно уверены, что ваши функции прерывания будут успешными, используя рецепт обертывания в редакции 2014-12-11!
Начиналось новое задание, также использующее Flask, и эта проблема возникла снова, прежде чем я применил рецепт обертывания. Поэтому я пересмотрел этот вопрос и, наконец, выяснил, что произошло.
Как я и думал, Flask подталкивает новый контекст запроса в стек контекста запроса каждый раз, когда новый запрос идет по строке. Это используется для поддержки локальных запросов-локальных, например сеанса.
В колбе также есть понятие контекста "приложения", которое отделено от контекста запроса. Он предназначен для поддержки таких функций, как тестирование и доступ к CLI, где HTTP не происходит. Я знал это, и я также знал, что там, где Flask-SQLA ставит свои сеансы БД.
Во время нормальной работы как запрос, так и контекст приложения помещаются в начале запроса и выталкиваются в конце.
Однако оказывается, что при нажатии контекста запроса контекст запроса проверяет, существует ли существующий контекст приложения, и если он присутствует, он не нажимает новый!
Итак, если контекст приложения не выставляется в конце запроса из-за повышения функции разрыва, он не только будет оставаться навсегда, но даже не будет иметь контекста приложения, расположенного поверх него.
Это также объясняет некоторые магии, которые я не понял в наших интеграционных тестах. Вы можете ВСТАВИТЬ некоторые тестовые данные, затем выполнить некоторые запросы, и эти запросы смогут получить доступ к этим данным, несмотря на то, что вы не совершаете. Это возможно только после того, как запрос имеет новый контекст запроса, но повторно использует контекст тестового приложения, поэтому он повторно использует существующее соединение с БД. Так что это действительно особенность, а не ошибка.
Тем не менее, это означает, что вам нужно быть абсолютно уверенными в том, что ваши функции разрыва преуспевают, используя что-то вроде обертки функции teardown. Это хорошая идея, даже без этой функции, чтобы избежать утечки памяти и соединений БД, но особенно важно в свете этих находок. По этой причине я отправлю документы PR для Flask. (Вот он)
Изменить 2014-12-11:
В результате мы создали следующий код (в нашем приложении factory), который обертывает каждую функцию teardown, чтобы убедиться, что он регистрирует исключение и не увеличивает его. Это гарантирует, что контекст приложения всегда успешно всплывает. Очевидно, это должно произойти после того, как вы убедитесь, что все функции разрывов зарегистрированы.
# Flask specifies that teardown functions should not raise.
# However, they might not have their own error handling,
# so we wrap them here to log any errors and prevent errors from
# propagating.
def wrap_teardown_func(teardown_func):
@wraps(teardown_func)
def log_teardown_error(*args, **kwargs):
try:
teardown_func(*args, **kwargs)
except Exception as exc:
app.logger.exception(exc)
return log_teardown_error
if app.teardown_request_funcs:
for bp, func_list in app.teardown_request_funcs.items():
for i, func in enumerate(func_list):
app.teardown_request_funcs[bp][i] = wrap_teardown_func(func)
if app.teardown_appcontext_funcs:
for i, func in enumerate(app.teardown_appcontext_funcs):
app.teardown_appcontext_funcs[i] = wrap_teardown_func(func)
Изменить 2014-09-19:
Хорошо, получается --reload-on-exception
не очень хорошая идея, если: 1.) вы используете несколько потоков и 2.) прекращение запроса на поток может вызвать проблемы. Я думал, что uWSGI будет ждать завершения всех запросов для этого работника, например, функция uWSGI "изящная перезагрузка", но, похоже, это не так. У нас возникли проблемы с тем, что нить приобретет блокировку Dople в Memcached, а затем прекратится, когда uWSGI перезагрузит рабочего из-за исключения в другом потоке, то есть блокировка никогда не будет выпущена.
Удаление SQLALCHEMY_COMMIT_ON_TEARDOWN
решило часть нашей проблемы, хотя мы по-прежнему получаем случайные ошибки во время отключения приложения во время session.remove()
. По-видимому, это вызвано проблемой SQLAlchemy 3043, которая была исправлена в версии 0.9.5, поэтому, надеюсь, обновление до 0.9.5 позволит нам полагаться при работе с контекстом приложения всегда работает.
Оригинал:
Как это произошло в первую очередь, это открытый вопрос, но я нашел способ его предотвратить: uWSGI --reload-on-exception
.
Ошибка обработки ошибок в приложении Flask должна быть улавлива-лась почти так, поэтому она может обслуживать пользовательский ответ об ошибке, а это означает, что только самые неожиданные исключения должны сделать все возможное для uWSGI. Поэтому имеет смысл перезагрузить все приложение, когда это произойдет.
Мы также отключим SQLALCHEMY_COMMIT_ON_TEARDOWN
, хотя мы, скорее всего, будем передавать явно, а не писать собственный обратный вызов для приложения teardown, так как мы редко записываем в базу данных.