WTF is going on with counters

This commit is contained in:
Igor Katson 2023-11-20 00:55:31 +00:00
parent 22ea146ff6
commit aa99872e52
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
2 changed files with 181 additions and 103 deletions

View file

@ -1,3 +1,4 @@
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration; use std::time::Duration;
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
@ -5,8 +6,10 @@ use anyhow::Context;
use backoff::{ExponentialBackoff, ExponentialBackoffBuilder}; use backoff::{ExponentialBackoff, ExponentialBackoffBuilder};
use librqbit_core::id20::Id20; use librqbit_core::id20::Id20;
use librqbit_core::lengths::{ChunkInfo, ValidPieceIndex}; use librqbit_core::lengths::{ChunkInfo, ValidPieceIndex};
use serde::Serialize;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::{Notify, Semaphore}; use tokio::sync::{Notify, Semaphore};
use tracing::trace;
use crate::peer_connection::WriterRequest; use crate::peer_connection::WriterRequest;
use crate::type_aliases::BF; use crate::type_aliases::BF;
@ -63,10 +66,61 @@ impl Default for PeerStats {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Peer { pub struct Peer {
pub state: PeerState, pub state: PeerStateNoMut,
pub stats: PeerStats, pub stats: PeerStats,
} }
#[derive(Debug, Default, Serialize)]
pub struct AggregatePeerStatsAtomic {
pub queued: AtomicU32,
pub connecting: AtomicU32,
pub live: AtomicU32,
pub seen: AtomicU32,
pub dead: AtomicU32,
pub not_needed: AtomicU32,
}
pub fn atomic_inc(c: &AtomicU32) -> u32 {
c.fetch_add(1, Ordering::Relaxed)
}
pub fn atomic_dec(c: &AtomicU32) -> u32 {
c.fetch_sub(1, Ordering::Relaxed)
}
impl AggregatePeerStatsAtomic {
pub fn counter(&self, state: &PeerState) -> &AtomicU32 {
match state {
PeerState::Connecting(_) => &self.connecting,
PeerState::Live(_) => &self.live,
PeerState::Queued => &self.queued,
PeerState::Dead => &self.dead,
PeerState::NotNeeded => &self.not_needed,
}
}
pub fn inc(&self, state: &PeerState) {
trace!(
"inc, new value = {}, state = {}",
atomic_inc(self.counter(state)),
state
);
}
pub fn dec(&self, state: &PeerState) {
trace!(
"dec, new value = {}, state = {}",
atomic_dec(self.counter(state)),
state
);
}
pub fn incdec(&self, old: &PeerState, new: &PeerState) {
self.dec(old);
self.inc(new);
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub enum PeerState { pub enum PeerState {
#[default] #[default]
@ -82,6 +136,12 @@ pub enum PeerState {
NotNeeded, NotNeeded,
} }
impl std::fmt::Display for PeerState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl PeerState { impl PeerState {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
match self { match self {
@ -93,70 +153,77 @@ impl PeerState {
} }
} }
fn take_connecting(&mut self) -> Option<PeerTx> { pub fn take_live_no_counters(self) -> Option<LivePeerState> {
if let PeerState::Connecting(_) = self { match self {
match std::mem::take(self) { PeerState::Live(l) => Some(l),
PeerState::Connecting(tx) => Some(tx), _ => None,
_ => unreachable!(),
}
} else {
None
} }
} }
}
pub fn take_live(&mut self) -> Option<LivePeerState> { #[derive(Debug, Default)]
if let PeerState::Live(_) = self { pub struct PeerStateNoMut(PeerState);
match std::mem::take(self) {
PeerState::Live(l) => Some(l), impl PeerStateNoMut {
_ => unreachable!(), pub fn get(&self) -> &PeerState {
} &self.0
} else { }
None
} pub fn take(&mut self, counters: &AggregatePeerStatsAtomic) -> PeerState {
self.set(Default::default(), counters)
}
pub fn set(&mut self, new: PeerState, counters: &AggregatePeerStatsAtomic) -> PeerState {
counters.incdec(&self.0, &new);
std::mem::replace(&mut self.0, new)
} }
pub fn get_live(&self) -> Option<&LivePeerState> { pub fn get_live(&self) -> Option<&LivePeerState> {
match self { match &self.0 {
PeerState::Live(l) => Some(l), PeerState::Live(l) => Some(l),
_ => None, _ => None,
} }
} }
pub fn get_live_mut(&mut self) -> Option<&mut LivePeerState> { pub fn get_live_mut(&mut self) -> Option<&mut LivePeerState> {
match self { match &mut self.0 {
PeerState::Live(l) => Some(l), PeerState::Live(l) => Some(l),
_ => None, _ => None,
} }
} }
pub fn queued_to_connecting(&mut self) -> Option<PeerRx> { pub fn queued_to_connecting(&mut self, counters: &AggregatePeerStatsAtomic) -> Option<PeerRx> {
if let PeerState::Queued = self { if let PeerState::Queued = &self.0 {
let (tx, rx) = unbounded_channel(); let (tx, rx) = unbounded_channel();
*self = PeerState::Connecting(tx); self.set(PeerState::Connecting(tx), counters);
Some(rx) Some(rx)
} else { } else {
None None
} }
} }
pub fn connecting_to_live(&mut self, peer_id: Id20) -> Option<&mut LivePeerState> { pub fn connecting_to_live(
let tx = self.take_connecting()?; &mut self,
*self = PeerState::Live(LivePeerState::new(peer_id, tx)); peer_id: Id20,
self.get_live_mut() counters: &AggregatePeerStatsAtomic,
} ) -> Option<&mut LivePeerState> {
if let PeerState::Connecting(_) = &self.0 {
pub fn to_dead(&mut self) -> Option<Option<LivePeerState>> { let tx = match self.take(counters) {
match std::mem::replace(self, PeerState::Dead) { PeerState::Connecting(tx) => tx,
PeerState::Live(l) => Some(Some(l)), _ => unreachable!(),
PeerState::Connecting(_) => Some(None), };
_ => None, self.set(PeerState::Live(LivePeerState::new(peer_id, tx)), counters);
self.get_live_mut()
} else {
None
} }
} }
pub fn to_not_needed(&mut self) -> Option<LivePeerState> { pub fn to_dead(&mut self, counters: &AggregatePeerStatsAtomic) -> PeerState {
match std::mem::replace(self, PeerState::NotNeeded) { self.set(PeerState::Dead, counters)
PeerState::Live(l) => Some(l), }
_ => None,
} pub fn to_not_needed(&mut self, counters: &AggregatePeerStatsAtomic) -> PeerState {
self.set(PeerState::NotNeeded, counters)
} }
} }

View file

@ -14,7 +14,7 @@ use std::{
net::SocketAddr, net::SocketAddr,
path::PathBuf, path::PathBuf,
sync::{ sync::{
atomic::{AtomicU32, AtomicU64, Ordering}, atomic::{AtomicU64, Ordering},
Arc, Arc,
}, },
time::{Duration, Instant}, time::{Duration, Instant},
@ -52,7 +52,10 @@ use crate::{
peer_connection::{ peer_connection::{
PeerConnection, PeerConnectionHandler, PeerConnectionOptions, WriterRequest, PeerConnection, PeerConnectionHandler, PeerConnectionOptions, WriterRequest,
}, },
peer_state::{InflightRequest, LivePeerState, Peer, PeerRx, PeerState, PeerTx, SendMany}, peer_state::{
atomic_inc, AggregatePeerStatsAtomic, InflightRequest, LivePeerState, Peer, PeerRx,
PeerState, PeerTx, SendMany,
},
spawn_utils::{spawn, BlockingSpawner}, spawn_utils::{spawn, BlockingSpawner},
type_aliases::{PeerHandle, BF}, type_aliases::{PeerHandle, BF},
}; };
@ -68,29 +71,7 @@ pub struct PeerStates {
states: DashMap<PeerHandle, Peer>, states: DashMap<PeerHandle, Peer>,
} }
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize, PartialEq, Eq)]
pub struct AggregatePeerStatsAtomic {
pub queued: AtomicU32,
pub connecting: AtomicU32,
pub live: AtomicU32,
pub seen: AtomicU32,
pub dead: AtomicU32,
pub not_needed: AtomicU32,
}
impl AggregatePeerStatsAtomic {
fn counter(&self, state: &PeerState) -> &AtomicU32 {
match state {
PeerState::Connecting(_) => &self.connecting,
PeerState::Live(_) => &self.live,
PeerState::Queued => &self.queued,
PeerState::Dead => &self.dead,
PeerState::NotNeeded => &self.not_needed,
}
}
}
#[derive(Debug, Default, Serialize)]
pub struct AggregatePeerStats { pub struct AggregatePeerStats {
pub queued: usize, pub queued: usize,
pub connecting: usize, pub connecting: usize,
@ -123,7 +104,7 @@ impl PeerStates {
.iter() .iter()
.fold(AggregatePeerStats::default(), |mut s, p| { .fold(AggregatePeerStats::default(), |mut s, p| {
s.seen += 1; s.seen += 1;
match &p.value().state { match &p.value().state.get() {
PeerState::Connecting(_) => s.connecting += 1, PeerState::Connecting(_) => s.connecting += 1,
PeerState::Live(_) => s.live += 1, PeerState::Live(_) => s.live += 1,
PeerState::Queued => s.queued += 1, PeerState::Queued => s.queued += 1,
@ -143,7 +124,8 @@ impl PeerStates {
Entry::Occupied(_) => None, Entry::Occupied(_) => None,
Entry::Vacant(vac) => { Entry::Vacant(vac) => {
vac.insert(Default::default()); vac.insert(Default::default());
self.stats.queued.fetch_add(1, Ordering::Relaxed); atomic_inc(&self.stats.queued);
atomic_inc(&self.stats.seen);
Some(addr) Some(addr)
} }
} }
@ -162,10 +144,12 @@ impl PeerStates {
.map(|e| f(TimedExistence::new(e, reason).value_mut())) .map(|e| f(TimedExistence::new(e, reason).value_mut()))
} }
pub fn with_live<R>(&self, addr: PeerHandle, f: impl FnOnce(&LivePeerState) -> R) -> Option<R> { pub fn with_live<R>(&self, addr: PeerHandle, f: impl FnOnce(&LivePeerState) -> R) -> Option<R> {
self.states.get(&addr).and_then(|e| match &e.value().state { self.states
PeerState::Live(l) => Some(f(l)), .get(&addr)
_ => None, .and_then(|e| match &e.value().state.get() {
}) PeerState::Live(l) => Some(f(l)),
_ => None,
})
} }
pub fn with_live_mut<R>( pub fn with_live_mut<R>(
&self, &self,
@ -173,20 +157,19 @@ impl PeerStates {
reason: &'static str, reason: &'static str,
f: impl FnOnce(&mut LivePeerState) -> R, f: impl FnOnce(&mut LivePeerState) -> R,
) -> Option<R> { ) -> Option<R> {
self.with_peer_mut(addr, reason, |peer| match &mut peer.state { self.with_peer_mut(addr, reason, |peer| peer.state.get_live_mut().map(f))
PeerState::Live(l) => Some(f(l)), .flatten()
_ => None,
})
.flatten()
} }
pub fn mark_peer_dead(&self, handle: PeerHandle) -> Option<Option<LivePeerState>> { pub fn mark_peer_dead(&self, handle: PeerHandle) -> Option<Option<LivePeerState>> {
self.with_peer_mut(handle, "mark_peer_dead", |peer| peer.state.to_dead()) let prev = self.with_peer_mut(handle, "mark_peer_dead", |peer| {
.flatten() peer.state.to_dead(&self.stats)
})?;
Some(prev.take_live_no_counters())
} }
pub fn drop_peer(&self, handle: PeerHandle) -> Option<Peer> { pub fn drop_peer(&self, handle: PeerHandle) -> Option<Peer> {
let p = self.states.remove(&handle).map(|r| r.1)?; let p = self.states.remove(&handle).map(|r| r.1)?;
self.stats.counter(&p.state).fetch_sub(1, Ordering::Relaxed); self.stats.dec(p.state.get());
Some(p) Some(p)
} }
pub fn mark_i_am_choked(&self, handle: PeerHandle, is_choked: bool) -> Option<bool> { pub fn mark_i_am_choked(&self, handle: PeerHandle, is_choked: bool) -> Option<bool> {
@ -216,12 +199,14 @@ impl PeerStates {
}) })
} }
pub fn mark_peer_connecting(&self, h: PeerHandle) -> anyhow::Result<PeerRx> { pub fn mark_peer_connecting(&self, h: PeerHandle) -> anyhow::Result<PeerRx> {
self.with_peer_mut(h, "mark_peer_connecting", |peer| { let rx = self
peer.state .with_peer_mut(h, "mark_peer_connecting", |peer| {
.queued_to_connecting() peer.state
.context("invalid peer state") .queued_to_connecting(&self.stats)
}) .context("invalid peer state")
.context("peer not found in states")? })
.context("peer not found in states")??;
Ok(rx)
} }
pub fn clone_tx(&self, handle: PeerHandle) -> Option<PeerTx> { pub fn clone_tx(&self, handle: PeerHandle) -> Option<PeerTx> {
@ -234,11 +219,11 @@ impl PeerStates {
}); });
} }
fn mark_peer_not_needed(&self, handle: PeerHandle) -> Option<LivePeerState> { fn mark_peer_not_needed(&self, handle: PeerHandle) -> Option<PeerState> {
self.with_peer_mut(handle, "mark_peer_not_needed", |peer| { let prev = self.with_peer_mut(handle, "mark_peer_not_needed", |peer| {
peer.state.to_not_needed() peer.state.to_not_needed(&self.stats)
}) })?;
.flatten() Some(prev)
} }
} }
@ -289,6 +274,7 @@ pub struct StatsSnapshot {
pub time: Instant, pub time: Instant,
pub total_piece_download_ms: u64, pub total_piece_download_ms: u64,
pub peer_stats: AggregatePeerStats, pub peer_stats: AggregatePeerStats,
pub new_peer_stats: AggregatePeerStats,
} }
impl StatsSnapshot { impl StatsSnapshot {
@ -677,10 +663,14 @@ impl TorrentState {
fn set_peer_live(&self, handle: PeerHandle, h: Handshake) { fn set_peer_live(&self, handle: PeerHandle, h: Handshake) {
let result = self.peers.with_peer_mut(handle, "set_peer_live", |p| { let result = self.peers.with_peer_mut(handle, "set_peer_live", |p| {
p.state.connecting_to_live(Id20(h.peer_id)).is_some() p.state
.connecting_to_live(Id20(h.peer_id), &self.peers.stats)
.is_some()
}); });
match result { match result {
Some(true) => debug!("set peer to live"), Some(true) => {
debug!("set peer to live")
}
Some(false) => debug!("can't set peer live, it was in wrong state"), Some(false) => debug!("can't set peer live, it was in wrong state"),
None => debug!("can't set peer live, it disappeared"), None => debug!("can't set peer live, it disappeared"),
} }
@ -694,7 +684,9 @@ impl TorrentState {
return; return;
} }
}; };
match std::mem::take(&mut pe.value_mut().state) { let prev = pe.value_mut().state.take(&self.peers.stats);
match prev {
PeerState::Connecting(_) => {} PeerState::Connecting(_) => {}
PeerState::Live(live) => { PeerState::Live(live) => {
let mut g = self.lock_write("mark_chunk_requests_canceled"); let mut g = self.lock_write("mark_chunk_requests_canceled");
@ -709,7 +701,9 @@ impl TorrentState {
} }
PeerState::NotNeeded => { PeerState::NotNeeded => {
// Restore it as std::mem::take() replaced it above. // Restore it as std::mem::take() replaced it above.
pe.value_mut().state = PeerState::NotNeeded; pe.value_mut()
.state
.set(PeerState::NotNeeded, &self.peers.stats);
return; return;
} }
s @ PeerState::Queued | s @ PeerState::Dead => { s @ PeerState::Queued | s @ PeerState::Dead => {
@ -723,17 +717,21 @@ impl TorrentState {
if error.is_none() { if error.is_none() {
debug!("peer died without errors, not re-queueing"); debug!("peer died without errors, not re-queueing");
pe.value_mut().state = PeerState::NotNeeded; pe.value_mut()
.state
.set(PeerState::NotNeeded, &self.peers.stats);
return; return;
} }
if self.is_finished() { if self.is_finished() {
debug!("torrent finished, not re-queueing"); debug!("torrent finished, not re-queueing");
pe.value_mut().state = PeerState::NotNeeded; pe.value_mut()
.state
.set(PeerState::NotNeeded, &self.peers.stats);
return; return;
} }
pe.value_mut().state = PeerState::Dead; pe.value_mut().state.set(PeerState::Dead, &self.peers.stats);
let backoff = pe.value_mut().stats.backoff.next_backoff(); let backoff = pe.value_mut().stats.backoff.next_backoff();
// Prevent deadlocks. // Prevent deadlocks.
@ -754,8 +752,10 @@ impl TorrentState {
state state
.peers .peers
.with_peer_mut(handle, "dead_to_queued", |peer| { .with_peer_mut(handle, "dead_to_queued", |peer| {
match &peer.state { match peer.state.get() {
PeerState::Dead => peer.state = PeerState::Queued, PeerState::Dead => {
peer.state.set(PeerState::Queued, &state.peers.stats)
}
other => bail!( other => bail!(
"peer is in unexpected state: {}. Expected dead", "peer is in unexpected state: {}. Expected dead",
other.name() other.name()
@ -793,7 +793,7 @@ impl TorrentState {
let mut futures = Vec::new(); let mut futures = Vec::new();
for pe in self.peers.states.iter() { for pe in self.peers.states.iter() {
match &pe.value().state { match &pe.value().state.get() {
PeerState::Live(live) => { PeerState::Live(live) => {
if !live.peer_interested { if !live.peer_interested {
continue; continue;
@ -856,11 +856,17 @@ impl TorrentState {
pub fn stats_snapshot(&self, with_peer_stats: bool) -> StatsSnapshot { pub fn stats_snapshot(&self, with_peer_stats: bool) -> StatsSnapshot {
use Ordering::*; use Ordering::*;
let new_peer_stats = self.peers.stats_from_atomic();
let peer_stats = if with_peer_stats { let peer_stats = if with_peer_stats {
self.peers.stats() let old_stats = self.peers.stats();
if old_stats != new_peer_stats {
warn!("old != new: {old_stats:?} != {new_peer_stats:?}")
}
old_stats
} else { } else {
Default::default() Default::default()
}; };
let downloaded = self.stats.downloaded_and_checked.load(Relaxed); let downloaded = self.stats.downloaded_and_checked.load(Relaxed);
let remaining = self.needed - downloaded; let remaining = self.needed - downloaded;
StatsSnapshot { StatsSnapshot {
@ -875,6 +881,7 @@ impl TorrentState {
remaining_bytes: remaining, remaining_bytes: remaining,
total_piece_download_ms: self.stats.total_piece_download_ms.load(Relaxed), total_piece_download_ms: self.stats.total_piece_download_ms.load(Relaxed),
peer_stats, peer_stats,
new_peer_stats,
} }
} }
@ -1367,10 +1374,14 @@ impl PeerHandler {
fn disconnect_all_peers_that_have_full_torrent(&self) { fn disconnect_all_peers_that_have_full_torrent(&self) {
for mut pe in self.state.peers.states.iter_mut() { for mut pe in self.state.peers.states.iter_mut() {
if let PeerState::Live(l) = &pe.value().state { if let PeerState::Live(l) = pe.value().state.get() {
if l.has_full_torrent(self.state.lengths.total_pieces() as usize) { if l.has_full_torrent(self.state.lengths.total_pieces() as usize) {
let live = pe.value_mut().state.to_not_needed().unwrap(); let prev = pe.value_mut().state.to_not_needed(&self.state.peers.stats);
let _ = live.tx.send(WriterRequest::Disconnect); let _ = prev
.take_live_no_counters()
.unwrap()
.tx
.send(WriterRequest::Disconnect);
} }
} }
} }