fix(peer): survive download token races on the listener path
A P-connection drop failed every download for its user, including ones actively transferring on their own healthy F connection: collect_failed_tokens now only fails queued downloads. And an F connection could look its token up before the client-operations thread had registered it: the listener now waits for the registration, and claims it only for the matching peer and only when not already claimed. Fixes #42
This commit is contained in:
@@ -520,36 +520,43 @@ fn connect_retry(addr: &str, timeout: Duration) -> std::io::Result<TcpStream> {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_mock_uploader(cfg: &MockUpload) -> std::io::Result<u64> {
|
||||
// 1. P (control) connection: register ourselves with the downloader.
|
||||
fn open_control_and_await_queue(
|
||||
cfg: &MockUpload,
|
||||
) -> std::io::Result<TcpStream> {
|
||||
let mut p = connect_retry(&cfg.listen_addr, Duration::from_secs(5))?;
|
||||
p.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
p.write_all(&peer_init_bytes(&cfg.peer_username, "P", 0))?;
|
||||
p.flush()?;
|
||||
let _ = cfg.ready.send(());
|
||||
|
||||
// 2. Wait for the downloader's QueueUpload (peer code 43).
|
||||
loop {
|
||||
let mut msg = read_framed(&mut p)?;
|
||||
let msg = read_framed(&mut p)?;
|
||||
if msg.get_message_code() == 43 {
|
||||
msg.set_pointer(8);
|
||||
let _requested = msg.read_string();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
// 3. Offer the transfer with a TransferRequest (peer code 40). The size here
|
||||
// becomes the download's expected size, so it must match the content.
|
||||
/// Build a TransferRequest (peer code 40). The size becomes the download's
|
||||
/// expected size, so it must match the content.
|
||||
fn transfer_request(cfg: &MockUpload) -> Message {
|
||||
let mut tr = Message::new();
|
||||
tr.write_int32(40)
|
||||
.write_int32(1) // direction: upload
|
||||
.write_int32(cfg.token)
|
||||
.write_string(&cfg.filename)
|
||||
.write_int64(cfg.content.len() as u64);
|
||||
p.write_all(&tr.get_buffer())?;
|
||||
tr
|
||||
}
|
||||
|
||||
fn run_mock_uploader(cfg: &MockUpload) -> std::io::Result<u64> {
|
||||
let mut p = open_control_and_await_queue(cfg)?;
|
||||
|
||||
p.write_all(&transfer_request(cfg).get_buffer())?;
|
||||
p.flush()?;
|
||||
|
||||
// 4. Wait for the downloader to allow it (TransferResponse, peer code 41).
|
||||
// Wait for the downloader to allow it (TransferResponse, peer code 41).
|
||||
loop {
|
||||
let msg = read_framed(&mut p)?;
|
||||
if msg.get_message_code() == 41 {
|
||||
@@ -557,7 +564,6 @@ fn run_mock_uploader(cfg: &MockUpload) -> std::io::Result<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Open the F (file) connection to the downloader's listener and stream.
|
||||
serve_file_over_f(
|
||||
&cfg.listen_addr,
|
||||
&cfg.peer_username,
|
||||
@@ -567,22 +573,38 @@ fn run_mock_uploader(cfg: &MockUpload) -> std::io::Result<u64> {
|
||||
)
|
||||
}
|
||||
|
||||
fn run_mock_uploader_f_first(cfg: &MockUpload) -> std::io::Result<u64> {
|
||||
let mut p = open_control_and_await_queue(cfg)?;
|
||||
|
||||
let listen_addr = cfg.listen_addr.clone();
|
||||
let peer_username = cfg.peer_username.clone();
|
||||
let token = cfg.token;
|
||||
let content = cfg.content.clone();
|
||||
let token_delay = cfg.token_delay;
|
||||
let f = std::thread::spawn(move || {
|
||||
serve_file_over_f(
|
||||
&listen_addr,
|
||||
&peer_username,
|
||||
token,
|
||||
&content,
|
||||
token_delay,
|
||||
)
|
||||
});
|
||||
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
|
||||
p.write_all(&transfer_request(cfg).get_buffer())?;
|
||||
p.flush()?;
|
||||
|
||||
f.join()
|
||||
.map_err(|_| std::io::Error::other("F connection thread panicked"))?
|
||||
}
|
||||
|
||||
fn run_refusing_uploader(
|
||||
cfg: &MockUpload,
|
||||
refusal: &Message,
|
||||
) -> std::io::Result<()> {
|
||||
let mut p = connect_retry(&cfg.listen_addr, Duration::from_secs(5))?;
|
||||
p.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
p.write_all(&peer_init_bytes(&cfg.peer_username, "P", 0))?;
|
||||
p.flush()?;
|
||||
let _ = cfg.ready.send(());
|
||||
|
||||
loop {
|
||||
let msg = read_framed(&mut p)?;
|
||||
if msg.get_message_code() == 43 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut p = open_control_and_await_queue(cfg)?;
|
||||
|
||||
p.write_all(&refusal.get_buffer())?;
|
||||
p.flush()?;
|
||||
@@ -724,7 +746,11 @@ fn unique_download_dir() -> PathBuf {
|
||||
dir
|
||||
}
|
||||
|
||||
fn file_downloads_over_p_and_f(peer_username: &str, token_delay: Duration) {
|
||||
fn file_downloads_over_p_and_f(
|
||||
peer_username: &str,
|
||||
token_delay: Duration,
|
||||
run_uploader: fn(&MockUpload) -> std::io::Result<u64>,
|
||||
) {
|
||||
let server = server_or_skip!();
|
||||
|
||||
// Downloader: connected to the server with its peer listener enabled.
|
||||
@@ -755,7 +781,7 @@ fn file_downloads_over_p_and_f(peer_username: &str, token_delay: Duration) {
|
||||
token_delay,
|
||||
};
|
||||
let uploader = std::thread::spawn(move || {
|
||||
if let Err(e) = run_mock_uploader(&cfg) {
|
||||
if let Err(e) = run_uploader(&cfg) {
|
||||
eprintln!("[mock uploader] {e}");
|
||||
}
|
||||
});
|
||||
@@ -792,12 +818,29 @@ fn file_downloads_over_p_and_f(peer_username: &str, token_delay: Duration) {
|
||||
|
||||
#[test]
|
||||
fn a_file_downloads_from_a_peer_over_p_and_f_connections() {
|
||||
file_downloads_over_p_and_f("e2e_mockpeer", Duration::ZERO);
|
||||
file_downloads_over_p_and_f(
|
||||
"e2e_mockpeer",
|
||||
Duration::ZERO,
|
||||
run_mock_uploader,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_downloads_when_the_transfer_token_arrives_late() {
|
||||
file_downloads_over_p_and_f("e2e_latepeer", Duration::from_millis(300));
|
||||
file_downloads_over_p_and_f(
|
||||
"e2e_latepeer",
|
||||
Duration::from_millis(300),
|
||||
run_mock_uploader,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_download_completes_when_the_f_connection_beats_token_registration() {
|
||||
file_downloads_over_p_and_f(
|
||||
"e2e_earlypeer",
|
||||
Duration::ZERO,
|
||||
run_mock_uploader_f_first,
|
||||
);
|
||||
}
|
||||
|
||||
fn refusal_fails_the_download_quickly(
|
||||
|
||||
Reference in New Issue
Block a user