|
5 | 5 | // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in |
6 | 6 | // accordance with one or both of these licenses. |
7 | 7 |
|
8 | | -use std::collections::{hash_map, HashMap}; |
| 8 | +use std::collections::HashMap; |
9 | 9 | use std::ops::Deref; |
10 | 10 | use std::sync::{Arc, Mutex}; |
11 | 11 |
|
@@ -83,28 +83,32 @@ where |
83 | 83 |
|
84 | 84 | pub(crate) async fn insert_or_update(&self, object: SO) -> Result<bool, Error> { |
85 | 85 | let _guard = self.mutation_lock.lock().await; |
86 | | - let (updated, data_to_persist) = { |
87 | | - let mut locked_objects = self.objects.lock().expect("lock"); |
88 | | - match locked_objects.entry(object.id()) { |
89 | | - hash_map::Entry::Occupied(mut e) => { |
90 | | - let update = object.to_update(); |
91 | | - let updated = e.get_mut().update(update); |
92 | | - let data_to_persist = |
93 | | - if updated { Some(Self::encode_object(e.get())) } else { None }; |
94 | | - (updated, data_to_persist) |
95 | | - }, |
96 | | - hash_map::Entry::Vacant(e) => { |
97 | | - let data_to_persist = Self::encode_object(&object); |
98 | | - e.insert(object); |
99 | | - (true, Some(data_to_persist)) |
100 | | - }, |
| 86 | + |
| 87 | + let id = object.id(); |
| 88 | + let data_to_persist = { |
| 89 | + let locked_objects = self.objects.lock().expect("lock"); |
| 90 | + if let Some(existing_object) = locked_objects.get(&id) { |
| 91 | + let mut updated_object = existing_object.clone(); |
| 92 | + let updated = updated_object.update(object.to_update()); |
| 93 | + if updated { |
| 94 | + Some(updated_object) |
| 95 | + } else { |
| 96 | + None |
| 97 | + } |
| 98 | + } else { |
| 99 | + Some(object) |
101 | 100 | } |
102 | 101 | }; |
103 | 102 |
|
104 | | - if let Some((store_key, data)) = data_to_persist { |
105 | | - self.persist_encoded(store_key, data).await?; |
| 103 | + match data_to_persist { |
| 104 | + Some(updated_object) => { |
| 105 | + self.persist(&updated_object).await?; |
| 106 | + let mut locked_objects = self.objects.lock().expect("lock"); |
| 107 | + locked_objects.insert(id, updated_object); |
| 108 | + Ok(true) |
| 109 | + }, |
| 110 | + None => Ok(false), |
106 | 111 | } |
107 | | - Ok(updated) |
108 | 112 | } |
109 | 113 |
|
110 | 114 | pub(crate) async fn remove(&self, id: &SO::Id) -> Result<(), Error> { |
@@ -219,6 +223,7 @@ where |
219 | 223 | #[cfg(test)] |
220 | 224 | mod tests { |
221 | 225 | use lightning::impl_writeable_tlv_based; |
| 226 | + use lightning::io; |
222 | 227 | use lightning::util::test_utils::TestLogger; |
223 | 228 |
|
224 | 229 | use super::*; |
@@ -281,6 +286,46 @@ mod tests { |
281 | 286 | (2, data, required), |
282 | 287 | }); |
283 | 288 |
|
| 289 | + struct FailingStore; |
| 290 | + |
| 291 | + impl KVStore for FailingStore { |
| 292 | + fn read( |
| 293 | + &self, _primary_namespace: &str, _secondary_namespace: &str, _key: &str, |
| 294 | + ) -> impl std::future::Future<Output = Result<Vec<u8>, io::Error>> + 'static + Send { |
| 295 | + async { Err(io::Error::new(io::ErrorKind::Other, "read failed")) } |
| 296 | + } |
| 297 | + |
| 298 | + fn write( |
| 299 | + &self, _primary_namespace: &str, _secondary_namespace: &str, _key: &str, _buf: Vec<u8>, |
| 300 | + ) -> impl std::future::Future<Output = Result<(), io::Error>> + 'static + Send { |
| 301 | + async { Err(io::Error::new(io::ErrorKind::Other, "write failed")) } |
| 302 | + } |
| 303 | + |
| 304 | + fn remove( |
| 305 | + &self, _primary_namespace: &str, _secondary_namespace: &str, _key: &str, _lazy: bool, |
| 306 | + ) -> impl std::future::Future<Output = Result<(), io::Error>> + 'static + Send { |
| 307 | + async { Err(io::Error::new(io::ErrorKind::Other, "remove failed")) } |
| 308 | + } |
| 309 | + |
| 310 | + fn list( |
| 311 | + &self, _primary_namespace: &str, _secondary_namespace: &str, |
| 312 | + ) -> impl std::future::Future<Output = Result<Vec<String>, io::Error>> + 'static + Send { |
| 313 | + async { Err(io::Error::new(io::ErrorKind::Other, "list failed")) } |
| 314 | + } |
| 315 | + } |
| 316 | + |
| 317 | + fn new_failing_data_store(objects: Vec<TestObject>) -> DataStore<TestObject, Arc<TestLogger>> { |
| 318 | + let store: Arc<DynStore> = Arc::new(DynStoreWrapper(FailingStore)); |
| 319 | + let logger = Arc::new(TestLogger::new()); |
| 320 | + DataStore::new( |
| 321 | + objects, |
| 322 | + "datastore_test_primary".to_string(), |
| 323 | + "datastore_test_secondary".to_string(), |
| 324 | + store, |
| 325 | + logger, |
| 326 | + ) |
| 327 | + } |
| 328 | + |
284 | 329 | #[tokio::test] |
285 | 330 | async fn data_is_persisted() { |
286 | 331 | let store: Arc<DynStore> = Arc::new(DynStoreWrapper(InMemoryStore::new())); |
@@ -346,4 +391,23 @@ mod tests { |
346 | 391 | new_iou_object.data[0] += 1; |
347 | 392 | assert_eq!(Ok(true), data_store.insert_or_update(new_iou_object).await); |
348 | 393 | } |
| 394 | + |
| 395 | + #[tokio::test] |
| 396 | + async fn insert_or_update_does_not_mutate_memory_if_persist_fails() { |
| 397 | + let existing_id = TestObjectId { id: [42u8; 4] }; |
| 398 | + let existing_object = TestObject { id: existing_id, data: [23u8; 3] }; |
| 399 | + let data_store = new_failing_data_store(vec![existing_object]); |
| 400 | + |
| 401 | + let updated_object = TestObject { id: existing_id, data: [24u8; 3] }; |
| 402 | + assert_eq!( |
| 403 | + Err(Error::PersistenceFailed), |
| 404 | + data_store.insert_or_update(updated_object).await |
| 405 | + ); |
| 406 | + assert_eq!(Some(existing_object), data_store.get(&existing_id)); |
| 407 | + |
| 408 | + let new_id = TestObjectId { id: [55u8; 4] }; |
| 409 | + let new_object = TestObject { id: new_id, data: [34u8; 3] }; |
| 410 | + assert_eq!(Err(Error::PersistenceFailed), data_store.insert_or_update(new_object).await); |
| 411 | + assert!(data_store.get(&new_id).is_none()); |
| 412 | + } |
349 | 413 | } |
0 commit comments