Graceful Shutdown (Mati Secara Anggun) dan Cleanup (Bersih-bersih)
Kode di Listing 21-20 itu benar-benar udah merespons ke requests secara asinkron (asynchronously) melalui
penggunaan thread pool, sesuai sama apa yang kita rencanakan (intended). Tapi kita ngedapetin
beberapa pesan peringatan (warnings) soal fields workers, id, dan thread
yang nampaknya tidak kita pakai secara langsung, yang mana ngingetin (reminds) kita
kalau kita ini belum ngelakuin acara bersih-bersih (cleaning up) apa pun. Pas kita
memakai metode pencet tombol ctrl-C yang agak bar-bar (less elegant)
buat ngehentiin (halt) eksekusi si main thread, semua threads lain di dalamnya juga bakal
dihentiin seketika itu juga (immediately), biarpun mereka posisinya saat itu lagi di tengah-tengah
nugas (in the middle of) ngelayanin sebuah request.
Maka dari itu selanjutnya, kita bakal mengimplementasikan trait Drop buat manggil join
pada masing-masing threads di dalam pool tersebut supaya mereka bisa nyelesaiin
dulu (finish) requests yang lagi mereka kerjain sebelum akhirnya bener-bener ditutup (closing).
Baru deh setelah itu kita bakal mengimplementasikan sebuah cara buat ngasih tahu para threads
kalau mereka seharusnya berhenti nerima requests baru lalu mematikan diri (shut down).
Buat ngelihat aksi langsung dari kode ini, kita bakal memodifikasi server kita supaya dia
cuma nerima maksimal dua buah requests aja sebelum akhirnya secara anggun (gracefully)
mematikan semua proses di thread pool-nya.
Satu hal yang perlu diperhatiin (to notice) seiringan kita jalan: tidak ada satu pun dari proses ini yang bakal berdampak (affects) sama bagian-bagian kode yang tugasnya mengeksekusi (executing) closures, jadi segala hal yang ada di sini bakal tetep sama persis andaikata kita ini lagi memakai sebuah thread pool buat sebuah runtime async.
Mengimplementasikan Trait Drop pada ThreadPool
Mari kita mulai dengan mengimplementasikan Drop pada thread pool kita.
Pas si pool tersebut di-drop, seharusnya semua threads kita ini di-join (digabungin)
buat ngebuktiin dan mastiin (make sure) kalau mereka semua udah beres (finish) ngerjain
kerjaan mereka. Listing 21-22 nunjukin percobaan perdana (first attempt) buat bikin
implementasi Drop ini; tapi kode ini masih belum bakal jalan lho ya.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
worker.thread.join().unwrap();
}
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {id} got a job; executing.");
job();
}
});
Worker { id, thread }
}
}
Pertama-tama kita melakukan perulangan (loop) ngelewatin masing-masing elemen di workers
kepunyaan si thread pool. Kita memakai referensi &mut di sini karena self itu sendiri
kan emang sebuah referensi mutable (bisa diubah), dan kita juga perlu bisa ngubah (mutate)
si variabel worker. Buat masing-masing worker, kita mencetak sebuah pesan yang ngasih tahu
kalau instance spesifik Worker ini tuh lagi bersiap dimatikan (shutting down), dan baru
setelahnya kita manggil join pada nilai thread yang dimiliki instance Worker
tersebut. Kalau pemanggilan ke join ini ternyata gagal (fails), kita memakai unwrap
buat maksa Rust jadi panic dan masuk ke fase mati secara tidak anggun (ungraceful shutdown).
Ini dia error yang bakal kita dapat pas kita mencoba buat men-compile kode ini:
$ cargo check
Checking hello v0.1.0 (file:///projects/hello)
error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference
--> src/lib.rs:52:13
|
52 | worker.thread.join().unwrap();
| ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this method call
| |
| move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
|
note: `JoinHandle::<T>::join` takes ownership of the receiver `self`, which moves `worker.thread`
--> /rustc/1159e78c4747b02ef996e55082b704c09b970588/library/std/src/thread/mod.rs:1921:17
For more information about this error, try `rustc --explain E0507`.
error: could not compile `hello` (lib) due to 1 previous error
Pesan error ini ngasih tahu kita kalau kita itu tidak bisa manggil join gara-gara kita cuma
punya sebuah pinjaman mutable (mutable borrow) dari masing-masing worker sedangkan si
join ini mengambil hak kepemilikan (ownership) dari argumennya. Buat memecahkan
(solve) isu ini, kita perlu buat mindahin (move) si nilai thread tersebut keluar dari
instance Worker yang tadinya nge-hak miliki (owns) si thread itu supaya si
join bisa narik memakan (consume) thread tersebut. Salah satu cara buat melakukan ini
adalah dengan ngambil pendekatan (approach) yang sama kayak yang pernah kita lakuin di
Listing 18-15. Kalau si Worker ini tadinya nampung Option<thread::JoinHandle<()>>,
kita bisa aja manggil method take pada nilai Option tersebut buat mindahin (move) nilai
aslinya dari varian Some lalu ninggalin sebuah varian None buat menempati posisinya
(in its place). Dengan kata lain, sebuah Worker yang posisinya lagi jalan (running) bakal
punya sebuah varian Some di dalam thread, dan pas kita pengen ngebersihin (clean up)
sebuah Worker, kita bakal ngeganti si Some jadi None sehingga si Worker ini
tidak bakal punya thread buat dijalanin.
Namun masalahnya, momen di mana proses ini itu dibutuhkan (come up) satu-satunya cuma
terjadi saat kita lagi nge-drop si Worker aja kan. Konsekuensinya sebagai ganti rugi
(in exchange), kita jadinya mesti terus-terusan berurusan (deal with) sama tipe
Option<thread::JoinHandle<()>> di mana pun kita lagi nyoba ngakses worker.thread.
Penulisan bahasa Rust yang idiomatik itu emang cukup sering sekali (quite a bit) memakai
Option, tapi pas kita mulai nemuin diri kita lagi asik ngebungkusin (wrapping) sesuatu yang mana
padahal kita udah tahu (know) nilai itu tuh selalu ada dan eksis (present) ke dalam
sebuah Option cuma murni dijadiin sekadar jalan pintas buat ngakalin (workaround) hal macem
gini, maka ini merupakan tanda ide yang bagus buat mulai nyari pendekatan alternatif
(alternative approaches) supaya bisa ngebikin kode kita lebih rapi (cleaner) dan tidak
gampang rawan error (less error-prone).
Di kasus yang ini, sebenernya ada jalan alternatif yang lebih oke (better alternative):
yaitu method Vec::drain. Method ini menerima sebuah parameter jarak (range parameter) buat
menentukan items mana aja yang mau dihilangkan dari dalam vector tersebut lalu dia bakal
mengembalikan (returns) sebuah iterator dari item-item yang ditarik itu. Dengan memberikan
sintaks range .. kita bakal mengosongkan dan menghilangkan semua (every) nilai dari vector tersebut.
Jadi kita perlu meng-update implementasi drop untuk ThreadPool supaya jadi kayak gini:
#![allow(unused)]
fn main() {
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in self.workers.drain(..) {
println!("Shutting down worker {}", worker.id);
worker.thread.join().unwrap();
}
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {id} got a job; executing.");
job();
}
});
Worker { id, thread }
}
}
}
Kode ini ngeberesin dan nyelesein pesan error compiler tadi tanpa perlu adanya (require)
perubahan lain lagi ke dalem kode kita. Perhatikan kalau, karena drop bisa aja dipanggil pas
lagi masa-masa terjadi kepanikan (panicking), si unwrap ini juga bisa aja tiba-tiba ikutan panic
yang mana akhirnya nyebabin kepanikan tumpuk ganda (double panic), yang mana bakal langsung
membikin programnya crash (rusak) dan secara terpaksa ngakhirin semua proses pembersihan
(cleanup) yang lagi berjalan. Buat ukuran program contoh kayak gini, tindakan panic ini sah-sah aja (fine),
tapi hal kayak gini ya tidak disarankan (isn’t recommended) lho buat kode di level produksi.
Mengirim Sinyal ke Threads Supaya Berhenti Mendengarkan Jobs (Tugas) Baru
Dengan serangkaian perubahan yang udah kita bikin barusan, kode kita sekarang udah bisa
sukses di-compile tanpa ngeluarin peringatan apa-apa. Namun, kabar buruknya adalah
kalau kode ini itu masih belum (doesn’t function) berjalan pakai cara yang kita harepin. Kunci permasalahannya ada di
logika yang ada di dalem closures yang lagi dijalanin (run by) sama threads kepunyaan
instances si Worker: saat ini, kita emang udah manggil join, tapi hal itu tidak
bakal bisa nutup mematikan (shut down) si threads ini lho, soalnya mereka itu pada asyik
berputar (loop) nyari kerjaan (looking for jobs) buat selama-lamanya (forever). Kalau
kita coba buat nge-drop si ThreadPool kita ini pakai implementasi drop kita yang
sekarang, main thread-nya bakal malah ikutan mandek keblokir (block forever), diam selamanya nungguin thread
yang pertama itu kelar (finish) yang mana tidak bakal bisa kelar.
Buat mbetulin (fix) masalah ini, kita perlu ngebikin satu ubahan (change) di dalam implementasi drop buat ThreadPool
dan abis itu juga butuh satu ubahan di dalem perulangan (loop) si Worker.
Pertama-tama kita bakal ngubah implementasi drop pada ThreadPool supaya dia secara eksplisit nge-drop
si sender (pengirim) ini sebelum dia nungguin para threads-nya pada kelar jalan. Listing 21-23
nunjukin rupa ubahan-ubahan ke ThreadPool tersebut buat secara eksplisit nge-drop sender. Tidak kayak
yang terjadi di thread, di sini kita emang benar-benar butuh (do need) memakai
Option biar kita bisa memindahkan (move) variabel sender keluar dari
ThreadPool dengan memanggil Option::take.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
// --snip--
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
// --snip--
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(sender),
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop(self.sender.take());
for worker in self.workers.drain(..) {
println!("Shutting down worker {}", worker.id);
worker.thread.join().unwrap();
}
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {id} got a job; executing.");
job();
}
});
Worker { id, thread }
}
}
sender sebelum nge-join para threads WorkerTindakan men-drop (dropping) sender ini otomatis bakal nutup (closes) channel-nya, yang mana mengindikasikan kalau
tidak bakal ada lagi pesan (messages) baru yang bakal dikirimin. Saat momen itu benar-benar terjadi,
semua pemanggilan (calls) ke recv yang lagi dikerjain sama instances si Worker di dalam infinite
loop (perulangan tiada henti)-nya bakal otomatis nge-return sebuah pesan error. Di Listing 21-24,
kita ngubah bagian perulangan Worker supaya dia mau keluar (exit the loop) dengan anggun (gracefully)
di dalam skenario kayak gitu (in that case), yang berarti threads-nya ini akhirnya benar-benar
bisa kelar (finish) pas implementasi drop milik ThreadPool manggil join
ke mereka-mereka semua.
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(sender),
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop(self.sender.take());
for worker in self.workers.drain(..) {
println!("Shutting down worker {}", worker.id);
worker.thread.join().unwrap();
}
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let message = receiver.lock().unwrap().recv();
match message {
Ok(job) => {
println!("Worker {id} got a job; executing.");
job();
}
Err(_) => {
println!("Worker {id} disconnected; shutting down.");
break;
}
}
}
});
Worker { id, thread }
}
}
recv nge-return sebuah errorBuat bisa ngelihat sendiri aksi kode (code in action) ini benar-benar berjalan, mari kita modifikasi (modify) main
supaya dia ini cuma nerima batas (accept only) dua requests aja sebelum akhirnya dia nutup
(shutting down) si server-nya dengan anggun (gracefully), kayak yang ditunjukin di Listing 21-25.
use hello::ThreadPool;
use std::{
fs,
io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
for stream in listener.incoming().take(2) {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
println!("Shutting down.");
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&stream);
let request_line = buf_reader.lines().next().unwrap().unwrap();
let (status_line, filename) = match &request_line[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "hello.html")
}
_ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
};
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
let response =
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
stream.write_all(response.as_bytes()).unwrap();
}
kita pastinya sangat tidak mau ngarep (wouldn’t want) kalau sebuah web server betulan di dunia nyata tiba-tiba mati nutup sendiri (shut down) sesudah cuma ngelayanin dua buah requests doang. Tapi kan kode ini itu cuma murni (just demonstrates) dibikin buat pamer ngedemoin kalau kemampuan matikan proses secara anggun (graceful shutdown) dan fitur bersih-bersihnya (cleanup) emang benar-benar udah bisa kerja lancar beroperasi (in working order).
Method take ini aslinya udah didefinisikan secara bawaan di dalem trait Iterator
yang mana dia berfungsi ngebatesin (limits) iteration tersebut supaya paling banter (at most) dia
itu cuma muter buat ngerjain dua item pertama (first two items) doang. Terus, si ThreadPool ini jadinya
bakal terpaksa dihempaskan keluar dari scope (go out of scope) pada momen titik paling
ujung batas akhir dari fungsi main, dan otomatis implementasi fungsi drop miliknya bakal segera dioperasikan (will run).
Silakan nyalakan kembali (start) si server ini pakai instruksi cargo run, terus cobain bikin tiga (three) buah requests
masuk ke sana. Pemanggilan request yang ketiga ini harusnya langsung ngebentur pesan error (should error),
dan terus di dalem layar terminal kita itu kita kudu ngelihat tulisan keluaran (output)
yang lumayan kelihatan persis mirip-mirip (similar) kayak ini:
$ cargo run
Compiling hello v0.1.0 (file:///projects/hello)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/hello`
Worker 0 got a job; executing.
Shutting down.
Shutting down worker 0
Worker 3 got a job; executing.
Worker 1 disconnected; shutting down.
Worker 2 disconnected; shutting down.
Worker 3 disconnected; shutting down.
Worker 0 disconnected; shutting down.
Shutting down worker 1
Shutting down worker 2
Shutting down worker 3
kita emang sangat mungkin ngelihat kalau rupa dari urutan-urutan (ordering) barisan teks Worker ID
dan tulisan pesannya yang nampil ke layar ini agak beda-beda tatanannya. Kita
bisa perhatiin dengan jelas (can see) gimana dalemnya proses cara kerja kode ini dari bacaan pesannya
(messages) tersebut: Instances si Worker urutan 0 dan 3 ternyata beruntung dapet
nyaplok ngerjain (got) kedua belah requests rentetan awal. Terus si server ini mulai ngadat berhenti dan ogah ngerima
(stopped accepting) adanya sambungan koneksi (connections) yang baru pas tepat habis selesai
ngurus koneksi yang ke-dua tadi, dan si implementasi metode Drop yang nempel pada si ThreadPool pun
langsung gas tancap jalan (starts executing) duluan padahal faktanya si Worker 3 aja malahan belom kelar nyalain (even starts)
pekerjaannya. Kejadian di-drop-nya (dropping) sender ini langsung tanpa tedeng aling-aling melepaskan putus (disconnects)
sambungan jembatan tali komunikasi ke semua penjuru instances si Worker
lalu lantang nyuruh ngomando ngasih tahu (tells) mereka biar pada mingser bubar barisan nutup layanan (shut down).
Tiap instances Worker masing-masing terus nyaut nge-print sebuah message saat tali
mereka terputus dilepas (disconnect), dan abis beres itu semua, si thread pool tadi gantian secara mandiri bergegas manggil
method join lurus dengan satu-satunya niat mau diem duduk setia menunggu (wait) supaya
setiap barisan satu per satu dari Worker thread kelar (finish) ngerampungin pekerjaannya.
Coba teliti merhatikan (notice) adanya satu aspek detail perlakuan (aspect) yang lumayan kerasa asik unik
(interesting) di balik serangkaian rentetan rupa spesifik proses eksekusi (execution) satu ini: di mana
si ThreadPool udah keburu kelar nge-drop si sender, dan bahkan sebelum sempet ada instances
Worker mana aja yang nangkep ngerima kode error (received an error), kita malahan udah ngebikin status program kita ini buat maksa nyoba nggabung
(join) ke Worker urutan 0. Waktu momen itu ya, si Worker 0 benar-benar belum ada dapetin sinyal kabar tangkepan error
apa-apa (not yet gotten an error) yang mana berasal dari tarikan recv, jadinya ya benar-benar wajar aja sih kalau si otak pusat jalan main thread-nya ini terpaksa mandeg macet diem nge-blok di jalan
(blocked), ngaso setia sembari nungguin si Worker 0 supaya ngerampungin pekerjaannya sampai tuntas (finish).
Eh pas lagi asik nunggu itu (In the meantime), si Worker 3 yang udah ketiban ngerima jatah mandat narik dapet
(received) satu butir kerjaan (a job) yang selanjutnya diikuti oleh riwayat sisa-sisa para threads sekaliannya yang ikutan kebagian serentak seragam dapet nerima cipratan pesan eror (error).
Terus nah pas begitu si Worker 0 kelar tuntas ngerjain jatah lapaknya (finished), si otak tengahnya (main thread) ini lantas lanjut ngecoba
nungguin antrean jejeran gerbong serombongan sisa komplotan (the rest) punggawa instances Worker ini biar pada cepetan (to finish) juga ikutan kelar nutup lapaknya. Pada pas
tibanya waktu masa (at that point) tersebut, eh ternyata semuanya tanpa basa-basi emang udah benar-benar pada
tuntas cabut ngeluarin diri (exited) berhamburan dari daleman siklus lingkar putaran (loops) rute hidup mereka itu lalu udah berhenti total dari segala aktivitas hidup (stopped).
Selamat ya (Congrats)! Kita bener-bener udah sanggup nyampe di titik purna ngerampungin project ini secara utuh (completed); kita sekarang ini udah sah punyain (have) sepenggal program web server murni kelas cetek mendasar (basic) yang aslinya juga udah bisa jalan gagah memanfaatkan pengerahan tenaga tatanan sekelompok balok (thread pool) demi sanggup ngeresponi secara sigap lalu melayani sambutan balik secara asinkron tak serentak (asynchronously). Kita udah sangat terbukti mampu benar-benar melancarkan (perform) atraksi sebuah tarian purna pemutusan nafas nyawa graceful shutdown terhadap si komponen server tersebut, yang mana benar-benar menuntaskan lalu ngebersihin angkat tuntas ngurus sisaan barang bongkaran kotoran riwayat sampah kelakuan (cleans up) riwayat para seantero rentetan threads di ruang bilik pool ini.
Nih ditaruh rupa segenap rincian barisan kode final seutuhnya (full code) secara utuh komplit di sini sebagai referensi pedoman (reference) ya:
use hello::ThreadPool;
use std::{
fs,
io::{BufReader, prelude::*},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
for stream in listener.incoming().take(2) {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
println!("Shutting down.");
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&stream);
let request_line = buf_reader.lines().next().unwrap().unwrap();
let (status_line, filename) = match &request_line[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
("HTTP/1.1 200 OK", "hello.html")
}
_ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
};
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
let response =
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
stream.write_all(response.as_bytes()).unwrap();
}
use std::{
sync::{Arc, Mutex, mpsc},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(sender),
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
drop(self.sender.take());
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
loop {
let message = receiver.lock().unwrap().recv();
match message {
Ok(job) => {
println!("Worker {id} got a job; executing.");
job();
}
Err(_) => {
println!("Worker {id} disconnected; shutting down.");
break;
}
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}
Kita emang nyatanya masih sangat bisa sih buat ngerjain (could do more) lebih jauh dan memodifikasi lebih heboh hal-hal lainnya lagi di tempat ini lho! Kalau seumpamanya emang hasrat di hati emang kita kepengen (want to) buat sudi terus merutinkan niat maju ngelanjut (continue) nambahin memoles (enhancing) gubahan kerangka arsitektur project ini jadi makin keren lagi mantep ke depannya, ini di bawah disediain list segelintir barisan rupa corak deretan bayangan pencerahan curhatan ide (some ideas) racikan kreasi mantap:
- Lengkapin dan imbuhin rentetan sekelumit tambalan isi dari dokumentasi tambahan (more documentation) buat struct
ThreadPoolbeserta metode-metode rentetan methodpublicmiliknya juga ya. - Coba isengin rakit bikinin sisipin sederet rentetan program tes asinkron (tests) buat nguji kemantapan jalan kelakuan isi fungsionalitas (functionality) jeroan library-nya ini.
- Ganti rombak dan obrak-abrik rentetan pemanggilan wujud rupa
unwrapyang kelewat asal nabrak paksa njerit ini supaya berubah format menjadi bentukan rentetan taktik kelakuan pengurusan error (error handling) yang karakternya terasa sifat jauh lumayan lebih kokoh perkasa pantang rontok (robust). - Manfaatin fitur keberadaan balok struktur
ThreadPoolini untuk sengaja nyobain ngelaksanain rentetan panggilan tasks rutinitas yang bentukannya ini murni lain di luar formatnya sekadar menugas melayani (serving) serapan requests dari kancah rupa lalu-lalang aliran arus penjelajahan halaman dunia maya (web requests). - Sisihkan cari lirik bongkar-bongkar iseng nyari (Find) bentukan tipe produk paket rak buku kreasi buatan anak lain dari komunitas (thread pool crate) bertebaran liar pating mencelat lepas terpajang bebas melenggang kangkung rilis tebar nongol tersedia gratis pampang mejeng tayang di galeri pustaka etalase repositori perpustakaan lapak kerdus bebas gratis wadah pasar kumpulan crates.io sono terus pasang dan terapkan ngrakit lalu implement rupa susunan formasi wujud web server yang gaya alur kelakuan pola lagaknya miripin sebelas-dua-belas percis plek kayak (similar) gubahan hasil rancangan ini dengan murni nekat sekadar sepenuhnya murni berpatokan menunggang makek library produk kreasi bikinan orang laen the crate instead tersebut doang. Barulah (Then) setelah tuntasnya praktek pembuktian tsb. kelar disituasi tersebut silahkan monggo silakan (compare) coba banding-banding rupa rentetan kerangka rute API miliknya library itu sama wujud tingkat derajat kekuatan ketahanan banting kokoh (robustness) ketangguhan tahan uji banting tangguhnya punya dia dengan diukur diselidik secara adil membedakan dan aduin lurus dibandingin langsung dipajang sejajar teliti diseret ke hadapan dan disejajarkan ngebentur ke thread pool rakikan tangan pribadi asli mandiri punya yang baru kemaren udah sempet kita implement bikin bangun praktekin dari kemaren itu.
Ringkasan (Summary)
Mantap (Well done)! Kita udah berhasil nyampe tuntas tembus tamat nyentuh ujung akhir pucuk paling buntut sampul ujung (end) dari seri halaman buku ini! Kita semua dari diri kami di sini ini secara pribadi kepengen bener sekali pingin ngelempar sepatah kata berterima kasih sedalam-dalamnya buat ngaturin wujud hormat salam terima kasih tulus buat panjenengan-panjenengan semua yang udah ikhlas (thank you for) ngabisin waktu ngikut jalan jejer iring bersamai (joining) kita-kita nyusurin merambat rute panjang pelesiran perjalanan (tour) petualangan liburan berburu pesona Rust ini sedari titik garis pinggir batas mula dulu. Kini emang pastinya seutuhnya (now) kita ini udah sepenuhnya dirasa matang dan bener-bener dirasa dipastikan udah sangat siap sanggup sedia (ready to) langsung nge-gass terjang nerapin ngimplementasiin sendiri kreasi murni orisinil pribadi gagasan wujud gubahan deretan gagasan tatanan project rintisan (projects) program rill asli bikinan sendiri punya kita yang berbasis ngusung bendera teknologi sistem Rust lalu sekalian sudi dan rela rela buat ngeringanin naruh bantu campur turut menaruh campur tenaga ulur ngasih derma tangan ngebantu nambahin andil campur gotong berderma (help with) berkontribusi di gubahan wujud program garapan rintisan projects sumbangsih kreasi racikan buatan anak punggawa temen sejawat people’s projects (orang-orang) pahlawan tetangga sanak rupa saudara kita di sekitar sana. Harus dimasukin paksa (Keep in mind) diranap di simpen lekat ingatan tanam patri ke dalem nalar benak kepala ingat-ingat simpan resapin ya di ingatan (mind) dalem bawah sadar otak memori paten benak kepalamu ini andaikata (that) ini tuh sesungguhnya nyata terang bersinar nyata terang benderang niscaya terbentang terbentang mekar membentang subur emang masih menyisa (there is a) terhuni rupa sepetak jajaran luas gerombolan wadah kerumunan kelompok perhimpunan welcoming community (komunitas yang sangat terbuka nyambut ramah dan sedia hangat menjamu rupa tangan senyum terbuka gembira lebar dada tulus nyambut seneng asri hangat peluk) persaudaraan ikatan erat rupa serumpun bangsa perkumpulan perserikatan komplotan jejaring other Rustaceans (kalangan para pengabdi programmer pemuja aliran seiman setia pejuang pendekar kode penyuka aliran kepercayaan kasta Rust ksatria lain-lainnya sesama punggawa yang sealiran) lainnya di pelosok buana pelosok sana luar sekitar pojokan pelosok dunia sana ini yang tak ada bosannya tak kan bakal pernah nyerah sudi ikhlas niscaya aslinya emang would love to (pada seneng rela hati bakal cinta mati gemar nyenengin cinta ngebet pengen gemar dan hobi pake cinta bahagia hepi benar-benar girang tulus girang rela asyik seru sumringah demen kepingin riang berhati tulus bakal doyan bakal suka bakal sayang dan sangat girang sekali teramat rindu sangat ingin mau rela) ikhlas turun nolong bantu menyambut nyuapin nyuapin kasih (help you with) membina mandu memayungi mendampingin mandorin nyodor tangan mbopong mbimbing nolong kita-kita pada ikhlas terjun dan andil nimbrung bantuin mberesin dan nyikat beresin mengurus sedia ngadepin dan ngeberesin (any challenges) seberapa parah gawatnya semua aneka segudang rupa kendala aral ragam himpitan halang aneka lika rupa jurang duri kesulitan ujian badai cobaan halangan ragam perkara problema jerat tikungan batu cadas tantangan rintang terjalan problem himpitan perkara ganjalan segenap segala seberat sedempet serepot rupa kendala seisi sebentang onak seisi segala segenap (any) ragam masalah kendala benturan apa jua sekalipun belaka apa pun bentuknya rupa aja yang di jalan nanti kebetulan emang rupa nyata riil fakta nyatanya you encounter (kita tabrak kepentok dapati tabrak papasi bersua sandung hantam ketatap kebentur derita alami jumpa dapet tebas terjang lalui pergokin tempuh alami libas gilas temui tabrak lewati hantam rasakan langgar cicipin terjang tatap derita lintasi rasai cicip rasain hadapi cicip deritain hadapi hadapi) ke-tubruk kepentok bersua melintang terlintas nyandung hadap temui hadapi sandung kita di belantara sepanjang masa-masa pelayaran peruntungan jalan pendakian jalur petualang perjalanan lintas jalur rute jelajah ngeluyur (journey) pengembaraan karir kiprah rekam pelesiran perjalanan Rust (kancah rute perjalanan karir Rust) asuhan panjenengan kita seiring maju langkah laju menapaki jejak berjejak kita maju panjang menjejak merentas jauh berjalan ini di waktu ke detik harinya nanti menjejak (your Rust journey).