Defer writes command line flag
This commit is contained in:
parent
b8bbe6a87f
commit
d25309e358
6 changed files with 166 additions and 90 deletions
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue