Gunicorn: Perjelas apa/bagaimana timeout dan graceful_timeout bekerja

Dibuat pada 3 Apr 2017  ·  30Komentar  ·  Sumber: benoitc/gunicorn

(Maaf untuk monolog di sini: hal-hal sederhana menjadi rumit dan saya akhirnya menggali melalui tumpukan. Semoga apa yang saya dokumentasikan bermanfaat bagi pembaca.)

Seperti yang saya pahami, secara default:

  • Setelah 30 detik (dapat dikonfigurasi dengan timeout ) dari pemrosesan permintaan, proses master gunicorn mengirimkan SIGTERM ke proses pekerja, untuk memulai restart dengan baik.
  • Jika pekerja tidak dimatikan selama 30 detik (dapat dikonfigurasi dengan graceful_timeout ), proses master mengirimkan SIGKILL . Sepertinya sinyal ini juga dikirim ketika pekerja _melakukan_ shutdown dengan anggun selama periode graceful_timeout (https://github.com/benoitc/gunicorn/commit/d1a09732256fa8db900a1fe75a71466cf2645ef9).

Pertanyaan-pertanyaan:

  • Apakah sinyalnya benar?
  • Apa yang sebenarnya terjadi ketika pekerja gunicorn (sinkronisasi) menerima sinyal ini? Bagaimana cara memberi tahu aplikasi WSGI bahwa sinyalnya tertangkap dan sesuatu akan terjadi (ok saya berasumsi itu hanya "menyampaikannya")?
  • Bagaimana, misalnya, Flask menangani sinyal SIGTERM - dalam praktiknya, apa yang terjadi selama pemrosesan permintaan? Apakah itu hanya menetapkan tanda untuk aplikasi WSGI (pada level werkzeug) yang harus dimatikan setelah pemrosesan permintaan selesai? Atau apakah SIGTERM entah bagaimana sudah memengaruhi pemrosesan permintaan yang sedang berlangsung - matikan koneksi IO atau sesuatu untuk mempercepat pemrosesan permintaan...?

Pada SIGKILL , saya kira pemrosesan permintaan dibatalkan secara paksa.

Saya dapat mengajukan PR kecil untuk meningkatkan dokumen tentang ini, jika saya memahami cara kerja sebenarnya.

Discussion Documentation

Komentar yang paling membantu

@tuukkamustonen --timeout tidak dimaksudkan sebagai batas waktu permintaan. Ini dimaksudkan sebagai cek keaktifan pekerja. Untuk pekerja sinkronisasi, ini berfungsi sebagai batas waktu permintaan karena pekerja tidak dapat melakukan apa pun selain memproses permintaan. Detak jantung pekerja asinkron bahkan saat mereka menangani permintaan yang berjalan lama, jadi kecuali pekerja memblokir/membekukan itu tidak akan dimatikan.

Mungkin ada baiknya kita mengganti nama jika orang lain menganggap ini membingungkan.

Semua 30 komentar

Hmm, saya pikir https://github.com/benoitc/gunicorn/issues/1236#issuecomment -254059927 mengonfirmasi asumsi saya tentang SIGTERM cukup menyetel pekerja ke shutdown setelah pemrosesan permintaan selesai (dan menyetel pekerja untuk tidak menerima koneksi baru).

Sepertinya cara saya menafsirkan timeout dan graceful_timeout salah. Kedua periode sebenarnya mengacu pada waktu di awal pemrosesan permintaan. Jadi, secara default, karena kedua pengaturan disetel ke 30 detik, tidak ada restart anggun yang diaktifkan. Jika saya melakukan sesuatu seperti --graceful-timeout 15 --timeout 30 itu berarti restart yang anggun dimulai pada 15 detik dan pekerja dimatikan secara paksa pada 30 detik jika permintaan tidak selesai sebelum itu.

Namun, sepertinya jika respons dikembalikan antara graceful_timeout dan timeout , maka pekerja tidak dimulai ulang sama sekali? bukan?

Saya diuji oleh app.py :

import time
from flask import Flask

app = Flask(__name__)

@app.route('/foo')
def foo():
    time.sleep(3)
    return 'ok'

Kemudian:

12:51 $ gunicorn app:app --timeout 5 --graceful-timeout 1
[2017-04-03 12:51:37 +0300] [356] [INFO] Starting gunicorn 19.6.0
[2017-04-03 12:51:37 +0300] [356] [INFO] Listening at: http://127.0.0.1:8000 (356)
[2017-04-03 12:51:37 +0300] [356] [INFO] Using worker: sync
[2017-04-03 12:51:37 +0300] [359] [INFO] Booting worker with pid: 359

Lalu saya mengirim curl localhost:8000/foo , yang kembali setelah 3 detik. Tapi tidak ada yang terjadi di gunicorn - saya tidak melihat jejak restart anggun yang dimulai atau terjadi?

Tampaknya pada timeout , SystemExit(1,) dilemparkan, membatalkan pemrosesan permintaan saat ini di Flask. Kode atau sinyal apa yang menghasilkannya, tidak bisa dikatakan.

Pengecualian ini dilempar melalui tumpukan Flask, dan penangan teardown_request akan menangkapnya. Ada cukup waktu untuk mencatat sesuatu, tetapi jika Anda melakukan time.sleep(1) atau hal lain yang memakan waktu di handler, itu akan dimatikan secara diam-diam. Seolah-olah ada waktu 100-200 ms sebelum proses benar-benar dihentikan secara paksa dan saya bertanya-tanya apa penundaan ini. Ini bukan batas waktu yang anggun, pengaturan itu tidak berdampak pada penundaan. Saya berharap prosesnya hanya dibunuh secara paksa di tempat, alih-alih melihat SystemExit dilemparkan melalui tumpukan, tetapi tetap berpotensi mematikan proses di udara.

Sebenarnya, saya tidak melihat graceful_timeout melakukan apa pun - mungkin itu tidak didukung untuk pekerja sinkronisasi, atau mungkin tidak berfungsi "berdiri sendiri" (atau bersama dengan timeout ) - hanya ketika Anda mengirim SIGTERM secara manual?

Juga yang mungkin aneh adalah https://github.com/benoitc/gunicorn/blob/master/gunicorn/arbiter.py#L392 tidak memeriksa flag graceful sama sekali. Saya kira https://github.com/benoitc/gunicorn/blob/master/gunicorn/arbiter.py#L390 memastikan bahwa self.WORKERS kosong sehingga batas waktu yang anggun tidak menunggu saat melakukan penghentian yang tidak anggun.

@benoitc @tilgovi Peduli untuk membantu di sini? Semoga tulisan saya di atas masuk akal...

@tuco86 graceful timeout hanya tersedia ketika Anda keluar dari arbiter, meningkatkannya (USR2), mengirim sinyal HUP ke arbiter atau mengirim sinyal QUIT ke pekerja. Yaitu hanya digunakan ketika aksinya normal

Batas waktu di sini untuk mencegah pekerja yang sibuk memblokir permintaan orang lain. Jika mereka tidak memberitahu arbiter dalam waktu kurang dari timeout pekerja hanya keluar dan koneksi dengan klien ditutup.

Baiklah. Apakah timeout berpengaruh saat Anda:

keluar dari arbiter, tingkatkan (USR2), kirim sinyal HUP ke arbiter atau kirim sinyal QUIT ke pekerja

Maksud saya, bagaimana jika pekerja tidak dimatikan di graceful_timeout - apakah timeout akan muncul setelah itu dan pekerja dibunuh secara paksa, atau terserah pengguna untuk memanggil SIGQUIT seandainya mereka tidak mati dengan anggun?

Sinyal BERHENTI kepada pekerja

Saya berasumsi maksud Anda TERM di sini (karena QUIT didokumentasikan sebagai _quick shutdown_ untuk master dan pekerja)?

jika pekerja tidak mematikan selama waktu tenggang itu akan dibunuh tanpa penundaan lainnya.

Tentu saja. Terima kasih telah mengklarifikasi semuanya!

@benoitc Bertanya dalam konteks tiket lama ini - apa arti kalimat terakhir dalam dokumentasi timeout sebenarnya?

Umumnya diatur ke tiga puluh detik. Hanya setel ini lebih tinggi jika Anda yakin akan akibatnya bagi pekerja sinkronisasi. Untuk pekerja yang tidak sinkron itu hanya berarti bahwa proses pekerja masih berkomunikasi dan tidak terikat dengan lamanya waktu yang dibutuhkan untuk menangani satu permintaan.

Karena bukan penutur asli bahasa Inggris, saya kesulitan memahami hal ini. Apakah itu berarti timeout tidak didukung untuk pekerja yang tidak sinkron (karena itulah yang tampaknya saya saksikan: Saya menggunakan gthread pekerja dan batas waktu tidak masuk dan mematikan permintaan yang terlalu lambat )?

@tuukkamustonen --timeout tidak dimaksudkan sebagai batas waktu permintaan. Ini dimaksudkan sebagai cek keaktifan pekerja. Untuk pekerja sinkronisasi, ini berfungsi sebagai batas waktu permintaan karena pekerja tidak dapat melakukan apa pun selain memproses permintaan. Detak jantung pekerja asinkron bahkan saat mereka menangani permintaan yang berjalan lama, jadi kecuali pekerja memblokir/membekukan itu tidak akan dimatikan.

Mungkin ada baiknya kita mengganti nama jika orang lain menganggap ini membingungkan.

@tilgovi timeout baik-baik saja, meskipun sesuatu seperti worker_timeout mungkin lebih deskriptif. Awalnya saya bingung karena timeout dan graceful_timeout dideklarasikan bersebelahan dalam dokumentasi, jadi otak saya berasumsi mereka terhubung erat, padahal sebenarnya tidak.

Untuk pekerja sinkronisasi, ini berfungsi sebagai batas waktu permintaan karena pekerja tidak dapat melakukan apa pun selain memproses permintaan. Detak jantung pekerja asinkron bahkan saat mereka menangani permintaan yang berjalan lama, jadi kecuali pekerja memblokir/membekukan itu tidak akan dimatikan.

Apakah Anda memiliki contoh saat timeout dimulai dengan pekerja yang tidak sinkron? Apakah itu sesuatu yang seharusnya tidak pernah terjadi, sungguh - mungkin hanya jika ada bug yang menyebabkan pekerja memblokir/membeku?

Itu benar. Pekerja asinkron yang bergantung pada inti loop peristiwa mungkin melakukan prosedur intensif CPU yang tidak menghasilkan dalam batas waktu.

Bukan hanya bug, dengan kata lain. Meskipun, terkadang ini mungkin mengindikasikan bug, seperti panggilan ke fungsi I/O pemblokiran ketika protokol asyncio akan lebih sesuai.

Terjebak dalam tugas intensif CPU adalah contoh yang baik, terima kasih.

Memanggil pemblokiran I/O dalam kode async juga salah satunya, tetapi saya tidak yakin bagaimana itu berlaku untuk konteks ini - Saya menjalankan aplikasi Flask tradisional dengan kode pemblokiran tetapi menjalankannya dengan pekerja async ( gthread ) tanpa penambalan monyet apa pun. Dan itu bekerja dengan baik. Saya tahu ini tidak benar-benar dalam konteks tiket ini lagi, tetapi tidak mencampur dan mencocokkan kode asinkron/sinkron seperti ini menyebabkan masalah?

Juga, berapa interval detak jantung? Apa nilai waras yang akan digunakan untuk timeout dengan pekerja yang tidak sinkron?

Pekerja gthread tidak asinkron, tetapi memiliki utas utama untuk detak jantung sehingga tidak akan kehabisan waktu. Dalam kasus pekerja itu, Anda mungkin tidak akan melihat batas waktu kecuali pekerja itu kelebihan beban atau, lebih mungkin, Anda memanggil modul ekstensi C yang tidak melepaskan GIL.

Anda mungkin tidak perlu mengubah batas waktu kecuali Anda mulai melihat batas waktu pekerja.

Baiklah. Hanya satu lagi:

Pekerja gthread tidak asinkron

Mungkin sedikit membingungkan bahwa gthread pekerja tidak asinkron tetapi terdaftar sebagai pekerja "AsyncIO" di http://docs.gunicorn.org/en/stable/design.html#asyncio -workers. Selain itu, menggunakan "utas" tidak perlu asyncio, sehingga juga menimbulkan pertanyaan pada pembaca. Hanya mengatakan ini dari sudut pandang pengguna yang naif, saya yakin itu semua beralasan secara teknis.

Singkatnya, pekerja gthread diimplementasikan dengan lib asyncio tetapi memunculkan utas untuk menangani kode sinkronisasi. Koreksi saya jika salah.

Senang Anda bertanya!

Pekerja berulir tidak menggunakan asyncio dan tidak mewarisi dari kelas pekerja asinkron dasar.

Kami harus mengklarifikasi dokumentasi. Saya pikir itu mungkin telah terdaftar sebagai async karena batas waktu pekerja ditangani secara bersamaan, membuatnya berperilaku lebih seperti pekerja async daripada pekerja sinkronisasi sehubungan dengan kemampuan untuk menangani permintaan panjang dan permintaan bersamaan.

Akan sangat bagus untuk memperjelas dokumentasi dan membuatnya lebih akurat menggambarkan semua pekerja.

ya pekerja gthreads tidak boleh terdaftar di pekerja asyncio. mungkin lebih baik memiliki bagian yang menggambarkan desain setiap pekerja?

Membuka kembali ini sehingga kami dapat melacaknya sebagai pekerjaan untuk memperjelas bagian tentang jenis pekerja dan batas waktu.

@tilgovi

--timeout tidak dimaksudkan sebagai batas waktu permintaan. Ini dimaksudkan sebagai cek keaktifan pekerja. Untuk pekerja sinkronisasi, ini berfungsi sebagai batas waktu permintaan karena pekerja tidak dapat melakukan apa pun selain memproses permintaan. Detak jantung pekerja asinkron bahkan saat mereka menangani permintaan yang berjalan lama, jadi kecuali pekerja memblokir/membekukan itu tidak akan dimatikan.

Apakah ada opsi batas waktu permintaan yang tersedia untuk pekerja asinkron? Dengan kata lain bagaimana membuat arbiter membunuh seorang pekerja yang tidak memproses permintaan dalam waktu yang ditentukan?

@aschatten tidak ada, sayangnya. Lihat juga #1658.

membunuh seorang pekerja yang tidak memproses permintaan dalam waktu yang ditentukan

Karena seorang pekerja dapat memproses beberapa permintaan secara bersamaan, membunuh seluruh pekerja karena satu permintaan waktu habis terdengar sangat ekstrem. Bukankah itu akan mengakibatkan semua permintaan lainnya terbunuh dengan sia-sia?

Saya ingat uWSGI berencana untuk memperkenalkan pembunuhan berbasis utas di 2.1 atau lebih, meskipun mungkin itu hanya berlaku untuk pekerja sinkronisasi/utas (dan ingatan saya tentang ini tidak jelas).

Karena seorang pekerja dapat memproses beberapa permintaan secara bersamaan, membunuh seluruh pekerja karena satu permintaan waktu habis terdengar sangat ekstrem. Bukankah itu akan mengakibatkan semua permintaan lainnya terbunuh dengan sia-sia?

Pendekatannya bisa sama seperti untuk max_request , di mana ada implementasi terpisah untuk setiap jenis pekerja.

Kami sedang mengerjakan rilis minggu ini, pada saat itu _mungkin_ saatnya untuk bercabang untuk R20, di mana kami berencana untuk menangani beberapa hal utama. Itu mungkin waktu yang tepat untuk membuat batas waktu saat ini menjadi batas waktu permintaan yang tepat untuk setiap jenis pekerja.

Berkomentar di sini alih-alih mengajukan masalah terpisah karena saya mencoba memahami bagaimana batas waktu seharusnya bekerja dan saya tidak yakin apakah ini bug atau bukan.

Perilaku tak terduga IMO yang saya lihat adalah ini:

Setiap permintaan max-requests'th (yang setelah itu pekerja akan dimulai ulang) memiliki batas waktu, sedangkan permintaan lainnya berhasil diselesaikan. Pada contoh di bawah ini 4 permintaan dilakukan, permintaan 1, 2, dan 4 berhasil, sedangkan permintaan 3 gagal.

Konfigurasi yang relevan:

  • pekerja utas
  • permintaan penyajian membutuhkan waktu lebih lama dari batas waktu
  • max-requests bukan nol
import time

def app(environ, start_response):
    start_response('200 OK', [('Content-type', 'text/plain; charset=utf-8')])
    time.sleep(5)
    return [b"Hello World\n"]

gunicorn:

gunicorn --log-level debug -k gthread -t 4 --max-requests 3 "app:app"
...
[2018-02-08 10:11:59 +0200] [28592] [INFO] Starting gunicorn 19.7.1
[2018-02-08 10:11:59 +0200] [28592] [DEBUG] Arbiter booted
[2018-02-08 10:11:59 +0200] [28592] [INFO] Listening at: http://127.0.0.1:8000 (28592)
[2018-02-08 10:11:59 +0200] [28592] [INFO] Using worker: gthread
[2018-02-08 10:11:59 +0200] [28595] [INFO] Booting worker with pid: 28595
[2018-02-08 10:11:59 +0200] [28592] [DEBUG] 1 workers
[2018-02-08 10:12:06 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:11 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:15 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:20 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:23 +0200] [28595] [DEBUG] GET /
[2018-02-08 10:12:23 +0200] [28595] [INFO] Autorestarting worker after current request.
[2018-02-08 10:12:27 +0200] [28592] [CRITICAL] WORKER TIMEOUT (pid:28595)
[2018-02-08 10:12:27 +0200] [28595] [INFO] Worker exiting (pid: 28595)
[2018-02-08 10:12:28 +0200] [28595] [DEBUG] Closing connection.
[2018-02-08 10:12:28 +0200] [28599] [INFO] Booting worker with pid: 28599
[2018-02-08 10:12:32 +0200] [28599] [DEBUG] GET /
[2018-02-08 10:12:37 +0200] [28599] [DEBUG] Closing connection.
^C[2018-02-08 10:12:39 +0200] [28592] [INFO] Handling signal: int

Klien:

[salonen<strong i="19">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World
[salonen<strong i="20">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World
[salonen<strong i="21">@mac</strong> ~]$ curl http://127.0.0.1:8000
curl: (52) Empty reply from server
[salonen<strong i="22">@mac</strong> ~]$ curl http://127.0.0.1:8000
Hello World

apa yang harus menjadi rencana di sana? Saya ada dalam pikiran berikut:

  • [ ] perbarui deskripsi pekerja (jika masih diperlukan)
  • [ ] mendokumentasikan protokol untuk mendeteksi pekerja yang mati atau diblokir

Haruskah 20,0 atau bisakah kita menundanya?

menunda.

Hei, jadi ini bukan bagian dari 20.0?

Itu mungkin waktu yang tepat untuk membuat batas waktu saat ini menjadi batas waktu permintaan yang tepat untuk setiap jenis pekerja.

diklarifikasi. @ lucas03 tidak jelas batas waktu permintaan apa yang ada. silakan buka tiket jika Anda membutuhkan sesuatu yang spesifik?.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat