From 3d02c7ee4aa98e98abc1657795ec68e58bdc9d95 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Wed, 6 Aug 2025 07:27:02 +0200 Subject: [PATCH] Properly added cancellation token to the remote synchronization tool --- Tools/RemoteSynchronization/Program.cs | 54 ++++++++++++++------------ 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/Tools/RemoteSynchronization/Program.cs b/Tools/RemoteSynchronization/Program.cs index 9af910aca..470a29649 100644 --- a/Tools/RemoteSynchronization/Program.cs +++ b/Tools/RemoteSynchronization/Program.cs @@ -131,11 +131,11 @@ destination will be verified before being overwritten (if they seemingly match). new Option(aliases: ["--verify-get-after-put"], description: "Verify the files after uploading them to ensure that they were uploaded correctly", getDefaultValue: () => false), }; - root_cmd.Handler = CommandHandler.Create((string backend_src, string backend_dst, Config config) => + root_cmd.Handler = CommandHandler.Create((string backend_src, string backend_dst, Config config, CancellationToken token) => { var config_with_args = config with { Dst = backend_dst, Src = backend_src }; - return Run(config_with_args); + return Run(config_with_args, token); }); return await root_cmd.InvokeAsync(args); @@ -144,11 +144,10 @@ destination will be verified before being overwritten (if they seemingly match). /// /// The main logic of the tool. /// - /// The connection string for the source backend. - /// The connection string for the destination backend. - /// Various options for the tool + /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// The return code for the main entry; 0 on success. - private static async Task Run(Config config) + private static async Task Run(Config config, CancellationToken token) { // Unpack and parse the multi token options var global_options = ParseOptions(config.GlobalOptions); @@ -216,7 +215,7 @@ destination will be verified before being overwritten (if they seemingly match). } } - var not_verified = await VerifyAsync(b1m, b2m, to_verify, config); + var not_verified = await VerifyAsync(b1m, b2m, to_verify, config, token); failed_verify = not_verified.Count(); verified = to_verify.Count() - failed_verify; @@ -257,19 +256,19 @@ destination will be verified before being overwritten (if they seemingly match). long renamed = 0, deleted = 0; if (config.Retention) { - renamed = await RenameAsync(b2m, to_delete, config); + renamed = await RenameAsync(b2m, to_delete, config, token); Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Renamed {0} files in {1}", renamed, b2m.DisplayName); } else { - deleted = await DeleteAsync(b2m, to_delete, config); + deleted = await DeleteAsync(b2m, to_delete, config, token); Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Deleted {0} files from {1}", deleted, b2m.DisplayName); } // Copy the files - var (copied, copy_errors) = await CopyAsync(b1m, b2m, to_copy, config); + var (copied, copy_errors) = await CopyAsync(b1m, b2m, to_copy, config, token); Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Copied {0} files from {1} to {2}", copied, b1m.DisplayName, b2m.DisplayName); @@ -286,7 +285,7 @@ destination will be verified before being overwritten (if they seemingly match). for (int i = 0; i < config.Retry; i++) { await Task.Delay(5000); // Wait 5 seconds before retrying - (copied, copy_errors) = await CopyAsync(b1m, b2m, copy_errors, config); + (copied, copy_errors) = await CopyAsync(b1m, b2m, copy_errors, config, token); Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Copied {0} files from {1} to {2}", copied, b1m.DisplayName, b2m.DisplayName); if (!copy_errors.Any()) @@ -341,8 +340,9 @@ destination will be verified before being overwritten (if they seemingly match). /// The destination backend. /// The files that will be copied. /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// A tuple holding the number of succesful copies and a List of the files that failed. - private static async Task<(long, IEnumerable)> CopyAsync(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, IEnumerable files, Config config) + private static async Task<(long, IEnumerable)> CopyAsync(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, IEnumerable files, Config config, CancellationToken token) { long successful_copies = 0; List errors = []; @@ -365,7 +365,7 @@ destination will be verified before being overwritten (if they seemingly match). try { sw_get_src.Start(); - await b_src.GetAsync(f.Name, s_src, CancellationToken.None); + await b_src.GetAsync(f.Name, s_src, token); s_src.Position = 0; sw_get_src.Stop(); if (config.DryRun) @@ -378,7 +378,7 @@ destination will be verified before being overwritten (if they seemingly match). else { sw_put_dst.Start(); - await b_dst.PutAsync(f.Name, s_src, CancellationToken.None); + await b_dst.PutAsync(f.Name, s_src, token); s_src.Position = 0; sw_put_dst.Stop(); if (config.VerifyGetAfterPut) @@ -393,7 +393,7 @@ destination will be verified before being overwritten (if they seemingly match). using var s_dst = Duplicati.Library.Utility.TempFileStream.Create(); sw_get_dst.Start(); - await b_dst.GetAsync(f.Name, s_dst, CancellationToken.None); + await b_dst.GetAsync(f.Name, s_dst, token); s_dst.Position = 0; sw_get_dst.Stop(); @@ -459,8 +459,9 @@ destination will be verified before being overwritten (if they seemingly match). /// The backend to delete the files from. /// The files to delete. /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// The number of successful deletions. - private static async Task DeleteAsync(LightWeightBackendManager b, IEnumerable files, Config config) + private static async Task DeleteAsync(LightWeightBackendManager b, IEnumerable files, Config config, CancellationToken token) { long successful_deletes = 0; long i = 0, n = files.Count(); @@ -485,7 +486,7 @@ destination will be verified before being overwritten (if they seemingly match). } else { - await b.DeleteAsync(f.Name, CancellationToken.None); + await b.DeleteAsync(f.Name, token); } successful_deletes++; } @@ -555,16 +556,17 @@ destination will be verified before being overwritten (if they seemingly match). /// The source lightweight backend manager. /// The destination lightweight backend manager. /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// A tuple of Lists each holding the files to copy, delete and verify. - private static async Task<(IEnumerable, IEnumerable, IEnumerable)> PrepareFileLists(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, Config config, CancellationToken cancelToken) + private static async Task<(IEnumerable, IEnumerable, IEnumerable)> PrepareFileLists(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, Config config, CancellationToken token) { IEnumerable files_src, files_dst; using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | List source")) - files_src = await b_src.ListAsync(cancelToken).ToListAsync(cancelToken).ConfigureAwait(false); + files_src = await b_src.ListAsync(token).ToListAsync(token).ConfigureAwait(false); using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | List destination")) - files_dst = await b_dst.ListAsync(cancelToken).ToListAsync(cancelToken).ConfigureAwait(false); + files_dst = await b_dst.ListAsync(token).ToListAsync(token).ConfigureAwait(false); // Shortcut for force if (config.Force) @@ -641,8 +643,9 @@ destination will be verified before being overwritten (if they seemingly match). /// The lightweight backend manager to issue rename operations to. /// The files to rename. /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// The number of successful renames. - private static async Task RenameAsync(LightWeightBackendManager bm, IEnumerable files, Config config) + private static async Task RenameAsync(LightWeightBackendManager bm, IEnumerable files, Config config, CancellationToken token) { long successful_renames = 0; string prefix = $"{System.DateTime.UtcNow:yyyyMMddHHmmss}.old"; @@ -671,7 +674,7 @@ destination will be verified before being overwritten (if they seemingly match). else { sw.Start(); - await bm.RenameAsync(f.Name, $"{prefix}.{f.Name}", CancellationToken.None); + await bm.RenameAsync(f.Name, $"{prefix}.{f.Name}", token); sw.Stop(); } successful_renames++; @@ -708,8 +711,9 @@ destination will be verified before being overwritten (if they seemingly match). /// The destination lightweight backend manager. /// The files to verify. /// The parsed configuration for the tool. + /// The cancellation token to use for the asynchronous operations. /// A list of the files that failed verification. - private static async Task> VerifyAsync(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, IEnumerable files, Config config) + private static async Task> VerifyAsync(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, IEnumerable files, Config config, CancellationToken token) { var errors = new List(); using var s_src = new MemoryStream(); @@ -733,8 +737,8 @@ destination will be verified before being overwritten (if they seemingly match). { // Get both files sw_get.Start(); - var fs = b_src.GetAsync(f.Name, s_src, CancellationToken.None); - var ds = b_dst.GetAsync(f.Name, s_dst, CancellationToken.None); + var fs = b_src.GetAsync(f.Name, s_src, token); + var ds = b_dst.GetAsync(f.Name, s_dst, token); await Task.WhenAll(fs, ds); sw_get.Stop();