diff --git a/Cargo.lock b/Cargo.lock index 80d77b8..e313706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,33 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "async-backtrace" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcb391558246d27a13f195c1e3a53eda422270fdd452bd57a5aa9c1da1bb198" +dependencies = [ + "async-backtrace-attributes", + "dashmap", + "futures", + "loom", + "once_cell", + "pin-project-lite", + "rustc-hash 1.1.0", + "static_assertions", +] + +[[package]] +name = "async-backtrace-attributes" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbba0d438add06462a0371997575927bc05052f7ec486e7a4ca405c956c3d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -963,6 +990,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -1467,6 +1507,7 @@ name = "librqbit" version = "7.0.0-beta.0" dependencies = [ "anyhow", + "async-backtrace", "async-stream", "async-trait", "axum 0.7.5", @@ -1689,6 +1730,19 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.4" @@ -2203,7 +2257,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "socket2", "thiserror", @@ -2220,7 +2274,7 @@ dependencies = [ "bytes", "rand", "ring", - "rustc-hash", + "rustc-hash 2.0.0", "rustls", "slab", "thiserror", @@ -2481,6 +2535,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.0.0" @@ -2562,6 +2622,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2992,6 +3058,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stringprep" version = "0.1.5" @@ -3696,6 +3768,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/crates/librqbit/Cargo.toml b/crates/librqbit/Cargo.toml index ac98be7..6ddab8b 100644 --- a/crates/librqbit/Cargo.toml +++ b/crates/librqbit/Cargo.toml @@ -88,6 +88,7 @@ lru = { version = "0.12.3", optional = true } mime_guess = { version = "2.0.5", default-features = false } tokio-socks = "0.5.2" async-trait = "0.1.81" +async-backtrace = "0.2" [build-dependencies] anyhow = "1" diff --git a/crates/librqbit/src/tests/e2e.rs b/crates/librqbit/src/tests/e2e.rs index e7ec84a..3edf7ea 100644 --- a/crates/librqbit/src/tests/e2e.rs +++ b/crates/librqbit/src/tests/e2e.rs @@ -14,7 +14,9 @@ use tracing::{error_span, info, Instrument}; use crate::{ create_torrent, - tests::test_util::{create_default_random_dir_with_torrents, TestPeerMetadata}, + tests::test_util::{ + create_default_random_dir_with_torrents, spawn_debug_server, TestPeerMetadata, + }, AddTorrentOptions, AddTorrentResponse, Session, SessionOptions, }; @@ -30,6 +32,8 @@ async fn test_e2e_download() { async fn _test_e2e_download() { let _ = tracing_subscriber::fmt::try_init(); + spawn_debug_server(); + // 1. Create a torrent // Ideally (for a more complicated test) with N files, and at least N pieces that span 2 files. diff --git a/crates/librqbit/src/tests/test_util.rs b/crates/librqbit/src/tests/test_util.rs index 965658a..1ffe9df 100644 --- a/crates/librqbit/src/tests/test_util.rs +++ b/crates/librqbit/src/tests/test_util.rs @@ -1,8 +1,11 @@ use std::{io::Write, path::Path}; +use anyhow::Context; +use axum::{response::IntoResponse, routing::get, Router}; use librqbit_core::Id20; use rand::{thread_rng, Rng, RngCore, SeedableRng}; use tempfile::TempDir; +use tracing::info; pub fn create_new_file_with_random_content(path: &Path, mut size: usize) { let mut file = std::fs::OpenOptions::new() @@ -79,3 +82,28 @@ impl TestPeerMetadata { 0f64 } } + +async fn debug_server() -> anyhow::Result<()> { + async fn backtraces() -> impl IntoResponse { + async_backtrace::taskdump_tree(true) + } + + let app = Router::new().route("/backtrace", get(backtraces)); + let app = app.into_make_service(); + + let addr = "127.0.0.1:3032"; + + info!(%addr, "starting HTTP server"); + + use tokio::net::TcpListener; + + let listener = TcpListener::bind(addr) + .await + .with_context(|| format!("error binding to {addr}"))?; + axum::serve(listener, app).await?; + Ok(()) +} + +pub fn spawn_debug_server() { + tokio::spawn(debug_server()); +} diff --git a/crates/librqbit/src/torrent_state/live/mod.rs b/crates/librqbit/src/torrent_state/live/mod.rs index ac5deae..a1b0cd6 100644 --- a/crates/librqbit/src/torrent_state/live/mod.rs +++ b/crates/librqbit/src/torrent_state/live/mod.rs @@ -353,6 +353,7 @@ impl TorrentStateLive { Ok(()) } + #[async_backtrace::framed] async fn task_manage_incoming_peer( self: Arc, checked_peer: CheckedIncomingConnection, @@ -413,6 +414,7 @@ impl TorrentStateLive { Ok(()) } + #[async_backtrace::framed] async fn task_manage_outgoing_peer( self: Arc, addr: SocketAddr, @@ -449,12 +451,12 @@ impl TorrentStateLive { state.meta.spawner, state.meta.connector.clone(), ); - let requester = handler + let requester = async_backtrace::frame!(handler .task_peer_chunk_requester() - .instrument(error_span!("chunk_requester")); - let conn_manager = peer_connection + .instrument(error_span!("chunk_requester"))); + let conn_manager = async_backtrace::frame!(peer_connection .manage_peer_outgoing(rx, state.have_broadcast_tx.subscribe()) - .instrument(error_span!("peer_connection")); + .instrument(error_span!("peer_connection"))); handler .counters @@ -1213,7 +1215,7 @@ impl PeerHandler { } loop { - self.wait_for_unchoke().await; + async_backtrace::frame!(self.wait_for_unchoke()).await; if self.state.is_finished_and_dont_need_peers() { debug!("nothing left to do, disconnecting peer"); @@ -1232,7 +1234,7 @@ impl PeerHandler { Some(next) => next, None => { debug!("no pieces to request"); - tokio::time::sleep(Duration::from_secs(10)).await; + async_backtrace::frame!(tokio::time::sleep(Duration::from_secs(10))).await; continue; } }; @@ -1266,7 +1268,12 @@ impl PeerHandler { }; loop { - match timeout(Duration::from_secs(10), self.requests_sem.acquire()).await { + match async_backtrace::frame!(timeout( + Duration::from_secs(10), + async_backtrace::frame!(self.requests_sem.acquire()) + )) + .await + { Ok(acq) => break acq?.forget(), Err(_) => continue, };