From 3c8e9a470cd243eee1f6c63e149b3af76cfc005b Mon Sep 17 00:00:00 2001 From: Tim Bruijnzeels Date: Sun, 21 Jul 2019 15:54:59 -0400 Subject: [PATCH] Fix racecondition (save the updated aggregate!), and deadlock (use outer_lock on pub functions). --- commons/src/eventsourcing/agg_store.rs | 47 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/commons/src/eventsourcing/agg_store.rs b/commons/src/eventsourcing/agg_store.rs index c509a1bc..00a8706f 100644 --- a/commons/src/eventsourcing/agg_store.rs +++ b/commons/src/eventsourcing/agg_store.rs @@ -63,8 +63,8 @@ pub enum AggregateStoreError { #[display(fmt = "Event not applicable to aggregate, id or version is off")] WrongEventForAggregate, - #[display(fmt = "Trying to update outdated aggregate")] - ConcurrentModification, + #[display(fmt = "Trying to update outdated aggregate: {}", _0)] + ConcurrentModification(Handle), } impl From for AggregateStoreError { @@ -76,7 +76,8 @@ pub struct DiskAggregateStore { store: DiskKeyStore, cache: RwLock>>, use_cache: bool, - listeners: Vec>> + listeners: Vec>>, + outer_lock: RwLock<()> } impl DiskAggregateStore { @@ -85,7 +86,8 @@ impl DiskAggregateStore { let cache = RwLock::new(HashMap::new()); let use_cache = true; let listeners = vec![]; - Ok(DiskAggregateStore { store, cache, use_cache, listeners }) + let lock = RwLock::new(()); + Ok(DiskAggregateStore { store, cache, use_cache, listeners, outer_lock: lock }) } } @@ -111,10 +113,8 @@ impl DiskAggregateStore { self.cache.write().unwrap().insert(id.clone(), arc); } } -} -impl AggregateStore for DiskAggregateStore { - fn get_latest(&self, handle: &Handle) -> StoreResult> { + fn get_latest_no_lock(&self, handle: &Handle) -> StoreResult> { debug!("Trying to load aggregate id: {}", handle); match self.cache_get(handle) { None => { @@ -141,11 +141,20 @@ impl AggregateStore for DiskAggregateStore { } } } +} + +impl AggregateStore for DiskAggregateStore { + fn get_latest(&self, handle: &Handle) -> StoreResult> { + let _lock = self.outer_lock.read().unwrap(); + self.get_latest_no_lock(handle) + } fn add( &self, init: A::InitEvent ) -> StoreResult> { + let _lock = self.outer_lock.write().unwrap(); + self.store.store_event(&init)?; let handle = init.handle().clone(); @@ -166,12 +175,14 @@ impl AggregateStore for DiskAggregateStore { prev: Arc, events: Vec ) -> StoreResult> { + let _lock = self.outer_lock.write().unwrap(); + // Get the latest arc. - let mut latest = self.get_latest(handle)?; + let mut latest = self.get_latest_no_lock(handle)?; { // Verify whether there is a concurrency issue if prev.version() != latest.version() { - return Err(AggregateStoreError::ConcurrentModification) + return Err(AggregateStoreError::ConcurrentModification(handle.clone())) } // forget the previous version @@ -190,7 +201,7 @@ impl AggregateStore for DiskAggregateStore { // // Also note that we don't need the lock to update the inner arc in the cache. We // just need it to be in scope until we are done updating. - let _write_lock = self.cache.write().unwrap(); + let mut cache = self.cache.write().unwrap(); // There is a possible race condition. We may only have obtained the lock if self.has_updates(handle, &agg)? { @@ -211,29 +222,35 @@ impl AggregateStore for DiskAggregateStore { for event in events { self.store.store_event(&event)?; - for listener in &self.listeners { - listener.as_ref().listen(agg, &event); - } - - agg.apply(event); + agg.apply(event.clone()); if agg.version() % SNAPSHOT_FREQ == 0 { self.store.store_aggregate(handle, agg)?; } + + for listener in &self.listeners { + listener.as_ref().listen(agg, &event); + } } + + cache.insert(handle.clone(), Arc::new(agg.clone())); } Ok(latest) } fn has(&self, id: &Handle) -> bool { + let _lock = self.outer_lock.read().unwrap(); self.store.has_aggregate(id) } fn list(&self) -> Vec { + let _lock = self.outer_lock.read().unwrap(); self.store.aggregates() } fn add_listener>(&mut self, listener: Arc) { + let _lock = self.outer_lock.write().unwrap(); + self.listeners.push(listener) } } \ No newline at end of file