Defer writes command line flag

This commit is contained in:
Igor Katson 2024-05-01 15:23:11 +01:00
parent b8bbe6a87f
commit d25309e358
6 changed files with 166 additions and 90 deletions

View file

@ -747,7 +747,7 @@ impl TorrentStateLive {
.take_live_no_counters()
.unwrap()
.tx
.send(WriterRequest::Disconnect);
.send(WriterRequest::Disconnect(Ok(())));
}
}
}
@ -1189,7 +1189,7 @@ impl PeerHandler {
.unwrap_or_default()
{
debug!("both peer and us have full torrent, disconnecting");
self.tx.send(WriterRequest::Disconnect)?;
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(());
@ -1382,93 +1382,137 @@ impl PeerHandler {
// By this time we reach here, no other peer can for this piece. All others, even if they steal pieces would
// have fallen off above in one of the defensive checks.
//
let work = {
fn write_to_disk(
state: &TorrentStateLive,
addr: PeerHandle,
counters: &AtomicPeerCounters,
piece: &Piece<impl AsRef<[u8]>>,
chunk_info: &ChunkInfo,
full_piece_download_time: Option<Duration>,
) -> anyhow::Result<()> {
let index = piece.index;
// Not being able to write to storage is a fatal error. You need to unpause the
// torrent to recover from it.
match state.file_ops().write_chunk(addr, piece, chunk_info) {
Ok(()) => {}
Err(e) => {
error!("FATAL: error writing chunk to disk: {:?}", e);
return state.on_fatal_error(e);
}
}
// Global chunk/byte counters.
state
.stats
.fetched_bytes
.fetch_add(piece.block.as_ref().len() as u64, Ordering::Relaxed);
let full_piece_download_time = match full_piece_download_time {
Some(t) => t,
None => return Ok(()),
};
match state
.file_ops()
.check_piece(addr, chunk_info.piece_index, chunk_info)
.with_context(|| format!("error checking piece={index}"))?
{
true => {
{
let mut g = state.lock_write("mark_piece_downloaded");
g.get_chunks_mut()?
.mark_piece_downloaded(chunk_info.piece_index);
}
// Global piece counters.
let piece_len = state.lengths.piece_length(chunk_info.piece_index) as u64;
state
.stats
.downloaded_and_checked_bytes
// This counter is used to compute "is_finished", so using
// stronger ordering.
.fetch_add(piece_len, Ordering::Release);
state
.stats
.downloaded_and_checked_pieces
// This counter is used to compute "is_finished", so using
// stronger ordering.
.fetch_add(1, Ordering::Release);
state
.stats
.have_bytes
.fetch_add(piece_len, Ordering::Relaxed);
#[allow(clippy::cast_possible_truncation)]
state.stats.total_piece_download_ms.fetch_add(
full_piece_download_time.as_millis() as u64,
Ordering::Relaxed,
);
// Per-peer piece counters.
counters.on_piece_completed(piece_len, full_piece_download_time);
state.peers.reset_peer_backoff(addr);
debug!("piece={} successfully downloaded and verified", index);
state.on_piece_completed(chunk_info.piece_index)?;
state.maybe_transmit_haves(chunk_info.piece_index);
}
false => {
warn!(
"checksum for piece={} did not validate. disconecting peer.",
index
);
state
.lock_write("mark_piece_broken")
.get_chunks_mut()?
.mark_piece_broken_if_not_have(chunk_info.piece_index);
anyhow::bail!("i am probably a bogus peer. dying.")
}
};
Ok(())
}
if self.state.meta().options.defer_writes {
let state = self.state.clone();
let addr = self.addr;
let counters = self.counters.clone();
let piece = piece.clone_to_owned();
move || {
let index = piece.index;
let tx = self.tx.clone();
// Not being able to write to storage is a fatal error. You need to unpause the
// torrent to recover from it.
match state.file_ops().write_chunk(addr, &piece, &chunk_info) {
Ok(()) => {}
Err(e) => {
error!("FATAL: error writing chunk to disk: {:?}", e);
return state.on_fatal_error(e);
}
let work = move || {
if let Err(e) = write_to_disk(
&state,
addr,
&counters,
&piece,
&chunk_info,
full_piece_download_time,
) {
let _ = tx.send(WriterRequest::Disconnect(Err(e)));
}
};
tokio::runtime::Handle::current().spawn_blocking(work);
} else {
self.state
.meta
.spawner
.spawn_block_in_place(|| {
write_to_disk(
&self.state,
self.addr,
&self.counters,
&piece,
&chunk_info,
full_piece_download_time,
)
})
.with_context(|| format!("error processing received chunk {chunk_info:?}"))?;
}
let full_piece_download_time = match full_piece_download_time {
Some(t) => t,
None => return Ok(()),
};
match state
.file_ops()
.check_piece(addr, chunk_info.piece_index, &chunk_info)
.with_context(|| format!("error checking piece={index}"))?
{
true => {
{
let mut g = state.lock_write("mark_piece_downloaded");
g.get_chunks_mut()?
.mark_piece_downloaded(chunk_info.piece_index);
}
// Global piece counters.
let piece_len = state.lengths.piece_length(chunk_info.piece_index) as u64;
state
.stats
.downloaded_and_checked_bytes
// This counter is used to compute "is_finished", so using
// stronger ordering.
.fetch_add(piece_len, Ordering::Release);
state
.stats
.downloaded_and_checked_pieces
// This counter is used to compute "is_finished", so using
// stronger ordering.
.fetch_add(1, Ordering::Release);
state
.stats
.have_bytes
.fetch_add(piece_len, Ordering::Relaxed);
#[allow(clippy::cast_possible_truncation)]
state.stats.total_piece_download_ms.fetch_add(
full_piece_download_time.as_millis() as u64,
Ordering::Relaxed,
);
// Per-peer piece counters.
counters.on_piece_completed(piece_len, full_piece_download_time);
state.peers.reset_peer_backoff(addr);
debug!("piece={} successfully downloaded and verified", index);
state.on_piece_completed(chunk_info.piece_index)?;
state.maybe_transmit_haves(chunk_info.piece_index);
}
false => {
warn!(
"checksum for piece={} did not validate. disconecting peer.",
index
);
state
.lock_write("mark_piece_broken")
.get_chunks_mut()?
.mark_piece_broken_if_not_have(chunk_info.piece_index);
anyhow::bail!("i am probably a bogus peer. dying.")
}
};
Ok::<_, anyhow::Error>(())
}
};
tokio::runtime::Handle::current().spawn_blocking(work);
Ok(())
}
}

View file

@ -92,6 +92,7 @@ pub(crate) struct ManagedTorrentOptions {
pub peer_read_write_timeout: Option<Duration>,
pub allow_overwrite: bool,
pub output_folder: PathBuf,
pub defer_writes: bool,
}
pub struct ManagedTorrentInfo {
@ -505,6 +506,7 @@ pub(crate) struct ManagedTorrentBuilder {
spawner: Option<BlockingSpawner>,
allow_overwrite: bool,
storage_factory: BoxStorageFactory,
defer_writes: bool,
}
impl ManagedTorrentBuilder {
@ -527,6 +529,7 @@ impl ManagedTorrentBuilder {
allow_overwrite: false,
output_folder,
storage_factory,
defer_writes: false,
}
}
@ -570,6 +573,11 @@ impl ManagedTorrentBuilder {
self
}
pub fn defer_writes(&mut self, value: bool) -> &mut Self {
self.defer_writes = value;
self
}
pub fn build(self, span: tracing::Span) -> anyhow::Result<ManagedTorrentHandle> {
let lengths = Lengths::from_torrent(&self.info)?;
let file_infos = self
@ -600,6 +608,7 @@ impl ManagedTorrentBuilder {
peer_read_write_timeout: self.peer_read_write_timeout,
allow_overwrite: self.allow_overwrite,
output_folder: self.output_folder,
defer_writes: self.defer_writes,
},
});