Skip to content

Commit cb60bc5

Browse files
author
Threepwood-7
committed
RUST-BUG-072: preserve queued source requests
1 parent 86c8f9b commit cb60bc5

4 files changed

Lines changed: 132 additions & 4 deletions

File tree

crates/emulebb-ed2k/src/ed2k_server/background.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,8 @@ pub async fn search_source_via_background_session(
158158

159159
tokio::select! {
160160
_ = cancel.cancelled() => Ok(Vec::new()),
161-
result = tokio::time::timeout(timeout, receive_response) => {
162-
let response = result
163-
.with_context(|| format!("timed out waiting for ED2K background source response after {timeout:?}"))?
164-
.context("ED2K background source responder dropped")?;
161+
response = receive_response => {
162+
let response = response.context("ED2K background source responder dropped")?;
165163
response.map_err(anyhow::Error::msg)
166164
}
167165
}

crates/emulebb-ed2k/src/ed2k_server/tests/background.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,61 @@ async fn background_source_search_channel_round_trips_results() {
9191
responder.await.unwrap();
9292
}
9393

94+
#[tokio::test]
95+
async fn background_source_search_waits_while_queued_before_dispatch() {
96+
let (handle, mut inbox) = new_ed2k_server_search_channel(1);
97+
let file_hash = Ed2kHash([0x52; 16]);
98+
let queued_handle = handle.clone();
99+
let search = tokio::spawn(async move {
100+
let cancel = CancellationToken::new();
101+
search_source_via_background_session(
102+
&queued_handle,
103+
file_hash,
104+
42,
105+
Duration::from_millis(20),
106+
&cancel,
107+
)
108+
.await
109+
});
110+
111+
tokio::time::sleep(Duration::from_millis(60)).await;
112+
let request = inbox.receiver.recv().await.unwrap();
113+
match request {
114+
BackgroundServerSearchRequest::Source {
115+
file_hash: requested_hash,
116+
timeout,
117+
response,
118+
..
119+
} => {
120+
assert_eq!(requested_hash, file_hash);
121+
assert_eq!(timeout, Duration::from_millis(20));
122+
let _ = response.send(Ok(Vec::new()));
123+
}
124+
other => panic!("unexpected background request: {other:?}"),
125+
}
126+
127+
assert_eq!(search.await.unwrap().unwrap(), Vec::new());
128+
}
129+
130+
#[tokio::test]
131+
async fn background_source_search_cancel_stops_queued_wait() {
132+
let (handle, _inbox) = new_ed2k_server_search_channel(1);
133+
let cancel = CancellationToken::new();
134+
cancel.cancel();
135+
136+
let results = search_source_via_background_session(
137+
&handle,
138+
Ed2kHash([0x53; 16]),
139+
42,
140+
Duration::from_secs(60),
141+
&cancel,
142+
)
143+
.await
144+
.unwrap();
145+
146+
assert!(results.is_empty());
147+
}
148+
94149
#[tokio::test]
95150
async fn background_udp_source_search_preserves_responding_server() {
96151
let server = test_udp_obfuscated_server();

docs/active/INDEX.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ items.
8383
| [RUST-BUG-069](items/RUST-BUG-069.md) | Major | DONE | Pace failed direct source retries per endpoint |
8484
| [RUST-BUG-070](items/RUST-BUG-070.md) | Major | DONE | Pace Kad source refreshes across retry attempts |
8585
| [RUST-BUG-071](items/RUST-BUG-071.md) | Major | DONE | Wait until direct source retry cooldown before reattempting |
86+
| [RUST-BUG-072](items/RUST-BUG-072.md) | Major | DONE | Do not expire queued connected-server source requests before dispatch |
8687

8788
## Refactors (`REF`)
8889

docs/active/items/RUST-BUG-072.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
id: RUST-BUG-072
3+
title: Do not expire queued connected-server source requests before dispatch
4+
status: done
5+
priority: Major
6+
category: bug
7+
workflow: local
8+
---
9+
10+
# RUST-BUG-072: Do not expire queued connected-server source requests before dispatch
11+
12+
## Problem
13+
14+
The post-`RUST-BUG-071` hide.me live-wire runs stayed VPN-bound, HighID, and
15+
Kad-connected, but failed to complete a payload. Packet and daemon logs showed
16+
that most started downloads never reached direct peers because connected-server
17+
source requests timed out at the caller while waiting in the background-session
18+
queue:
19+
20+
- `rust-hideme-20260618T201235Z`: 18
21+
`timed out waiting for ED2K background source response after 15s` failures.
22+
- `rust-hideme-20260618T202723Z`: 35 caller-side 15-second source-response
23+
timeouts plus one in-session `OP_FOUNDSOURCES` timeout.
24+
25+
eMuleBB MFC buffers local server source requests into TCP frames in
26+
`DownloadQueue.cpp` and later accepts `OP_FOUNDSOURCES` asynchronously in
27+
`ServerSocket.cpp`. It does not start a per-file source-response timeout before
28+
the request is dispatched on the connected server session. Rust used the same
29+
15-second timeout both inside the session driver and around the caller's
30+
oneshot wait, so concurrent downloads could expire before their request was
31+
even sent.
32+
33+
## Acceptance
34+
35+
- [x] Connected-server source requests still carry a timeout used after dispatch.
36+
- [x] Waiting in the background-session queue does not consume the dispatch
37+
timeout.
38+
- [x] Cancellation still aborts the caller wait promptly.
39+
- [x] Focused unit coverage proves a queued source request can wait longer than
40+
its dispatch timeout before being answered.
41+
- [x] The next hide.me live-wire diagnostics run shows fewer caller-side
42+
connected-server source timeouts and more source acquisition opportunity.
43+
44+
## Implementation Notes
45+
46+
- Keep keyword searches unchanged; user-facing keyword searches still need a
47+
caller-visible timeout.
48+
- This fix only changes connected-server source lookup queue semantics. It does
49+
not increase server request rate or add new server login sessions.
50+
51+
## Evidence
52+
53+
- `cargo test -p emulebb-ed2k background_source_search_waits_while_queued_before_dispatch --locked`
54+
- `cargo test -p emulebb-ed2k background_source_search_cancel_stops_queued_wait --locked`
55+
- `cargo test -p emulebb-ed2k background_source_search_channel_round_trips_results --locked`
56+
- `cargo test -p emulebb-ed2k background_udp_source_search_preserves_responding_server --locked`
57+
- `python tools\rust_quality_gate.py quick`
58+
- Diagnostics build
59+
`EMULEBB_WORKSPACE_OUTPUT_ROOT\logs\builds\20260618T210610Z-build-clients\build-result.json`:
60+
Release diagnostics build passed with zero warnings.
61+
- Live-wire hide.me diagnostics run
62+
`EMULEBB_WORKSPACE_OUTPUT_ROOT\live-wire\rust-hideme-20260618T210709Z\report.json`:
63+
VPN-bound HighID run passed, started 17 downloads, completed 4 files
64+
(5388230 bytes total), reached 32 peak reported sources, stayed Kad-connected
65+
with 70 contacts, and captured packet diagnostics (5491 diagnostic records,
66+
968 ED2K packet records, 4609 Kad UDP packet records).
67+
- Source-acquisition comparison:
68+
`rust-hideme-20260618T201235Z` had 18 caller-side
69+
`timed out waiting for ED2K background source response after 15s` failures,
70+
8 direct attempts, and 0 completed files. `rust-hideme-20260618T202723Z` had
71+
35 caller-side source-response timeouts, 14 direct attempts, and 0 completed
72+
files. The fixed run had 0 caller-side source-response timeouts, 29 direct
73+
attempts, 4 completed files, and retained the RUST-BUG-071 scheduler fix
74+
(`reason=direct_sources_deferred` remained 0).

0 commit comments

Comments
 (0)