datastore: lookup: reuse ChunkStore on stale datastore re-open

When re-opening a datastore due to the cached entry being stale
(config change) but also if the last re-open was >60s ago). On
datastore open the chunk store was also re-opened, which in turn
creates a new ProcessLocker, loosing any existing shared lock which
can cause conflicts between long running (24h+) backups  and GC.

To fix this, reuse the existing ChunkStore, and thus  its
ProcessLocker, when creating a up-to-date datastore instance on
lookup, since only the datastore config should be reloaded. This is
fine as the ChunkStore path is not updatable over our API.

This was always a potential issue but got exposed in practice by
commit 118deb4db8 which introduced the
unconditional "re-open after 60s" mechanism.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
 [ T: reword commit message a bit and reference commit that made the
   issue much more likely ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Dominik Csapak 2022-06-02 16:27:44 +02:00 committed by Thomas Lamprecht
parent fbfb64a6b2
commit 0bd9c87010
1 changed files with 15 additions and 6 deletions

View File

@ -145,16 +145,20 @@ impl DataStore {
let mut map = DATASTORE_MAP.lock().unwrap(); let mut map = DATASTORE_MAP.lock().unwrap();
let entry = map.get(name); let entry = map.get(name);
if let Some(datastore) = &entry { // reuse chunk_store, we only want to reload the datastore config, and the path
// is normally not editable and requires a restart of the proxy
let chunk_store = if let Some(datastore) = &entry {
if datastore.last_generation == generation && now < (datastore.last_update + 60) { if datastore.last_generation == generation && now < (datastore.last_update + 60) {
return Ok(Arc::new(Self { return Ok(Arc::new(Self {
inner: Arc::clone(datastore), inner: Arc::clone(datastore),
operation, operation,
})); }));
} }
} Arc::clone(&datastore.chunk_store)
} else {
Arc::new(ChunkStore::open(name, &config.path)?)
};
let chunk_store = ChunkStore::open(name, &config.path)?;
let datastore = DataStore::with_store_and_config(chunk_store, config, generation, now)?; let datastore = DataStore::with_store_and_config(chunk_store, config, generation, now)?;
let datastore = Arc::new(datastore); let datastore = Arc::new(datastore);
@ -198,7 +202,12 @@ impl DataStore {
let name = config.name.clone(); let name = config.name.clone();
let chunk_store = ChunkStore::open(&name, &config.path)?; let chunk_store = ChunkStore::open(&name, &config.path)?;
let inner = Arc::new(Self::with_store_and_config(chunk_store, config, 0, 0)?); let inner = Arc::new(Self::with_store_and_config(
Arc::new(chunk_store),
config,
0,
0,
)?);
if let Some(operation) = operation { if let Some(operation) = operation {
update_active_operations(&name, operation, 1)?; update_active_operations(&name, operation, 1)?;
@ -208,7 +217,7 @@ impl DataStore {
} }
fn with_store_and_config( fn with_store_and_config(
chunk_store: ChunkStore, chunk_store: Arc<ChunkStore>,
config: DataStoreConfig, config: DataStoreConfig,
last_generation: usize, last_generation: usize,
last_update: i64, last_update: i64,
@ -235,7 +244,7 @@ impl DataStore {
let chunk_order = tuning.chunk_order.unwrap_or(ChunkOrder::Inode); let chunk_order = tuning.chunk_order.unwrap_or(ChunkOrder::Inode);
Ok(DataStoreImpl { Ok(DataStoreImpl {
chunk_store: Arc::new(chunk_store), chunk_store,
gc_mutex: Mutex::new(()), gc_mutex: Mutex::new(()),
last_gc_status: Mutex::new(gc_status), last_gc_status: Mutex::new(gc_status),
verify_new: config.verify_new.unwrap_or(false), verify_new: config.verify_new.unwrap_or(false),