From 1963e585608338bbd9a7bcd42015f28eee09436c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:32:26 +0100 Subject: [PATCH] perf(copy): async batch file flushes Instead of calling `sync_all()` on every file individually during the Copy operation, the flushing is now batched and done at the end. Flushing now also happens for Move. --- src/operation/recursive.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index 4c19bc3..b4825e9 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -1,6 +1,7 @@ use compio::BufResult; use compio::buf::{IntoInner, IoBuf}; use compio::io::{AsyncReadAt, AsyncWriteAt}; +use futures::{StreamExt, stream}; use std::future::Future; use std::pin::Pin; use std::time::Instant; @@ -57,6 +58,8 @@ impl Context { ) -> Result { let mut ops = Vec::new(); let mut cleanup_ops = Vec::new(); + let mut written_files = Vec::new(); + let mut target_dirs = std::collections::HashSet::new(); for (from_parent, to_parent) in from_to_pairs { self.controller .check() @@ -141,6 +144,9 @@ impl Context { cleanup_ops.push(cleanup_op); } } + if let Some(parent) = op.to.parent() { + target_dirs.insert(parent.to_path_buf()); + } ops.push(op); } @@ -177,10 +183,19 @@ impl Context { &self.controller, ) })? { + if matches!( + op.kind, + OpKind::Copy + | OpKind::Move { + cross_device_copy: true + } + ) { + written_files.push(op.to.clone()); + } // The from path is ignored in the operation selection if it is a top level item if self.op_sel.ignored.contains(&op.from) { // So add the to path to the selection - self.op_sel.selected.push(op.to.clone()); + self.op_sel.selected.push(op.to); } } else { // Cancelled @@ -188,6 +203,22 @@ impl Context { } } + // Sync files to disk + let file_stream = stream::iter(written_files.into_iter().map(|path| async move { + if let Ok(file) = compio::fs::OpenOptions::new().write(true).open(&path).await { + let _ = file.sync_all().await; + } + })); + file_stream.buffer_unordered(32).collect::>().await; + + // Sync directories to disk + let dir_stream = stream::iter(target_dirs.into_iter().map(|path| async move { + if let Ok(dir) = compio::fs::OpenOptions::new().read(true).open(&path).await { + let _ = dir.sync_all().await; + } + })); + dir_stream.buffer_unordered(16).collect::>().await; + Ok(true) } @@ -411,8 +442,6 @@ impl Op { } } } - - to_file.sync_all().await?; } OpKind::Move { cross_device_copy } => { // Remove `to` if overwriting and it is an existing file