public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: Lukas Wagner <l.wagner@proxmox.com>, pdm-devel@lists.proxmox.com
Subject: Re: [PATCH datacenter-manager 15/15] task cache: handle potentially duplicated archive files after 'compress_archive_file'
Date: Fri, 10 Jul 2026 13:36:03 +0200	[thread overview]
Message-ID: <ddb738ae-ec11-4f91-bcce-04098b13fa55@proxmox.com> (raw)
In-Reply-To: <20260702092258.174740-16-l.wagner@proxmox.com>

comment inline

On 7/2/26 11:23 AM, Lukas Wagner wrote:
> `compress_archive_file` cannot be made fully atomic, therefore we must
> consider the case where we have both, the old, uncompressed file and the
> new, compressed file in the archive directory.
> 
> This is handled by ignoring duplicates files when reading/writing
> from/into the task archive, and by actively cleaning up the duplicates
> during `rotate`.
> 
> Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
> ---
>   server/src/remote_tasks/task_cache.rs | 97 +++++++++++++++++++++++++--
>   1 file changed, 93 insertions(+), 4 deletions(-)
> 
> diff --git a/server/src/remote_tasks/task_cache.rs b/server/src/remote_tasks/task_cache.rs
> index 23238cca..2110037b 100644
> --- a/server/src/remote_tasks/task_cache.rs
> +++ b/server/src/remote_tasks/task_cache.rs
> @@ -289,13 +289,34 @@ impl<'a> WritableTaskCache<'a> {
>           Ok(file)
>       }
>   
> -    /// Rotate task archive if the the newest archive file is older than `rotate_after`.
> +    /// Rotate task archive if the newest archive file is older than `rotate_after`.
>       ///
>       /// The oldest archive files are removed if the total number of archive files exceeds
>       /// `max_files`. `now` is supposed to be a UNIX timestamp (seconds).
> +    ///
> +    /// If there are any duplicate archive files (can happen if `compress` is interrupted at the
> +    /// wrong time), the uncompressed version is deleted.
>       pub fn rotate(&self, now: i64) -> Result<bool, Error> {
>           let mut did_rotate = false;
> -        let mut archive_files = self.cache.archive_files(&self.lock)?;
> +        let archive_files_with_dupes = self.cache.archive_files_with_dupes(&self.lock)?;
> +
> +        let mut deduped: Vec<ArchiveFile> = Vec::new();
> +
> +        for file in archive_files_with_dupes {
> +            if deduped.iter().any(|e| e.starttime == file.starttime) {
> +                // Found dupe, remove it.
> +                if let Err(err) = std::fs::remove_file(&file.path) {
> +                    log::error!(
> +                        "could not clean up duplicate archive file '{path}': {err}",
> +                        path = file.path.display()
> +                    )
> +                }
> +            } else {
> +                deduped.push(file);
> +            }
> +        }

i think we could avoid this double loop here if we use a hashset?

also we only detect duplicates by the starttime?
sound a bit strange to me, wouldn't the more natural way
by looking at e.g. the filenames?

> +
> +        let mut archive_files = deduped;
>   
>           let mut start_new_file = |files: &mut Vec<ArchiveFile>| -> Result<(), Error> {
>               let new_file = self.new_file(now, self.cache.uncompressed_files == 0)?;
> @@ -848,6 +869,11 @@ impl<'a> WritableTaskCache<'a> {
>               return Err(err);
>           }
>   
> +        // If we crash here or if `remove_file` fails, we might end up with the original,
> +        // uncompressed file as well as the new, compressed file. `archive_files` filters
> +        // duplicates out, so we should never read from the duplicated file. Any left-over
> +        // duplicates are cleaned during `rotate`.
> +
>           std::fs::remove_file(&file.path).context("failed to remove uncompressed archive file")?;
>   
>           Ok(file_state)
> @@ -970,7 +996,7 @@ impl TaskCache {
>       /// cut-off timestamp. The result is sorted ascending by cut-off timestamp (most recent one
>       /// first).
>       /// The task archive should be locked for reading when calling this function.
> -    fn archive_files(&self, _lock: &TaskCacheLock) -> Result<Vec<ArchiveFile>, Error> {
> +    fn archive_files_with_dupes(&self, _lock: &TaskCacheLock) -> Result<Vec<ArchiveFile>, Error> {
>           let mut names = Vec::new();
>   
>           for entry in std::fs::read_dir(&self.base_path)? {
> @@ -983,7 +1009,23 @@ impl TaskCache {
>               }
>           }
>   
> -        names.sort_by_key(|e| -e.starttime);
> +        names.sort_by(|a, b| {
> +            b.starttime
> +                .cmp(&a.starttime)
> +                .then(b.compressed.cmp(&a.compressed))
> +        });
> +
> +        Ok(names)
> +    }
> +
> +    /// Returns a list of existing archive files, together with their respective
> +    /// cut-off timestamp. The result is sorted ascending by cut-off timestamp (most recent one
> +    /// first). Any duplicates (equal starttime but different compression status) are removed.
> +    ///
> +    /// The task archive should be locked for reading when calling this function.
> +    fn archive_files(&self, lock: &TaskCacheLock) -> Result<Vec<ArchiveFile>, Error> {
> +        let mut names = self.archive_files_with_dupes(lock)?;
> +        names.dedup_by_key(|e| e.starttime);
>   
>           Ok(names)
>       }
> @@ -2104,4 +2146,51 @@ mod tests {
>               "cutoff timestamp should be reset to the lower bound of the corrupted archive file"
>           );
>       }
> +
> +    /// Ensure that if for any reason there exist multiple files with the same start time (only
> +    /// possible if one of them is compressed and the other one uncompressed), we only return one
> +    /// of them. Also verify that compressed files are preferred.
> +    #[test]
> +    fn dedup_duplicate_archive_files() {
> +        let (_tmp_dir, mut cache) = make_cache().unwrap();
> +        cache.rotate_after = 100;
> +        cache.uncompressed_files = 1;
> +        cache.max_files = 3;
> +
> +        let cache = cache.write().unwrap();
> +
> +        cache.new_file(1000, false).unwrap();
> +        cache.new_file(1000, true).unwrap();
> +        cache.new_file(2000, true).unwrap();
> +        cache.new_file(2000, false).unwrap();
> +
> +        let files = cache.cache.archive_files(&cache.lock).unwrap();
> +
> +        assert_eq!(files.len(), 2);
> +        let first = files.get(0).unwrap();
> +        let second = files.get(1).unwrap();
> +
> +        assert!(first.compressed);
> +        assert_eq!(first.starttime, 2000);
> +
> +        assert!(second.compressed);
> +        assert_eq!(second.starttime, 1000);
> +
> +        let files = cache.cache.archive_files_with_dupes(&cache.lock).unwrap();
> +        assert_eq!(files.len(), 4);
> +
> +        cache.rotate(2050).unwrap();
> +
> +        let files = cache.cache.archive_files_with_dupes(&cache.lock).unwrap();
> +        assert_eq!(files.len(), 2);
> +
> +        let first = files.get(0).unwrap();
> +        let second = files.get(1).unwrap();
> +
> +        assert!(first.compressed);
> +        assert_eq!(first.starttime, 2000);
> +
> +        assert!(second.compressed);
> +        assert_eq!(second.starttime, 1000);
> +    }
>   }





  reply	other threads:[~2026-07-10 11:37 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-02  9:22 [PATCH datacenter-manager/proxmox 00/15] task cache improvements (archive corruption handling, error handling) Lukas Wagner
2026-07-02  9:22 ` [PATCH proxmox 01/15] sys: fs: don't replace file extension make_tmp_file Lukas Wagner
2026-07-02  9:29   ` Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 02/15] task cache: fix missing cutoff state for PBS remotes Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 03/15] task cache: refresh task: don't apply journal if the archive was rotated Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 04/15] task cache: rotate: align timestamp for new files to UTC midnight Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-10 12:02     ` Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 05/15] task cache: add test case for task cache rotation Lukas Wagner
2026-07-10 11:35   ` Dominik Csapak
2026-07-10 12:13     ` Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 06/15] task cache: pre-compute static paths during initialization Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 07/15] task cache: only initialize `TaskCache` struct once Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 08/15] task cache: archive iterator: don't yield more items if reading from file failed Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-10 12:36     ` Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 09/15] task cache: include archive file path in error log messages Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-10 12:53     ` Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 10/15] task cache: introduce ArchiveFileWriter Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-02  9:22 ` [PATCH datacenter-manager 11/15] task cache: use ArchiveFileWriter when creating new archive files Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 12/15] task cache: trigger repair of corruption when applying journal Lukas Wagner
2026-07-02  9:22 ` [PATCH datacenter-manager 13/15] task cache: trigger repair of corruption when compressing archive files Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-02  9:22 ` [PATCH datacenter-manager 14/15] task cache: trigger repair of corruption after read-accesses Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak
2026-07-02  9:22 ` [PATCH datacenter-manager 15/15] task cache: handle potentially duplicated archive files after 'compress_archive_file' Lukas Wagner
2026-07-10 11:36   ` Dominik Csapak [this message]
2026-07-10 11:36 ` [PATCH datacenter-manager/proxmox 00/15] task cache improvements (archive corruption handling, error handling) Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ddb738ae-ec11-4f91-bcce-04098b13fa55@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=l.wagner@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal