Привет, мы столкнулись с этой проблемой в производственной среде с помощью Flask + Gunicorn + Heroku и не смогли найти причину или обходной путь.
Для одного конкретного запроса POST с параметрами POST запрос завершится ошибкой H18 (sock = backend) в маршрутизаторе Heroku, указывая на то, что сервер закрыл сокет, когда этого не должно было быть.
Мы начали уменьшать размер ответа этой отказавшей конечной точки, пока не сузили его до отметки около 13 тыс. Если мы отправим менее 13 КБ, ответ всегда будет работать. Если бы мы отправили более 13к, ответ почти всегда не работал.
Код для его воспроизведения доступен по адресу https://github.com/erjiang/gunicorn-issue - просто разверните репо в Heroku как есть и следуйте инструкциям в README.
Действительно полезный отчет, спасибо @erjiang.
У меня нет учетной записи heroku для тестирования. Может ли кто-нибудь с такой учетной записью проверить это? cc @tilgovi @kennethreitz
Рада, но я, вероятно, не скоро доберусь до этого.
В качестве быстрой проверки работоспособности я запустил его локально и проверил несколько вещей с помощью curl, чтобы сравнить официантку и Gunicorn:
Затем мне интересно, есть ли различия на уровне TCP. Я tcpdump их и посмотрю, замечу ли я что-нибудь подозрительное.
Я заметил, что даже с той же линией завитка Gunicorn разрывает соединение, но официантка оставляет его открытым. Пока нет никаких подсказок, но это единственное, что я мог видеть, было другим.
@tilgovi Я предполагаю, что поведение, которое вы видите с официанткой, может быть воспроизведено с помощью потокового рабочего. В любом случае, спасибо, что позаботились об этом :)
Всем привет,
У меня такая же проблема. Удалось ли кому-нибудь из вас изучить этот вопрос более тщательно?
@tilgovi @erjiang @benoitc
Ваше здоровье
Максим
@maximkgn вы тоже пользуетесь
Я использую django 1.7.
У нас был определенный пост-ответ, который всегда был длиннее 13 КБ, и с определенной вероятностью ~ 0,5 ответ в клиенте был бы усечен до чуть более 13 КБ. В журналах heroku мы увидели ту же ошибку h18, и после того, как мы убедились, что в нашем коде python нет ошибок, мы должны были сделать вывод, что это происходит на уровне пулеметчика между heroku и нашим python.
Когда мы перешли на официантку / uwsgi, ошибка перестала происходить ..
@maximkgn что произойдет, если вы воспользуетесь настройкой --threads
?
Кто-нибудь может это проверить?
У меня такая же проблема с флягой и пулеметом (проверенные версии 19.3 и 19.4.5). @benoitc Я пробовал 1, 2 и 4 потока (с параметром --threads), и это не имеет значения.
Дайте мне знать, могу ли я как-нибудь помочь проверить это?
@cbaines как выглядят запросы?
Friendpaste может принимать более 1 миллиона сообщений .... так что, конечно, нет ограничений внутри Gunicorn.
никогда не имел ответа. закрытие проблемы, так как она не воспроизводится. Не стесняйтесь открывать его снова, если это необходимо.
Все еще воспроизводится после обновления зависимостей, включая Flask 1.0.2 и gunicorn 19.9.0. Было бы неплохо привлечь внимание кого-нибудь в Heroku по этому поводу - я слышал, что у них есть несколько преданных Python людей.
См. Последнюю фиксацию здесь: https://github.com/erjiang/gunicorn-issue/
Я также регулярно получаю эту ошибку H18 при большом запросе GET.
Переключение на официантку устранило проблему. Не уверен, почему Gunicorn производит его, но выполняется тот же самый код.
тело ответа - 21,54 КБ
Все еще воспроизводится после обновления зависимостей, включая Flask 1.0.2 и gunicorn 19.9.0. Было бы неплохо привлечь внимание кого-нибудь в Heroku по этому поводу - я слышал, что у них есть несколько преданных Python людей.
См. Последнюю фиксацию здесь: https://github.com/erjiang/gunicorn-issue/
Я создал заявку в службу поддержки на Heroku. Буду обновлять здесь, если из этого появится что-нибудь полезное.
@benoitc выглядит так, как будто @erjiang предоставил воспроизводимый пример. Можем ли мы открыть это резервное копирование?
Повторно открыт. Я сам назначу и посмотрю, когда смогу.
Все еще воспроизводится после обновления зависимостей, включая Flask 1.0.2 и gunicorn 19.9.0. Было бы неплохо привлечь внимание кого-нибудь в Heroku по этому поводу - я слышал, что у них есть несколько преданных Python людей.
См. Последнюю фиксацию здесь: https://github.com/erjiang/gunicorn-issue/Я создал заявку в службу поддержки на Heroku. Буду обновлять здесь, если из этого появится что-нибудь полезное.
Ты получил ответ от героку?
Мне удалось воспроизвести, используя тестовый пример на https://github.com/erjiang/gunicorn-issue (который использует gunicorn 19.9.0, Python 2.7.14, синхронизатор, --workers 4
). Следует отметить, что вывод журнала доступа Gunicorn сообщает, что, по его мнению, он вернул HTTP 200.
Обновление до Python 3.7.3 + gunicorn master
и уменьшение до --workers 1
не повлияло на воспроизводимость, однако переключение с синхронизатора на gevent привело к тому, что ошибка возникла реже (хотя это все еще происходило). Использование --log-level debug
не выявило ничего существенного (единственным дополнительным выводом во время запроса была строка [DEBUG] POST /test1
).
Затем я попробовал --spew
, но проблема больше не воспроизводилась. Это заставило меня попробовать добавить time.sleep(1)
до того , как resp.close()
здесь , которые так же предотвратить проблему.
Таким образом, похоже, что причина в том, что буфер отправки сокета может быть не пустым во время close()
, что может привести к потере ответа:
Примечание.
close()
освобождает ресурс, связанный с соединением, но не обязательно немедленно закрывает соединение. Если вы хотите своевременно закрыть соединение, вызовитеshutdown()
передclose()
.
(См. Https://docs.python.org/3/library/socket.html#socket.socket.close)
Добавление sock.shutdown(socket.SHUT_RDWR)
( документы ) до sock.close()
здесь решен вопрос для меня. Альтернативным исправлением, возможно, могло бы быть использование SO_LINGER
, хотя, судя по тому, что я читал, здесь есть компромиссы.
Документы по этой теме найти сложно, но я обнаружил:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
Надеюсь, это поможет :-)
Полный STR:
heroku login
git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
heroku create
(это создает бесплатное приложение Heroku со случайно сгенерированным именем и настраивает удаленный git с именем heroku
)git push heroku master
curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1
(не работает> 75% случаев)heroku destroy
чтобы удалить приложение.@tilgovi Похоже, @edmorley представил правдоподобное объяснение того, что не так. Сможете ли вы взглянуть и увидеть, что такое правильное исправление? Я также мог бы отправить PR, чтобы добавить sock.shutdown()
но я не знаю достаточно, чтобы сказать, правильное ли это исправление или это отрицательно повлияет на другие ситуации.
Здравствуйте, я столкнулся с той же проблемой с размером ответа 503 КБ. Данные ответа представляют собой массив JSON.
Наблюдаемое поведение :
Это воспроизводимо на обоих:
Настройка среды выполнения
изображение meinheld-gunicorn-docker с тегом _python3.6_ с Python 3.6.7, Flask 1.0.2, flask-restplus 0.12.1, simpe Flask-caching
Конфигурация Docker : 3 процессора, ОЗУ 1024 МБ
Конфигурация Gunicorn :
В https://github.com/benoitc/gunicorn/issues/2015 у кого-то еще были проблемы с зависанием meinheld worker, и использование другого типа worker решило проблему. Интересно, есть ли с этим общая проблема. @stapetro можешь попробовать другого
Привет @jamadden!
Ваше предложение устранило проблему. Нет проблем с рабочими классами _gevent_ и _gthread_. Я отошел от меня. Спасибо за быстрый ответ и помощь! :)
Полный STR:
- Создайте бесплатную учетную запись Heroku на https://signup.heroku.com
- Установите Heroku CLI (см. Https://devcenter.heroku.com/articles/heroku-cli)
- Войдите в интерфейс командной строки, используя
heroku login
git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
heroku create
(это создает бесплатное приложение Heroku со случайно сгенерированным именем и настраивает удаленный git с именемheroku
)git push heroku master
curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1
(не работает> 75% случаев)- По завершении запустите
heroku destroy
чтобы удалить приложение.
У меня было очень похожее поведение в моем приложении, и я обнаружил, что при использовании curl -H вместо curl --data (поскольку это запрос GET) он работает для моего приложения (Django, Gunicorn, Heruko). Я не тестировал приложение Gunicorn-issue. Думал, что это может быть кому-то полезно.
@mikkelhn Yesss. Приложение с Flask / Flask RestPlus и Gunicorn ведет себя следующим образом: ответ на запрос POST дает ошибку 503 [если полезная нагрузка> 13 КБ], тогда как ошибка не возникает , если приложение отвечает на GET. Точно такой же код!
Кто-нибудь может объяснить это очень раздражающее поведение? Переключение на официантку - единственный способ решить эту проблему? Я считаю, что модификация Gunicorn «вручную» не является жизнеспособным решением ...
Я пошел дальше и открыл PR, чтобы вызвать shutdown () перед close (). Честно говоря, немного странно, что Heroku продолжает рекомендовать Gunicorn, хотя он по умолчанию не работает на Heroku.
Если, как правильно утверждает @erijang , Heroku рекомендует Gunicorn, когда Gunicorn не подходит: какие простые и жизнеспособные альтернативы Gunicorn (и как их лучше всего настроить на Heroku)?
AFAIK, многие клиенты выбирают Heroku только потому, что он не требует глубоких знаний в архитектуре серверов и деталях конфигурации ...: |
@RinaldoNani, что ты имеешь в виду? Также о каком работнике идет речь? .
@benoitc Эта проблема затрагивает несколько типов рабочих, как указано в:
https://github.com/benoitc/gunicorn/issues/840#issuecomment -482491267
Привет @benoitc. Как я уже упоминал в предыдущем посте, мы развернули довольно простое приложение Flask / FlaskRestPlus на Heroku, внимательно следуя рекомендациям Heroku по развертыванию серверных приложений Python / Flask (которые, как я понимаю, предполагают использование синхронизации Gunicorn "веб" рабочие ).
Поведение нашего приложения отражает заголовок этой беседы.
Протестировано локально, все работает нормально, приложение без проблем доставляет 20k + JSON; но когда приложение развертывается на Heroku, проблема с ошибкой 503 становится систематической: даже при буквально полном отсутствии трафика вывод не доставляется.
Как указывали другие, журналы показывают, что на уровне HTTP все выглядит нормально (регистрируется код ответа 200).
Если полезная нагрузка меньше 13 КБ, Heroku / Gunicorn отвечает на POST, как и ожидалось.
Мы последовали идее @mikkelhn об
Мы не являемся экспертами по Gunicorn и, честно говоря, ожидали, что наш простой вариант использования может работать «из коробки».
Если у вас есть предложения, которые помогут нам, мы будем бесконечно благодарны :)
@RinaldoNani Снято в темноте ... где-нибудь в обработчике запросов попробуйте прочитать все request.data
. Например:
@route('/whatever', methods=['POST'])
def whatever_handler():
str(request.data)
return flask.jsonify(...)
Это как-то влияет на ваши ошибки?
Я пишу это в 1:00 после того, как уже более 2 недель возился с проблемой H18 (не мог дождаться, чтобы поделиться).
Я работаю с огромным набором данных и отвечаю на записи от 18 до 20 тысяч для построения графика. H18 возникла как очень случайная ошибка. Иногда он работал бы нормально, но вызывал бы ошибку «Длина заголовка содержимого не совпадает» во всех браузерах. Я перепробовал почти все решения, обсуждаемые по этой проблеме, но безуспешно. Я попробовал две вещи, которые, наконец, сработали:
Самый полезный комментарий
Мне удалось воспроизвести, используя тестовый пример на https://github.com/erjiang/gunicorn-issue (который использует gunicorn 19.9.0, Python 2.7.14, синхронизатор,
--workers 4
). Следует отметить, что вывод журнала доступа Gunicorn сообщает, что, по его мнению, он вернул HTTP 200.Обновление до Python 3.7.3 + gunicorn
master
и уменьшение до--workers 1
не повлияло на воспроизводимость, однако переключение с синхронизатора на gevent привело к тому, что ошибка возникла реже (хотя это все еще происходило). Использование--log-level debug
не выявило ничего существенного (единственным дополнительным выводом во время запроса была строка[DEBUG] POST /test1
).Затем я попробовал
--spew
, но проблема больше не воспроизводилась. Это заставило меня попробовать добавитьtime.sleep(1)
до того , какresp.close()
здесь , которые так же предотвратить проблему.Таким образом, похоже, что причина в том, что буфер отправки сокета может быть не пустым во время
close()
, что может привести к потере ответа:(См. Https://docs.python.org/3/library/socket.html#socket.socket.close)
Добавление
sock.shutdown(socket.SHUT_RDWR)
( документы ) доsock.close()
здесь решен вопрос для меня. Альтернативным исправлением, возможно, могло бы быть использованиеSO_LINGER
, хотя, судя по тому, что я читал, здесь есть компромиссы.Документы по этой теме найти сложно, но я обнаружил:
https://stackoverflow.com/questions/8874021/close-socket-directly-after-send-unsafe
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
Надеюсь, это поможет :-)