/// <inheritdoc /> public async Task <DataPersisterUpdateResult <T> > Update(CancellationToken ct, DataPersisterUpdaterWithContext <T> updater) { using (await _updateGate.LockAsync(ct)) { var result = await _inner.Update(ct, updater); if (result.IsUpdated) { _update.OnNext(result); } return(result); } }
/// <inheritdoc /> public async Task <DataPersisterUpdateResult <T> > Update(CancellationToken ct, DataPersisterUpdaterWithContext <T> updater) { using (await _lock.LockAsync(ct)) { // 1) Lock the file (see LOCKING PROCESS below) using (await GetFileLock(ct)) { // 2) Read the COMMITTED file T data = default(T); bool exists = false; DataPersisterTransactionContext <T> control; try { if (File.Exists(_committedFile)) { using (var stream = File.OpenRead(_committedFile)) { data = await _read(ct, stream); exists = true; } } control = new DataPersisterTransactionContext <T>(data, exists); } catch (FileNotFoundException) { control = new DataPersisterTransactionContext <T>(data, isValuePresent: false); } catch (Exception ex) { var exceptionInfo = ExceptionDispatchInfo.Capture(ex); control = new DataPersisterTransactionContext <T>(exceptionInfo); } // 3) Call code updater (update callback func) updater(control); if (!control.IsCommitted) { // 4) If the context is not committed, go to step 9 return(new DataPersisterUpdateResult <T>(data, exists, isUpdated: false)); } // x) Alternate flow if the updater ask for a delete if (control.IsRemoved) { if (exists) { File.Delete(_committedFile); return(new DataPersisterUpdateResult <T>(default(T), isValuePresent: false, isUpdated: true)); } return(new DataPersisterUpdateResult <T>(default(T), isValuePresent: false, isUpdated: false)); } // 5) Save the result in the NEW file, flush & close the file. using (var stream = File.OpenWrite(_newFile)) { await _write(ct, control.CommittedValue, stream); } if (File.Exists(_committedFile)) { // 6) Rename the COMMITTED as OLD file (atomic operation in OS) -- STARTING HERE THE CHANGE IS DURABLE File.Move(_committedFile, _oldFile); // 7) Rename the NEW as COMMITTED file (atomic operation in OS) File.Move(_newFile, _committedFile); // 8) Delete the OLD file File.Delete(_oldFile); } else { // 6-7-8) Rename the NEW as COMMITTED file (atomic operation in OS) File.Move(_newFile, _committedFile); } return(new DataPersisterUpdateResult <T>(control.CommittedValue, isValuePresent: true, isUpdated: true)); // 9) Close & delete the LOCK file } } }
/// <inheritdoc /> public async Task <DataPersisterUpdateResult <T> > Update(CancellationToken ct, DataPersisterUpdaterWithContext <T> updater) { var innerUpdated = false; var result = await _inner.Update( ct, context => { innerUpdated = false; var adjustedContext = GetAdjustedReadResult(context); var innerContext = new DataPersisterTransactionContext <T>(adjustedContext); updater(innerContext); if (innerContext.IsCommitted) { innerUpdated = true; if (innerContext.IsRemoved) { context.RemoveAndCommit(); } else { var value = innerContext.CommittedValue; if (CheckMode(DefaultValueDataPersisterDecoratorMode.WriteCustomDefaultToEmpty) && _comparer.Equals(value, _customDefaultValue)) { context.RemoveAndCommit(); } else if (CheckMode(DefaultValueDataPersisterDecoratorMode.WriteDefaultToEmpty) && _comparer.Equals(value, default(T))) { context.RemoveAndCommit(); } else { context.Commit(value); } } } } ); var adjustedResult = GetAdjustedReadResult(result); return(new DataPersisterUpdateResult <T>(adjustedResult, innerUpdated || result.IsUpdated)); }