Merge pull request #198 from ikatson/further-e2e-fixes

Further e2e fixes for reliability
This commit is contained in:
Igor Katson 2024-08-19 16:42:57 +01:00 • committed by GitHub
commit e3ab7e2413
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 74 additions and 37 deletions

View file

@ -47,7 +47,7 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
net::SocketAddr, net::SocketAddr,
sync::{ sync::{
atomic::{AtomicU64, Ordering}, atomic::{AtomicBool, AtomicU64, Ordering},
Arc, Arc,
}, },
time::{Duration, Instant}, time::{Duration, Instant},
@ -371,6 +371,7 @@ impl TorrentStateLive {
state: self.clone(), state: self.clone(),
tx, tx,
counters, counters,
first_message_received: AtomicBool::new(false),
}; };
let options = PeerConnectionOptions { let options = PeerConnectionOptions {
connect_timeout: self.meta.options.peer_connect_timeout, connect_timeout: self.meta.options.peer_connect_timeout,
@ -434,6 +435,7 @@ impl TorrentStateLive {
state: state.clone(), state: state.clone(),
tx, tx,
counters, counters,
first_message_received: AtomicBool::new(false),
}; };
let options = PeerConnectionOptions { let options = PeerConnectionOptions {
connect_timeout: state.meta.options.peer_connect_timeout, connect_timeout: state.meta.options.peer_connect_timeout,
@ -486,7 +488,7 @@ impl TorrentStateLive {
let state = self; let state = self;
loop { loop {
let addr = peer_queue_rx.recv().await.context("torrent closed")?; let addr = peer_queue_rx.recv().await.context("torrent closed")?;
if state.is_finished_and_dont_need_peers() { if state.is_finished_and_no_active_streams() {
debug!("ignoring peer {} as we are finished", addr); debug!("ignoring peer {} as we are finished", addr);
state.peers.mark_peer_not_needed(addr); state.peers.mark_peer_not_needed(addr);
continue; continue;
@ -656,6 +658,7 @@ impl TorrentStateLive {
Ok(()) Ok(())
} }
// If we have all selected pieces but not necessarily all pieces.
pub(crate) fn is_finished(&self) -> bool { pub(crate) fn is_finished(&self) -> bool {
self.get_hns().map(|h| h.finished()).unwrap_or_default() self.get_hns().map(|h| h.finished()).unwrap_or_default()
} }
@ -670,7 +673,9 @@ impl TorrentStateLive {
.any(|file_id| !chunks.is_file_finished(&self.meta.file_infos[file_id])) .any(|file_id| !chunks.is_file_finished(&self.meta.file_infos[file_id]))
} }
fn is_finished_and_dont_need_peers(&self) -> bool { // We might have the torrent "finished" i.e. no selected files. But if someone is streaming files despite
// them being selected, we aren't fully "finished".
fn is_finished_and_no_active_streams(&self) -> bool {
self.is_finished() self.is_finished()
&& !self.has_active_streams_unfinished_files( && !self.has_active_streams_unfinished_files(
&self.lock_read("is_finished_and_dont_need_peers"), &self.lock_read("is_finished_and_dont_need_peers"),
@ -766,6 +771,8 @@ struct PeerHandler {
addr: SocketAddr, addr: SocketAddr,
tx: PeerTx, tx: PeerTx,
first_message_received: AtomicBool,
} }
impl<'a> PeerConnectionHandler for &'a PeerHandler { impl<'a> PeerConnectionHandler for &'a PeerHandler {
@ -780,6 +787,14 @@ impl<'a> PeerConnectionHandler for &'a PeerHandler {
} }
async fn on_received_message(&self, message: Message<ByteBuf<'_>>) -> anyhow::Result<()> { async fn on_received_message(&self, message: Message<ByteBuf<'_>>) -> anyhow::Result<()> {
// The first message must be "bitfield", but if it's not sent,
// assume the bitfield is all zeroes and was sent.
if !matches!(&message, Message::Bitfield(..))
&& !self.first_message_received.swap(true, Ordering::Relaxed)
{
self.on_bitfield_notify.notify_waiters();
}
match message { match message {
Message::Request(request) => { Message::Request(request) => {
self.on_download_request(request) self.on_download_request(request)
@ -931,7 +946,7 @@ impl PeerHandler {
self.counters.errors.fetch_add(1, Ordering::Relaxed); self.counters.errors.fetch_add(1, Ordering::Relaxed);
if self.state.is_finished_and_dont_need_peers() { if self.state.is_finished_and_no_active_streams() {
debug!("torrent finished, not re-queueing"); debug!("torrent finished, not re-queueing");
pe.value_mut().state.set(PeerState::NotNeeded, pstats); pe.value_mut().state.set(PeerState::NotNeeded, pstats);
return Ok(()); return Ok(());
@ -953,7 +968,9 @@ impl PeerHandler {
duration = format!("{dur:?}") duration = format!("{dur:?}")
), ),
async move { async move {
debug!("waiting to reconnect again");
tokio::time::sleep(dur).await; tokio::time::sleep(dur).await;
debug!("finished waiting");
self.state self.state
.peers .peers
.with_peer_mut(handle, "dead_to_queued", |peer| { .with_peer_mut(handle, "dead_to_queued", |peer| {
@ -1184,43 +1201,50 @@ impl PeerHandler {
.await; .await;
} }
// The job of this is to request chunks and also to keep peer alive.
// The moment this ends, the peer is disconnected.
async fn task_peer_chunk_requester(&self) -> anyhow::Result<()> { async fn task_peer_chunk_requester(&self) -> anyhow::Result<()> {
let handle = self.addr; let handle = self.addr;
self.wait_for_bitfield().await; self.wait_for_bitfield().await;
// TODO: this check needs to happen more often, we need to update our let mut update_interest = {
// interested state with the other side, for now we send it only once. let mut current = false;
if self.state.is_finished_and_dont_need_peers() { move |h: &PeerHandler, new_value: bool| -> anyhow::Result<()> {
self.tx if new_value != current {
.send(WriterRequest::Message(MessageOwned::NotInterested))?; h.tx.send(if new_value {
WriterRequest::Message(MessageOwned::Interested)
if self } else {
.state WriterRequest::Message(MessageOwned::NotInterested)
.peers })?;
.with_live(self.addr, |l| { current = new_value;
l.has_full_torrent(self.state.lengths.total_pieces() as usize) }
}) Ok(())
.unwrap_or_default()
{
debug!("both peer and us have full torrent, disconnecting");
self.tx.send(WriterRequest::Disconnect(Ok(())))?;
// Sleep a bit to ensure this gets written to the network by manage_peer
tokio::time::sleep(Duration::from_millis(100)).await;
return Ok(());
} }
} else { };
self.tx
.send(WriterRequest::Message(MessageOwned::Interested))?;
}
loop { loop {
aframe!(self.wait_for_unchoke()).await; aframe!(self.wait_for_unchoke()).await;
if self.state.is_finished_and_dont_need_peers() { // If we have full torrent, we don't need to request more pieces.
debug!("nothing left to do, disconnecting peer"); // However we might still need to seed them to the peer.
return Ok(()); if self.state.is_finished_and_no_active_streams() {
update_interest(self, false)?;
if !self.state.peers.is_peer_interested(self.addr) {
debug!("nothing left to do, neither of us is interested, disconnecting peer");
self.tx.send(WriterRequest::Disconnect(Ok(())))?;
// wait until the receiver gets the message so that it doesn't finish with an error.
tokio::time::sleep(Duration::from_millis(100)).await;
return Ok(());
} else {
// TODO: wait for a notification of interest, e.g. update of selected files or new streams or change
// in peer interest.
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
} }
update_interest(self, true)?;
// Try steal a pice from a very slow peer first. Otherwise we might wait too long // Try steal a pice from a very slow peer first. Otherwise we might wait too long
// to download early pieces. // to download early pieces.
// Then try get the next one in queue. // Then try get the next one in queue.
@ -1235,7 +1259,8 @@ impl PeerHandler {
None => { None => {
debug!("no pieces to request"); debug!("no pieces to request");
match aframe!(tokio::time::timeout( match aframe!(tokio::time::timeout(
Duration::from_secs(10), // Half of default rw timeout not to race with it.
Duration::from_secs(5),
new_piece_notify new_piece_notify
)) ))
.await .await
@ -1277,7 +1302,7 @@ impl PeerHandler {
loop { loop {
match aframe!(tokio::time::timeout( match aframe!(tokio::time::timeout(
Duration::from_secs(10), Duration::from_secs(5),
aframe!(self.requests_sem.acquire()) aframe!(self.requests_sem.acquire())
)) ))
.await .await

View file

@ -28,7 +28,7 @@ impl Peer {
tx: PeerTx, tx: PeerTx,
counters: &AggregatePeerStatsAtomic, counters: &AggregatePeerStatsAtomic,
) -> Self { ) -> Self {
let state = PeerStateNoMut(PeerState::Live(LivePeerState::new(peer_id, tx))); let state = PeerStateNoMut(PeerState::Live(LivePeerState::new(peer_id, tx, true)));
counters.inc(&state.0); counters.inc(&state.0);
Self { Self {
state, state,
@ -142,7 +142,10 @@ impl PeerStateNoMut {
} }
match self.take(counters) { match self.take(counters) {
PeerState::Queued | PeerState::Dead | PeerState::NotNeeded => { PeerState::Queued | PeerState::Dead | PeerState::NotNeeded => {
self.set(PeerState::Live(LivePeerState::new(peer_id, tx)), counters); self.set(
PeerState::Live(LivePeerState::new(peer_id, tx, true)),
counters,
);
} }
PeerState::Connecting(..) | PeerState::Live(..) => unreachable!(), PeerState::Connecting(..) | PeerState::Live(..) => unreachable!(),
} }
@ -159,7 +162,10 @@ impl PeerStateNoMut {
PeerState::Connecting(tx) => tx, PeerState::Connecting(tx) => tx,
_ => unreachable!(), _ => unreachable!(),
}; };
self.set(PeerState::Live(LivePeerState::new(peer_id, tx)), counters); self.set(
PeerState::Live(LivePeerState::new(peer_id, tx, false)),
counters,
);
self.get_live_mut() self.get_live_mut()
} else { } else {
None None
@ -189,10 +195,10 @@ pub(crate) struct LivePeerState {
} }
impl LivePeerState { impl LivePeerState {
pub fn new(peer_id: Id20, tx: PeerTx) -> Self { pub fn new(peer_id: Id20, tx: PeerTx, initial_interested: bool) -> Self {
LivePeerState { LivePeerState {
peer_id, peer_id,
peer_interested: false, peer_interested: initial_interested,
bitfield: BF::default(), bitfield: BF::default(),
inflight_requests: Default::default(), inflight_requests: Default::default(),
tx, tx,

View file

@ -77,6 +77,11 @@ impl PeerStates {
Some(p) Some(p)
} }
pub fn is_peer_interested(&self, handle: PeerHandle) -> bool {
self.with_live(handle, |live| live.peer_interested)
.unwrap_or(false)
}
pub fn mark_peer_interested(&self, handle: PeerHandle, is_interested: bool) -> Option<bool> { pub fn mark_peer_interested(&self, handle: PeerHandle, is_interested: bool) -> Option<bool> {
self.with_live_mut(handle, "mark_peer_interested", |live| { self.with_live_mut(handle, "mark_peer_interested", |live| {
let prev = live.peer_interested; let prev = live.peer_interested;
@ -84,6 +89,7 @@ impl PeerStates {
prev prev
}) })
} }
pub fn update_bitfield_from_vec(&self, handle: PeerHandle, bitfield: Box<[u8]>) -> Option<()> { pub fn update_bitfield_from_vec(&self, handle: PeerHandle, bitfield: Box<[u8]>) -> Option<()> {
self.with_live_mut(handle, "update_bitfield_from_vec", |live| { self.with_live_mut(handle, "update_bitfield_from_vec", |live| {
live.bitfield = BF::from_boxed_slice(bitfield); live.bitfield = BF::from_boxed_slice(bitfield);