/// <summary> /// Transactional Write procedure. /// </summary> public void Save() { var info = TransactionContext.GetTransactionInfo(); if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug("Write {0}", info); } if (info.IsReadOnly) { // For obvious reasons... throw new OrleansReadOnlyViolatedException(info.TransactionId); } Rollback(); var copiedValue = this.transactionCopy[info.TransactionId]; // // Validation // if (this.version.TransactionId > info.TransactionId || this.highestReadTransactionId >= info.TransactionId) { // Prevent cycles. Wait-die throw new OrleansTransactionWaitDieException(info.TransactionId); } TransactionalResourceVersion nextVersion = TransactionalResourceVersion.Create(info.TransactionId, this.version.TransactionId == info.TransactionId ? this.version.WriteNumber + 1 : 1); // // Update Transaction Context // info.RecordWrite(transactionalResource, this.version, this.metadata.StableVersion.TransactionId); // // Modify the State // if (!this.log.ContainsKey(info.TransactionId)) { LogRecord <TState> r = new LogRecord <TState>(); this.log[info.TransactionId] = r; } LogRecord <TState> logRecord = this.log[info.TransactionId]; logRecord.NewVal = copiedValue; logRecord.Version = nextVersion; this.value = copiedValue; this.version = nextVersion; this.transactionCopy.Remove(info.TransactionId); }
/// <summary> /// Transactional Read procedure. /// </summary> private TState GetState() { var info = TransactionContext.GetTransactionInfo(); Rollback(); if (this.transactionCopy.TryGetValue(info.TransactionId, out TState state)) { return(state); } if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug("GetState {0}", info); } if (!TryGetVersion(info.TransactionId, out TState readState, out TransactionalResourceVersion readVersion)) { // This can only happen if old versions are gone due to checkpointing. throw new OrleansTransactionVersionDeletedException(info.TransactionId); } if (info.IsReadOnly && readVersion.TransactionId > this.metadata.StableVersion.TransactionId) { throw new OrleansTransactionUnstableVersionException(info.TransactionId); } info.RecordRead(transactionalResource, readVersion, this.metadata.StableVersion.TransactionId); this.highestReadTransactionId = Math.Max(this.highestReadTransactionId, info.TransactionId - 1); TState copy = this.copier.DeepCopy(readState); if (!info.IsReadOnly) { this.transactionCopy[info.TransactionId] = copy; } return(copy); }
/// <summary> /// Transactional Read procedure. /// </summary> private TState GetState() { var info = TransactionContext.GetTransactionInfo(); if (this.logger.IsVerbose2) { this.logger.Verbose2("Read {0}", info); } Restore(); if (this.transactionCopy.TryGetValue(info.TransactionId, out TState state)) { return(state); } if (!TryGetVersion(info.TransactionId, out TState readState, out TransactionalResourceVersion readVersion)) { // This can only happen if old versions are gone due to checkpointing. throw new OrleansTransactionVersionDeletedException(info.TransactionId); } if (info.IsReadOnly && readVersion.TransactionId > this.stableVersion) { throw new OrleansTransactionUnstableVersionException(info.TransactionId); } info.RecordRead(transactionalResource, readVersion, this.storage.State.StableVersion); writeLowerBound = Math.Max(writeLowerBound, info.TransactionId - 1); TState copy = this.copier.DeepCopy(readState); if (!info.IsReadOnly) { this.transactionCopy[info.TransactionId] = copy; } return(copy); }
/// <summary> /// Read the current state. /// </summary> public Task <TResult> PerformRead <TResult>(Func <TState, TResult> operation) { if (detectReentrancy) { throw new LockRecursionException("cannot perform a read operation from within another operation"); } var info = (TransactionInfo)TransactionContext.GetTransactionInfo(); if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"StartRead {info}"); } info.Participants.TryGetValue(thisParticipant, out var recordedaccesses); // schedule read access to happen under the lock return(EnterLock <TResult>(info.TransactionId, info.Priority, recordedaccesses, true, new Task <TResult>(() => { // check if our record is gone because we expired while waiting if (!currentGroup.TryGetValue(info.TransactionId, out var record)) { throw new OrleansTransactionLockAcquireTimeoutException(info.TransactionId.ToString()); } // merge the current clock into the transaction time stamp record.Timestamp = MergeAndReadClock(info.TimeStamp); if (record.State == null) { GetMostRecentState(out record.State, out record.SequenceNumber); } if (logger.IsEnabled(LogLevel.Debug)) { logger.Debug($"update-lock read v{record.SequenceNumber} {record.TransactionId} {record.Timestamp:o}"); } // record this read in the transaction info data structure info.RecordRead(thisParticipant, record.Timestamp); // perform the read TResult result = default(TResult); try { detectReentrancy = true; result = operation(record.State); } finally { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"EndRead {info} {result} {record.State}"); } detectReentrancy = false; } return result; }))); }
// to avoid code duplication, we call this with exactly one of update1, update2 non-null private Task <TResult> PerformUpdate <TResult>(Func <TState, TResult> update1, Action <TState> update2) { if (detectReentrancy) { throw new LockRecursionException("cannot perform an update operation from within another operation"); } var info = (TransactionInfo)TransactionContext.GetTransactionInfo(); if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"StartWrite {info}"); } if (info.IsReadOnly) { throw new OrleansReadOnlyViolatedException(info.Id); } info.Participants.TryGetValue(thisParticipant, out var recordedaccesses); return(EnterLock <TResult>(info.TransactionId, info.Priority, recordedaccesses, false, new Task <TResult>(() => { // check if we expired while waiting if (!currentGroup.TryGetValue(info.TransactionId, out var record)) { throw new OrleansTransactionLockAcquireTimeoutException(info.TransactionId.ToString()); } // merge the current clock into the transaction time stamp record.Timestamp = MergeAndReadClock(info.TimeStamp); // link to the latest state if (record.State == null) { GetMostRecentState(out record.State, out record.SequenceNumber); } // if this is the first write, make a deep copy of the state if (record.NumberWrites == 0) { record.State = copier.DeepCopy(record.State); record.SequenceNumber++; } if (logger.IsEnabled(LogLevel.Debug)) { logger.Debug($"update-lock write v{record.SequenceNumber} {record.TransactionId} {record.Timestamp:o}"); } // record this write in the transaction info data structure info.RecordWrite(thisParticipant, record.Timestamp); // record this participant as a TM candidate if (info.TMCandidate != thisParticipant) { int batchsize = CountBatchableOperationsAtTopOfCommitQueue(); if (info.TMCandidate == null || batchsize > info.TMBatchSize) { info.TMCandidate = thisParticipant; info.TMBatchSize = batchsize; } } // perform the write TResult result = default(TResult); try { detectReentrancy = true; if (update1 != null) { result = update1(record.State); } else if (update2 != null) { update2(record.State); } return result; } finally { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"EndWrite {info} {result} {record.State}"); } detectReentrancy = false; } } ))); }