From 11861a482dcb38394776105cce0f1a70e58bb2da Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Sun, 31 Mar 2019 17:21:36 +0200 Subject: [PATCH] src/backup/chunk_store.rs: fix GC Added option to get oldest_writer timestamp from ProcessLocker. --- src/backup/chunk_store.rs | 22 ++++++++++++++++++++-- src/backup/datastore.rs | 4 +++- src/tools/process_locker.rs | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/backup/chunk_store.rs b/src/backup/chunk_store.rs index 7dfff0af..4be684e8 100644 --- a/src/backup/chunk_store.rs +++ b/src/backup/chunk_store.rs @@ -247,11 +247,29 @@ impl ChunkStore { })) } - pub fn sweep_unused_chunks(&self, status: &mut GarbageCollectionStatus) -> Result<(), Error> { + pub fn oldest_writer(&self) -> Option { + tools::ProcessLocker::oldest_shared_lock(self.locker.clone()) + } + + pub fn sweep_unused_chunks( + &self, + oldest_writer: Option, + status: &mut GarbageCollectionStatus + ) -> Result<(), Error> { use nix::sys::stat::fstatat; let now = unsafe { libc::time(std::ptr::null_mut()) }; + let mut min_atime = now - 3600*24; // at least 24h (see mount option relatime) + + if let Some(stamp) = oldest_writer { + if stamp < min_atime { + min_atime = stamp; + } + } + + min_atime -= 300; // add 5 mins gap for safety + for entry in self.get_chunk_iterator(true)? { let (dirfd, entry) = match entry { Ok(entry) => (entry.parent_fd(), entry), @@ -273,7 +291,7 @@ impl ChunkStore { if let Ok(stat) = fstatat(dirfd, filename, nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW) { let age = now - stat.st_atime; //println!("FOUND {} {:?}", age/(3600*24), filename); - if age/(3600*24) >= 2 { + if stat.st_atime < min_atime { println!("UNLINK {} {:?}", age/(3600*24), filename); let res = unsafe { libc::unlinkat(dirfd, filename.as_ptr(), 0) }; if res != 0 { diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs index ca078798..cfd7c36a 100644 --- a/src/backup/datastore.rs +++ b/src/backup/datastore.rs @@ -229,6 +229,8 @@ impl DataStore { let _exclusive_lock = self.chunk_store.try_exclusive_lock()?; + let oldest_writer = self.chunk_store.oldest_writer(); + let mut gc_status = GarbageCollectionStatus::default(); gc_status.used_bytes = 0; @@ -237,7 +239,7 @@ impl DataStore { self.mark_used_chunks(&mut gc_status)?; println!("Start GC phase2 (sweep unused chunks)"); - self.chunk_store.sweep_unused_chunks(&mut gc_status)?; + self.chunk_store.sweep_unused_chunks(oldest_writer, &mut gc_status)?; println!("Used bytes: {}", gc_status.used_bytes); println!("Used chunks: {}", gc_status.used_chunks); diff --git a/src/tools/process_locker.rs b/src/tools/process_locker.rs index 45c3ab22..e60992d6 100644 --- a/src/tools/process_locker.rs +++ b/src/tools/process_locker.rs @@ -2,11 +2,16 @@ //! //! This implemenation uses fcntl record locks with non-blocking //! F_SETLK command (never blocks). +//! +//! We maintain a map of shared locks with time stamps, so you can get +//! the timestamp for the oldest open lock with +//! `oldest_shared_lock()`. use failure::*; use std::sync::{Arc, Mutex}; use std::os::unix::io::AsRawFd; +use std::collections::HashMap; // fixme: use F_OFD_ locks when implemented with nix::fcntl @@ -17,12 +22,15 @@ pub struct ProcessLocker { file: std::fs::File, exclusive: bool, writers: usize, + next_guard_id: u64, + shared_guard_list: HashMap, // guard_id => timestamp } /// Lock guard for shared locks /// /// Release the lock when it goes out of scope. pub struct ProcessLockSharedGuard { + guard_id: u64, locker: Arc>, } @@ -32,6 +40,8 @@ impl Drop for ProcessLockSharedGuard { if data.writers == 0 { panic!("unexpected ProcessLocker state"); } + data.shared_guard_list.remove(&self.guard_id); + if data.writers == 1 && !data.exclusive { let op = libc::flock { @@ -97,6 +107,8 @@ impl ProcessLocker { file: file, exclusive: false, writers: 0, + next_guard_id: 0, + shared_guard_list: HashMap::new(), }))) } @@ -130,7 +142,30 @@ impl ProcessLocker { data.writers += 1; - Ok(ProcessLockSharedGuard { locker: locker.clone() }) + let guard = ProcessLockSharedGuard { locker: locker.clone(), guard_id: data.next_guard_id }; + data.next_guard_id += 1; + + let now = unsafe { libc::time(std::ptr::null_mut()) }; + + data.shared_guard_list.insert(guard.guard_id, now); + + Ok(guard) + } + + /// Get oldest shared lock timestamp + pub fn oldest_shared_lock(locker: Arc>) -> Option { + let mut result = None; + + let data = locker.lock().unwrap(); + + for (_k, v) in &data.shared_guard_list { + result = match result { + None => Some(*v), + Some(x) => if x < *v { Some(x) } else { Some(*v) }, + }; + } + + result } /// Try to aquire a exclusive lock