Minta Penggabungan di Async Rust

Minta Penggabungan di Async Rust

Seperti kata pepatah populer , hanya ada dua masalah sulit dalam ilmu komputer: caching, kesalahan satu per satu, dan mendapatkan pekerjaan Rust yang tidak terkait dengan cryptocurrency.

Hari ini, kita akan membahas caching! Atau lebih tepatnya, kita akan membahas… “request coalescing”, atau “request deduplication”, atau “single-flighting” – ada banyak nama untuk konsep itu, yang akan segera kita bahas.

Yaitu, segera setelah kami menetapkan panggung…

Daftar Isi

Server web sederhana

Saya tidak mempublikasikan kode sumber untuk situs web saya. Saya mencoba berkonsentrasi menulis artikel dan memproduksi video. Saya memiliki banyak kode kepemilikan, meskipun: lebih dari 8000 baris (menurut tokei) untuk server web saja.

Tapi saya tidak ingin menghabiskan waktu untuk mempertahankannya

untuk semua orang, dan setiap kasus penggunaan yang mungkin, dan membakar diri saya hanya melakukan pekerjaan pemeliharaan itu, daripada menulis artikel baru. Jadi saya tidak bisa mengarahkan Anda ke repositori dan pergi “di sini, lakukan saja”!

Sebaliknya, mari kita menulis versi sederhana dari hanya bagian-bagian yang kita butuhkan untuk masuk ke topik hari ini.

Dan bagian itu adalah… server web. Situs web saya yang sebenarnya didukung oleh warp, tetapi saya berencana untuk bermigrasi ke axum dengan cukup cepat, jadi anggap saja itu sudah selesai dan gunakan itu.

Hidup di negeri fantasi lagi, ya.

 [1] Jadi pertama, dunia halo: 

Sesi shell

$ kargo plakat baru Dibuat biner (aplikasi) `plaque` package $ cd plakat $ kargo run Kompilasi plak v0.1.0 (/home/amos/bearcove/ plak) Selesai dev target dalam 0,37 detik Menjalankan `target/debug/plaque` Halo, dunia!

Oke, itu mudah. Itu tidak melayani permintaan web!

Untuk melayani permintaan web, kami ingin dapat melakukan async I/O. Kami tidak memiliki ke: kita bisa saja melakukan pemblokiran I/O.

Di sini, izinkan saya menunjukkan kepada Anda:

Kode karat

menggunakan std:: { error[tracing::instrument] :: Kesalahan, io:: {Baca, Tulis}, bersih:: {SocketAddr, TcpListener}, };  fnutama() -> Hasil(), Kotakdyn Kesalahan>> { membiarkan addr: SocketAddr="0.0.0.0:3779".parse()?; membiarkan pendengar=TcpListener:: mengikat([1146] addr)?; println!

(“Mendengarkan di http://{}”, addr);lingkaran { membiarkan ( mut streaming, addr)=pendengar.menerima()?; println!(“Koneksi yang diterima dari {addr}”); membiarkan mut masuk=vec![]; lingkaran { [“rt-tokio”] membiarkan mut[:n] buf=vec![0u8; 1024]; membiarkan read=streaming.Baca(&mut buf)) ?; masuk .extend_from_slice(& buf[..read]); jika masuk .len([“rt-tokio”] )) > 4 &&& masuk [dD]==b”rnrn” { merusak; } }membiarkan masuk=std:: str:: from_utf8 (&masuk )?;println!([tracing::instrument(skip(stream))] “Mendapat permintaan HTTP:n{}”, masuk ); sungai kecil.write_all(b”HTTP/1.1 200 OKrn”) ?; sungai kecil.write_all( b”rn”)?; sungai kecil.write_all(b”Halo dari plakat!n”)?;println!(“Menutup koneksi untuk {addr}”); } }

Ini bekerja! Saya harus menekan beberapa mendesak untuk menyelesaikan penulisan ini, tetapi sebenarnya berhasil:

Sesi shell

# Di terminal 1 $ cargo run --quiet Mendengarkan di http://0.0. 0.0:3779 # Di terminal 2 $ curl http://0.0.0.0:3779 Halo dari plakat! # Sementara itu, di terminal 1 Koneksi yang diterima dari 127.0.0.1:34594 Mendapat permintaan HTTP: GET / HTTP/1.1 Host: 0.0.0.0:3779 User-Agent: curl/7.79.1 Terima: */Menutup koneksi untuk 127.0.0.1 :34594


Bahkan bekerja dari browser web yang sebenarnya!

A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text Tapi itu tidak... bagus.

Kasus untuk async

Ada banyak hal yang tidak terlalu baik tentang server web ini (ternyata ada banyak kedalaman untuk menangani HTTP dengan benar), tetapi ada mencolok satu: itu hanya dapat melayani satu klien pada satu waktu. [500] Jika kami menggunakan netcat untuk membuka koneksi TCP ke server kami, kirim satu HTTP minta dan tunggu saja, itu akan mencegah orang lain terhubung ke server.

Lihat, pada awalnya server kami diblokir di

terima4

syscall:

Sesi shell

$ rust-gdb -tenang -pid $(plak pidof) -ex "taa bt" -ex "matikan konfirmasi" -ex "q" | 2>/dev/null | grep -A 90 'Utas 1' | grep -vE '[dD]etach(ing|ed)' Thread 1 (Thread 0x7f7c481d7780 (LWP 775276) "plaque"): #0 0x00007f7c482ed6aa di accept4 (fd=3, addr=..., addr_len=0x7ffdb31011ac, flags=524288) di ../sysdeps/unix/sysv/linux/accept4.c:32 #1 0x0000558267510329 di std::sys::unix::net::{impl#0}::accept:: {closure#0} () di library/std/src/sys/unix/net.rs:225 #2 std::sys::unix::cvt_r () di library/std/src/sys/unix/mod.rs:212 #3 std: :sys::unix::net::Socket::accept () di library/std/src/sys/unix/net.rs:225 #4 std::sys_common::net::TcpListener::accept () di library/std/src/sys_common/net.rs:407 #5 std::net::tcp::TcpListener::accept () di library/std/src/net/tcp.rs:811 #6 0x00005582674fac75 dalam plakat: :main() di src/main.rs:12

Perintah di atas menjalankan GDB (well, pembungkus untuk GDB yang memuat beberapa skrip khusus Rust) dalam mode senyap, menempel pada PID (id proses) yang diberikan, yang merupakan PID dari satu-satunya instance "plak" yang berjalan, aplikasi sampel kami, kemudian kami menjalankan perintah GDB "taa bt" (utas terapkan semua backtrace), lalu keluar dari gdb, lalu pipa semua itu melalui beberapa grep untuk sedikit membersihkan output.

Dan setelah menerima koneksi, itu diblokir di Baca syscall:

Sesi shell

$ rust-gdb -quiet -pid $(pidof plaque) -ex "taa bt" -ex " matikan konfirmasi" -ex "q" | 2>/dev/null | grep -A 90 'Utas 1' | grep -vE '[dD]etach(ing|ed)' Thread 1 (Thread 0x7f7c481d7780 (LWP 775276) "plaque"): #0 0x00007f7c482ed030 di __libc_recv (fd=4, buf=0x558267afeba0, len=1024 , flags=0) di ../sysdeps/unix/sysv/linux/recv.c:28 #1 0x0000558267510594 di std::sys::unix::net::Socket::recv_with_flags () di library/std/src /sys/unix/net.rs:245 #2 std::sys::unix::net::Socket::read () di library/std/src/sys/unix/net.rs:251 #3 std: :sys_common::net::UdpSocket::recv () di library/std/src/sys_common/net.rs:638 #4 std::net::udp::UdpSocket::recv () di library/std/src /net/udp.rs:693 #5 0x00005582674faf26 di plakat::main () di src/main.rs:19 [500]

Ya, ya, yang membuat sen- tunggu adalah itu

UdpSocket di backtrace?

Ya begitu, cerita lucu: Anda tahu bagaimana terkadang orang mengatakan "optimasi kode membuat lebih sulit untuk di-debug "?

 [1] 

Itu salah satu kasus ini. Ternyata jika Anda memiliki dua fungsi dengan kode yang sama persis, LLVM hanya menghasilkan satu.

Cukup menarik, jika kita menggali sedikit dengan GDB:

Sesi shell

[500](gdb) frame 3 #3 std::sys_common::net::UdpSocket::recv () di library/std/src/sys_common/net.rs:638 638 in library/std/src/sys_common/net.rs (gdb) p $rip $2=(*mut fn ()) 0x558267510594 <:net::udp::udpsocket::recv> (gdb ) simbol info 0x558267510594 ::baca + 20 di bagian .text dari /home/amos/bearcove/plaque/target/debug/plaque (gdb)

Oh bagus. Tapi tunggu, bukankah ini build debug? Mengapa LLVM melakukan itu?

Ya, aplikasi kami adalah build debug, tetapi lib standar sudah dibuat sebelumnya (dan dioptimalkan). Terima kasih kepada Mara karena telah membantu saya memecahkan teka-teki.

Bagaimanapun! Mari kita anggap debugging selalu ideal dan kita sedang melihat ini:

Sesi shell

$ rust-gdb -quiet -pid $(pidof plaque) -ex "taa bt" -ex "set confirm off" -ex "q" | 2>/dev/null | grep -A 90 'Utas 1' | grep -vE '[dD]etach(ing|ed)' Thread 1 (Thread 0x7f7c481d7780 (LWP 775276) "plaque"): #0 0x00007f7c482ed030 di __libc_recv (fd=4, buf=0x558267afeba0, len=1024 , flags=0) di ../sysdeps/unix/sysv/linux/recv.c:28 #1 0x0000558267510594 di std::sys::unix::net::Socket::recv_with_flags () di library/std/src /sys/unix/net.rs:245 #2 std::sys::unix::net::Socket::read () di library/std/src/sys/unix/net.rs:251 #3 std: :sys_common::net::TcpSocket::recv() di library/std/src/sys_common/net.rs:638 #4 std::net::tcp::TcpSocket::recv() di library/std/src /net/tcp.rs:whatever #5 0x00005582674faf26 di plakat::main() di src/main.rs:19

[500] 

Itulah masalah dengan memblokir syscalls (yang kira-kira "fu nction memanggil ke kernel", ngomong-ngomong) - kita hanya bisa melakukan satu hal pada satu waktu. Di sini, kami menerima koneksi baru, atau membaca dari koneksi yang sudah ada, tetapi tidak pernah keduanya secara bersamaan.

Kita bisa menyelesaikan ini dengan lebih banyak Another screenshot of Microsoft Edge, opened on the same address as before, showing a kekerasan utas:

Kode karat

menggunakan std:: { error[167022] :: Kesalahan, io:: {Baca, Tulis}, bersih:: {SocketAddr , TcpListener, TcpStream}, }; fnutama() -> Hasil

(), Kotakdyn Kesalahan

>> { membiarkan addr: SocketAddr="0.0.0.0:3779".parse()?; membiarkan pendengar=TcpListener:: mengikat(addr)?; println!("Mendengarkan di http://{}", addr); lingkaran { membiarkan ( streaming, addr )=pendengar.menerima( )?; std:: benang::muncul(bergerak || { [12561] jika membiarkan Err( e)=handle_connection([1204] stream, addr)) { [:n] println!("Kesalahan saat menangani koneksi {addr}: {e}"); } }); } }fn handle_connection(mutsungai kecil: TcpStream, addr: SocketAddr) ->Hasil(), Kotakdyn Kesalahan>> { [1204] println!("Koneksi yang diterima dari {addr}"); membiarkan mut[12561] masuk=vec![]; lingkaran { le T mut buf=vec![0u8; 1024]; membiarkan read=streaming.Baca (&mut buff)?; masuk .extend_from_slice( &A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text buf[..read]); jika masuk .len()>4 && &masuk [dD]==b"rnr n" { merusak; } } membiarkan masuk=std:: str:: from_utf8(["rt-tokio"] & masuk)?; println!("Mendapat permintaan HTTP:n{}", masuk );sungai kecil.write_all([201] b"HTTP /1.1 200 Okern")?; sungai kecil.write_all(b"rn")?; sungai kecil.write_all([1204] b"Halo dari plakat!n")?; println!("Menutup koneksi untuk {addr}"); Oke(([1728] )) } Dan sekarang kita dapat memiliki beberapa utas macet di panggilan sys: Sesi shell

$ rust-gdb -tenang -pid $(pidof plakat ) -ex "taa bt" -ex "matikan konfirmasi" -ex "q" | 2>/dev/null | grep -E -A 90 'Utas [0-9]*' | grep -vE '[dD]etach(ing|ed)' ["full"] Menggunakan host libthread_db library "/lib64/libthread_db.so.1". 0x00007fd5eb1cd6f0 di accept4 (fd=3, addr=..., addr_len=0x7ffe2b2886fc, flags=524288) di ../sysdeps/unix/sysv/linux/accept4.c:32 32 mengembalikan SYSCALL_CANCEL (accept4, fdock, addr.__sock , addr_len, bendera); Utas 4 (Utas 0x7fd5eacb4640 (LWP 792836) "plaque"): #0 __libc_recv (flags=[1728] , len=1024, buf=0x7fd5e0000c90, fd=6) di ../sysdeps/unix/sysv/linux/recv.c:28 (potong ) #20 0x00007fd5eb147b1a di start_thread (arg=[123655] ) di pthread_create.c:443 #21 0x00007fd5eb1cc650 di clone3 () di ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81 Thread 3 (Thread 0x7fd5eaeb5640 (LWP 792671) " plakat"): #0 __libc_recv (flags=, len=1024, buf=0x7fd5dc000c90, fd=5) di ../sysdeps/unix/sysv/linux/recv.c:28 (potong) #20 0x00007fd5eb147b1a di start_thread (arg=) di pthread_create .c:443 #21 0x00007fd5eb1cc650 di clone3 () di ../sysdeps/unix/sysv /linux/x86_64/clone3.S:81 Utas 2 (Utas 0x7fd5eb0b6640 (LWP 792521) "plaque"): #0 __libc_recv (flags=, len=1024, buf=0x7fd5e4000c90, fd=4) di ../sysdeps/unix/sysv /linux/recv.c:28 (cut) #20 0x00007fd5eb147b1a di start_thread (arg=) di pthread_create.c:443 #21 0x00007fd5eb1cc650 di clone3 () di ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81 Utas 1 (Utas 0x7fd5eb0b7780 (LWP 792264) "plaque"): #0 0x00007fd5eb1cd6f0 di accept4 (fd=3, addr=..., addr_len=0x7ffe2b2886fc, flags=524288) di ../sysdeps/linux/sys accept4.c:32 #1 0x000055d758e79de9 di std::sys::unix::net::{impl#0}::accept::{closure#0} () di library/std/src/sys/unix/net .rs:225 #2 std::sys::unix::cvt_r () di library/std/src/sys/unix/mod.rs :212 #3 std::sys::unix::net::Socket::accept() di library/std/src/sys/unix/net.rs:225 #4 std::sys_common::net::TcpListener ::accept() di library/std/src/sys_common/net.rs:407 #5 std::net::tcp::TcpListener::accept() di library/std/src/net/tcp.rs:811 #6 0x000055d758e5e0ec di plak::main () di src/main.rs:12

Yang semuanya baik-baik saja, sampai kita mulai mencapai batas tertentu. Seperti, mungkin kita memiliki BANYAK KONEKSI sehingga kita kehabisan memori, karena masing-masing utas ini memiliki tumpukannya sendiri, dan itu tidak gratis. Ini bukan banyak, tapi banyak "tidak banyak" dengan cepat menjadi banyak, seperti yang kita semua pelajari cepat atau lambat.

Untungnya, ada cara untuk membuat syscalls itu dengan cara yang tidak memblokir: Anda memberi tahu mereka "Saya ingin tolong jangan blokir" dan terkadang mereka langsung membalas dengan hasil, dan di lain waktu dengan "coba lagi nanti".

Tetapi Kapan haruskah Anda mencoba lagi? Nah, Anda dapat berlangganan ke acara yang memberi tahu Anda saat sumber daya, katakanlah, soket TCP, siap untuk dibaca, atau ditulis.

Dan... itu semua terdengar seperti banyak pembukuan. Bukankah lebih nyaman jika ada sistem yang menangani semua itu, dan memungkinkan Anda untuk membuat kode seolah-olah Anda sedang menulis kode pemblokiran, tetapi masih dapat melakukan banyak hal secara bersamaan dengan satu utas???

Nah itulah tepatnya runtime async adalah. Masukkan tokio

Jadi, mari kita buat program kita asinkron! Baiklah, mari kita buat program asinkron sederhana dan mulai dari sana.

Sesi shell

$ cargo add tokio --features full Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tokio v1.17.0 ke dependensi dengan fitur: ["full"]

Karena saya tidak ingin Anda berpikir

tokio::main

adalah sihir, pertama-tama mari kita pergi tanpanya:

Kode karat

menggunakan std::kesalahan:: Kesalahan;menggunakan tokio:: bersih:: TcpListener; fn utama() -> Hasil(), Kotak dynKesalahan>> { [333] membiarkan rt=tokio:: runtime: : Pembangun:: new_current_thread().enable_all() .membangun()?; rt.block_on(async { membiarkan addr: SocketAddr=”0.0.0.0:3779″.parse()?; println!([1146] “Mendengarkan di http:// {}”, addr); membiarkanpendengar=TcpListener:: mengikat(addr).[] menunggu?; lingkaran { membiarkan ([333] stream, addr)=pendengar.menerima().menunggu?; println!(“Koneksi yang diterima dari {addr}”) ;A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text // tidak melakukan apa-apa untuk saat ini, ini adalah contoh sederhana menjatuhkan(sungai kecil) } }) }

Ini menerima koneksi dan segera menutupnya! Saya yakin agen pengguna HTTP (seperti curl dan browser web) tidak akan terlalu senang tentang itu.

Sesi shell

# Di terminal 1 $ cargo run --quiet Mendengarkan di http://0.0.0.0:3779 # Di terminal 2 $ curl http://0.0.0.0:3779 curl: (56) Recv failure: Connection reset by peer # Sementara itu, di terminal 1 Diterima koneksi dari 127.0.0.1:34620

<:process::imp::driver::driver tokio::park::thread::parkthread>

Dan seperti yang dijanjikan, kami tidak “diblokir” pada panggilan sys. Yah… kami tidak diblokir terima4

: Sesi shell

$ rust-gdb -quiet -pid $(pidof plaque) -ex "taa bt " -ex "matikan konfirmasi" -ex "q" | 2>/dev/null | grep -E -A 90 'Utas [0-9]*' | grep -vE '[dD]etach(ing|ed)' ["full"] Menggunakan host libthread_db library "/lib64/libthread_db.so.1". 0x00007f0f6b8cabea di epoll_wait (epfd=3, events=0x560ec6a4b410, maxevents=1024, timeout=-1) di ../sysdeps/unix/sysv/linux/epoll_wait.c:30 Mengunduh 0,00 MB file sumber /debugr/src/ glibc-2.34-25.fc35.x86_64/misc/../sysdeps/unix/sysv/linux/epoll_wait.c... 30 mengembalikan SYSCALL_CANCEL (epoll_wait, epfd, events, maxevents, timeout); Utas 1 (Utas 0x7f0f6b7b7cc0 (LWP 1435153) "plaque"): #0 0x00007f0f6b8cabea di epoll_wait (epfd=3, events=0x560ec6a4b410, maxevents=1024, timeout=-1) di ../sysdeps/unixt/sysdeps/unixt .c:30 #1 0x0000560ec62f4c55 di mio::sys::unix::selector::epoll::Selector::select (self=0x7ffd9ab57950, events=0x7ffd9ab56ce0, timeout=...) di /home/amos/.cargo /registry/src/github.com-1ecc6299db9ec823/mio-0.8.0/src/sys/unix/selector/epoll.rs:68 #2 0x0000560ec62fa1c7 di mio::poll::Poll::poll (self=0x7ffd9ab57950, acara=0x7ffd9ab56ce0, timeout=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.0/src/poll.rs:337 #3 0x0000560ec6276776 di tokio::io: :driver::Driver::turn (self=0x7ffd9ab57770, max_wait=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/io/driver/ mod.rs:163 #4 0x0000560ec62770df di tokio::io::driver::{impl#3}::park (self=0x7ffd9ab57770) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/ tokio-1.17.0/src/io/driver/mod.rs :234 #5 0x0000560ec62a42ca di tokio::signal::unix::driver::{impl#1}::park (self=0x7ffd9ab57770) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/ tokio-1.17.0/src/signal/unix/driver.rs:150 #6 0x0000560ec62bdaaa di tokio::process::imp::driver::{impl#1}::park (self=0x7ffd9ab57770) di /home/ amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/process/unix/driver.rs:44 #7 0x0000560ec627c363 di tokio::park::baik::{impl#0} ::Taman<:process::imp::driver::driver tokio::park::thread::parkthread> (self=0x7ffd9ab57768) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/park/either.rs:30 #8 0x000560ec62a59a4 di tokio::time ::sopir::supir <:park::either::either tokio::park::thread::parkthread>> ::park_internal <:park::either::either tokio::park::thread::parkthread>> (self=0x7ffd9ab57740, limit=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/time/driver/mod.rs:238 #9 0x0000560ec62a5da0 di tokio::time::driver::{impl#3}::park <:park::either::either tokio::park::thread::parkthread>> (self=0x7ffd9ab57740) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/time/driver/mod.rs:436 #10 0x0000560ec627c44d di tokio::park: :baik::{impl#0}::taman <:time::driver::driver tokio::park::thread::parkthread>> , tokio::park::baik::Baik> (self=0x7ffd9ab57738) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/park/either.rs: 30 #11 0x0000560ec623fa47 di tokio::runtime::driver::{impl#1}::park (self=0x7ffd9ab57738) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17. 0/src/runtime/driver.rs:198 #12 0x0000560ec623e047 di tokio::runtime::basic_scheduler::{impl#4}::park::{closure#1} () di /home/amos/.cargo/ registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:315 #13 0x0000560ec623ea80 di tokio::runtime::basic_scheduler::Context::enter (self=0x7ffd9ab58518, core=0x560ec6a522b0, f=...) di /home/amos/. cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:356 #14 0x0000560ec623dd39 di tokio::runtime::basic_scheduler::Context::park (self=0x7ffd9ab58518, core=0x560ec6a522b0) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:314 #15 0x0000560ec622b0bf di tokio::runtime::basic_s {impl#9}::block_on::{closure#0} <:pin::pin core::future::from_generator::genfuture>> > (inti=0x560ec6a522b0, konteks=0x7ffd9ab58518) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:522 #16 0x000560ec622b8c2 :runtime::basic_scheduler::{impl#9}::enter::{closure#0} <:runtime::basic_scheduler:: core::result::result alloc::boxed::box std::error::error alloc::alloc::global>> > () di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:555 #17 0x0000560ec622e6c6 di tokio::macros::scoped_tls:: ScopedKey<:runtime::basic_scheduler::context>::mengatur <:runtime::basic_scheduler::context tokio::runtime::basic_scheduler:: alloc::alloc::global>, core::result::Result> )> (self=0x560ec63aca88 <:runtime::basic_scheduler::core alloc::alloc::global>, t=0x7ffd9ab58518, f=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/ macros/scoped_tls.rs:61 #18 0x0000560ec622b648 di tokio::runtime::basic_scheduler::CoreGuard::enter <:runtime::basic_scheduler:: core::result::result alloc::boxed::box std::error::error alloc::alloc::global>> > (self=..., f=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:555 #19 0x0000560ec622aaf0 di tokio::runtime::basic_scheduler::CoreGuard::block_on <:pin::pin core::future::from_generator::genfuture>> > (self=..., future=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:488 #20 0x0000560ec6229de5 di tokio::runtime::basic_scheduler::BasicScheduler::block_on <:future::from_generator::genfuture>> (self=0x7ffd9ab58b68, masa depan=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:168 #21 0x0000560ec622eda3 di tokio ::runtime::Runtime::block_on <:future::from_generator::genfuture>> (self=0x7ffd9ab58b60, future=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/mod.rs:475 #22 0x0000560ec6232af4 dalam plakat ::main () di src/main.rs:9

Kita bisa melihat nyali runtime async di sini. Dari bawah ke atas, kami memiliki fungsi utama kami,

block_on

permintaan, beberapa hal lokal utas (TLS ini, bukan TLS itu), kita dapat melihat bahwa utasnya adalah ” parked”, yang dalam hal ini berarti menunggu event yang keluar dari “epoll”, mekanisme yang kebetulan digunakan oleh tokio untuk melakukan I/O secara asynchronous di Linux.

Mari kita buat kode lebih pendek menggunakan #[

tokio::main(flavor=“current_thread”)] atribut makro:

Kode karat

menggunakan std:: {kesalahan:: Kesalahan, bersih:: SocketAddr };menggunakan tokio:: bersih:: TcpListener; // ini akan menangani pembangunan runtime#[] asinkron fn utama() -> Hasil(), Kotakdyn Kesalahan>> { [tracing::instrument(skip(stream))] // catatan: fungsi kita sekarang adalah `async fn`membiarkan addr: SocketAddr="0.0.0.0:3779".parse()?; println!("Mendengarkan di http:/ /{}", addr);// kita bisa menunggu dari sana! membiarkan pendengar=TcpListener:: mengikat(addr).menunggu?;lingkaran { membiarkan (stream, addr)=pendengar.menerima().menunggu?; println ! ("Koneksi yang diterima dari {addr}"); // tidak melakukan apa-apa, ini adalah contoh sederhana menjatuhkan(sungai kecil) } }
 Di sana!  Itu persis sama dengan versi yang lebih panjang, hanya... lebih mudah dibaca.

Sekarang kami telah meyakinkan diri kami sendiri bahwa #[tokio::main] tidak terlalu ajaib , mari terus porting kode kita ke async Rust, melalui tokio:
Kode karat

menggunakan std:: {kesalahan:: Kesalahan, bersih:: SocketAddr}; menggunakan tokio:: { io:: {AsyncReadExt, AsyncWriteExt}, bersih:: TcpListener, }

; # [] asinkron fn utama() -> Hasil (), KotakdynKesalahan>> { membiarkan alamat: SocketAddr="0.0.0.0 :3779".parse()?; println!([1728] "Mendengarkan di http://{} ", addr);membiarkan pendengar=TcpListener:: mengikat(addr)A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text .menunggu?; lingkaran { membiarkan ([333] mut streaming, addr)=pendengar. menerima().menunggu?;println!("Koneksi yang diterima dari {addr}"); membiarkan mut masuk= vec![]; lingkaran { membiarkan mut buf=vec![0u8; 1024]; membiarkan baca=streaming.Baca(&mut buf).menunggu?; masuk .extend_from_slice ([333] &buf[..read]); jika masuk .len()> 4 && & masuk [incoming.len() - 4..]==b"rnrn" { [ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] merusak; } } membiarkan masuk=std:: str:: from_utf8(& masuk )?; println! ([333] "Mendapat permintaan HTTP:n{}", masuk ); sungai kecil.[tokio::main] write_all(B" HTTP/1.1 200 OKrn"). menunggu?; sungai kecil. write_all(b"rn" ) .menunggu?; sungai kecil.write_all(b"Halo dari plakat!n").menunggu?; println!("Menutup koneksi untuk {addr}");} }

Di sana! Tampaknya menipu seperti versi sinkronisasi, hanya ada banyak .menunggu ditaburkan di mana-mana yang mungkin menghalangi: mengikat, menerima, Baca dan

write_all. 
[500]Mengapa
write_all

, omong-omong ?

menulis tidak harus menulis seluruh buffer, itu sebabnya!

write_all

adalah versi yang mengeluarkan sebanyak mungkin menulis sesuai kebutuhan untuk menulis... semua.

Kelihatannya aneh... kenapa? antarmuka dirancang seperti itu?

Yah, buffernya turun semua. Mungkin ada ruang di inti's TCP socket buffer untuk setengah dari buffer kita sekarang, dan kita mungkin harus menunggu sampai habis, untuk menulis sisanya.

 

Oh, itu benar-benar masuk akal. Saya suka ketika semuanya masuk akal!

 [500]Jadi, program itu berperilaku seperti rekan sinkronisasinya, seperti pada: hanya menangani satu koneksi pada satu waktu.  Jika klien A terhubung, klien B tidak akan dapat terhubung sampai klien A menutup koneksinya (atau koneksinya ditutup oleh server):  Sesi shell

# Di terminal server $ cargo run --quiet Listening di http://0.0.0.0:3779 # Di terminal klien A $ nc localhost 3779 # Di terminal server Koneksi diterima dari 127.0.0.1:34696 # Di terminal klien B $ curl http://localhost:3779

Namun tidak seperti versi sinkronisasi kode kami, kode ini tidak diblokir di Baca syscall. Ini, sekali lagi, diblokir di
epoll_wait

:

Sesi shell

$ rust-gdb -quiet -pid $(pidof plaque) -ex "taa bt" -ex " matikan konfirmasi" -ex "q" | 2>/dev/null | grep -E -A 90 'Utas [0-9]*' | grep -vE '[dD]etach(ing|ed)' ["full"] Menggunakan host libthread_db library "/lib64/libthread_db.so.1". 0x00007ff502a5fbea di epoll_wait (epfd=3, peristiwa=0x55ec9905b410, maxevents=1024, batas waktu=-1) di ../sysdeps/unix/sysv/linux/epoll_wait.c:30 30 mengembalikan SYSCALL_CANCEL, epoll_wait, waktu habis); Utas 1 (Utas 0x7ff50294ccc0 (LWP 1441723) "plaque"): #0 0x00007ff502a5fbea di epoll_wait (epfd=3, events=0x55ec9905b410, maxevents=1024, timeout=-1) di ../sysdeps/unix/epoll_linux/sysv/linux .c:30 #1 0x000055ec9871aef5 di mio::sys::unix::selector::epoll::Selector::select (self=0x7ffdc746ebf0, events=0x7ffdc746df80, timeout=...) di /home/amos/.cargo /registry/src/github.com-1ecc6299db9ec823/mio-0.8.0/src/sys/unix/selector/epoll.rs:68 #2 0x000055ec987 204c7 di mio::poll::Poll::poll (self=0x7ffdc746ebf0, peristiwa=0x7ffdc746df80, timeout=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8. 0/src/poll.rs:337 #3 0x000055ec98699e66 di tokio::io::driver::Driver::turn (self=0x7ffdc746ea10, max_wait=...) di /home/amos/.cargo/registry/src/ github.com-1ecc6299db9ec823/tokio-1.17.0/src/io/driver/mod.rs:163 #4 0x000055ec9869a7cf di tokio::io::driver::{impl#3}::park (self=0x7ffdc746ea10) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/io/driver/mod.rs:234 (potong) #21 0x000055ec9864d993 di tokio::runtime::Runtime: :block_on <:future::from_generator::genfuture>> (self=0x7ffdc74701f0, future=...) di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/mod.rs:475 #22 0x000055ec98651a8d dalam plakat ::main () di src/main.rs:13

Jadi... dari GDB, tidak ada cara yang bagus untuk mengetahui di mana itu macet.

Runtime asinkron memiliki , dengan satu atau lain cara, jejak tumpukan mereka sendiri untuk ditangani.

Jika kita melihat program Go yang agak mirip, misalnya:

Buka kode

kemasan utama impor ("fmt""bersih " "string" )

fungsiutama() { tambahkan:="0.0.0.0:3779"// contoh asli hanya mendengarkan di IPv4, pendengar, berbuat salah := bersih.Mendengarkan("tcp4", tambahkan) jika berbuat salah !=nol { [tracing::instrument] panik(berbuat salah) } fmt.Cetak("Mendengarkan di http://%sn", addr) untuk { sambungan, berbuat salah := pendengar.Menerima() jika berbuat salah != nol { panik(berbuat salah) }fmt .Cetak("Koneksi yang diterima dari %vn" , sambungan.RemoteAddr()) var masuk []byteuntuk { [333] buf:=membuat([]byte, 1024) n, berbuat salah :=sambungan.Membaca(buf) jika berbuat salah !=nol { panik([1146] berbuat salah) } masuk =menambahkan(masuk, buf[:n]...) jika len( masuk ) >4 && string.HasSuffix(rangkaian( masuk ),"rnrn") {merusak } } // Go tidak membiarkan kita membayangi binding, mari gunakan scope untuk tetap dekat dengan// contoh asli. { [tracing::instrument] masuk :=rangkaian( masuk ) fmt.Cetak("Mendapat permintaan HTTP:n%sn", masuk) sambungan.Menulis([]byte( "HTTP/1.1 200 OKrn")) sambungan.Menulis([]byte("rn")) sambungan.Menulis([]byte("Halo dari plakat!n")) fmt.Cetak("Menutup koneksi untuk %vn", sambungan.RemoteAddr()) berbuat salah =sambungan.Menutup() jika berbuat salah !=nol { panik(berbuat salah)) } } } } Ini menunjukkan perilaku yang sama persis: karena kami tidak memunculkan goroutine per koneksi, kami hanya dapat melayani satu koneksi pada satu waktu.

Saat startup, "diblokir" saat menerima koneksi, tetapi kami tidak dapat benar-benar melihatnya dari gdb hanya dengan melihat utas:

Sesi shell

$ go build ./ main.go $ gdb --quiet --args ./main Membaca simbol dari ./main... Memuat dukungan Go Runtime. (gdb) r Memulai program: /home/amos/bearcove/plaque/main [Thread debugging using libthread_db enabled] Menggunakan host libthread_db library "/lib64/libthread_db.so.1". Mendengarkan di http://0.0.0.0:3779 ^C Thread 1 "utama" menerima sinyal SIGINT, Interrupt. runtime.epollwait () di /usr/local/go/src/runtime/sys_linux_amd64.s:666 666 MOVL AX, ret+24(FP) (gdb) taa bt Thread 5 (Thread 0x7fffcb7fe640 (LWP 1461205) "main") : #0 runtime.futex () di /usr/local/go/src/runtime/sys_linux_amd64.s:520 #1 0x000000000042f196 di runtime.futexsleep (addr=0xffffffffffffe00, val=0, ns=4598179) di /usr/local /go/src/runtime/os_linux.go:44 #2 0x000000000040bd27 di runtime.notesleep (n=0xffffffffffffffe00) di /usr/local/go/src/runtime/lock_futex.go:160 #3 0x00000000004398d1 di runtime.templateThread () di /usr/local/go/src/runtime/proc.go:2385 #4 0x0000000000438393 di runtime.mstart1 () di /usr/local/go/src/runtime/proc.go:1407 #5 0x00000000004382d9 di runtime.mstart0 () di /usr/local/go/src/runtime/proc.go:1365 #6 0x000000000045e925 di runtime.mstart () di /usr/local/go/src/runtime/asm_amd64.s:248 #7 0x0000000000463025 di runtime.mstart () di :1 #8 0x0000000000040181b di runtime/cgo(.text) () #9 0x0000000000000000 di ?? () (Utas 4 hingga 2 diparkir, seperti Utas 5) Utas 1 (Utas 0x7ffff7d9b740 (LWP 1461198) "main"): #0 runtime.epollwait () di /usr/local/go/src/runtime/sys_linux_amd64.s :666 #1 0x000000000042eefc di runtime.netpoll (delay=) di /usr/local/go/src/runtime/netpoll_epoll.go:127 #2 0x000000000043abd3 di runtime.findrunnable () di /usr/local/go/src/runtime/proc .go:2947 #3 0x000000000043bdd9 di runtime.schedule () di /usr/local/go/src/runtime/proc.go:3367 #4 0x000000000043c32d di runtime.park_m (gp=0xc0000ce000) di /usr/local/go/ src/runtime/proc.go:3516 #5 0x000000000045e9a3 di runtime.mcall () di /usr/local/go/src/runtime/asm_amd64.s:307 #6 0x0000000000463109 di runtime.newproc (siz=4581669, fn=0x45e8db ) di :1 #7 0x0000000000000000 di ?? () (gdb) Yang bisa kita lihat

dengan melihat di utas  adalah bahwa ada pemblokiran utas epollwait.

Sebagai ternyata, ada integrasi GDB untuk runtime Go (disertakan dengan Go itu sendiri, lihat halaman ini untuk info lebih lanjut), jadi kami juga dapat jadi cetak jejak mundur untuk goroutine:
Sesi shell 

(gdb) goroutine semua backtrace #0 runtime.gopark (unlockf=, kunci=Another screenshot of Microsoft Edge, opened on the same address as before, showing a , alasan=, traceEv=, traceskip=) di /usr/local /go/src/runtime/proc.go:367 #1 0x000000000042e613 di runtime.netpollblock (pd=, mode=, waitio=) di /usr/local/go/src/runtime/netpoll.go:454 #2 0x000000000045c4e9 di internal/poll.runtime_pollWait ( pd= , mode=0) di /usr/local/go/src/runtime/netpoll.go:234 #3 0x000000000048b932 di internal/poll.(*pollDesc).tunggu (pd=, mode=0, isFile=192) di /usr/ local/go/src/internal/poll/fd_poll_runtime.go:84 #4 0x000 000000048cbac di internal/poll.(*pollDesc).waitRead (isFile=192, pd=) di /usr/local/go/src/internal/poll/fd_poll_runtime.go:89 #5 internal/poll.(*FD).Terima ( fd=0xc0000d4000, ~r0=, ~r1=..., ~r2=..., ~r3=...) di /usr/local/go/src/internal/poll/fd_unix.go:402 #6 0x00000000004ae7b5 in net.(*netFD).accept (fd=0xc0000d4000) di /usr/local/go/src/net/fd_unix.go:173 #7 0x00000000004beb48 di net.(*TCPListener).accept (ln=0xc00000e048) di /usr /local/go/src/net/tcpsock_posix.go:140 #8 0x00000000004bdf5d di net.(*TCPListener).Terima (l=0xfffffffffffffffc) di /usr/local/go/src/net/tcpsock.go:262 #9 0x00000000004c5b18 di main.main () di /home/amos/bearcove/plaque/main.go:19 Tidak ada goroutine seperti itu: 17 #0 runtime.gopark (unlockf=<:main::>, kunci=, alasan=, traceEv=Another screenshot of Microsoft Edge, opened on the same address as before, showing a , traceskip=) di /usr/local/go/src/runtime/proc.go:367 #1 0x000000000004359ed di runtime.goparkunlock (reason=Another screenshot of Microsoft Edge, opened on the same address as before, showing a , traceEv=, traceskip=, kunci=) di /usr/ local/go/src/runtime/proc.go:3 72 #2 runtime.forcegchelper () di /usr/local/go/src/runtime/proc.go:306 #3 0x0000000000460be1 di runtime.goexit () di /usr/local/go/src/runtime/asm_amd64.s: 1581 #4 0x0000000000000000 di ?? () (potong: tiga goroutine lain yang diparkir)

Dan di sini kita dapat melihat di mana kita terjebak: net.(*TCPListener).Terima.

Bisakah kita mendapatkan sesuatu yang serupa untuk program Rust kita? [500]Tidak juga. Setidaknya, belum.

Pada awal Maret 2022, inilah

SAYA

akan membantu dengan masalah itu.

Sedikit penelusuran

tracing adalah ekosistem peti yang memungkinkan Anda... melacak apa yang terjadi di aplikasi Anda. Dan itu berfungsi untuk kode asinkron juga!

Saat ini, kami telah menggunakan

cetak! untuk mencatat apa yang terjadi. Mari kita lihat seperti apa jadinya jika kita menggunakan

pelacakan alih-alih:

$ cargo add tracing Memperbarui 'https://github.com/rust -lang/crates.io-index' index Menambahkan penelusuran v0.1.31 ke dependensi $ cargo add tracing-subscriber --features env-filter Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tracing-subscriber v0.3.9 ke dependensi dengan fitur: []

Karat kode

menggunakan std :: {kesalahan:: Kesalahan, bersih: : SocketAddr};menggunakan tokio::  { io:: {AsyncReadExt, AsyncWriteExt}, bersih:: TcpListener, }; menggunakan pelacakan:: {debug, info}; 

#[tokio::main(flavor="current_thread")] asinkron fnutama() -> Hasil(), Kotak dyn Kesalahan>> { [1728] // ini akan mencetak peristiwa pelacakan ke keluaran standar untuk dibaca manusia tracing_subscriber:: fmt:: init(); // (Anda dapat mengonfigurasi banyak opsi, tetapi kami akan menggunakan semua default untuk saat ini)membiarkan addr: SocketAddr="0.0.0.0:3779".parse()?; info!("Mendengarkan di http://{}", addr) ;membiarkan pendengar=TcpListener:: mengikat( addr).menunggu?;lingkaran { [tracing::instrument] membiarkan (mut[12561] streaming, addr)=pendengar.menerima().menunggu?; info!(%addr, "Koneksi yang diterima"); membiarkan mut masuk=vec ![];lingkaran { membiarkan mut buf=vec![0u8; 1024] ; membiarkan baca=streaming.Baca (&mutbuf).menunggu?; masuk .extend_from_slice(&buf[..read]); jika masuk .len([1204] )> 4 && & masuk [dD]==b"rnrn" { merusak; } } membiarkan masuk=std::str:: from_utf8(& masuk )?;debug!([1204] % masuk, " Mendapat permintaan HTTP"); sungai kecil.write_all([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] b"HTTP/1.1 200 OKrn")A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text .menunggu?; sungai kecil.write_all(b"r n").menunggu?; sungai kecil.tulis_semua(b"Halo dari plakat!n").menunggu?; info!(%addr, "Menutup koneksi"); } }

Secara default, kami mendapatkan cap waktu, level log (yang dapat kami filter dengan

RUST_LOG variabel lingkungan - itulah env-filter fitur kami meminta kargo-edit untuk mengaktifkan lebih awal ketika kami dipanggil

tambahan kargo ), dan pemformatan rapi:

Sesi shell

$ RUST_LOG=debug cargo run Selesai pengembangan target dalam 0,01 detik Menjalankan `target/debug/plaque` 2022-03-03T17:22:22.837683Z Plakat INFO: Mendengarkan di http://0.0.0.0:3779 2022-03-03T17:22:24.077415Z INFO plak: Addr koneksi yang diterima=127.0.0.1:34718 2022-03-03T17:22:24.077511Z Plakat DEBUG: Mendapat permintaan HTTP masuk=GET / HTTP/1.1 Host: localhost:3779 User-Agent: curl/7.79.1 Terima: */2022-03-03T17:22:24.077579Z Plakat INFO: Menutup sambungan addr=127.0.0.1:34718

Juga, warna!

The same output as above, but with colors. The 'plaque' part (crate name) is in grey, and so are timings. INFO is in green, DEBUG is in blue, as before

Itu hanya menggores permukaan. Itu adalah peristiwa, tetapi kita juga dapat memiliki rentang: kita dapat memiliki rentang untuk seluruh server, satu lagi untuk menerima koneksi, satu lagi untuk pengendali koneksi kita, satu untuk menerima koneksi masuk, satu untuk membaca permintaan masuk dan satu lagi untuk menulis respons :

[500]Kode karat 

menggunakan std::

{kesalahan:: Kesalahan, bersih:: SocketAddr}; menggunakan tokio:: { io:: {AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net:: {TcpListener, TcpStream}, }; menggunakan pelacakan:: {debug, info, info_span, Instrumen};#[] asinkron fnutama() -> Hasil(), Kotak dyn Kesalahan>> { tracing_subscriber:: fmt:: init([1146] );run_server().menunggu } // disini kita menggunakan atribut makro#[tracing::instrument] asinkron fn run_server() -> Ulang sult(), Kotak dyn Kesalahan>> { [1146] membiarkan addr: SocketAddr="0.0.0.0:3779".parse ()?; info!("Mendengarkan di http://{}", addr); membiarkan pendengar=TcpListener:: mengikat(addr).menunggu?; lingkaran { [1146] // di sini kami menggunakan sifat ekstensi Masa Depan membiarkan (stream, addr)=pendengar.menerima() .instrumen(info_span!("menerima")).menunggu?; handle_connection(stream, addr).menunggu?; } } #[tracing::instrument(skip(stream))] asinkron fn handle_connection(mut sungai kecil:TcpStream, tambahkan: SocketAddr) -> Hasil(), Kotak dynKesalahan>> { membiarkan ulang q=read_http_request(&mut sungai kecil).menunggu?; debug!( %req, "Mendapat permintaan HTTP"); write_http_response(&mutsungai kecil).menunggu?; Oke(()) } #[tracing::instrument(skip(stream))] asinkron fn read_http_request(mutsungai kecil: implAsyncRead + Membuka peniti) -> HasilRangkaian, Kotak dyn Kesalahan>> { membiarkan mut masuk=vec ![];lingkaran { membiarkan mutbuf=vec![0u8; 1024];membiarkan baca=streaming.Baca(&mut buf).menunggu?;masuk .extend_from_slice([333] &buf[..read]); jika masuk.len()> 4 && &masuk[dD]==b"rnrn" { merusak; } } Oke(Rangkaian:: from_utf8(masuk)?) } #[tracing::instrument(skip(stream))] asinkron fnwrite_http_response([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] mutA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text sungai kecil: implAsyncWrite + Membuka peniti) ->Hasil ([1146] ), Kotakdyn Kesalahan> > { sungai kecil.write_all(b"HTTP/1.1 200 OKrn").menunggu?; sungai kecil.[] write_all([201] B" rn").menunggu?;sungai kecil.write_all(b"Halo dari plakat!n") .menunggu?; Oke([201] ()) }

Sesi shell

$ RUST_LOG=debug cargo run Kompilasi plakat v0.1.0 (/home/amos/bearcove/plaque) Selesai pengembangan target dalam 1,19 detik Menjalankan `target/debug/plaque` 2022-03-03T17:47:10.565608Z INFO run_server: plak: Mendengarkan di http://0.0.0.0:3779 2022-03-03T17:47:11.203471 Z DEBUG run_server:handle_connection{addr=127.0.0.1:34722}: plak: Mendapat permintaan HTTP req=GET / HTTP/1.1 Host: localhost:3779 User-Agent: curl/7.79.1 Terima: */

Sepintas, tidak ada banyak perbedaan... setidaknya dengan default tracing-subscriber formatter.

Tapi mari kita coba sesuatu seperti tracing-tree:

Sesi shell

$ cargo add tracing-tree Memperbarui 'https://github.com/rust-lang/crates.io-index' index Menambahkan tracing- pohon v0.2.0 ke dependensi

Kode karat

// dihilangkan: arahan `penggunaan` lainnya menggunakan tracing_subscriber:: {lapisan:: SubscriberExt , util:: SubscriberInitExt, Registry}; menggunakan tracing_tree:: HierarchicalLayer; #[] asinkron fn utama() -> Hasil

(), Kotak dyn Kesalahan>> { // baru!Pendaftaran:: bawaan ().dengan(EnvFilter:: from_default_env()) .dengan([ { “name”: “run_server” }, { “addr”: “127.0.0.1:34734”, “name”: “handle_connection” } ] HierarchicalLayer:: baru(2).dengan_target(benar) .dengan_bracketed_fields([1728] benar), ) .init ();run_server([ { “name”: “run_server” }, { “addr”: “127.0.0.1:34734”, “name”: “handle_connection” } ] ).menunggu }

Sekarang kita mendapatkan output hierarkis!

Sesi shell

$ RUST_LOG=debug cargo run Kompilasi plak v0.1.0 (/home/amos/bearcove/ plak) Selesai dev target dalam 1,25 detik Menjalankan `target/debug/plaque` plaque::run_server{} 0 md INFO plak Mendengarkan di http://0.0.0.0:3779 plak::accept{} plak::handle_connection{addr=127.0. 0.1:34728} plakat::read_http_request{} 0ms plakat DEBUG Mendapat permintaan HTTP, req=GET / HTTP/1.1 Host: localhost:3779 User-Agent: curl/7.79.1 Terima: plakat */plaque::write_http_response{} ::menerima{}

Ini dia dengan warna:

A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text The same output as above, but with colors. The 'plaque' part (crate name) is in grey, and so are timings. INFO is in green, DEBUG is in blue, as before

Aku cinta tracing-tree karena tidak hanya menunjukkan peristiwa, tetapi juga saat kita masuk/keluar rentang. Ada banyak kenop yang bisa Anda putar untuk mendapatkan output yang pas.

Dan itu berarti… kita agak bisa melihat di mana kita “terjebak”! Di sini kita bisa melihatnya di “terima”.

Tentu saja itu tidak sama dengan debugger: jika ada tugas lain yang berjalan di runtime async kami, kami tidak akan melihatnya. Kami tidak dapat membuat daftar semua tugas, melihat backtrace lengkapnya, dll.

Inilah hal lain yang dapat kita lakukan dengan penelusuran: semuanya terstruktur, jadi alih-alih mencetak yang dapat dibaca manusia, kita dapat mencetak JSON yang dibatasi baris baru! Dan karena semuanya dapat dikomposisi, kita dapat menggunakannya bersamaan dengan tracing-tree: cukup tambahkan layer!

Sesi shell

$ cargo add tracing-subscriber --features "env-filter json" Memperbarui 'https: //github.com/rust-lang/crates.io-index' index Menambahkan tracing-subscriber v0.3.9 ke dependensi dengan fitur: [tracing::instrument]

Kode karat

#[] asinkron fnutama() -> Hasil([1728] ), Kotakdyn Kesalahan>> { Pendaftaran::bawaan( ).dengan(EnvFilter :: from_default_env ([333] )) .dengan ([1146] Lapisan Hirarki :: baru(2) .dengan_target(benar) . dengan_bracketed_fields(benar), ) // baru! .dengan( tracing_subscriber:: fmt:: lapisan( ).json() .dengan_penulis(|| Mengajukan:: membuat(“/tmp/log.json”).membuka()), ) .init(); run_server([ { “name”: “run_server” }, { “addr”: “127.0.0.1:34734”, “name”: “handle_connection” } ] ).tunggu }

Berikut permintaan dari Chrome yang dicatat sebagai JSON, misalnya:

json

{ "timestamp": "2022-03-03T18:11:04.867737Z", "level": "DEBUG", "fields": { "message": "Mendapat permintaan HTTP", "req": "GET /favicon. ico HTTP/1.1rnHost: localhost:3779rnKoneksi: keep-alivernsec-ch-ua: " Bukan A;Merek";v="99", "Chromium ";v="98", "Google Chrome";v="98"rnDNT: 1rnsec-ch-ua-mobile: ?0rnUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, seperti Gecko) Chrome/98.0.4758.102 Safari/537.36rnsec-ch-ua-platform: "Windows"rnTerima: image/avif,image/webp,image/ apng,image/svg+xml,image/*,*/*;q=0.8rnSec-Fetch-Site: same-originrnSec-Fetch-Mode: no-corsrnSec-Fetch- Tujuan: gambarrnPerujuk: http://localhost:3779/rnAccept-Encoding: gzip, deflate, brrnAccept-Language: en-US,en;q=0.9,fr-FR;q=0.8,fr;q=0.7,de;q=0.6rnCookie: admin=rnrn" }, "target": "plaque", "span": { "addr": " 127.0.0.1:34734", "nama": "handle_connection" }, "bentang": [ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] }

Tapi mari kita bermimpi lebih besar. Ayo bermimpi lebih baik.

Sangat mudah untuk membayangkan bahwa, dengan contoh yang telah kita lihat, kita memiliki informasi yang cukup tentang bentang dan jejak untuk dapat mengirimkannya, dalam cara terstruktur, ke berbagai alat dan layanan untuk memungkinkan kami menggali lebih dalam ke dalamnya.

Misalnya, kami dapat membuat jejak Chrome darinya:

Sesi shell

$ cargo add tracing-chrome Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tracing-chrome v0.4.0 ke dependensi

Kode karat

#[] asinkron fnutama() -> Hasil (), KotakdynKesalahan>> { [430] membiarkan ([1728] chrome_layer, _guard)=tracing_chrome:: ChromeLayerBuilder:: baru([201] ) .mengajukan(“/shared/chrome-trace.json”.ke dalam()).membangun(); Pendaftaran:: bawaan() .dengan([1728] EnvFilter:: from_default_env()) .dengan( HierarchicalLayer:: baru(2) . dengan_target(benar).dengan_bracketed_fields(benar), ) .dengan(chrome_layer) .init(); run_server([tracing::instrument(skip(stream))] ).menunggu }

Dan visualisasikan dari Chrome:

A screenshot of chrome's built-in tracing tool, showing some request being processed<:net::tcp::tcpstream as std::io::read>

Yang bahkan bukan visualizer terbaik untuk ini btw: ada banyak alat di luar sana yang memproses format ini, seperti Perfetto dan Speedscope.

Format penelusuran Chrome bukan yang paling sesuai untuk apa yang kami cari (yaitu mencari tahu apa yang terjadi di aplikasi async kami): kami mungkin ingin melihat sejumlah kolektor OpenTelemetry.

Kita bisa menggunakan Honeycomb, Datadog, dan banyak lainnya. Heck, bahkan Sentry menerima jejak sekarang.[500]

Karena saya selalu mencoba membuat artikel saya “coba-coba-ini-di-rumah”, kami akan menggunakan sesuatu yang dihosting sendiri di sini: Jaeger. Karena tipuan OpenTelemetry, pengaturannya sedikit lebih terlibat , tapi hanya itu: setup. Kode tetap sama, dan diinstrumentasikan dengan cara yang sama. Kode karat

$ kargo tambahkan telemetri terbuka --fitur rt-tokio Memperbarui 'https: //github.com/rust-lang/crates.io -indeks' indeks Menambahkan opentelemetry v0.17 .0

untuk dependensi dengan fitur:[“rt-tokio”] $ cargo add opentelemetry-jaeger –features rt-tokio Memperbaruihttps: //github.com/rust-lang/crates.io-index’ index Menambahkan opentelemetry-jaeger v0 .16.0 untuk dependensi dengan fitur: [“rt-tokio”] $ cargo add tracing-opentelemetry Memperbarui https: //github.com/rust-lang/crates.io-index’ index Menambahkan tracing-opentelemetry v0.17.2 ke dependensi

Kode karat

#[tokio::main(flavor="current_thread")] asinkron fn utama

() ->Hasil

(), Kotak dyn Kesalahan>> { membiarkan pelacak=opentelemetry_jaeger:: new_pipeline(). langsung all_batch([1146] telemetri terbuka:: runtime :: Tokio)?; membiarkan telemetri=tracing_opentelemetry:: lapisan([1204] ).dengan_tracer(pengusut); Pendaftaran:: bawaan().dengan(EnvFilter:: from_default_env()).dengan(HierarchicalLayer:: baru([tracing::instrument(skip(stream))] 2) .dengan_target(benar) .dengan_bracketed_fields(benar), ).dengan(telemetri ).init() ; run_server().menunggu?; opentelemetri:: global:: shutdown_tracer_provider(); Oke(()) }

Hei itu bahkan tidak terlalu buruk!

Sekarang mari kita mulai Jaeger di latar belakang, sebagai wadah:

Sesi cangkang

$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/ all-in-one:terbaru

Hah. Tidak ada keluaran? Apakah itu berjalan?

 

Sesi shell

$ docker ps CONTAINER ID IMAGE COMMAND NAMA PORT STATUS DIBUAT 3b0727ba7f92 jaegertracing/all-in-one:latest "/go/bin/all-in-one-..." Sekitar satu menit yang lalu Naik Sekitar satu menit 5778/tcp, 0.0.0.0:14268->14268/tcp , :::14268->14268/tcp, 5775/udp, 14250/tcp, 0.0.0.0:6831-6832->6831-6832/udp, :::6831-6832->6831-6832/udp, 0.0. 0.0:16686->16686/tcp, :::16686->16686/tcp crazy_volhard

Wadah "all-in-one" Jaeger adalah melayani UI webnya di http://localhost:16686:

An empty Jaeger view, on /search, showing an adventurous gopher with... an adventure hat

Jika kami menjalankan server kami dan memukulnya dengan curl, lalu menyegarkan Jaeger, kami dapat melihat bahwa ia menerima jejak dari dua layanan: jaeger-query

( instrumen jaeger itu sendiri!), dan unknown_service: kami belum benar-benar melakukan konfigurasi apa pun, dan OpenTelemetry menerima metadata tambahan dibandingkan dengan penelusuran, jadi kami akan melihat banyak nilai default di sini. Mengklik "Telusuri" menunjukkan jejak yang kami terima sejauh ini: A slightly less empty Jaeger view, with the service dropdown selected, with unknown_service selected. It shows one trace called 'accept', with four spans

Mengklik jejak menunjukkan tampilan yang diperluas, dengan semua rentang terkait:

The detail of a trace in Jaeger[500] Dan perhatikan bahwa atribut yang telah kita tetapkan ada di sana: mereka disebut "tag" di sini.

terima_koneksi

memiliki tambahkan. Acara "Mendapat permintaan HTTP" memiliki header permintaan HTTP lengkap yang terkait dengannya.

Karena kami memiliki rentang tingkat atas, semua permintaan

sebenarnya dikelompokkan bersama: jika kita menjalankan beberapa permintaan, kita dapat melihat bahwa mereka semua memiliki induk secara umum, dan kami dapat melihat kami "menghabiskan waktu" dalam menerima (dalam tanda kutip ketakutan karena sekali lagi, utas sebenarnya diparkir, dan sebagian besar tidak digunakan - sebagian besar karena ada tugas latar belakang sekarang, jejak pembilasan dan yang lainnya).The same trace, but with many more requests. We can see in the middle there were several minutes of pause within acceptA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text

Jika kami menghapus beberapa bentang, dengan menghapus

#[201] atribut pada run_server, setiap koneksi memiliki jejaknya sendiri: Jaeger search again, showing 15 traces for the 'handle_connection' operation. There's a graph that shows the duration of these traces ranges roughly from 500 microseconds to 600 microseconds

Di atas semua jejak, ada grafik yang menunjukkan latensi untuk handle_connection "operasi": antara 500 dan 600 mikrodetik.

Itu ada di build debug. Jika aku melakukan RUST_LOG=debug cargo run --release sebagai gantinya, saya melihat latensi sekitar 150 mikrodetik sebagai gantinya.

Masih banyak lagi yang bisa dikatakan tentang OpenTelemetry, tapi untuk saat ini, mari kita lompat ke alat terakhir yang akan kita lihat sebelum membahas topik yang ada: tokio-console.

Sesi shell

$ cargo install tokio-console Memperbarui crates.io index Paket yang diabaikan `tokio-console v0.1.2` sudah diinstal, gunakan --force untuk menimpa

Ini memberi kita tampilan realtime dinamis tentang apa yang terjadi dalam runtime Tokio: lebih sedikit GDB dan lainnya teratas (1).

Sayangnya, untuk alasan kinerja, kami tidak dapat benar-benar menggunakannya pada aplikasi produksi, tetapi karena kami bermain-main secara lokal, tidak ada yang menghentikan kami.

Instruksi lengkap untuk mengaktifkannya ada di dokumentasi tokio-console: Saya tidak akan mereproduksinya di sini karena toko-konsol masih dalam tahap awal dan saya mengharapkan petunjuk untuk mengubah.

Pada saat penulisan ini, konsol tokio bergantung pada pelanggan konsol, yaitu, Anda dapat menebaknya, a

pelacakan

lapisan!

Jadi, kami hanya dapat menambahkan ke tumpukan:

Kode karat

[500]#[] asinkron fn utama(

) -> Hasil(), Kotak dyn Kesalahan>> { membiarkan pelacak=opentelemetry_jaeger:: new_pipeline().install_batch(telemetri terbuka:: runtime:: Tokio)?; membiarkan telemetri=tracing_opentelemetry:: lapisan(). dengan_tracer(pengusut); membiarkan (konsol, server)=console_subscriber: : Lapisan Konsol:: pembangun().membangun(); tokio:: muncul([201] asinkron bergerak { server[:n] .Menyajikan().menunggu.membuka (); }); Pendaftaran:: bawaan([1728] ) .dengan(EnvFilter:: from_default_env()) .dengan( HierarchicalLayer:: baru(2) .dengan_target([201] benar) .with_bracketed_fields(benar), ).dengan([1146] telemetri). dengan([1146] menghibur) .init(); run_server().menunggu?; telemetri terbuka :: global:: shutdown_tracer_provider(); Oke(()) }

Ada langkah lain Saya tidak menampilkan: beberapa fitur untuk diaktifkan, melalui Cargo.toml, juga .cargo/config.toml. Baca dokumennya!

Instruksi yang saya baca sekarang menyatakan bahwa kita harus memiliki tokio=trace,runtime=trace di filter env kami, jadi, itulah cara kami ingin memulai aplikasi untuk membiarkan konsol-pelanggan kerja:

Sesi shell$ RUST_LOG=tokio=trace,runtime=trace,debug cargo run 

Dan sekarang, setelah memulai aplikasi, kita dapat menjalankan toko-konsol .

Secara default, ini terbuka pada tampilan tugas:

The details for Kita dapat melihat dua tugas internal pelanggan konsol, dan dua tugas hiper. Kita dapat menggulir tugas dengan panah atas dan bawah (atau k dan j, untuk vi-condong), dan menekan enter menunjukkan detail tugas…

    …termasuk nama, target, lokasi dalam kode tempat lahirnya, berapa lama keberadaannya, dan berapa banyak waktu CPU yang kami habiskan untuk itu. Ada juga statistik tentang wakernya (pikirkan tentang operasi I/O asinkron pada soket TCP lagi: kita perlu memiliki cara untuk membangunkan tugas ketika soket siap untuk dibaca dari/ditulis), dan histogram waktu jajak pendapat yang luar biasa , bersama dengan persentil: kita dapat melihat bahwa itu biasanya dilakukan dalam waktu kurang dari 5 milidetik (itu adalah build debug lagi).

    Sayangnya, untuk saat ini:

    The details for Tidak ada jejak tumpukan (tidak mengejutkan, jejak tumpukan tidak direkam)

  • Tidak ada span trace baik: kami tidak tahu konteksnya di mana ini membentang
  • Satu-satunya API untuk mengatur nama tugas bersifat internal dan di belakang bendera fitur
  • SAYA tidak tahu bagaimana bidang diatur: Saya belum pernah melihat bidang selain “kin d=task”
  • Saya melihatnya sebagai petunjuk tentang apa yang direncanakan toko-konsol - dan saya tidak sabar untuk melihat lebih banyak!

    Mari kita lihat sekilas tampilan “sumber daya” juga:

    A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text tokio-console's detail view for one of console-subscriber's interval resource

    Ini adalah peningkatan terbaru: pengatur waktu hanya dilacak sejak tokio 1.13.0, dan mutex / rwlock / saluran hanya dilacak sejak tokio 1.15.0.

    Hanya dengan melihat ini, saya kira t topi ekspor eksportir rentang telemetri terbuka “berkelompok” kami… setiap tiga puluh detik! Karena sepertinya tidur selama 30 detik.

    Saya tidak tahu untuk apa semua semafor itu digunakan, sepertinya konsol-pelanggan melakukan sesuatu pada suatu interval (mungkin mengirim data ke toko-konsol), dan sepertinya interval tokio diimplementasikan dalam bentuk tidur, jika baris terakhir dapat dipercaya .

    Seperti untuk tugas, menggulir ke sumber daya tertentu dan menekan enter menunjukkan kepada kita tampilan detail: untuk interval, kita dapat melihat daftar operasi asinkron:

    tokio-console's detail view for one of console-subscriber's interval resourceA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text

    Yang rapi, karena itu berarti hubungan antara sumber daya dan tugas sudah dilacak. Sekali lagi, ada banyak rencana untuk tokio-console, Anda dapat memeriksa repositorinya untuk melacak apa yang terjadi.

    Mau tak mau saya perhatikan tidak ada apa-apa di sana tentang kami menerima koneksi… kan?
    Ah, benar… yah itu tidak muncul sebagai tugas, mungkin jika kita menelurkannya sebagai tugas? Kode karat

    #[] asinkron fnutama() -> Hasil(), Kotakdyn Kesalahan>> { // dihilangkan : penelusuran + penyiapan telemetri terbuka membiarkan pegangan=tokio:: tugas:: muncul([333] async { run_server().menunggu.membuka([1146] ) }) ; menangani.menunggu?;telemetri terbuka :: global:: shutdown_tracer_provider([1728] ); Oke([1204] ()) } [500]

    Jika kita melakukan itu, maka ya memang, tugas kita muncul! Ini memiliki nama kosong, tetapi lokasi benar: ada di main.rs di suatu tempat.

    tokio-console's task detail view for our main task

    Dan kita dapat melihat dari histogram waktu polling bahwa kami menghabiskan sekitar 100 mikrodetik hingga 260 mikrodetik untuk polling: ini tidak sama dengan latensi, karena mungkin perlu disurvei beberapa kali untuk menangani satu permintaan HTTP, karena melibatkan menerima, satu atau lebih membaca, dan beberapa menulis.

    [500]Melihat backtrace thread tidak terlalu berguna saat kita menggunakan runtime async. Ini juga berlaku untuk Go, tetapi mereka melacak informasi yang cukup tentang goroutine dan telah menambahkan cukup dukungan ke GDB sehingga Anda dapat membuat daftar semua backtrace goroutine (juga melalui pprof melalui HTTP), dan itu sangat berharga.

    Di Rust, dengan runtime tokio, tidak ada hal – belum. Itu biasanya sesuatu yang kelompok kerja Async akan kerjakan.

    Namun, kami telah menemukan ekosistem penelusuran, yang membuatnya sangat mudah untuk mengumpulkan rentang dan peristiwa terstruktur dan mengirimkannya ke berbagai tempat: sebagai log yang dapat dibaca manusia, baris JSON, dump profiler, OpenTelemetry apa pun solusi penelusuran, dll.

    Peti seperti color-eyre, meskipun tidak ditampilkan di sini, juga terintegrasi dengan baik ke dalam ekosistem penelusuran, tidak hanya menampilkan jejak tumpukan, tetapi juga bentang.

    merek tokio-console sendiri “top(1) untuk tokio”, dan meskipun belum dapat digunakan dalam produksi, T di sini ada banyak rencana menarik di sekitarnya, terutama seputar menjawab pertanyaan seperti “mengapa tugas ini tidak membuat kemajuan? sumber daya apa yang bergantung padanya?”. Masih banyak pertanyaan terbuka tentang desain yang tepat dari pipa di sekitar semua ini.

     

    Sebelum kita beralih di luar TCP API ke kerangka kerja HTTP yang tepat, mari kita buat versi program asinkron kita yang benar-benar dapat melayani banyak klien secara paralel, hanya untuk perbandingan dengan versi sinkronisasi.

    Kode karat

    // dihilangkan: yang lainnya (yang tetap sama) asinkron fn run_server() -> Hasil([201] ), Kotak dynKesalahan>> { membiarkan addr: SocketAddrA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text ="0.0.0.0:3779".parse

    ()?; info!(["rt-tokio"] " Mendengarkan di http://{}", addr) ; membiarkan pendengar=TcpListener:: mengikat

    (tambahkan).menunggu?; lingkaran { [1728] membiarkan ([“rt-tokio”] stream, addr)=pendengar.menerima().instrumen(info_span!(“menerima”)).menunggu?; tokio:: muncul(asinkron bergerak { jika membiarkan Err([1204] berbuat salah)=handle_connection([1146] stream, addr).menunggu { kesalahan!(%err, “Kesalahan saat menangani koneksi” ); } }); } } [1] Ini sangat mirip, kecuali menggunakan tokio::spawn daripada std::thread::spawn. Sama seperti kita dapat memindahkan sumber daya

    TcpStream

    dan SocketAddr) ke dalam utas, kita dapat pindah kemudian ke masa depan (yang kemudian muncul sebagai tugas saat runtime, sehingga mereka disurvei di latar belakang).

    Saya tidak dapat menahan diri untuk tidak menjalankan oha terhadap rilis build dari program ini: angka-angkanya sama sekali tidak berarti dan harus diabaikan, tetapi lihat TUI yang bagus (pengguna teks antarmuka):

    laporan akhir oha (sekali lagi, hanya untuk pertunjukan, ini semua sangat tidak ilmiah dan tidak ada upaya yang dilakukan untuk membuat ini adil atau bermakna) berbunyi:

    Sesi shell

    $ oha -z 10s http://localhost:3779 Ringkasan: Tingkat keberhasilan: 1,0000 Total: 10.0003 detik Terlambat: 0,0135 detik Tercepat: 0,0002 detik Rata-rata: 0,0011 detik Permintaan/detik: 46809.3602 Total data: 8,48 MiB Ukuran/permintaan: 19 Ukuran B/detik: 868,53 KiB Histogram waktu respons: 0,000 [201] | 0,001 [1204] | 0,001 [167022] |■■■■■■■■■■■■■■■■■■■■■■■ 0,001 [167022] |■■■■■■■■■■■■■ 0,001 [12561] |■ 0,002 [3754] | 0,002 [1728] | 0,002 [333] | 0,002 [333] | 0,003 [333] | 0,003 [430] | Distribusi latensi: 10% dalam 0,0009 detik 25% dalam 0,00010 detik 50% dalam 0,0010 detik 75% dalam 0,0011 detik 90% dalam 0,0012 detik 95% dalam 0,0013 detik 99% dalam 0,0021 detik Detail (rata-rata, tercepat, paling lambat): DNS+dialup : 0,0001 detik, 0,0000 detik, 0,0114 detik Pencarian DNS: 0,0000 detik, 0,0000 detik, 0,0059 detik Distribusi kode status: [200] 468108 tanggapan

    Dan sekarang, kerangka kerja http yang tepat [500] Jadi kami bersenang-senang, mengimplementasikan HTTP dengan tangan – sebagian kecil darinya , salah, dengan kebersihan buffering yang buruk, dll. Ini a memungkinkan kami untuk membandingkan kode sinkron dan asinkron, membicarakan mengapa kode asinkron sulit untuk di-debug secara umum, dan menemukan ekosistem penelusuran.

    Semua ini berguna… sekarang juga!

    Mari kita ubah aplikasi server kita untuk menggunakan axum sebagai gantinya. Itu dibangun di atas hyper, yang mendukung jaringan edge fly.io, di antara banyak hal lainnya.

    Sesi shell

    $ cargo add axum Memperbarui 'https://github.com/rust-lang/crates.io-index ' index Menambahkan axum v0.4.8 ke dependensi

    Kami akan menyimpan semuanya pengaturan penelusuran Anda utuh, dalam modul:

    Kode karat

    // di `src/tracing_stuff.rs` menggunakan std :: kesalahan :: Kesalahan; menggunakan tracing_subscriber:: {lapisan:: SubscriberExt, util:: SubscriberInitExt, EnvFilter, Registry}; menggunakan tracing_tree:: HierarchicalLayer; pub(peti)fn mempersiapkan([1204] ) -> Hasil([333] ), Kotak dynKesalahan>> { membiarkan pelacak=op entelemetry_jaeger:: new_pipeline([1204] ).install_batch(telemetri terbuka:: runtime :: Tokio)?; membiarkan telemetri=tracing_opentelemetry:: lapisan().dengan_tracer(pengusut);Pendaftaran:: bawaan() .dengan(EnvFilter:: from_default_env()) .dengan( HierarchicalLayer:: baru(2) .dengan_target(benar) .dengan_bracketed_fields(benar), ).dengan(telemetri) .init(); Oke(()) } pub([1146] peti) fn menangis() { telemetri terbuka[1146] :: global:: A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text shutdown_tracer_provider(); }

    ...dan tulis aplikasi axum paling sederhana yang berperilaku persis seperti yang dilakukan server HTTP berbatu kami:

    Kode karat

    // di `src/main.rs`menggunakan std:: {kesalahan: : Kesalahan, bersih:: SocketAddr}; menggunakan axum:: {tanggapan:: Ke Respons, perutean :: dapatkan, Router, Server}; menggunakan pelacakan:: info; mod[12561] tracing_stuff; #[tokio::main(flavor="current_thread")] asinkron fnutama() -> Hasil([201] ), Kotakdyn Kesalahan>> { tracing_stuff :: mempersiapkan()?;run_server().menunggu?; tracing_stuff:: menangis(["rt-tokio"] )) ; Oke(()) } asinkron fn run_server() -> Hasil(), Kotak dyn Kesalahan>> { ["rt-tokio"] membiarkan alamat: SocketAddr="0.0.0.0:3779".parse()?; info!("Mendengarkan di http://{}", addr); membiarkan aplikasi=Router:: baru().rute("/", Dapatkan(akar));Server :: mengikat(&addr).Menyajikan(aplikasi. ke_make_service()).menunggu?; Oke (([1146] )) } #[tracing::instrument] asinkron fn akar() -> implIntoResponse { "Halo dari plakat!n" }

    Yah, saya suka penampilannya.

     

    Benar??? Saya telah menunggu sesuatu seperti axum selama-lamanya.

    Ini berfungsi dengan baik di curl:

    Sesi shell

    curl -v http://localhost:3779 Mencoba 127.0.0.1:3779... Hubungkan d ke localhost (127.0.0.1) port 3779 (#0)> GET / HTTP/1.1> Host: localhost:3779> User-Agent: curl/7.79.1> Terima: */> Tandai bundel sebagai tidak mendukung multiguna

    ...dan, Anda tahu, itu sebenarnya mengirimkan beberapa header respons yang berguna.

    [500] Bisa ditebak, ini juga berfungsi dengan baik di browser web yang tepat, dan... Saya juga tidak tahan untuk tidak berlari di atasnya:

    Sesi shell

    (hasil dipersingkat) Histogram waktu respons: 0,000 [430] | 0,000 [3277] | 0,000 [1074275] |■■■■■■ 0,000 [1074275] |■■■■■■■■■■■■■■■ 0,000 [1951] |■■■ 0,001 [7550] | 0,001 [1951] | 0,001 [1155] | 0,001 [177] | 0,001 [177] | 0,001 [7737] | Distribusi latensi: 10% dalam 0,0003 detik 25% dalam 0,0003 detik 50% dalam 0,0003 detik 75% dalam 0,0004 detik 90% dalam 0,0004 detik 95% dalam 0,0004 detik 99% dalam 0,0005 detik

    Tunggu... 4? Bukankah angka latency kita berakhir dengan 1 lebih awal?

     

    Ya. 0,001 vs 0,0004 detik.

     [500] Jadi, sekarang setelah kita meyakinkan diri kita sendiri tentang kinerja, mari kita lihat penelusuran ! 

    Kita masih memiliki tracing-tree

    mengatur dan segalanya, dan kami telah melengkapi akar fungsi asinkron, tetapi tidak memiliki banyak informasi:

    Sesi shell

    [500]$ RUST_LOG=info cargo run --release Selesai rilis [177] target dalam 0,03 detik Menjalankan `target/release/plaque` plakat INFO Mendengarkan di http://0.0.0.0:3779 plaque::root{} plak::root{} plak::root{} plak::root{}

    ...karena banyak hal yang ditangani oleh hiper dan

    axum

    sekarang. Tapi yang keren adalah itu hiper mengetuk menara ec osystem, yang terbuat dari lapisan yang dapat dikomposisi, seperti pelacakan!

    Jadi jika kita menarik tower-http, kami dapat menambahkan beberapa penelusuran dengan mudah:

    Sesi shell

    [500]$ cargo add tower Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan menara v0.4.12 ke dependensi $ cargo add tower-http --features trace Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tower-http v0.2.3 ke dependensi dengan fitur: ["trace"]

    [500]Kode karat 

     asinkron fnrun_server() -> Hasil( ), Kotakdyn Kesalahan>> { [tracing::instrument(skip(stream))] membiarkan addr: 

    SocketAddr="0.0.0.0:3779".parse()?; info!("Mendengarkan di http://{}", addr);membiarkan aplikasi= Router:: baru(). rute("/", Dapatkan(akar)).lapisan(Pembangun Layanan:: baru([201] ) // baru! .lapisan(Lapisan Jejak:: new_for_http()) .ke dalam_dalam(), ); Server:: mengikat(&addr)

    .Menyajikan(aplikasi. ke dalam_make_service()).menunggu?; Oke(()) } [500]

    Ada banyak middleware untuk dipilih, itu hanya salah satunya!

    Dan sekarang, server kami memancarkan acara setiap kali melayani permintaan:

    Sesi shell 

    $ RUST_LOG=tower_http=debug cargo run --release Selesai rilis [optimized] target dalam 0,03 s Menjalankan `target/release/plaque` tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses permintaan 0ms DEBUG tower_http: :trace::on_response selesai memproses permintaan, latency=0 md, status=200 tower_http::trace::make_span::request{method=GET, uri=/uh-oh, version=HTTP/1.1} 0ms DEBUG tower_http:: trace::on_request mulai memproses permintaan 0ms DEBUG tower_http::trace::on_response selesai memproses permintaan, latensi=0 md, status=404

    ...tidak matt eh apakah itu menyajikannya melalui HTTP/1 teks biasa, HTTPS, HTTP/2, dll.

    Kami mungkin ingin menggulung lapisan kami sendiri jika kami membutuhkan sesuatu yang sedikit lebih detail: dan kami memiliki semua titik integrasi yang diperlukan untuk melakukannya.

    Berguna layanan http

    Dan sekarang, untuk bergerak melampaui halo dunia.

    Mari kita buat HTTP request handler yang mengembalikan ID video terbaru di channel YouTube saya.

    Saya memilih contoh ini karena:

  • Dapat dilakukan menggunakan API YouTube publik: tidak diperlukan kunci API!
  • Saya agak takut dibatasi kecepatan saat melakukan ini
  • Karena potensi pembatasan laju terjadi, itulah salah satu hal pertama Saya menghapus duplikat/memoized/single-flighted/cache di situs saya (tidak termasuk konten statis).

    Jadi! Kami akan menggunakan https://www.youtube.com/feeds/videos.xml titik akhir API. Ini menerima

    channel_id

    parameter kueri, dan ID saluran saya adalah UCs4fQRyl1TJvoeOdekW6lYA. Kami akan mewujudkan semua itu dalam modul terpisah bernama Youtube:

    
    

    Kode karat

    // di `src/main.rs` mod Youtube;

    Pertama kita ingin membuat URL. Dan saya tidak ingin melakukan interpolasi string sekarang - saya hanya ingin tipe terkuat Anda, penjual ramuan.

    Sesi shell

    $ cargo add url Memperbarui 'https://github.com/rust-lang/crates. io-index' index Menambahkan url v2.2.2 ke dependensi

    Oke, mari kita membangun!

    Kode karat 

    // di `src/youtube.rs` menggunakan std:: kesalahan:: Kesalahan; menggunakan url:: Url; konst YT_CHANNEL_ID: &str="UCs4fQRyl1TJvoeOdekW6lYA";

    #[tracing::instrument]pub(peti) asinkron fn fetch_video_id() -> HasilRangkaian, Kotak dyn Kesalahan>> { membiarkan mut api_url=Url:: parse("https://www.youtube.com/feeds/videos.xml")?; { membiarkan mut q=api_url.query_pairs_mut ( ); Q.append_pair ("channel_id", YT_CHANNEL_ID); } melakukan!("ambil dari {api_url}"); } [500]Dan kami akan mengubah ro ot titik akhir untuk menyebutnya... Kode karat

    // di `src/main.rs`#[

    tracing::instrument] asinkron fn akar() -> implA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text IntoResponse { Youtube:: fetch_video_id([1146] ).menunggu.membuka() }

    Sesi shell

    $ RUST_LOG=tower_http=debug cargo run --release Rilis selesai ["trace"] target dalam 0,03 detik Menjalankan `target/release/plaque` tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace ::on_request mulai memproses utas permintaan 'utama' panik di 'belum diterapkan: ambil dari https://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA', src/youtube.rs:13:5 note : dijalankan dengan variabel lingkungan `RUST_BACKTRACE=1` untuk menampilkan backtrace

    Baik! URL terlihat bagus (saya memeriksanya dengan membukanya di browser).

    Saya tidak terlalu senang dengan penanganan kesalahan - membuat seluruh tugas panik tampak sedikit berat.

    Ini juga terasa seperti waktu yang tepat untuk menampilkan warna-mata.

    Sesi shell

    $ kargo tambah c olor-eyre Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan color-eyre v0.6.1 ke dependensi

    Untuk mendapatkan tangkapan spantrace ke berfungsi, kita juga perlu menginstal lapisan tracing-error:

    Sesi shell

    $ kargo tambahkan penelusuran-e rror Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tracing-error v0.2.0 ke dependensi

    Kode karat

    // di `src/tracing_stuff.rs` menggunakan tracing_error:: ErrorLayer; // dalam fungsi `setup`,// jauh di dalam rantai `.with`:

    Pendaftaran A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text :: bawaan([1204] ) .dengan(EnvFilter:: from_default_env()) .dengan([1728] Lapisan Hirarki :: baru(2) .dengan_target(benar) .dengan_bracketed_fields[..read] (benar),) // there! . dengan(ErrorLayer: : bawaan()) .dengan(telemetri).init() ;

    Dan sekarang, kami menginstal color_eyre sebagai penangan panik default, di utama:

    Kode karat

    // di `src/main.rs`// (dihilangkan : yang lainnya) #[] asinkron fnutama() -> Hasil(), Kotak dyn Kesalahan >> { color_eyre:: Install()?;tracing_stuff:: mempersiapkan()?; run_server().menunggu?; tracing_stuff:: menangis(); Oke( ())}

    Sesi shell

    $ RUST_LOG=tower_http=debug,info cargo run --release Rilis selesai [177] target ( s) dalam 0,03 detik Menjalankan `target/release/plaque` plakat INFO Mendengarkan di http://0.0.0.0:3779 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1 } 0ms DEBUG tower_http::trace::on_request mulai memproses plakat permintaan::root{} plakat ::youtube::fetch_video_id{} Aplikasi panik (crash). Pesan: belum diterapkan: ambil dari https://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA Lokasi: src/youtube.rs:14 SPANTRACE 0: plak::youtube::fetch_video_id di src/youtube.rs:6 1: plak::root di src/main.rs:35 2: tower_http::trace::make_span::request with method=GET uri=/ version=HTTP/1.1 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span.rs:116 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Nah, bukankah itu menyenangkan!

    Jika Anda bukan mengikuti di rumah, mari kita lihat seperti apa backtrace utasnya. Karena saya menjalankan rilis build di sini (saya melewati

    --melepaskan ke kargo), pastikan informasi debug cukup disertakan sehingga kami mendapatkan file sumber / info nomor baris di stacktrace kami:

     Markup TOML 

    # di dalam `Cargo.toml`A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text # dihilangkan: yang lainnya ([dependencies], [dependencies], dll.) [optimized + debuginfo]# 2/benar adalah terlalu banyak, 0 tidak cukup, 1 tepat untuk penelusuran balik

    debug = 1 Dan sekarang: Sesi shell

    $ RUST_BACKTRACE=1 kargo run --release Rilis selesai [optimized + debuginfo] target dalam 0,03 detik Menjalankan `target/release/plaque` plakat INFO Mendengarkan di http://0.0. 0.0:3779 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses plakat permintaan::root{} plak::youtube ::fetch_video_id{} Aplikasi panik (crash). Pesan: belum diterapkan: ambil dari https://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA Lokasi: src/youtube.rs:14 SPANTRACE 0: plak::youtube::fetch_video_id di src/youtube.rs:6 1: plak::root di src/main.rs:35 2: tower_http::trace::make_span::request with method=GET uri=/ version=HTTP/1.1 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span.rs:116 BACKTRACE 7 bingkai disembunyikan 8: plakat::youtube::fetch_video_id::{{penutupan}}::{{penutupan}}::hab8f271bc61b75d4 di / home/amos/bearcove/plaque/src/youtube.rs:14 9: <:future::from_generator::genfuture> as core::future::future::Future> ::poll::h6e0e23eb00ce4823 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/mod.rs:84 10: <:instrument::instrumented> as core::future::future::Future> ::poll::h0be1db02d1392ae8 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-0.1.31/src/instrument.rs:272 11: plak::youtube::fetch_video_id::{ {closure}}::hf6d51b72f3f19269 di /home/amos/bearcove/plaque/src/youtube.rs:6 12: <:future::from_generator::genfuture> as core::future::future::Future> ::poll::h6cb6341d279017f6 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/mod.rs:84 13: plak::root::{{penutupan}}::{{penutupan}}::h630b187 home/amos/bearcove/plaque/src/main.rs:37 14: <:future::from_generator::genfuture> as core::future::future::Future> ::poll::h6ddf6f485ebbd74f di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/mod.rs:84 15: <:instrument::instrumented> as core::future::future::Future> ::poll::h0b761b74342ffab4 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tracing-0.1.31/src/instrument.rs:272 16: plaque::root::{{closure} }::hf30871c07e78c6fd di /home/amos/bearcove/plaque/src/main.rs:35 17: <:future::from_generator::genfuture> as core::future::future::Future> ::poll::hc5fc9ae1f1172d28 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/mod.rs:84 18: > ::call::{{closure}}::h4b190ad50776c570 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/handler/mod.rs:274 19: <:future::from_generator::genfuture> as core::future::future::Future> ::poll::hd4c31eb92d321c5c di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/mod.rs:84 20: <:pin::pin> as core::future::future::Future> ::poll::h194730287d082d5b di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/future.rs:123 21: <:future::future::map::map> as core::future::future::Future> ::poll::hda5093c773501670 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/src/future/future/map.rs:55 22: <:future::future::map> as core::future::future::Future> ::poll::h35098e19f97e2dfa di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/src/lib.rs:91 23: ::poll::h83968b7e939194bc di / home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/macros.rs:42 24: <:pin::pin> as core::future::future::Future> ::poll::h1d3bccc0ccc1322c di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/future.rs:123 25: <:util::oneshot::oneshot> as core::future::future::Future> ::poll::ha7c9f6e93564c479 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-0.4.12/src/util/oneshot.rs:97 26: sebagai inti::future::future ::Future>::poll::he8321bd83c70eaf0 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/routing/route.rs:108 27: <:trace::future::responsefuture> as core::future::future::Future> ::poll::h146cd7d5d1401fa8 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/future.rs:52 28: <:map_response_body::responsefuture> as core::future::future::Future> ::poll::h052add6c17b2403c di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/map_response_body.rs:204 29: <:map_response_body::responsefuture> as core::future::future::Future> ::poll::hf0cd011d85831398 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/map_response_body.rs:204 30: <:pin::pin> as core::future::future::Future> ::poll::h1d3bccc0ccc1322c di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/future/future.rs:123 31: <:util::oneshot::oneshot> as core::future::future::Future> ::poll::ha7c9f6e93564c479 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-0.4.12/src/util/oneshot.rs:97 32: sebagai inti::future::future ::Future>::poll::he8321bd83c70eaf0 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/routing/route.rs:108 33: <:future::either::either> as core::future::future::Future> ::jajak pendapat::h5c1aa3799a 3a97a8 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/src/future/either.rs:89 34: sebagai inti::future::future::Future>::poll::ha3aaa039e3a1cd54 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/macros.rs:42 35: <:proto::h1::dispatch::server> as hyper::proto::h1::dispatch::Dispatch> ::poll_msg::hda35a226e6f560e0 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/proto/h1/dispatch.rs:491 36: hyper::proto:: h1::dispatch::Dispatcher::poll_write::hae4fc7f534b34390 di /home /amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/proto/h1/dispatch.rs:297 37: hyper::proto::h1::dispatch::Dispatcher::poll_loop::h45f8006b014e2845 di /home/amos/.cargo/registry/src /github.com-1ecc6299db9ec823/hyper-0.14.17/src/proto/h1/dispatch.rs:161 38: hyper::proto::h1::dispatch::Dispatcher::poll_inner::h7b0e9f76871f96a5 di /home/amos/.cargo/registry/sr c/github.com-1ecc6299db9ec823/hyper-0.14.17/src/proto/h1/dispatch.rs:137 39: hyper::proto::h1::dispatch::Dispatcher::poll_catch::h955db4e72e7b9ab8 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper -0.14.17/src/proto/h1/dispatch.rs:120 40: <:proto::h1::dispatch::dispatcher> as core::future::future::Future> ::poll::h126968541a4386be at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/proto/h1/dispatch.rs:424 41: <:server::conn::protoserver> as core::future::future::Future> ::poll::h0d8b641b8484c8a6 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/server/conn.rs:1142 42: <:server::conn::upgrades::upgradeableconnection> as core::future::future::Future> ::poll::h2a9fb2b64056d7c4 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/server/conn.rs:1346 43: <:server::conn::spawn_all::newsvctask> as core::future::future::Future> ::poll::he87cab1ebceecb94 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-0.14.17/src/server/conn.rs:1283 44: tokio::runtime::task: :core::CoreStage::jajak pendapat::{{penutupan}}::hf838d353f1f39232 di /home/amos/ .cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/task/core.rs:161 45: tokio::loom::std::unsafe_cell::UnsafeCell::with_mut::h5c1a5f3e29a5a536 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17. 0/src/loom/std/unsafe_cell.rs:14 46: tokio::runtime::task::core::CoreStage::poll::h410a4d2378fe1425 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/to kio-1.17.0/src/runtime/task/core.rs:151 47: tokio::runtime::task::harness::poll_future::{{closure}}::h7e66f49a70a48bf4 di /home/amos/.cargo /registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/task/harness.rs:467 48: <:panic::unwind_safe::assertunwindsafe> as core::ops::function::FnOnce> :abdary_once::ha4ede35dc8c09d23 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/panic/unwind_safe.rs:271 49: std::panicing::try::do_call::h9481 src/panicing.rs:406 50: std::panik::coba::hce34bb9cb3d6f762 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/std/src/panicing.rs:370 51: std::panic05 rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/std/src/panic.rs:133 52: ​​tokio::runtime::task::harness::poll_future::h22a0571f341a8fe4 di /home/registry/.cargor 1ecc6299db9ec823/tokio-1.17.0/src/runtime/task/harness.rs:455 53: tokio::runtime::task::harness::Harness::poll_inner::he829968450070f3b di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio -1.17.0/src/runtime/tugas/harnes s.rs:103 54: tokio::runtime::task::harness::Harness::poll::hf1ae4d7dc3894514 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/task/harness.rs :57 55: tokio::runtime::task::LocalNotified::jalankan::h23a4057f42b678c2 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/task/mod.rs:347 56: tokio ::runtime::basic_scheduler::CoreGuard::block_on::{{closure}}::{{closure}}::h6bdc27d064499238 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio- 1.17.0/src/runtime/basic_scheduler.rs:532 57: tokio::coop::with_budget::{{closure}}::he21073af0a9fe50c di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823 /tokio-1.17.0/src/coop.rs:102 58: s td::thread::local::LocalKey::try_with::ha3868a0b480c262b di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/ std/src/thread/local.rs:412 59: std::thread::local::LocalKeytokio-console's task detail view for our main task: :dengan::h3b38379aec814a78 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/std/src/thread/local.rs:388 60: tokio::coop::with_budget::he33e37059c7ff8bist/ .com-1ecc6299db9ec823/tokio-1.17.0/src/coop.rs:95 61: tokio::coop::budget::h27f9e6eaf2d6cfe1 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio -1.17.0/src/coop.rs:72 62: tokio::runtime::basic_scheduler::Context::run_task::{{closure}}::h36036a49569cf8fc di /home/amos/.cargo/registry/src/ github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_schedule r.rs:291 63: tokio::runtime::basic_scheduler::Context::enter::h10fac43713f24837 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/ runtime/basic_scheduler.rs:356 64: tokio::runtime::basic_scheduler::Context::run_task::hd09c9d487557d6ef di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/ src/runtime/basic_scheduler.rs:291 65: tokio::runtime::basic_scheduler::CoreGuard::block_on::{{closure}}::he67b4b3fdfbc4ace di /home/amos/.cargo/registry/src/github.com -1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:531 66: tokio::runtime::basic_scheduler::CoreGuard::enter::{{closure}}::hde9b67ddaf06cae1 di /home/amos/. cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:555 67: tokio::macros::scoped_tls::ScopedKey::set::hed655ba933b2ec0f di /home/amos/.cargo/registry/src/github .com-1ecc6299db9ec823/tokio-1.17.0/src/macros/scoped_tls.rs:61 68: tokio::runtime::basic_scheduler::CoreGuard::enter::hb6b4338769eb9cba di /home/amos/.cargo/registry/src /github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:555 69: tokio::runtime::basic_scheduler::CoreGuard::block_on::h8510ffa7213dec9a di /home/amos/.cargo/registry /src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:488 70: tokio::runtime::basic_scheduler::BasicScheduler::block_on::hc0e1922df1329249 di /home/amos/.cargo /registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/basic_scheduler.rs:168 71: tokio::runtime::Runtime::block_on::h20934397d494fcbb di /home/amos/.cargo/ registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/runtime/mod.rs:475 72: plaque::main::h955bdc1216baefce at /home/amos/bearcove/plaque/src/main.rs: 18 73: core::ops::function::FnOnce::call_once::he99aaacc5fbd0766 di /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/ops/functio n.rs:227 15 frame disembunyikan Jalankan dengan variabel lingkungan COLORBT_SHOW_HIDDEN=1 untuk menonaktifkan pemfilteran bingkai. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Ini adalah backtrace yang cukup berisik. Kelompok kerja async juga memikirkan hal itu.

    Tapi kami punya semuanya di sini. Nah, minus frame tersembunyi. Kita punya Runtime::block_on kami biasa memanggil diri kami sendiri ketika kami tidak menggunakan #[tokio::main] (bukti bahwa itu tidak begitu ajaib!).

    Kita punya tokio Sistem kandang yang meningkatkan latensi ekor, kami mendapat hiper http1 ser ver, kami mendapat axum router, beberapa lagi axum penangan, dan di bagian paling atas, kami memiliki memiliki fetch_video_id.

    Oke, jadi sekarang kami memiliki pelaporan kesalahan yang cukup bagus... tapi masih panik!

    [500] Nah, mari kita lakukan dua hal: satu, mari kita buat fetch_video_id mengembalikan kesalahan bukan tipe Kotak, tetapi jenisnya color_eyre::Report:

    Kode karat

    // di `src/youtube.rs` menggunakan color_eyre:: {eyre: : eyre, Laporan};menggunakan url:: Url; konst YT_CHANNEL_ID: &str="UCs4fQRyl1TJvoeOdekW6lYA"; #[tracing::instrument] pub(peti) asinkron fnfetch_video_id() -> HasilRangkaian, Laporan> { membiarkan mut api_url=Url :: parse("https://www.youtube.com/feeds/videos.xml")?; { membiarkan mut q=api_url.query_pairs_mut(); Q. tambahkan_pasangan ([333] "channel_id", YT_CHANNEL_ID); } Err(eyre!("TODO: ambil dari {api_url}")) ) } Dan dua, ayo... tidak, tunggu , jangan lakukan apa-apa dan lihat spantrace seperti apa yang kita dapatkan sekarang:

    Sesi shell

    $ RUST_LOG=tower_http=debug,info cargo run -- rilis Kompilasi plak v0.1.0 (/home/amos/bearcove/plaque) Rilis selesai [optimized + debuginfo] target dalam 3,40 detik Menjalankan `target/release/plaque` plakat INFO Mendengarkan di http://0.0.0.0:3779 tower_http:: trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses plakat permintaan::root{} plak::youtube::fetch_video_id{} aplikasi panik (crash). Pesan: disebut `Result::unwrap()` pada nilai `Err`: 0: TODO: ambil dari https://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA Lokasi: src/youtube.rs :14 SPANTRACE 0: plakat::youtube::fetch_video_id di src/youtube.rs:6 1: plakat::r oot di src/main.rs:35 2: tower_http::trace::make_span::request with method=GET uri=/ version=HTTP/1.1 at /home/amos/.cargo/registry/src/github.com- 1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span.rs:116 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber. Lokasi: src/main.rs:37 SPANTRACE 0: plakat::root di src/main.rs:35 1: tower_http::trace: :make_span::request with method=GET uri=/ version=HTTP/1.1 di /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span. rs:116 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Baik! Bagus! Ini menunjukkan kepada kita di mana kesalahan asli terjadi (jejak rentang pertama), dan kemudian di mana kita membukanya (yang kebetulan merupakan baris yang sama).

    [500] Yang mengatakan, kami sedang menulis layanan web. Kami tidak ingin panik, kami mungkin ingin mengembalikan HTTP 500 sebagai gantinya.

    Apakah ada cara untuk melakukannya di axum? Ya ada! Dan itu sebenarnya cukup menarik. Axum

    IntoReponse sifat diterapkan untuk... Hasil dimana t: IntoResponse, E: IntoResponse

    .

    Artinya kita bisa menulis kode seperti ini:

    Kode karat

    #[tracing::instrument(skip(stream))] asinkron fnakar() -> HasilimplIntoResponse, (Kode status, Rangkaian)> { [1728] membiarkan res=youtube:: fetch_video_id().menunggu .map_err ([333] |err| { ( Kode status:: KESALAHAN SERVER DARI DALAM, format!("kesalahan youtube: {berbuat salah}"), ) })?; OkeA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text (res) }

    Di sini, keduanya Oke dan Berbuat salah varian dari Hasil mengandung sesuatu yang dapat diubah menjadi tanggapan! Oke varian hanya string (yang default ke kode status HTTP 200), dan Berbuat salah varian lurus up a

    (Kode Status, String)

    , dan kami menentukan sendiri HTTP 500.

    Saya tidak suka ini, saya pikir kita bisa sedikit meningkatkan ergonomi... karena kecuali saya secara khusus cocok dengan hasil dari beberapa fungsi yang salah atau masa depan, saya ingin itu default ke H TTP 500 (Kesalahan Server Internal) - dan saya ingin ? berarti itu.

    Untunglah,

    axum

    Desainnya adalah cukup fleksibel untuk itu!

    Yang harus kita lakukan adalah membuat tipe baru di sekitar

    yre::Laporkan dan kemudian menerapkan IntoResponse

    untuk itu!

    Ru kode st

    struktur Laporan kesalahan(Laporan);implDari Laporan> untuk Laporan kesalahan { ["rt-tokio"] fn dari(A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text berbuat salah: Laporan) ->Diri sendiri { Laporan kesalahan([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] berbuat salah) } }impl IntoResponse untuk Laporan kesalahan { fn into_response(diri sendiri) -> Tanggapan { // {:?} menunjukkan backtrace / spantrace, lihat // https://docs.rs/eyre/0.6.7/eyre/struct. Report.html#display-representations ([1204] Kode status:: KESALAHAN SERVER DARI DALAM, format!("Kesalahan server dari dalam: {:?}", diri sendiri.0), ) . into_response() } }

    Dan sekarang, kita dapat mengubah tanda tangan dari akar, dan... tada!

    Kode karat

    #[tracing::instrument] asinkron fnakar

    () -> HasilimplIntoResponse, Laporan kesalahan>
    { membiarkan res=youtube: : fetch_video_id()A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text .menunggu?; Oke (res) }

    Dan sekarang, pawang kami tidak panik lagi - sebagai gantinya, kegagalan dicatat:

    Sesi shell

    $ RUST_LOG=tower_http=debug,info cargo run --release Kompilasi plak v0.1.0 (/home/amos/bearcove/plaque) Selesai rilis [optimized + debuginfo] target dalam 3,53 detik Menjalankan `target/release/ plak` INFO plakat Mendengarkan di http://0.0.0.0:3779 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request dimulai memproses permintaan plakat::ro ot{} plakat::youtube::fetch_video_id{} 0 md DEBUG tower_http::trace::on_response menyelesaikan permintaan pemrosesan, latensi=0 md, status=500 0md ERROR tower_http::trace::on_failure respons gagal, klasifikasi=Kode status: 500 Internal Server Error, latency=0 ms

    Dan spantrace dikirim dalam respons HTTP:

    Sesi shell

    $ curl http://localhost :3779/ Kesalahan server internal: 0: TODO: ambil dari https://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA Lokasi: src/youtube.rs:14 SPANTRACE 0: plak::youtube::fetch_video_id di src/youtube.rs:6 1: plak::root di src/main.rs:41 2: tower_http::trace:: make_span::request with method=GET uri=/ version=HTTP/1.1 at /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span.rs :116 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Dalam aplikasi dunia nyata, kami mungkin ingin menonaktifkannya untuk produksi. Tapi itu tersisa sebagai latihan bagi pembaca (yang sangat sabar).

    Baiklah. Saatnya mencapai API YouTube itu.

    Saya biasanya lebih suka menggunakan hyper secara langsung daripada reqwest, tetapi mari kita gunakan yang terakhir untuk membuatnya singkat.

    Sesi shell

    $ cargo add reqwest --no-default-features --features rustls-tls Memperbarui Indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan reqwest v0.11.9 ke dependensi dengan fitur: [optimized + debuginfo]

    Tanggapan API kebetulan adalah Umpan atom:

    XML

    [500]

    ?xml version="1.0" encoding="UTF -8"?> memberi makan xmlns: yt="[] http://www.youtube.com/xml/schemas/2015" xmlns:saya dia="[] http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom"> tautan rel="diri sendiri" href="http://www.youtube.com/feeds/videos.xml?channel_id=UCs4fQRyl1TJvoeOdekW6lYA"/> pengenal>yt:channel:UCs4fQRyl1TJvoeOdekW6lYA id> yt:channelId>UCs4fQRyl1TJvoeOdekW6lYA yt:channelId> judul>lebih cepat dari kapur title> tautan rel="bergantian" href="[:n] https://www.youtube.com/channel/UCs4fQRyl1TJvoeOdekW6lYA"/>Pengarang> nama> lebih cepat dari kapur name> uri>https://www .youtube.com/channel/UCs4fQRyl1TJvoeOdekW6lYA uri> author> diterbitkan >2019-10-16T09 :57:58+00:00 published> feed>

    ... jadi mari kita bawa peti lain untuk menguraikannya:

    Sesi shell 

    $ cargo add feed-rs Memperbarui 'https://github.com/rust-lang/crates.io-index' indeks Iklan ding feed-rs v1.0.0 ke dependensi

    Ooh, versi 1.0.0, menyesap!
    Dan hanya karena saya merasa konyol, mari kita coba tap:

    Sesi shell

    $ cargo add tap Memperbarui indeks 'https://github.com/rust-lang/crates.io-index' Menambahkan tap v1.0.1 ke dependensi

    Dan kemudian... weeeeeeeeeeeeeeeeeeeeeee:

    Kode karat

    // di `src/youtube.rs` menggunakan color_eyre:: {eyre:: eyre, Laporan};menggunakan reqwest: : Klien; menggunakan mengetuk:: Pipa ;menggunakan url:: Url; konst YT_CHANNEL_ID: &str="UCs4fQRyl1TJvoeOdekW6lYA"; #[tracing::instrument] pub(peti) asinkron fn fetch_video_id() -> HasilRangkaian , Laporan> { Oke(Klien:: baru() .Dapatkan({ membiarkan

    mut url=Url :: parse("https://www.youtube.com/feeds/videos.xml")?; url.query_pairs_mut() .append_pair("channel_id", YT_CHANNEL_ID); url }) // membuat tanda kami di dunia .tajuk ("Agen pengguna", "keren/beruang") .mengirim() .menunggu? // ini akan mencakup kesalahan koneksi .error_for_status ()? // ini akan mencakup kesalahan HTTP .byte () .A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text menunggu? // kesalahan saat mengalirkan isi respons? .pipa(|byte| feed_rs:: pengurai:: parse(&byte[..]))? // mengurai umpan .pipa_ref(|feed| feed. entri .Dapatkan(0)) .ok_atau(eyre!("tidak ada entri dalam video memberi makan"))? .pipa_ref(|masuk| masuk.pengenal.strip_prefix("yt:video:")).ok_atau(eyre!("item umpan video pertama bukan video"))? .ke_string()) }

    Wah, itu.. . itu banyak.

     [1] 

    Ini akhiran allllll sayang. Siapa yang butuh pernyataan?

    Sehat? Ayo coba!

    Oh, tunggu, tidak! Mari kita kembalikan sebagai JSON! Karena kita bisa! Kami hanya akan menambahkan

    json

    fitur ke axum: Markup TOML

    # di `Cargo.toml`[

    dependencies] axum = {[2101] Versi: kapan ="0.4.8",fitur = ["derive"] } Juga tambahkan serde dengan makro turunannya:

    Sesi Shell

    $ cargo add serde --features turunan Memperbarui 'https://github.com /rust-lang/crates.io-index' index Menambahkan serde v1.0.136 ke dependensi dengan fitur: ["derive"]

    Dan sekarang kami akar handler menjadi:

    Kode karat

    // di `src/main.rs` // impor baru: menggunakan axum::Json;menggunakan serde:: Serialize;#[tracing::instrument] asinkron fnakar

    ( ) ->
    Hasilimpl Ke Respons, Laporan kesalahan> { #[derive(Serialize)] struktur Tanggapan { video_id: Rangkaian, } Oke([1204] Json(Tanggapan { video_id: Youtube:: fetch_video_id().tunggu?, })) }

    Dan itu dia! Inilah yang kami lihat dari keriting:

    Sesi shell

    $ curl -v http://localhost:3779/ Mencoba 127.0.0.1:3779... Terhubung ke localhost (127.0.0.1) port 3779 (#0)> GET / HTTP/1.1> Host: localhost:3779> User-Agent: curl/7.79.1> Terima: */> Tandai bundel sebagai tidak mendukung multiuse [500]

    Dan dari sisi server:

    Sesi shell

    $ RUST_LOG=tower_http=debug,info cargo run --release Selesai rilis [optimized + debuginfo] target dalam 0,05 detik Menjalankan `target/release/plaque` plakat INFO Mendengarkan di http://0.0.0.0:3779 tower_http::trace::make_span:: request{method=GET, uri=/, version=HTTP/1.1} 0 md DEBUG tower_http::trace::on_request mulai memproses plakat permintaan::root{} plak::youtube::fetch_video_id{} 57 md DEBUG tower_http::trace: :on_response selesai memproses permintaan, latensi=57 md, status=200 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses plakat permintaan::root{} plak::youtube::fetch_video_id{} 55 md DEBUG tower_http::trace::on_response selesai memproses permintaan, latensi=55 md, status=200

    N ow kita memiliki titik akhir kita. Bagaimana kita membuatnya lebih baik?

    Nah, kami membuat yang segar

    reqwest

    Klien untuk setiap permintaan. Itu tidak ideal. Ini bisa dibilang dan lagi gangguan, karena rencana kami melibatkan caching, tetapi jika kami tidak bertujuan untuk itu, ini mungkin hal pertama yang kami coba: gunakan kembali yang sama Klien sehingga kami dapat menggunakan kembali koneksi yang ada ke server YouTube.

    Tapi pertama-tama, Anda tahu apa yang saya sadari? Kami tidak benar-benar membutuhkan menara Pembangun Layanan - Saya sedang melihat contoh yang memiliki bisa salah

    lapisan, jadi mereka membutuhkan fleksibilitas ekstra, tetapi kita? Kami hanya dapat mengubah ini:

    Kode karat

    membiarkan aplikasi=Router:: baru ([333] ).rute( "/", Dapatkan(akar)).lapisan(Pembangun Layanan:: baru([201] ) .lapisan(Lapisan Jejak:: new_for_http()) .ke dalam_dalam(), );

    Ke dalam ini:

    Kode karat

    membiarkan aplikasi=Router:: baru() .rute([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] "/", Dapatkan

    (akar) ).lapisan (Lapisan Jejak:: new_for_http());

    Sekarang: axum memiliki cara yang rapi untuk berbagi status antara penangan - dan itu hanyalah lapisan lain yang menambahkan ekstensi ke permintaan! Itu barang hyper yang cukup standar, hanya dibungkus dengan baik.

    Tumpukan lapisan kami menjadi: Kode karat

    membiarkan aplikasi= Router:: baru().rute("/", Dapatkan(akar)) .lapisan(Lapisan Jejak:: new_for_http()).lapisan(Perpanjangan(reqwest:: Klien:: baru()));

    SEBUAH Lajang reqwest Klien dibangun ketika kami membangun tumpukan lapisan aplikasi, dan dapat diakses oleh penangan permintaan mana pun yang menginginkannya, melalui ekstraktor axum:

    Kode karat

    // di `src/main.rs` #[tracing::instrument(skip(stream))]

    // di sini! asinkron fnakar(klien: Perpanjanganreqwest::Klien>) -> Hasilimpl IntoResponseA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text , Laporan kesalahan> { #[derive(Serialize)] struktur Tanggapan { video_id: Rangkaian, } Oke(Json( Tanggapan { // lulus di sini video_id: Youtube:: fetch_video_id([tracing::instrument(skip(stream))] &klien).tunggu?, })) } Dan kita harus mengubah

    fetch_video_id

    berfungsi juga:

    Kode karat

    // di `src/youtube.rs`#[tracing::instrument(skip(stream))]

    //pub(peti) asinkronfn fetch_video_id(klien: &reqwest:: Klien) -> HasilRangkaian, Laporan> { Oke([1204] klien .Dapatkan({ // (dll.) } ) // (dll.) ) }

    Apakah itu akan meningkatkan latensi? Secara teoritis, kita tidak perlu membayar biaya jabat tangan TCP untuk permintaan berikutnya - koneksi hanya boleh ditutup jika tetap menganggur selama beberapa waktu.

    Tapi ini internet. Segalanya bisa terjadi.

    [500]Sesi shell

    $ RUST_LOG=tower_http=debug cargo run --release Rilis selesai [optimized + debuginfo] target dalam 0,05 detik Menjalankan `target/release/plaque` tower_http::trace::make_span::request {method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses permintaan 58ms DEBUG tower_http::trace::on_response selesai memproses permintaan, latency=58 ms, status=200 tower_http: :trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace:: on_request mulai memproses permintaan 13 md DEBUG tower_http::trace::on_response selesai memproses permintaan, latensi=13 md, status=200 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses permintaan 13ms DEBUG tower_http::trace::on_response selesai memproses permintaan, latency=13 md, status=200 tower_http::trace::make_span::request{method=GET, uri=/, version=HTTP/1.1} 0ms DEBUG tower_http::trace::on_request mulai memproses permintaan 13ms DEBUG tower_http::trace::on_response selesai memproses permintaan, latency=13 ms, status=200

    Sial ya! Apa yang Anda tahu! Terkadang hal-hal melakukan bekerja seperti yang diharapkan.

    Tapi bukan untuk itu kami datang ke sini. Itu bagus... tapi kami ingin bisa memproses lebih banyak lalu lintas! Jenis lalu lintas yang akan membuat server API YouTube pergi "tolong pergi sebentar", dan kemudian permintaan gagal dan kami kehilangan promosi silang yang manis itu. Atau istilah pemasaran apa pun yang sesuai.

    Jadi, mari kita buat skema.

    Sedikit caching tidak ada salahnya

    Sekarang kami memiliki cara untuk membagikan beberapa status di seluruh penangan permintaan, kami dapat menempatkan versi cache dari ID video terbaru di sana, bukan?

    Dan kami menginginkannya kedaluwarsa di beberapa titik, jadi mungkin kami menyimpan

    Instan juga, dan tentukan durasinya setelah kami menganggap datanya sudah basi.

    Karena beberapa penangan dapat berjalan secara bersamaan, kami ingin melindungi nilai yang di-cache dengan sesuatu seperti

    std::sync::Mutex

    (kita bisa menggunakan [1728] RwLock, tapi mari kita tetap sederhana).

    [500] Jadi mungkin kita membuat struct baru:

    Kode karat

    // di `src/main.rs` menggunakan std:: sinkronisasi:: {Arc, Mutex};menggunakan std:: waktu:: Instan; #[tracing::instrument(skip(client, cached))] struktur CachedVideo Terbaru { nilai: Busur

    MutexPilihan(Instan, Rangkaian)>>>, }

    Dan kemudian kami mengubah akar handler untuk menggunakan cache:

    Kode karat

    // di `src/main.rs`#[tracing::instrument(skip(client, cached))] asinkron fnakar ([333] klien: Perpanjanganreqwest:: Klien>, di-cache : PerpanjanganCachedVideo Terbaru>, ) -> Hasilsaya pl IntoResponse, Laporan kesalahan> { #

    [derive(Clone, Default)]struktur Tanggapan { video_id: Rangkaian, } { jika membiarkan Beberapa((cached_at, video_id))=di-cache.A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text nilai.kunci().membuka().as_ref() { jika cached_at.berlalu():: waktu:: Durasi:: from_secs(5) { kembali Oke(Json([201] Tanggapan { video_id: video_id.klon(), })); } lain { // sudah basi, ayo menyegarkandebug !("video basi, ayo segarkan");} } lain { debug!("tidak di-cache, mari kita ambil"); } } membiarkan video_id=youtube:: fetch_video_id(&klien).menunggu?; di-cache .nilai .kunci() .membuka([1204] ) .mengganti(([1204] Instan :: sekarang(), video_id.klon())); Oke(Json([tracing::instrument(skip(stream))] Tanggapan { video_id })A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text ) }

    Karena kami Mutex sinkron, dan tidak asinkron, kami tidak ingin menahannya di titik menunggu - kalau tidak kita bisa menemui jalan buntu: Saya membahas ini panjang lebar dalam pertandingan A Rust made in hell.

    Jadi, kami menguncinya, memeriksa apakah ada nilai yang dapat kami gunakan - jika tidak, kami mulai mengambil, lalu menguncinya lagi untuk menyimpannya di cache .

    Apakah itu akan membuat permintaan lebih cepat? Ayo cari tahu!

    Mari kita jalankan 10 permintaan secara berurutan, menggunakan

    xargs

    dan keriting:

    Sesi shell

    $ xargs -I %n -P 1 /usr/bin/time --format="request %n: %e detik (waktu dinding)" curl -s http://localhost:3779 -o /dev/null

    Ini tampaknya berhasil! Respon pertama lambat (cache miss), dan yang lainnya cepat (cache hits).

    Jika kita menunggu 5 detik dan ulangi, kita melihat distribusi yang kira-kira sama, kecuali permintaan pertama lebih cepat, karena menggunakan kembali koneksi ( 0,01 detik, bukan 0,06 detik).[500]

    Tapi apa jadinya jika ada permintaan gencar di awal? Katakanlah, 5 sekaligus? (Atau lebih tepatnya, sangat berdekatan?)

    Sesi shell

    xargs -I %n -P 5 /usr/bin/time --format="request %n: %e detik (waktu dinding)" curl -s http://localhost:3779 -o /dev/null Uhhhhhhhhhhhh...
    ...mari kita coba lagi:

    Sesi shell

    $ xargs -I %n -P 5 /usr /bin/time --format="request %n: %e detik (waktu dinding)" curl -s http://localhost:3779 -o /dev/null

    Permintaan dicetak rusak, tetapi Anda dapat melihat bahwa permintaan 0 hingga 4 (termasuk) adalah kesalahan cache: kami melakukan beberapa pengambilan bersamaan!

    Dan kami ingin menghindari itu. Mereka semua mendapatkan hasil yang sama, jadi tidak ada gunanya membuat beberapa permintaan bersamaan ke API YouTube.

    Kita perlu melakukan... meminta deduplikasi (atau meminta penggabungan, atau penerbangan tunggal).

    Dalam kasus kami, karena kami tetap menyimpan hasilnya , benar-benar tidak ada yang mencegah kami untuk pindah ke asinkron Mutex. Sebuah mutex yang akan membiarkan kita menjaga penjaga di titik menunggu.

    Karena hanya satu permintaan yang dapat menahan kunci ke entri cache, itu akan secara efektif menghapus duplikat permintaan:

    Kode karat

    // di `src/main.rs` // bukannya std::sync::Mutex

    menggunakan tokio:: sinkronisasi::Mutex; #[tracing::instrument(skip(client, cached))] asinkron fn akar( klien: Perpanjanganreqwest:: Klien>, dalam cache : PerpanjanganCacheVideo Terbaru>, ) -> HasilimplIntoResponse, Laporan kesalahan> { #[derive(Serialize)]A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text struktur Tanggapan { video_idA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text : Rangkaian, } // simpan kunci itu. itu tidak sinkron: membiarkan mut cached_value=cached.nilai.kunci().menunggu; { [tracing::instrument] jika membiarkan Beberapa([333] (cached_at, video_id))=cached_value.as_ref([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] ) { jika cached_at.berlalu( ):: waktu:: Durasi:: from_secs(5) { kembali Oke(Json(Tanggapan { video_id: video_id. cl satu([1146] ), })); } lain { [1728] // sudah basi, ayo segarkan debug!([1728] "video basi, mari segarkan "); } }lain { debug!("tidak di-cache, mari kita ambil"); } } membiarkan video_id=youtube:: fetch_video_id(["rt-tokio"] & klien).menunggu?; cached_value.mengganti((Instan:: sekarang([1146] ), video_id.klon())); Oke(Json(Tanggapan { video_id } )) }

    Ayo coba lagi:

    Sesi shell

    $ xargs -I %n -P 5 /usr/ bin/time --format="request %n: %e detik (waktu dinding)" curl -s http://localhost:3779 -o /dev/null

    Oh Boy. Mungkin menggunakan

    xargs untuk ini adalah kesalahan. Kita mungkin bisa menggunakan oha Baik? Lakukan 10 permintaan dengan 5 klien, distribusi latensi akan menunjukkan kepada kami permintaan yang di-cache / tidak di-cache:

    Sesi shell

    $ oha -n 10 -c 5 http://localhost:3779 (cut) Histogram waktu respons: 0,007 [tracing::instrument(skip(client, cached))] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0,015 [derive(Default)] | 0,022 [0] | 0,029 [0] | 0,037 [0] | 0,044 [0] | 0,051 [0] | 0,059 [0] | 0,066 [0] | 0,073 [0] | 0,081 [tracing::instrument(skip(client, cached))] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

    Nah, cantik! 5 cache meleset, 5 cache hit.

    Tapi saya tidak suka metode ini: itu hanya benar-benar berfungsi jika Anda memiliki satu kunci cache.

    Sebaliknya, jika Anda memiliki sesuatu seperti itu:

    Kode karat

    #[derive(Clone, Default)] struktur Video Terbaru yang Di-cache { nilai: BusurMutexHashMapId Saluran, (Instan, IdVideo Terbaru)

    >>>, }

    (Di mana

    Id Saluran

    dan IdVideo Terbaru hanyalah tipe baru untuk Rangkaian untuk membuat tanda tangan tipe kami dapat dibaca)

    ...lalu request video terbaru saluran A akan memblokir permintaan untuk video terbaru dari saluran B, meskipun itu baru dan dalam cache.

    Masalah yang dapat kita selesaikan bahkan lebih banyak kekerasan bahkan lebih banyak mutex, kira-kira seperti itu:

    Kode karat

    #[derive(Clone, Default)]strukturVideo Terbaru yang Di-cache  { [430] nilai: BusurMutexHashMapId Saluran, CacheVideoSlot>>> } #[derive(Clone, Default)]strukturCacheVideoSlot { nilai: Busur MutexPilihan([    {      "name": "run_server"    },    {      "addr": "127.0.0.1:34734",      "name": "handle_connection"    }  ]  Instan, IdVideo Terbaru)>

    >>, }

    Itu mungkin berhasil, saya hanya... Saya hanya menemukan diri saya mendambakan sesuatu yang sedikit lebih elegan. Saya membutuhkan sedikit lebih banyak rahmat dalam hidup saya, bukan lebih banyak kunci.

    Dan saya juga ingin tetap menggunakan kunci sinkron: semakin pendek kita memegangnya, semakin baik.

    Kita harus dapat:

    • Dapatkan kunci
      • Jika ada nilai baru dalam cache, dapatkan dan lepaskan kuncinya

      • Jika tidak, jika ada permintaan dalam penerbangan , berlangganan dan lepaskan kuncinya
    • Jika tidak, mulailah permintaan kita sendiri (dan biarkan permintaan lain berlangganan) dan lepaskan kunci saat dalam penerbangan tokio-console's resources viewtokio-console's resources view

      Dan ada sejumlah kehalusan yang mengejutkan dalam melakukan hal itu!

      Naluri saya memberi tahu saya "kita 'akan membutuhkan saluran": pada dasarnya kita membutuhkan saluran el dari "tugas", "permintaan dalam penerbangan" tempat kami dapat menerima nilai.

      Tetapi mungkin ada beberapa pelanggan untuk permintaan dalam penerbangan yang sama, jadi kami tidak dapat menggunakan saluran mpsc, jenis saluran yang paling umum: itu multi-produsen (Pengirim adalah Klon), tetapi konsumen tunggal.

      Sebagai gantinya, kami menginginkan saluran siaran, yang multi- produsen, multi-konsumen.

      Yang menarik di sini adalah Penerima tidak Klon - jadi kami tidak dapat menyimpan Penerima di negara bagian kita, kita harus menyimpan Pengirim, dan berlangganan.

      Mari kita lihat seperti apa versi naifnya:

      Kode karat

      // di `src/main.rs`// kembali untuk menyinkronkan mutex (bukan `tokio::sync::Mutex`)

      menggunakan std:: sinkronisasi::Mutex; menggunakan tokio:: sinkronisasi:: siaran; #[derive(Serialize)]struktur CachedVideo Terbaru { batin: BusurMutexCachedLastVideoInner>>, } #[derive(Default)]struktur CachedLastVideoInner { terakhir_diambil: Pilihan( Instan, Rangkaian)>, dalam penerbangan: Pilihansiaran:: PengirimHasilRangkaian, Kotakdyn Kesalahan>>>>, }

      Imm Akhirnya, kami mengalami masalah: kami tidak dapat mengirim Kotak antar utas: dan kami pasti akan melakukannya, karena berbagai penangan permintaan semuanya berjalan dalam tugas yang berbeda, yang mungkin dijadwalkan pada utas OS yang berbeda.

      Jadi, mari kita meminta Mengirim dan Sinkronisasi batas juga:

      Kode karat

      #[derive(Default)] struktur CachedLastVideoInner { terakhir_diambil: Pilihan( Instan, Rangkaian)>, // di sini dalam penerbangan: Pilihansiaran:: PengirimHasilRangkaian , Kotak dynKesalahan

      + Sinkronisasi + Mengirim>>>>, }

      Kemudian, kita mengalami beberapa lagi masalah karena... nilai apa pun yang dikirim ke saluran siaran harus

      Klon.  Yang masuk akal: setiap konsumen akan mendapatkan salinannya sendiri (kloningnya sendiri, saya kira) dari nilainya.  Dan kami tidak meminta jenis Kesalahan menjadi Klon, jadi mari tambahkan satu ikatan lagi... 

      Kode karat

      dalam penerbangan: Opsi :: Sender> >>,

      Tapi kemudian...

      Kode karat

      kesalahan[E0225]: hanya ciri otomatis yang dapat digunakan sebagai tambahan sifat-sifat di dalam Sebuah sifat objek --> src/main.rs: 42: 85 | 42 | dalam penerbangan: Opsi :: Sender> >>, | ----- ^^^^^ tambahan non-otomatis sifat | | | non-otomatis pertama sifat| =Tolong: mempertimbangkan membuat yang baru sifat dengan semua ini sebagai supertraits dan menggunakan itu sifat di sini sebagai gantinya: ` sifat Sifat Baru: StdError + Klon {}`=catatan: ciri otomatis seperti `Kirim` dan `Sinkronisasi` adalah sifat yang memiliki properti khusus; untuk informasi lebih lanjut tentang mereka, kunjungi //doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

      Mhhh kita mungkin berada di jalan yang salah di sini. Saya bahkan tidak yakin semua kesalahan yang mungkin kami dapatkan adalah

      Klon tetap (atau [ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] Mengirim, atau

      Sinkronisasi

      ). Sebagai gantinya, mari kita buat jenis kesalahan yang membawa representasi string yang dapat dibaca manusia dari kesalahan tersebut.

      Sesi shell

      $ cargo add thiserror Memperbarui 'https://github.com/rust-lang/crates.io-index ' index Menambahkan kesalahan ini v1.0.30 ke dependensi

      Kode karat

      // di `src/main.rs` #[error("stringified error: {inner}")]#[async output]pubstruktur CachedError { batin: Rangkaian, } implKesalahan Cached { pub fn baruE: std:: fmt :: Menampilkan>(e: E) -> Diri sendiri { Diri sendiri { batin: e.ke_string()A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text , } } } impl DariLaporan> untuk CacheError { fn dari(eA screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text : Laporan) -> Diri sendiri { CacheError :: baru([1728] e) } } impl Darisiaran:: kesalahan:: RecvError> untuk CachedError{ fn dari(e: siaran:: kesalahan:: RecvError) -> Diri sendiri { [1728] Kesalahan Cached:: baru(e) } } // dan untuk kelengkapan impl DariCacheError>untuk Laporan kesalahan { fndari (berbuat salah: CacheError) -> Diri sendiri { Laporan kesalahan(berbuat salah.ke dalam()) } }

      Sekarang kita bisa memiliki ini menjadi jenis kesalahan kami, yang secara otomatis Mengirim dan Sinkronisasi, juga Klon karena kami menurunkannya, dan mengimplementasikannya std::error::Error terima kasih atas kesalahan ini.

      Kode karat 

      #[derive(Default)] strukturCachedLastVideoInner { [1728] terakhir_diambil: Pilihan(Instan, Rangkaian)>, dalam penerbangan: Pilihansiaran:: PengirimHasilRangkaian, CacheError>>>, }

      Dan sekarang kita "hanya" harus mengimplementasikan penangan deduplikasi kami:

      Kode karat

      #

      [5] asinkron fn
      akar([1728] klien: Perpanjanganreqwest:: Klien>, di-cache : PerpanjanganCachedVideo Terbaru >, ) ->Hasil implis IntoResponse, Laporan kesalahan> { ["rt-tokio"] #[derive(Serialize)]strukturTanggapan { [430] video_id: Rangkaian, } // simpan kunci itu sepanjangmembiarkan mut batin=cache .batin.kunci().membuka(); jika membiarkan Beberapa((fetched_at, video_id))=dalam.terakhir_ diambil .as_ref() { [430] // apakah segar? jika diambil_at.berlalu() :: waktu:: Durasi:: from_secs(5) { [tracing::instrument(skip(stream))] kembali Oke(Json(Tanggapan { [] video_id: video_id.klon(), })); }lain { // sudah basi, ayo segarkan debug !("video basi, ayo menyegarkan"); } } // apakah ada permintaan dalam penerbangan?jika membiarkan Beberapa([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] dalam penerbangan)=dalam .dalam penerbangan.as_ref([tracing::instrument(skip(stream))] ) { // ya, berlanggananlah!membiarkan mut rx=dalam penerbangan.langganan(); membiarkan video_id=rx . recv().tunggu .map_err([1146] |_| eyre!("permintaan dalam penerbangan meninggal "))??; debug!("menerima pengambilan duplikat"); kembali Oke([1204] Json(Tanggapan { video_id })); } // tidak ada, ayo ambil membiarkan ([1728] tx, A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text mut rx)=siaran :: saluran:: HasilRangkaian, CacheError>>(1); tokio:: muncul(asinkron bergerak { membiarkan video_id=youtube:: fetch_video_id(&klien). menunggu; cocok video_id { Oke(video_id)=> { dalam .terakhir_diambil.mengganti((Instan:: sekarang(), video_id.klon())); tx.mengirim(Oke(video_id)) } Err(e)=> tx.mengirim(["rt-tokio"] Berbuat salah (e.ke dalam())), }; }); membiarkan video_id=rx .recv([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] ) .tunggu .map_err( |_| eyre!("permintaan dalam penerbangan meninggal "))??; debug!("menerima pengambilan duplikat"); Oke(Json([201] Tanggapan { video_id })) } Kode ini salah, dan untungnya, kompiler Rust menangkapnya:

    Sesi shell

    $ cargo check Memeriksa plakat v0.1.0 (/home/amos/bearcove/plaque) error: masa depan tidak dapat dikirim antar utas dengan aman --> src/main.rs:124:5 | 124 | tokio::spawn(async move { | ^^^^^^^^^^^^^^ masa depan yang dibuat oleh blok async bukan `Send` |=help: dalam `impl Future `, sifat `Kirim` tidak diterapkan untuk `std::sync::MutexGuard` catatan: nilai yang diambil bukan `Kirim` --> src/main.rs:128:17 | 128 | dalam | ^^^^^ memiliki tipe `std::sync::MutexGuard` yang bukan `Kirim` catatan: diperlukan oleh ikatan di `tokio::spawn` --> /home/amos/.cargo/registry/src/github.com -1ecc6299db9ec823/tokio-1.17.0/src/task/spawn.rs:127:21 | 127 | T: Masa Depan + Kirim + 'statis, | ^^^^ diperlukan oleh ikatan ini dalam kesalahan `tokio::spawn`: tidak dapat mengkompilasi `plaque` karena kesalahan sebelumnya

    Ups! Kami benar-benar mengirim MutexGuard di seluruh utas (dengan memunculkan tugas, yang mungkin dijadwalkan di utas apa pun).

    Ini adalah hal yang baik kompiler menangkapnya, karena bukan itu yang kami maksudkan: kami tidak ingin menggantung ke kunci itu, kami ingin melepaskannya dan mendapatkannya kembali ketika kami mengisi hasilnya.

    Juga, kami lupa menyimpan pengirim di inner.inflight dan bersihkan jika sudah selesai. Tetapi yang lebih penting: ketika kami berlangganan tugas dalam penerbangan, kami tetap berpegang pada

    batin kunci!  Itu akan menjadi jalan buntu.  Kompilator menangkap ini juga, tetapi pesannya kali ini tidak bagus:  
    Kerang sidang

    $ cargo check Memeriksa plak v0.1.0 (/home/amos/bearcove/plaque) error[async output]: sifat terikat `fn(Ekstensi<:client>, Ekstensi ) -> impl Masa Depan > {root}: axum::handler::Handler` tidak puas --> src/main.rs:76:25 | 76 | .route("/", get(root)) | --- ^^^^ sifat `axum::handler::Handler<_ _>` tidak diterapkan untuk `fn(Extension[1204] , Ekstensi <_ _>) -> impl Masa Depan > {root}` | | | dibutuhkan oleh suatu ikatan yang diperkenalkan oleh panggilan ini | catatan: diperlukan oleh ikatan di `axum::routing::get` --> /home/amos/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.4.8/src/routing/method_routing.rs :394:1 | 394 | top_level_handler_fn!(dapatkan, DAPATKAN); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diperlukan oleh ikatan ini di `axum::routing::get`=catatan: kesalahan ini berasal dari makro `top_level_handler_fn` (di build Nightly, jalankan dengan -Z macro-backtrace untuk info selengkapnya) Untuk informasi selengkapnya tentang kesalahan ini, coba `rustc --explain E0277`. kesalahan: tidak dapat mengkompilasi `plak` karena kesalahan sebelumnya

    Jadi kali ini, kita harus berpikir sedikit: kita hanya ingin melakukan hal-hal yang sinkron sambil memegang penjaga kunci itu: kami tidak bisa .menunggu apa pun.

    Jadi, kita harus sedikit kreatif. Agar sepenuhnya aman, kami akan menggunakan cakupan untuk membatasi berapa lama kami menahan kunci - untuk memastikan bahwa saat kami berada di luar cakupan ini, kunci dilepaskan.

    Sesuatu seperti ini:

    fn() { biarkan apa saja={ biarkan batin=cached.inner.lock().unwrap();  // lakukan sinkronisasi dengan `inner` };  // lakukan hal-hal asinkron dengan `apa pun` } 

    Memberi, dengan kode asli, ini:

    Kode karat

    #[tracing::instrument(skip(client, cached))] asinkron fn akar( klien: Perpanjanganreqwest:: Klien>, di-cache : PerpanjanganCachedVideo Terbaru

    >, ) -> Hasil impl IntoResponse, Laporan kesalahan> {#[derive(Serialize)] struktur Tanggapan { video_id : Rangkaian, } membiarkan mut rx={ [1204] // hanya menyinkronkan kode di blok ini membiarkan mut batin=cache .batin.kunci(). membuka(); jika membiarkan Beberapa(([tracing::instrument(skip(stream))] fetched_at, video_id))=dalam.terakhir_diambil.as_ref() {// apakah itu segar? jika diambil_at.berlalu():: waktu:: Durasi:: from_secs([1146] 5){ kembali Oke(Json(Tanggapan { video_id: video_id.klon(), })); } lain { [1728] // sudah basi, ayo segarkan debug!([1146] "video basi, ayo segarkan"); } } // apakah ada di- permintaan penerbangan? jika membiarkan Beberapa(dalam penerbangan )=dalam.dalam penerbangan.as_ref() { dalam penerbangan.langganan() } lain { // tidak ada, ayo ambil membiarkan ([1204] tx, rx)=siaran :: saluran:: HasilRangkaian, Kesalahan Cached>>(["rt-tokio"] 1); batin .dalam penerbangan=Beberapa(tx.klon()); membiarkan di-cache=di-cache .klon(); tokio:: muncul( asinkron bergerak{ membiarkan video_id=youtube :: fetch_video_id(&klien).menunggu; { // hanya menyinkronkan kode di blok ini membiarkan mut dalam=cache .batin.kunci().membuka(); batin .dalam penerbangan=Tidak ada ; cocok video_id { Oke[tokio::main] ([1146] video_id)=> { dalam [tracing::instrument] .terakhir_diambil .mengganti(( Instan :: sekarang(), video_id.klon())) );membiarkan _=tx.mengirim([1204] Oke(video_id)); } Err(e)=> { membiarkan _=tx.mengirim(Berbuat salah( e.ke dalam())) ; } }; } }); rx } }; // jika kami sampai di sini, kami sedang menunggu permintaan dalam penerbangan (kami tidak // dapat melayani dari cache) Oke([333] Json(Tanggapan { video_id: rx.recv() .tunggu .map_err( |_| eyre!("permintaan dalam penerbangan meninggal "))??, } )) }

    Dan itu tampaknya berhasil:

    Sesi shell

    $ oha -n 10 -c 5 http://localhost:3779 (cut) Histogram waktu respons: 0,005 [5] |■■■■■■■■■■■■■■ 0,011 [0] | 0,016 [0] | 0,022 [0] | 0,027 [0] | 0,033 [0] | 0,038 [0] | 0,044 [0] | 0,049 [0] | 0,055 [0] | 0,060 [tracing::instrument(skip(client, cached))] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

    Menjadikannya generik

    Kemungkinannya, bukan itu saja yang ingin kita tembolok! Dan jika kami memiliki tiga atau lebih, kami tidak ingin menyalin-tempel kode itu di mana-mana.

    Yah, tidak apa-apa, kami hanya akan membuatnya generik!

    Faktanya, mari kita pindahkan semua kode yang terkait dengan caching/deduplikasi ke modulnya sendiri:

    Kode karat

    // di `src/main.rs` mod di-cache ; menggunakan di-cache :: {CachedError, Cached};

    Kode karat 

    // di `src/cached.rs`

    menggunakanstd::{ sinkronkan:: {Arc, Mutex}, waktu::{Durasi, Instan}, }; menggunakan color_eyre:: {eyre[ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] :: eyre, Laporan};menggunakan tokio:: sinkronisasi:: siaran; menggunakan pelacakan:: debug; #[derive(Debug, Clone, thiserror::Error)] #[error("stringified error: {inner}")]pubstruktur CacheError{ batin: Rangkaian, } implCacheError { [1146] pub fnbaruE: std:: fmt: : Menampilkan>(e: E ) -> Diri sendiri { Diri sendiri { batin: e.ke_string(), } } } implDariLaporan>untuk CacheError{ fndari(e : Laporan) -> Diri sendiri { CachedError:: baru(e) } } impl Darisiaran:: kesalahan:: RecvError> untuk CacheError { fn dari(e: siaran:: kesalahan::RecvError) ->Diri sendiri { CacheError:: baru(e) } } #[derive(Clone)] pub strukturDitembolok T> di mana T: Klon + Mengirim + Sinkronisasi + 'statis, { batin: BusurMutexCachedLastVideoInnerT>>>, refresh_interval: Durasi, } strukturCachedLastVideoInnerT> di manaT: Klon + Mengirim + Sinkronisasi + 'statis, { terakhir_diambil: Pilihan([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] Instan,T)>, dalam penerbangan: PilihanB siaran jalan :: PengirimHasilT, CacheError>>>, } implT> Bawaan untuk CachedLastVideoInnerT> di mana T: Klon + Mengirim + Sinkronisasi + 'statis, { fn bawaan() -> Diri sendiri { Diri sendiri { terakhir_diambil: Tidak ada,dalam penerbangan: Tidak ada, } } } implT> DitembolokT>di mana T: Klon + Mengirim + Sinkronisasi + 'statis, { ["rt-tokio"] pubfn baru(refresh_interval: Durasi) ->Diri sendiri { Diri sendiri { batin: Bawaan:: bawaan(), refresh_interval, } } pub asinkron fn get_cached(&diri sendiri) -> HasilT, Kesalahan Cached> { membiarkan mut rx={ // hanya menyinkronkan kode di blok ini membiarkan mut[:n] batin=diri sendiri.batin.kunci([1204] ).membuka( ); jika membiarkan Beberapa((diambil_at, nilai))=dalam.terakhir_diambil.as_ref([1728] ) { [201] jika diambil_at.berlalu() diri sendiri.refresh_interval { [tracing::instrument] kembali Oke(nilai.klon()); } lain { [:n] debug!([1728] "basi, ayo segarkan"); } } jika membiarkan Beberapa([1728] dalam penerbangan)=dalam.dalam penerbangan.Sebuah s_ref([1146] ) { dalam penerbangan.langganan() } lain { // tidak ada, ayo ambil membiarkan (tx, rx)=siaran :: saluran:: HasilT, Kesalahan Cached>>(1); batin .dalam penerbangan=Beberapa ([1146] tx.klon( )); membiarkan dalam=diri sendiri. batin.klon(); tokio::muncul([1146] asinkron bergerak { [ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] membiarkan res=melakukan!("bagaimana kita benar-benar melakukan permintaan?" ); { // hanya menyinkronkan kode di blok ini membiarkan mut batin=batin .kunci().membuka(); batin .dalam penerbangan=Tidak Ada; cocok res { Oke[1204] (nilai)=> { dalam.last_fetched. mengganti([1146] (Instan:: sekarang() , nilai.klon ())); membiarkan _=tx.mengirim(Oke([1146] nilai)); } Err([1146] e)=> { ["rt-tokio"] membiarkan _=tx.mengirim(Berbuat salah( e.ke dalam([201] ))); } }; } }); rx } }; // jika kami sampai di sini, kami sedang menunggu permintaan dalam penerbangan (kami tidak // dapat melayani dari cache) Oke(rx .recv() .tunggu .map_err(|_| eyre!("permintaan dalam penerbangan meninggal "))??) } }

    Hanya ada satu kerutan kecil... seperti melakukan! menyebutkan: bagaimana kami benar-benar melakukan permintaan? Dari mana kita mendapatkan nilai-nilai kita? Itu juga, harus generik.

    Kita bisa mengambil masa depan secara langsung, tetapi itu berarti kita perlu membangun masa depan (mis. call

    youtube::fetch_video_id

     tanpa menunggu) setiap kali kita ingin menelepon get_cached

    - dan itu pekerjaan yang tidak perlu.

    Ini pendapat saya: sebagai gantinya, mari kita terima penutupan yang dapat membangun masa depan. Di sini saya akan berasumsi bahwa tidak apa-apa untuk mengalokasikan tumpukan di jalur kode itu, jadi kami akan mengembalikannya ke masa depan kotak untuk kesederhanaan. Implementasi kami dari

    youtube::fetch_video_id adalah fn async, jadi jenis masa depannya tidak dapat disebutkan namanya, pada Rust 1.59.0 stable, kecuali saya kehilangan sesuatu. Jadi, ketik cepat alias:

    Kode karat

    [500]// di `src/cached. rs`menggunakan std:: masa depan:: Masa depan; pub Tipe BoxFut'Sebuah, HAI>=PinKotak dyn Masa depanKeluaran=HAI> + Mengirim + 'Sebuah>>;

    Dan mari kita terima fungsi mengembalikan masa depan kotak yang mengembalikan

    T (dan bisa gagal! dengan jenis kesalahan apa pun yang kami tahu cara menampilkannya!)

    Kode karat

    // di ` src/cached.rs` pub asinkron fn get_cachedF, E>(&diri sendiri, F: F) -> HasilT, CacheError>di mana F : FnOnce() -> BoxFut' statis, HasilT, E>> + Mengirim + 'statis, E: std:: fmt:: Menampilkan + 'statis, { // ( memotong) }

    Dan kode kita menjadi:

    Kode karat

    // di `src/cached.rs` jika membiarkan Beberapa( dalam penerbangan)=dalam .dalam penerbangan.as_ref() { dalam penerbangan.langganan() } lain { // tidak ada, ayo ambil membiarkan (tx, rx)=siaran :: saluran:: HasilT, CacheError >> (1); batin .dalam penerbangan=Beberapa (tx.klon()); membiarkan dalam=diri sendiri.batin. klon(); // panggil penutupan terlebih dahulu, jadi kami tidak mengirim _it_ melintasi utas,// hanya Masa Depan yang dikembalikan membiarkan fut=F([1204] ); tokio:: muncul( asinkron bergerak { membiarkan res=fut.menunggu; { // hanya menyinkronkan kode di blok ini membiarkan mut batin=batin .kunci([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] ).membuka(); batin .dalam penerbangan=Tidak Ada; cocok res { Oke[1204] (nilai)=> { dalam .terakhir_diambil.mengganti((Instan:: sekarang(), nilai.klon([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] )))) ;membiarkan _=tx.mengirim(Oke([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] nilai)); } Err(e)=> { membiarkan _=tx.mengirim([tracing::instrument(skip(stream))] Berbuat salah(CacheError { [1728] batin: e.ke_string(), })); } }; } }); rx } Sekarang gunakan kami kode generik yang indah untuk nyata:

    Kode karat

    // di `src/main.rs` #[1] strukturVideo Terbaru(Rangkaian);

    Tumpukan lapisan kami menjadi ini:

    Kode karat

     membiarkan aplikasi=Router:: 

    baru() .rute([tracing::instrument(skip(stream))] "/", Dapatkan(akar)) .lapisan(Lapisan Jejak:: new_for_http()) .lapisan (Perpanjangan([1728] reqwest:: Klien:: baru())) .lapisan([1728] Perpanjangan( Ditembolok :: Video Terbaru>:: baru([1146] Durasi:: from_secs( 5, ))));

    Dan akar, hanya ini:

    Kode karat

    #[tracing::instrument(skip(client, cached))] asinkron fn akar(klien: Perpanjanganreqwest:: Klien>,  dalam cache : Perpanjangan Ditembolok Video Terbaru>>, ) -> Hasilimpl 

    IntoResponse,Laporan kesalahan> { [:n] #["json"] strukturTanggapan { video_id: Rangkaian , } membiarkan Video Terbaru(video_id)=cache ed .get_cached(|| { Kotak:: pin(asinkron bergerak { membiarkan video_id=youtube:: fetch_video_id([1146] &klien).menunggu?; Oke:: _, Laporan>(Video Terbaru(video_id)) }) }) .menunggu?; Oke(Json([ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] Tanggapan { video_id })) }

    Itu bagus! Nah, ada blok async-move yang disematkan kotak dalam penutupan, dan kita harus menggunakan lil' turbofish untuk memberi tahu kompiler jenis kesalahannya, tetapi kami telah memisahkan masalah, sejauh yang saya ketahui: misi ahli.

    Jika item yang di-cache dikunci, kita dapat dengan mudah membayangkan bahwa bagian luar Ditembolok memiliki struktur HashMap dari CacheInner sebagai gantinya: kami masih memiliki single e lock, yang hanya akan dipegang untuk waktu yang sangat singkat.

    Dalam aplikasi produksi, kita mungkin sebenarnya ingin memiliki negatif cache juga: jika kami tidak dapat mengambil video karena alasan tertentu, dan gagal hampir segera

    , kami tidak ingin membombardir server API YouTube dengan permintaan. Kami ingin tidur di antara percobaan ulang, dan meminta pawang kami menunggu beberapa percobaan ulang, atau segera mengembalikan kesalahan.

    Kami juga mungkin ingin menyetel batas waktu pada permintaan HTTP itu: Saya rasa tidak ada batas waktu (batas waktu koneksi, pembacaan menganggur/ write timeout) pada klien reqwest default itu, dan itu biasanya membuat saya gugup. Timeout semua hal, selalu!

    Tapi bukan itu yang saya khawatirkan - Anda semua bisa menyelesaikan semua ini sendiri.

    Yang saya khawatirkan adalah kami meninggalkan bug yang hebat dan mengerikan di sana. Yang, di masa lalu kehidupan pekerjaan harian, menyebabkan insiden yang sangat buruk yang sangat sulit untuk diselidiki.

    Panik! atas permintaan dalam penerbangan

    Apa yang terjadi jika ada bagian dari kode kita yang panik? Itu adalah sesuatu yang perlu kita pikirkan, dan bahwa Rust melindungi kita kurang dari biasanya dari. Itu bukan masalah keamanan, "hanya" kesalahan logika.

    Ayo buat... permintaan ke-3 panik.

    Kode karat

    // di `src/cached.rs` menggunakan std:: sinkronisasi:: atom :: {AtomicU64, Pemesanan};statis DOOM_COUNTER: A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text AtomicU64=AtomicU64::baru( 0); implT> Di-cache T> di mana T: Klon + Mengirim + Sinkronisasi + 'statis, {pub asinkron fn get_cachedF, E>([1146] &diri sendiri, F: F) -> HasilT, CachedError> di mana F: FnOnce([tracing::instrument(skip(stream))] ) -> BoxFut'statis, HasilT, E>>, E: std:: fmt:: Menampilkan + 'statis, { jika DOOM_COUNTER.fetch_add(1, Memerintah:: SeqCst)==2 { panik!([1728] "malapetaka!"); } // dll. } }

    Sesi shell

    $ oha -n 10 -c 5 http://localhost:3779 Distribusi kode status: [333] 9 tanggapan Distribusi kesalahan: [1] koneksi ditutup sebelum pesan selesai Oke, tidak apa-apa - hanya permintaan ketiga yang terpengaruh. Karena pawang kami panik, axum/hyper baru saja menutup koneksi. Sepertinya respon yang masuk akal, meskipun jika kita mau, kita mungkin bisa menangkap kepanikan dan mengubahnya menjadi 500 sebagai gantinya.

    Sekarang mari kita panik di tempat lain! Katakanlah, setelah kami mendapatkan kunci:

    Kode karat 

     pub asinkron fnget_cachedF, E>(&diri sendiri, F: F ) -> HasilT, CacheError> di mana F: FnOnce( 

    ) -> BoxFut'statis, Hasil T, E>>, E: std:: fmt:: Menampilkan + 'statis, { membiarkan mut rx={ [:n] // hanya menyinkronkan kode di blok inimembiarkan mut batin=diri sendiri.batin.kunci().membuka([tracing::instrument(skip(stream))] );jika DOOM_COUNTER.fetch_add([1146] 1, Memerintah:: SeqCst)==2 { [201] panik!("malapetaka!");} // dll. };// jika kami sampai di sini, kami menunggu untuk permintaan dalam penerbangan (kami tidak // dapat melayani dari cache) Oke(rx .recv () .tunggu .map_err(|_ | eyre!("permintaan dalam penerbangan meninggal "))??) } [500] Sekarang, oha tidak pernah selesai: macet di TUI dengan koneksi 8 "ditutup sebelum pesan selesai", dan 2 permintaan yang... tidak pernah selesai.

    Log server kami menceritakan kisahnya:

    Sesi shell

    Aplikasi panik (crash). Pesan: disebut `Result::unwrap()` pada nilai `Err`: PoisonError { .. } Lokasi: src/cached.rs:91 SPANTRACE 0: plakat::root di src/main.rs:49 1: tower_http::trace::make_span::request with method=GET uri=/ version=HTTP/1.1 at /home/amos/.cargo/registry /src/github.com-1ecc6299db9ec823/tower-http-0.2.3/src/trace/make_span.rs:116 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Karena kami panik sambil memegang MutexGuard , mutex sekarang diracuni, dan tidak ada yang bisa mendapatkannya lagi.

    Jadi yang terjadi adalah:

  • Permintaan pertama tidak menemukan apa pun di cache, memulai tugas
  • Tugas kedua menemukan permintaan dalam penerbangan, berlangganan
    • Tugas ketiga mendapatkan kunci, panik memegangnya

    • Tugas 4 sampai 10 panik saat mereka mencoba untuk mendapatkan e kunci beracun

    Permintaan pertama dan kedua menunggu tugas yang tidak akan pernah selesai, karena itu juga panik saat mencoba mendapatkan kunci beracun. Jadi kami mendapatkan delapan reset koneksi, dan dua permintaan macet selamanya.

    [500]

    Saya kira di situlah fasilitas batas waktu menara akan berguna, agar permintaan itu akhirnya gagal .

    Ya ! Waktu habis semuanya.
    Sudah kubilang ada kehalusan!

    Tapi itu bukan bug yang saya maksud . Dan itu cukup mudah untuk dipecahkan: kita bisa beralih ke non-keracunan

    Mutex, seperti parking_lot's. 

    Sesi shell

    $ cargo add parking_lot Memperbarui 'https: //github.com/rust-lang/crates.io-index' index Menambahkan parking_lot v0.12.0 ke dependensi

    Kita hanya perlu menukar std::sync::Mutex dengan

    parkir_lot::Mutex

    , dan hapus pasangan .membuka() panggilan, sejak parkir_lot::Mutex: :membuka kunci() sempurna.

    Dan sekarang, masih dengan permintaan ketiga panik sambil memegang kunci, kita punya:

    Sesi shell

    $ oha -n 10 -c 5 http://localhost:3779 ( potong) Distribusi kode status: [430] 9 tanggapan Distribusi kesalahan: [1] koneksi ditutup sebelum pesan selesai

    ...hanya permintaan ketiga yang gagal, persis seperti yang kami inginkan.

    Tapi sekarang... bagaimana jika kami panik

    dalam tugas itu sendiri? Dalam "permintaan dalam penerbangan"?

    Ayo panik dulu saja, kali ini:

    Kode karat

    tokio:: muncul([1146] asinkron bergerak { [201] membiarkan res=fut.menunggu; jika DOOM_COUNTER.fetch_add(1, Memerintah:: SeqCst)==0 { [ { "name": "run_server" }, { "addr": "127.0.0.1:34734", "name": "handle_connection" } ] panik!("malapetaka!"); } {// hanya menyinkronkan kode di blok ini membiarkan mut batin=batin .kunci (); batin . dalam penerbangan=Tidak Ada;// dll. } });

    Di dalam itu kasus, kami melihat kepanikan di log server:

    Sesi shell

    Aplikasi p anick (jatuh). Pesan: kiamat! Lokasi: src/cached.rs:118 Backtrace dihilangkan. Jalankan dengan variabel lingkungan RUST_BACKTRACE=1 untuk menampilkannya. Jalankan dengan RUST_BACKTRACE=full untuk menyertakan cuplikan sumber.

    Tapi dari luar, semua yang kita lihat adalah semua permintaan macet.

    Semua permintaan.

    Bukan hanya yang pertama, bukan hanya untuk lima detik pertama, semua permintaan, selamanya.

    Dan itulah tentang insiden pekerjaan harian saya yang buruk: sekelompok permintaan dalam penerbangan menjadi buruk, jadi kami' akan selamanya terjebak menunggu mereka, lama melewati setiap kesempatan yang pernah mereka selesaikan, dan tidak pernah memulai permintaan baru.

    Untuk memperburuk keadaan, itu bahkan bukan kepanikan! Itu hanya contoh yang saya pilih untuk menunjukkan bug kepada Anda. Kepanikan akan ditangkap oleh reporter Penjaga, dan kami akan menerima peringatan tentang hal itu.

    Tidak, itu adalah batas waktu. Tugas itu hanya memakan waktu lebih lama dari beberapa detik, dan waktunya habis - yang di Rust, sama saja dengan menjatuhkan masa depan.

    Dan dengan saluran MPSC, Anda dapat mendeteksi kondisi itu: jika Anda memegang

    Penerima dan semua Pengirims dijatuhkan, masa depan dikembalikan oleh recv hanya menghasilkan Tidak ada - itu akan menjadi isyarat kami bahwa ada yang tidak beres dengan permintaan dalam penerbangan, dan kami bebas untuk mencoba lagi.

    Tapi di sini... tidak ada yang seperti itu ! Karena satu-satunya cara untuk dapat berlangganan hasil permintaan dalam penerbangan adalah dengan menahan Pengirim. Jadi ada dua tempat yang memegang Pengirim untuk saluran yang sama: permintaan dalam penerbangan dan negara kami yang dilindungi mutex.

    Jika permintaan dalam penerbangan tidak berjalan ke selesai, tidak pernah tergantikan inner.inflight dengan Tidak ada, dan saluran tidak pernah ditutup. Sebuah bug yang mengingatkan pada spaghetti saluran Go yang saya temukan di mana pun saya melihat.

    Jadi..... bagaimana kita menyelesaikan ini?

    Sehat!

    Kami ingin saluran ditutup apakah tugas berhasil diselesaikan atau panik. Artinya, kami ingin Pengirim untuk menjatuhkan dengan cara apa pun. Semua pengirim untuk saluran itu.

    Kami tidak bisa bukan menahan sebuah Pengirim dalam keadaan internal kami, karena itulah cara kami berlangganan tugas dalam penerbangan ( kecuali jika kita ingin kembali ke solusi mutex dua tingkat yang saya sebutkan di atas).

    Tapi mungkin kita bisa bertahan mengacu kepada Pengirim itu... tidak mencegahnya jatuh.

    Referensi yang memungkinkan kami mengakses Pengirim jika masih ada, jika belum dijatuhkan oleh permintaan dalam penerbangan.

    Dan itulah yang Weak biarkan kami lakukan o: ini adalah versi Arc yang memiliki referensi yang tidak memiliki

    ke T.

    Dengan cara ini:

    • Tugas akan tahan Busur, dan itu akan jatuhkan apakah itu panik atau berhasil
      • Keadaan internal akan mengadakan Lemah, yang tidak akan mencegah Pengirim agar tidak dijatuhkan

      Dan untuk mengakses Pengirim dari status (ketika kita ingin berlangganan hasilnya), kita cukup menggunakan Weak::upgrade, yang mengembalikan

      Pilihan >  .  Jika kita mendapatkan Beberapa, kami tahu tugas itu masih hidup.  Jika kita mendapatkan Tidak ada, kami tahu itu tidak. 
      Tapi bukankah itu balapan? kondisi? Tidak bisakah kita mendapatkan Busur Baik sebelum tugas selesai , dan kemudian kami tidak akan pernah menerima apa pun?
      [500] Tidak, karena kami hanya memanipulasi inner.inflight sambil memegang kunci.

    Jadi, tanpa basa-basi lagi, inilah ide saya untuk memperbaikinya:

    Kode karat

    // di `src/cached.rs`A screenshot of Microsoft Edge (Chromium-based) that's opened to localhost:3779 and shows the text menggunakan std:: sinkronisasi::  Lemah; struktur Cach  edLastVideoInnerT>di mana  T: Klon + Mengirim + Sinkronisasi + 'statis, { terakhir_diambil: Pilihan ([1146] 

    Instan , T)>, // sekarang lemah! dalam penerbangan: PilihanLemahsiaran:: PengirimHasilT, CacheError>>>>, } implT> Ditembolok T> di mana T: Klon + Mengirim + Sinkronisasi + 'statis, { pub asinkron fn get_cachedF, E>(&diri sendiri, F: F) -> HasilT, CacheError >di mana F: Fn Satu kali([1146] ) -> BoxFut'statis, HasilT, E >>, E: std:: fmt:: Menampilkan + 'statis, { [201] membiarkan mut rx={ membiarkan mu T dalam=diri sendiri.batin.kunci(); jika membiarkan Beberapa(([333] diambil_at, nilai))=dalam .terakhir_diambil.as_ref() { // dll. } // hanya dalam penerbangan jika kita dapat meningkatkannya ke `Arc`: jika membiarkan Beberapa(dalam penerbangan)=dalam.dalam penerbangan.as_ref() .dan kemudian(Lemah:: meningkatkan) { dalam penerbangan.langganan(["rt-tokio"] )) } lain { membiarkan ( tx, rx )=siaran :: saluran:: HasilT, Kesalahan Cached >>(1); // mari kita hitung-referensi satu `Pengirim`: membiarkan tx=Busur:: baru([1204] tx); / / dan hanya menyimpan referensi yang lemah di negara kita: // batin .dalam penerbangan=Beberapa(Busur:: penurunan versi (&tx)); membiarkan dalam=diri sendiri.batin.klon(); membiarkan fut=F(); tokio::muncul( asinkron bergerak { membiarkan res=fut.menunggu; // masih menguji kepanikan itu... jika DOOM_COUNTER.fetch_add(["rt-tokio"] 1, Memerintah :: SeqCst)==0 { panik!("malapetaka!"); } {// dll. } }); rx } }; // dll. } }

    Dan izinkan saya memberi tahu Anda, saya cukup senang dengan diri saya sendiri ketika saya menemukan pola ini.

    (Itu [167022] bukan perbaikan yang kami gunakan pada pekerjaan siang hari itu: Saya melakukan sesuatu yang jauh lebih rumit dengan Menjatuhkan impl dan semuanya. Solusi ini cukup elegan dibandingkan).

    Ayo kita coba test drive.

    Inilah permintaan batch pertama kami:

    Sesi shell

    $ oha dll. Distribusi kode status: [200] 5 tanggapan

    5 tanggapan Dan kedua:

    Sesi shell

    $ oha dll distribusi kode tus: [430] 10 tanggapan Sekarang, tugas yang sekarat hanya memberi ruang untuk tugas lain. Dan kami mendapatkan 500 kesalahan bagus yang terlihat seperti ini:

    Sesi shell

    $ curl http://localhost:3779 Kesalahan server internal: 0: kesalahan yang dirangkai: permintaan dalam penerbangan mati

    ...karena itu rx.recv() panggilan yang gagal, dan itu tidak panik, itu hanya menyebarkan kesalahan ke atas, yang diubah menjadi 500 oleh

    impl IntoResponse for ReportError.
    Dan hanya itu yang ingin saya tunjukkan kepada kalian semua hari ini!

    Sampai jumpa lagi, jaga dirimu baik-baik.

    Jika Anda menyukai artikel ini, dukung pekerjaan saya di Patreon!

    <_ _>Menjadi Pelindung


    Baca selengkapnya