/// <summary> /// Writes an <see cref="IOperation" /> to the log. /// </summary> /// <param name="resource">The parent <see cref="ITransactedResource" /> responsible for serializing the operation.</param> /// <param name="operation">The operation to be written.</param> /// <remarks> /// <note> /// This property can only be called if the operation log is in <see cref="OperationLogMode.Undo" /> /// mode. /// </note> /// </remarks> /// <exception cref="TransactionException">Thrown if the log isn't open or if the mode isn't <see cref="OperationLogMode.Undo" />.</exception> public void Write(ITransactedResource resource, IOperation operation) { long cbPos; long cb; using (TimedLock.Lock(this)) { if (file == null) { throw new TransactionException(ClosedMsg); } if (mode != OperationLogMode.Undo) { throw new TransactionException("Write is available only when the log is in UNDO mode."); } file.WriteInt32(Magic); // Magic number cbPos = file.Position; // Length place holder file.WriteInt32(0); file.WriteString32(operation.Description); // Description resource.WriteOperation(file, operation); // Serialized operation cb = file.Position - cbPos - 4; if (cb < 0 || cb > int.MaxValue) { throw new TransactionException("ITransactedResource.WriteOperation() returned with an unexpected stream position."); } file.Position = cbPos; file.WriteInt32((int)cb); file.Position = file.Length; file.Flush(); } }
/// <summary> /// Rolls back the current transaction. /// </summary> /// <exception cref="TransactionException">Thrown if the stack is empty.</exception> internal void Rollback() { Transaction transaction; using (TimedLock.Lock(syncLock)) { if (base.Count == 0) { throw new TransactionException(EmptyMsg); } transaction = base.Pop(); if (base.Count == 0) { manager.Trace(0, "Begin rollback BASE", "ID=" + id.ToString()); } else { manager.Trace(0, string.Format("Begin rollback NESTED [{0}]", base.Count), "ID=" + id.ToString()); } // Submit the operations being undone back to the // resource in the reverse order that they were // originally performed. ITransactedResource resource = manager.Resource; UpdateContext context = new UpdateContext(manager, false, false, true, ID); List <ILogPosition> positions; positions = operationLog.GetPositionsTo(transaction.Position); if (resource.BeginUndo(context)) { for (int i = 0; i < positions.Count; i++) { var operation = operationLog.Read(manager.Resource, positions[i]); manager.Trace(0, "Undo: " + operation.Description, "ID=" + id.ToString()); resource.Undo(context, operation); } } resource.EndUndo(context); if (base.Count == 0) { manager.EndTransaction(this); manager.Trace(0, "End rollback BASE", "ID=" + id.ToString()); } else { operationLog.Truncate(transaction.Position); manager.Trace(0, string.Format("End rollback NESTED [{0}]", base.Count), "ID=" + id.ToString()); } } }
/// <summary> /// Reads the operation from the specified position in the log. /// </summary> /// <param name="resource">The parent <see cref="ITransactedResource" /> responsible for deserializing the operation.</param> /// <param name="position">See the <see cref="ILogPosition" />.</param> /// <returns>The <see cref="IOperation" /> read from the log.</returns> /// <exception cref="TransactionException">Thrown if the log is not open.</exception> public IOperation Read(ITransactedResource resource, ILogPosition position) { using (TimedLock.Lock(syncLock)) { if (!isOpen) { throw new TransactionException(NotOpenMsg); } return(operations[((MemoryLogPosition)position).Index]); } }
/// <summary> /// Commits the current transaction. If this is the base transaction then /// the operation will be commited to the <see cref="ITransactedResource" />. /// </summary> /// <exception cref="TransactionException">Thrown if the stack is empty.</exception> internal void Commit() { Transaction transaction; using (TimedLock.Lock(syncLock)) { if (base.Count == 0) { throw new TransactionException(EmptyMsg); } transaction = base.Pop(); if (base.Count == 0) { manager.Trace(0, "Begin commit BASE", "ID=" + id.ToString()); // We're committing the base transaction so play the redo log // to the resource and then delete the log when we're finished. ITransactedResource resource = manager.Resource; UpdateContext context = new UpdateContext(manager, false, true, false, ID); List <ILogPosition> positions; positions = operationLog.GetPositions(false); if (resource.BeginRedo(context)) { for (int i = 0; i < positions.Count; i++) { var operation = operationLog.Read(manager.Resource, positions[i]); manager.Trace(0, "Redo: " + operation.Description, "ID=" + id.ToString()); resource.Redo(context, operation); } } resource.EndRedo(context); manager.EndTransaction(this); manager.Trace(0, "End commit BASE", "ID=" + id.ToString()); } else { manager.Trace(0, string.Format("Commit NESTED [{0}]", base.Count), "ID=" + id.ToString()); } } }
/// <summary> /// Writes an <see cref="IOperation" /> to the log. /// </summary> /// <param name="resource">The parent <see cref="ITransactedResource" /> responsible for serializing the operation.</param> /// <param name="operation">The operation to be written.</param> /// <remarks> /// <note> /// This property can only be called if the operation log is in <see cref="OperationLogMode.Undo" /> /// mode. /// </note> /// </remarks> /// <exception cref="TransactionException">Thrown if the log isn't open or if the mode isn't <see cref="OperationLogMode.Undo" />.</exception> public void Write(ITransactedResource resource, IOperation operation) { using (TimedLock.Lock(syncLock)) { if (!isOpen) { throw new TransactionException(NotOpenMsg); } if (mode != OperationLogMode.Undo) { throw new TransactionException("Write is available only when the log is in UNDO mode."); } operations.Add(operation); } }
/// <summary> /// Starts the transaction manager. /// </summary> /// <param name="resource">The <see cref="ITransactedResource" /> to be managed.</param> /// <param name="log">The unopened <see cref="ITransactionLog" /> implementation to be used.</param> /// <param name="recoverCorrupt">Pass as <c>true</c> if recovery of corrupt transaction logs should be attempted.</param> public void Start(ITransactedResource resource, ITransactionLog log, bool recoverCorrupt) { using (TimedLock.Lock(this)) { if (running) { throw new TransactionException("Transaction manager has already started for [{0}].", resource.Name); } switch (log.Open(this)) { case LogStatus.Ready: break; case LogStatus.Recover: this.resource = resource; this.log = log; Recover(); break; case LogStatus.Corrupt: if (!recoverCorrupt) { throw new TransactionException("Transaction log for [{0]] is corrupt.", resource.Name); } SysLog.LogWarning("Corrupt transaction log detected for [{0}]. Best efforts are being taken to recover.", resource.Name); Recover(); break; } this.running = true; this.resource = resource; this.log = log; this.transactions = new Dictionary <Guid, BaseTransaction>(); this.threadTransMap = new Dictionary <int, BaseTransaction>(); } }
/// <summary> /// Reads the operation from the specified position in the log. /// </summary> /// <param name="resource">The parent <see cref="ITransactedResource" /> responsible for deserializing the operation.</param> /// <param name="position">See the <see cref="ILogPosition" />.</param> /// <returns>The <see cref="IOperation" /> read from the log.</returns> public IOperation Read(ITransactedResource resource, ILogPosition position) { int cb; long recEndPos; string description; IOperation operation; using (TimedLock.Lock(this)) { if (file == null) { throw new TransactionException(ClosedMsg); } file.Position = ((FileLogPosition)position).Position; if (file.ReadInt32() != Magic) { throw new TransactionException(CorruptMsg); } cb = file.ReadInt32(); if (cb <= 0 || cb + file.Position > file.Length) { throw new TransactionException(CorruptMsg); } recEndPos = file.Position + cb; description = file.ReadString32(); operation = resource.ReadOperation(file); if (file.Position != recEndPos) { SysLog.LogWarning("ITransactedResource.ReadOperation() returned with an unexpected stream position."); file.Position = recEndPos; } return(operation); } }