Masalah Port Collision (EADDRINUSE) pada Cargo-Watch Standar
Pengembangan API menggunakan Actix Web umumnya memanfaatkan cargo-watch untuk memantau perubahan file dan menjalankan ulang binary secara otomatis via cargo watch -x run. Kendala muncul ketika proses lama belum tuntas melepaskan socket TCP sementara binary baru hasil kompilasi sudah mencoba melakukan bind() ke port yang sama. Hasilnya adalah crash runtime dengan pesan kesalahan Os { code: 98, kind: AddrInUse, message: "Address already in use" } (EADDRINUSE).
Kondisi race condition ini merusak ritme pengembangan karena developer harus menghentikan proses secara manual, menunggu alokasi socket OS kembali bebas, atau mencari dan membunuh PID proses Rust yang menggantung.
Solusi: Socket Activation via Systemfd dan Listenfd
Socket activation memisahkan kepemilikan listening socket dari lifecycle aplikasi. Alih-alih aplikasi Actix Web yang membuat dan membuka socket TCP, tool eksternal (systemfd) bertindak sebagai supervisor yang membuka socket sekali di awal dan menahannya tetap terbuka di level sistem operasi.
Ketika cargo-watch mengompilasi ulang dan meluncurkan binary baru, file descriptor (FD) dari socket yang sudah terbuka diteruskan langsung ke proses Actix Web melalui variabel lingkungan. Actix Web membaca FD tersebut via crate listenfd. Dampaknya: tidak ada proses rebinding ke network card, tidak ada downtime port lock, dan request HTTP yang masuk saat kompilasi berlangsung akan ditampung di kernel buffer antrean socket alih-alih ditolak dengan status connection refused.
Implementasi Kode Actix Web dengan Fallback Bind
Tambahkan crate listenfd ke dalam konfigurasi dependensi proyek:
# Cargo.toml
[dependencies]
actix-web = "4.9"
listenfd = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Perbarui entry point aplikasi pada src/main.rs agar memprioritaskan penerimaan file descriptor dari socket activator. Jika file descriptor tidak ditemukan (seperti saat berjalan di environment production atau container Docker biasa), sistem otomatis fallback menggunakan bind() standar:
// src/main.rs
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use listenfd::ListenFd;
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hot reload Actix Web aktif!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut listenfd = ListenFd::from_env();
let mut server = HttpServer::new(|| {
App::new().service(index)
});
// ponytail: fallback port hardcoded; upgrade ke crate config/env vars saat butuh konfigurasi dinamis.
server = match listenfd.take_tcp_listener(0)? {
Some(listener) => {
println!("Running via socket activation (systemfd)");
server.listen(listener)?
}
None => {
let host = "127.0.0.1:8080";
println!("Running via standard bind: {}", host);
server.bind(host)?
}
};
server.run().await
}
Instalasi Tooling dan Eksekusi Development Workflow
Instalasi utility systemfd dan cargo-watch via cargo CLI lokal:
cargo install systemfd cargo-watch
Jalankan server development dengan merantai systemfd ke cargo-watch:
systemfd --no-pid -s http::8080 -- cargo watch -x run
Penjelasan flag command:
-s http::8080: Membuka socket TCP pada port 8080 dan mengarahkannya ke file descriptor pertama (index 0).--no-pid: Mencegah error matching PID pada platform yang mendaur ulang proses secara agresif.-- cargo watch -x run: Menjalankan worker watcher untuk mengompilasi dan mengeksekusi binary baru setiap ada perubahan file source code.
Verifikasi Alur Kerja dan Batasan Sistem
Untuk menguji alur tanpa error port collision:
- Jalankan server melalui perintah
systemfddi atas. - Akses endpoint via terminal:
curl http://127.0.0.1:8080/. - Ubah return string pada handler
indexdisrc/main.rsdan simpan file. - Perhatikan terminal:
cargo-watchmendeteksi diff, mengompilasi ulang binary, dan langsung menyajikan versi baru tanpa ada errorEADDRINUSE.
Batasan dan Pertimbangan Arsitektur
- Waktu Kompilasi Rust Tetap Berlaku: Socket activation hanya mengeliminasi socket rebinding overhead dan port collision, bukan mempercepat waktu kerja
rustc. Gunakan linker cepat sepertimoldataulldjika incremental compilation masih lambat. - Ephemeral In-Memory State: Setiap reload akan mematikan proses lama dan me-reset state in-memory (misalnya hash map lokal atau instance Actix
web::Datayang tidak persisten). Gunakan database eksternal atau Redis untuk state yang persisten antar reload. - Isolasi Tooling:
systemfdhanya ditujukan untuk environment development lokal. Di server staging/production berbasis Linux systemd, socket activation native systemd dapat digunakan langsung tanpa utility tambahan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!