Request accept biasa memiliki pola satu banding satu: satu operasi yang dikirim pada akhirnya menghasilkan satu completion. io_uring multishot accept mengubah hubungan tersebut. Satu SQE accept dapat tetap aktif untuk banyak koneksi masuk dan menghasilkan completion queue entry terpisah bagi setiap socket yang diterima.
Request itu persisten, tetapi tidak permanen. Setiap CQE membawa state yang memungkinkan aplikasi menentukan apakah request awal masih dapat menghasilkan completion berikutnya. Boundary ini penting karena server yang menganggap setiap CQE sukses sebagai bukti bahwa accept masih aktif dapat berhenti menerima koneksi secara diam-diam setelah request multishot berakhir.
Satu SQE dapat menghasilkan banyak CQE
io_uring_prep_multishot_accept() menyiapkan operasi accept yang dapat selesai berulang kali. Untuk setiap koneksi yang diterima, hasil CQE berisi file descriptor yang baru dipasang.
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_multishot_accept(
sqe,
listen_fd,
NULL,
NULL,
0
);
sqe->user_data = ACCEPT_TOKEN;Hubungan antarkuenya berbeda dari operasi one-shot:
submission queue completion queue
multishot accept SQE -------> fd 41, MORE
\----> fd 42, MORE
\---> fd 43, MORE
\--> CQE final, tanpa MOREIni bukan batch koneksi yang dikumpulkan saat submission. Kernel mempertahankan request accept tetap aktif dan mem-posting completion ketika connection request masuk, sampai operasi mencapai kondisi terminasi.
Persistensi tersebut mengurangi kebutuhan untuk menyiapkan dan mengirim SQE accept pengganti setelah setiap koneksi sukses. Konsekuensinya, pencatatan lifecycle juga berubah: jumlah submission tidak lagi setara dengan jumlah completion.
IORING_CQE_F_MORE adalah sinyal lifecycle
Field flags pada setiap struct io_uring_cqe membawa IORING_CQE_F_MORE selama request multishot masih dapat menghasilkan CQE berikutnya. Completion tanpa flag tersebut menandai akhir request itu.
struct io_uring_cqe *cqe;
while (io_uring_peek_cqe(&ring, &cqe) == 0) {
bool active = cqe->flags & IORING_CQE_F_MORE;
if (cqe->res >= 0)
handle_connection(cqe->res);
else
handle_accept_error(cqe->res);
if (!active)
arm_multishot_accept_again();
io_uring_cqe_seen(&ring, cqe);
}Flag tersebut perlu diperiksa secara independen dari hasil yang sukses. Invariant yang tepat bukan “sebuah koneksi diterima, maka request masih aktif.” Invariant-nya adalah “CQE menyatakan bahwa completion lain masih dapat menyusul.”
Pemisahan ini juga mencegah kesalahan bookkeeping pada CQE terakhir. Setelah CQE tiba tanpa IORING_CQE_F_MORE, SQE awal telah selesai. Server yang masih memerlukan operasi accept harus mengirim request baru.
Identitas completion tetap milik request
Semua completion yang dihasilkan satu request multishot mempertahankan identitas request yang diberikan melalui user_data. File descriptor hasil accept dikembalikan dalam cqe->res; user_data tetap mengidentifikasi request accept logis.
Pemisahan itu berguna pada ring yang membawa berbagai jenis pekerjaan:
user_data = ACCEPT_TOKEN
cqe->res = 41
flags = MORE
user_data = READ_TOKEN_FOR_CONN_7
cqe->res = 2048
flags = ...
user_data = ACCEPT_TOKEN
cqe->res = 42
flags = MOREDispatcher dapat mengarahkan CQE berdasarkan user_data, lalu menafsirkan res sesuai tipe operasinya. Untuk multishot accept, satu identitas request dapat muncul berkali-kali. Kode yang menganggap user_data habis setelah CQE pertama tidak sesuai dengan model ini.
Hal yang sama berlaku untuk memori milik request. State yang terkait dengan request accept tidak dapat dilepas setelah completion sukses pertama jika CQE berikutnya masih merujuk ke operasi logis yang sama.
Error mengakhiri rangkaian aktif
Persistensi multishot tidak mengubah setiap kegagalan accept menjadi event yang dapat dipulihkan di dalam request yang sama. Ketika operasi berakhir, CQE terakhir datang tanpa IORING_CQE_F_MORE. Hasil error dilaporkan langsung sebagai nilai errno negatif dalam cqe->res, mengikuti konvensi completion io_uring.
Aplikasi karena itu memiliki dua keputusan terpisah:
cqe->res
|
+-- >= 0 -> descriptor hasil accept tersedia
|
+-- < 0 -> operasi melaporkan error
cqe->flags
|
+-- MORE -> request awal masih aktif
|
+-- tanpa MORE -> request awal telah selesaiMemperlakukan kedua field itu secara terpisah menghasilkan state machine yang lebih jelas. res menjelaskan completion saat ini; IORING_CQE_F_MORE menjelaskan apakah request multishot asal masih berlanjut.
Cancellation mengikuti boundary lifecycle yang sama. Request multishot dapat menjadi target mekanisme cancellation io_uring. Setelah dihentikan, rangkaian accept tidak lagi menghasilkan CQE koneksi dan harus diaktifkan kembali secara eksplisit jika listener masih perlu menerima koneksi.
Direct accept mengubah lokasi descriptor
io_uring juga menyediakan varian multishot accept yang menempatkan socket hasil accept ke direct descriptor table milik ring. Direct descriptor bersifat privat untuk io_uring, bukan entry pada file descriptor table normal milik proses.
Dengan io_uring_prep_multishot_accept_direct(), direct descriptor yang dialokasikan secara dinamis dapat dikembalikan melalui CQE. Ini mengubah lokasi pemasangan socket, tetapi tidak menghapus aturan lifecycle multishot: aplikasi tetap memeriksa IORING_CQE_F_MORE untuk menentukan apakah completion berikutnya masih dapat datang dari request tersebut.
Perbedaannya bersifat arsitektural:
multishot accept
|
+-- varian regular -> file descriptor table proses
|
+-- varian direct -> direct descriptor table io_uringDirect descriptor dapat mengurangi interaksi dengan descriptor table yang dibagi antarthread pada workload yang mempertahankan I/O berikutnya di dalam io_uring. Model ini juga memiliki pengelolaan resource berbeda, termasuk kapasitas registered file table. Lokasi descriptor dan persistensi request merupakan dua pilihan yang terpisah.
Kapasitas CQ menjadi bagian dari kapasitas accept
Request accept persisten dapat menghasilkan CQE lebih cepat daripada kemampuan aplikasi mengonsumsinya. Karena itu, ukuran completion queue dan perilaku draining menjadi bagian dari jalur admission server.
Request multishot menghilangkan submission SQE berulang dari steady-state accept loop, tetapi tidak menghapus pekerjaan setelah accept. Setiap koneksi tetap menghasilkan completion yang harus dikonsumsi, descriptor yang harus dimiliki atau ditutup, serta state aplikasi yang mungkin perlu dialokasikan.
koneksi masuk
|
v
kernel accept path
|
v
CQE menumpuk
|
v
laju drain aplikasiJika aplikasi terhenti, tekanan berpindah ke jalur completion dan resource yang terkait dengan socket hasil accept. Interface multishot mengubah frekuensi submission; interface tersebut tidak membuat penanganan koneksi menjadi tanpa batas.
Urutan shutdown juga menjadi eksplisit. Server dapat menghentikan admission baru dengan membatalkan multishot accept, mengonsumsi completion terminal, lalu melepas state milik request. Menutup atau menguras koneksi yang sudah ada merupakan fase terpisah.
Persistent accept mengubah control loop
Pada one-shot accept, control loop biasanya bergantian antara completion dan resubmission. Multishot accept memindahkan rearming keluar dari steady state yang sukses dan menempatkannya pada jalur terminasi.
one-shot:
submit -> accept CQE -> submit -> accept CQE -> submit
multishot:
submit -> accept CQE -> accept CQE -> accept CQE
|
v
CQE terminal
|
v
resubmitLoop submission yang lebih kecil merupakan perubahan semantik utamanya. Aplikasi tidak lagi menangani rearming setelah setiap koneksi, tetapi tetap bertanggung jawab atas deteksi terminasi, konsumsi CQ, lifetime descriptor, cancellation, dan recovery.
Boundary yang dapat diandalkan adalah CQE terakhir tanpa IORING_CQE_F_MORE. Semua completion sebelumnya termasuk dalam satu request yang masih hidup; setelah boundary itu, operasi accept baru memerlukan submission baru.
Referensi
- Linux
io_uring_multishot(7): https://man7.org/linux/man-pages/man7/io_uring_multishot.7.html - Linux
io_uring_prep_accept_direct(3): https://man7.org/linux/man-pages/man3/io_uring_prep_accept_direct.3.html - Linux
io_uring(7): https://man7.org/linux/man-pages/man7/io_uring.7.html