Gunicorn: POST gagal saat mengembalikan >13k respons di Heroku

Dibuat pada 4 Agu 2014  ·  34Komentar  ·  Sumber: benoitc/gunicorn

Hai, kami mengalami masalah ini dalam produksi menggunakan Flask + Gunicorn + Heroku dan tidak dapat menemukan penyebab atau solusi.

Untuk satu permintaan POST tertentu dengan parameter POST, permintaan akan gagal dengan kesalahan H18 (sock=backend) di router Heroku yang menunjukkan bahwa server menutup soket padahal seharusnya tidak.

Kami mulai mengurangi ukuran respons dari titik akhir yang gagal itu hingga kami mempersempitnya menjadi sekitar angka 13k. Jika kami mengirim kurang dari 13k, respons akan selalu berhasil. Jika kami mengirim lebih dari 13k, responsnya hampir selalu tidak berfungsi.

Kode untuk mereproduksi ini tersedia di https://github.com/erjiang/gunicorn-issue - cukup gunakan repo ke Heroku apa adanya dan ikuti instruksi di README.

( Feedback Requested unconfirmed help wanted - Bugs -

Komentar yang paling membantu

Saya dapat mereproduksi menggunakan testcase di https://github.com/erjiang/gunicorn-issue (yang menggunakan gunicorn 19.9.0, Python 2.7.14, sync worker, --workers 4 ). Yang perlu diperhatikan adalah bahwa output log akses gunicorn melaporkan bahwa ia yakin telah mengembalikan HTTP 200.

Memperbarui ke Python 3.7.3 + gunicorn master , dan mengurangi ke --workers 1 tidak berpengaruh pada reproduktifitas, namun beralih dari pekerja sinkronisasi ke gevent membuat kesalahan lebih jarang terjadi (meskipun masih terjadi). Menggunakan --log-level debug tidak mengungkapkan sesuatu yang signifikan (satu-satunya keluaran tambahan selama permintaan adalah baris [DEBUG] POST /test1 ).

Selanjutnya saya mencoba --spew , namun masalahnya tidak lagi direproduksi. Ini membuat saya mencoba menambahkan time.sleep(1) sebelum resp.close() sini yang juga mencegah masalah.

Karena itu tampaknya penyebabnya adalah soket send buffer mungkin tidak kosong pada saat close() , yang dapat menyebabkan respons hilang:

Catatan: close() melepaskan sumber daya yang terkait dengan koneksi tetapi tidak serta merta menutup koneksi. Jika Anda ingin menutup koneksi tepat waktu, hubungi shutdown() sebelum close() .

(Lihat https://docs.python.org/3/library/socket.html#socket.socket.close)

Menambahkan sock.shutdown(socket.SHUT_RDWR) ( docs ) sebelum sock.close() sini menyelesaikan masalah bagi saya. Perbaikan alternatif mungkin menggunakan SO_LINGER , meskipun dari apa yang saya baca ada kompromi.

Dokumen tentang hal ini sulit didapat, tetapi saya menemukan:
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

Semoga membantu :-)

Semua 34 komentar

Laporan yang sangat membantu, terima kasih @erjiang.

saya tidak punya akun heroku untuk diuji. Adakah yang bisa mengujinya dengan akun seperti itu? cc @tilgovi @kennethreitz

Senang tapi saya mungkin tidak akan segera melakukannya.

Sebagai pemeriksaan kewarasan cepat, saya menjalankannya secara lokal dan memeriksa beberapa hal dengan curl untuk membandingkan pelayan dan gunicorn:

  • [x] Konten-Panjangnya sama
  • [x] Isi tubuh yang sama
  • [x] Encoding transfer yang sama (tidak ada yang menentukan chunked, keduanya menggunakan Content-Length)

Selanjutnya saya penasaran mungkin ada perbedaan di level TCP. Saya akan tcpdump mereka dan melihat apakah saya melihat sesuatu yang mencurigakan.

Saya memang memperhatikan bahwa bahkan dengan garis ikal yang sama, gunicorn menjatuhkan koneksi tetapi pelayan membiarkannya terbuka. Belum ada petunjuk dari itu, tapi itu _only_ hal yang bisa saya lihat yang berbeda.

@tilgovi Saya kira perilaku yang Anda lihat dengan pelayan dapat direproduksi dengan pekerja berulir. Pokoknya terima kasih untuk mengurus ini :)

Halo semua,
Aku mengalami masalah yang sama. Apakah ada di antara Anda yang mendapat kesempatan untuk memeriksa masalah ini lebih teliti?
@tilgovi @erjiang @benoitc

Bersulang
Pepatah

@maximkgn apakah Anda juga menggunakan labu? Ada detail lebih lanjut?

Saya menggunakan Django 1.7.
Kami memiliki respons posting tertentu yang selalu lebih panjang dari 13k, dan dengan probabilitas tertentu ~0,5 respons di klien akan terpotong menjadi sedikit di atas 13k. Di log heroku kami melihat kesalahan h18 yang sama, dan setelah kami memastikan tidak ada kesalahan yang terjadi pada kode python kami, kami harus menyimpulkan itu terjadi di lapisan gunicorn antara heroku dan python kami.
Ketika kami beralih ke pelayan / uwsgi, bug berhenti terjadi. .

@maximkgn apa yang terjadi jika Anda menggunakan pengaturan --threads ?

Adakah yang bisa menguji ini?

Saya memiliki masalah yang sama dengan labu dan gunicorn (versi yang diuji 19.3 dan 19.4.5). @benoitc Saya mencoba

Beri tahu saya jika saya dapat membantu menguji ini dengan cara apa pun?

@cbaines seperti apa permintaannya?

Friendpaste dapat menerima lebih dari 1 juta posting.... jadi pasti tidak ada batasan di dalam gunicorn.

tidak pernah punya jawaban. menutup masalah karena tidak dapat direproduksi. Jangan ragu untuk membuka kembali satu jika diperlukan.

Masih mereproduksi setelah memperbarui dependensi untuk memasukkan Flask 1.0.2 dan gunicorn 19.9.0. Mungkin menyenangkan untuk mendapatkan perhatian dari seseorang di Heroku tentang hal ini - saya mendengar mereka memiliki beberapa orang Python yang berdedikasi.

Lihat komit terbaru di sini: https://github.com/erjiang/gunicorn-issue/

Saya juga menerima kesalahan H18 ini pada permintaan GET besar secara teratur.

Beralih ke pelayan memang memperbaiki masalah. Tidak yakin mengapa gunicorn memproduksinya, tetapi kode yang sama persis sedang dieksekusi.

badan respons adalah 21,54 KB

Masih mereproduksi setelah memperbarui dependensi untuk memasukkan Flask 1.0.2 dan gunicorn 19.9.0. Mungkin menyenangkan untuk mendapatkan perhatian dari seseorang di Heroku tentang hal ini - saya mendengar mereka memiliki beberapa orang Python yang berdedikasi.

Lihat komit terbaru di sini: https://github.com/erjiang/gunicorn-issue/

Saya membuat tiket dukungan di Heroku. Akan memperbarui di sini jika sesuatu yang bermanfaat berasal darinya.

@benoitc sepertinya @erjiang memberikan contoh yang dapat direproduksi. Bisakah kita membuka ini kembali?

Dibuka kembali. Saya akan menetapkan sendiri dan melihat kapan saya bisa.

Masih mereproduksi setelah memperbarui dependensi untuk memasukkan Flask 1.0.2 dan gunicorn 19.9.0. Mungkin menyenangkan untuk mendapatkan perhatian dari seseorang di Heroku tentang hal ini - saya mendengar mereka memiliki beberapa orang Python yang berdedikasi.
Lihat komit terbaru di sini: https://github.com/erjiang/gunicorn-issue/

Saya membuat tiket dukungan di Heroku. Akan memperbarui di sini jika sesuatu yang bermanfaat berasal darinya.

Apakah Anda mendapat balasan dari heroku?

Saya dapat mereproduksi menggunakan testcase di https://github.com/erjiang/gunicorn-issue (yang menggunakan gunicorn 19.9.0, Python 2.7.14, sync worker, --workers 4 ). Yang perlu diperhatikan adalah bahwa output log akses gunicorn melaporkan bahwa ia yakin telah mengembalikan HTTP 200.

Memperbarui ke Python 3.7.3 + gunicorn master , dan mengurangi ke --workers 1 tidak berpengaruh pada reproduktifitas, namun beralih dari pekerja sinkronisasi ke gevent membuat kesalahan lebih jarang terjadi (meskipun masih terjadi). Menggunakan --log-level debug tidak mengungkapkan sesuatu yang signifikan (satu-satunya keluaran tambahan selama permintaan adalah baris [DEBUG] POST /test1 ).

Selanjutnya saya mencoba --spew , namun masalahnya tidak lagi direproduksi. Ini membuat saya mencoba menambahkan time.sleep(1) sebelum resp.close() sini yang juga mencegah masalah.

Karena itu tampaknya penyebabnya adalah soket send buffer mungkin tidak kosong pada saat close() , yang dapat menyebabkan respons hilang:

Catatan: close() melepaskan sumber daya yang terkait dengan koneksi tetapi tidak serta merta menutup koneksi. Jika Anda ingin menutup koneksi tepat waktu, hubungi shutdown() sebelum close() .

(Lihat https://docs.python.org/3/library/socket.html#socket.socket.close)

Menambahkan sock.shutdown(socket.SHUT_RDWR) ( docs ) sebelum sock.close() sini menyelesaikan masalah bagi saya. Perbaikan alternatif mungkin menggunakan SO_LINGER , meskipun dari apa yang saya baca ada kompromi.

Dokumen tentang hal ini sulit didapat, tetapi saya menemukan:
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

Semoga membantu :-)

STR lengkap:

  1. Buat akun Heroku gratis di https://signup.heroku.com
  2. Instal Heroku CLI (lihat https://devcenter.heroku.com/articles/heroku-cli)
  3. Masuk ke CLI menggunakan heroku login
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (ini membuat aplikasi Heroku gratis dengan nama yang dibuat secara acak dan mengonfigurasi git remote bernama heroku )
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (gagal >75% dari waktu)
  8. Setelah selesai, jalankan heroku destroy untuk menghapus aplikasi.

@tilgovi Kedengarannya seperti @edmorley menghasilkan penjelasan yang masuk akal tentang apa yang salah. Apakah Anda dapat melihat dan melihat apa perbaikan yang tepat? Saya juga dapat mengirimkan PR untuk menambahkan sock.shutdown() tetapi saya tidak cukup tahu untuk mengatakan apakah itu perbaikan yang tepat atau apakah itu akan berdampak negatif pada situasi lain.

Halo, saya mengalami masalah yang sama dengan ukuran respons 503 KB. Data respons adalah array JSON.
Perilaku yang diamati adalah:

  1. Saya melihat badan respons terpotong dan klien http (Chrome, curl) masih menunggu respons.
  2. ~75% permintaan mengalami waktu respons antara 120-130 detik. Permintaan yang tersisa diselesaikan di bawah 400 md.
  3. Permintaan dengan ukuran respons kecil cepat.

Ini dapat direproduksi pada keduanya:

  1. instalasi Docker lokal pada Windows 10
  2. Menjalankan wadah buruh pelabuhan di AWS ECS

Pengaturan lingkungan waktu proses
gambar meinheld-gunicorn-docker ditandai sebagai _python3.6_ dengan Python 3.6.7, Flask 1.0.2, flask-restplus 0.12.1, simpe Flask-caching

Konfigurasi Docker : 3 CPU, RAM 1024 MB

Konfigurasi gunicorn :

Di https://github.com/benoitc/gunicorn/issues/2015 orang lain memiliki masalah dengan pekerja meinheld yang digantung, dan menggunakan tipe pekerja yang berbeda memecahkan masalah. Saya ingin tahu apakah ada masalah umum dengannya. @stapetro apakah Anda dapat mencoba pekerja yang berbeda?

Halo @jamadden ,
Saran Anda memperbaiki masalah. Tidak ada masalah dengan kelas pekerja _gevent_ dan _gthread_. Aku pindah dari meinheld. Terima kasih atas balasan cepat dan bantuannya! :)

STR lengkap:

  1. Buat akun Heroku gratis di https://signup.heroku.com
  2. Instal Heroku CLI (lihat https://devcenter.heroku.com/articles/heroku-cli)
  3. Masuk ke CLI menggunakan heroku login
  4. git clone https://github.com/erjiang/gunicorn-issue && cd gunicorn-issue
  5. heroku create (ini membuat aplikasi Heroku gratis dengan nama yang dibuat secara acak dan mengonfigurasi git remote bernama heroku )
  6. git push heroku master
  7. curl --data "foo=bar" https://YOUR_GENERATED_APP_NAME.herokuapp.com/test1 (gagal >75% dari waktu)
  8. Setelah selesai, jalankan heroku destroy untuk menghapus aplikasi.

Saya memiliki perilaku yang sangat mirip pada aplikasi saya, dan menemukan bahwa ketika menggunakan curl -H alih-alih curl --data (karena ini adalah permintaan GET) itu berfungsi untuk aplikasi saya (Django, Gunicorn, Heruko). Saya belum menguji pada aplikasi masalah gunicorn. Pikir ini mungkin berguna untuk seseorang.

@mikkelhn Yess . Aplikasi dengan Flask/Flask RestPlus dan Gunicorn berperilaku seperti ini: membalas permintaan POST memberikan kesalahan 503 [jika muatan > 13k], sedangkan kesalahan tidak terjadi jika aplikasi membalas ke GET. Kode yang sama persis!
Adakah yang bisa menjelaskan perilaku yang sangat menjengkelkan ini? Apakah beralih ke pelayan satu-satunya solusi untuk memperbaiki masalah ini? Saya merasa bahwa memodifikasi Gunicorn "dengan tangan" bukanlah solusi yang layak...

Saya melanjutkan dan membuka PR untuk memanggil shutdown() sebelum close(). Terus terang, agak liar bahwa Heroku terus merekomendasikan Gunicorn ketika rusak secara default di Heroku.

Jika, seperti yang dinyatakan dengan benar oleh
AFAIK, banyak pelanggan memilih Heroku hanya karena tidak memerlukan pengetahuan mendalam tentang arsitektur server dan detail konfigurasi... :|

@RinaldoNani apa maksudmu? Juga pekerja mana yang kita bicarakan? .

@benoitc Masalah ini memengaruhi beberapa jenis pekerja, seperti yang disebutkan dalam:
https://github.com/benoitc/gunicorn/issues/840#issuecomment -482491267

Hai @benoitc. Seperti yang saya sebutkan di posting sebelumnya, kami telah menerapkan aplikasi Flask / FlaskRestPlus yang cukup sederhana di Heroku, mengikuti dengan hati-hati panduan Heroku untuk penyebaran aplikasi sisi server Python/Flask (yang, seperti yang saya pahami, menyarankan penggunaan "web" sinkronisasi Gunicorn pekerja ).

Perilaku aplikasi kami mencerminkan judul utas ini.

Diuji secara lokal, semuanya berfungsi dengan baik, aplikasi memberikan 20k+ JSON tanpa masalah; tetapi ketika aplikasi dikerahkan di Heroku, masalah kesalahan 503 menjadi sistematis: bahkan tanpa lalu lintas secara harfiah, hasilnya tidak terkirim.
Seperti yang ditunjukkan orang lain, log menunjukkan bahwa pada level HTTP semuanya tampak baik-baik saja (200 kode respons dicatat).
Jika payload kurang dari 13k, maka Heroku/Gunicorn merespon POST seperti yang diharapkan.
Kami mengikuti ide @mikkelhn untuk menghindari titik akhir POST (?!?) dan menggunakan GET sebagai gantinya, dan ini sepertinya cara (tidak terlalu bagus) untuk mengatasi masalah.

Kami bukan ahli Gunicorn, dan sejujurnya kami berharap bahwa kasus penggunaan sederhana kami dapat bekerja "di luar kotak".
Jika Anda memiliki saran untuk membantu kami, kami akan selamanya berterima kasih :)

@RinaldoNani Ditembak dalam gelap... di suatu tempat di penangan permintaan Anda, coba baca semua request.data . Sebagai contoh:

@route('/whatever', methods=['POST'])
def whatever_handler():
    str(request.data)
    return flask.jsonify(...)

Apakah itu berpengaruh pada kesalahan Anda?

Saya menulis ini pada jam 1:00 pagi setelah sibuk dengan masalah H18 selama lebih dari 2 minggu sekarang (tidak sabar untuk berbagi).

Saya bekerja dengan kumpulan data besar dan merespons 18K hingga 20K catatan untuk diplot. H18 datang sebagai kesalahan yang sangat acak. Kadang-kadang akan berfungsi dengan baik, tetapi akan memunculkan "Panjang header konten tidak cocok" di semua browser. Saya mencoba hampir semua solusi yang dibahas tentang masalah ini tetapi tidak berhasil. Ada 2 hal yang saya coba yang akhirnya berhasil:

  1. Mengubah permintaan POST menjadi GET.
  2. Data saya memiliki nilai NaN/Null jadi saya mengubah model saya dan memberikan nilai default. (Saya pikir ini menyelesaikan masalah)
    Setelah ini, saya berhenti mendapatkan kesalahan ini.
    Semoga ini bisa membantu seseorang!
Apakah halaman ini membantu?
0 / 5 - 0 peringkat