public void Add(TransactionRecord <E> entry) { // ensure we have room if (buffer == null) { buffer = new TransactionRecord <E> [defaultCapacity]; } else if (count == buffer.Length) { var newbuffer = new TransactionRecord <E> [buffer.Length * 2]; Array.Copy(buffer, pos, newbuffer, 0, buffer.Length - pos); Array.Copy(buffer, 0, newbuffer, buffer.Length - pos, pos); buffer = newbuffer; pos = 0; } if (count > 0 && buffer[(pos + count - 1) % buffer.Length].Timestamp > entry.Timestamp) { throw new ArgumentException($"elements must be added in timestamp order, but {entry.Timestamp:o} is before {buffer[(pos + count - 1) % buffer.Length].Timestamp:o}", nameof(entry)); } // add the element buffer[(pos + count) % buffer.Length] = entry; count++; }
private async Task ConfirmationTask(TransactionRecord <TState> record) { try { var tasks = new List <Task>(); record.LastConfirmationAttempt = DateTime.UtcNow; foreach (var p in record.WriteParticipants) { if (p != thisParticipant) { tasks.Add(p.Confirm(record.TransactionId, record.Timestamp)); } } await Task.WhenAll(tasks); confirmationTasks.Remove(record.TransactionId); // all prepare records have been removed from all participants. // Now we can remove the commit record. storageBatch.Collect(record.TransactionId); storageWorker.Notify(); } catch (Exception e) { // we are giving up for now. // if pinged or reloaded from storage, we'll try again. logger.Warn(333, $"Could not notify/collect:", e); } }
private void EnqueueCommit(TransactionRecord <TState> record) { try { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"start two-phase-commit {record.TransactionId} {record.Timestamp:o}"); } commitQueue.Add(record); // additional actions for each commit type switch (record.Role) { case CommitRole.ReadOnly: { // no extra actions needed break; } case CommitRole.LocalCommit: { // process prepared messages received ahead of time if (unprocessedPreparedMessages != null && unprocessedPreparedMessages.TryGetValue(record.Timestamp, out var info)) { if (info.Status == TransactionalStatus.Ok) { record.WaitCount -= info.Count; } else { AbortCommits(info.Status, commitQueue.Count - 1); lockWorker.Notify(); } unprocessedPreparedMessages.Remove(record.Timestamp); } break; } case CommitRole.RemoteCommit: { // optimization: can immediately proceed if dependency is implied bool behindRemoteEntryBySameTM = commitQueue.Count >= 2 && commitQueue[commitQueue.Count - 2] is TransactionRecord <TState> rce && rce.Role == CommitRole.RemoteCommit && rce.TransactionManager.Equals(record.TransactionManager); if (record.NumberWrites > 0) { storageBatch.Prepare(record.SequenceNumber, record.TransactionId, record.Timestamp, record.TransactionManager, record.State); } else { storageBatch.Read(record.Timestamp); } storageBatch.FollowUpAction(() => { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"prepared {record.TransactionId} {record.Timestamp:o}"); } record.PrepareIsPersisted = true; if (behindRemoteEntryBySameTM) { // can send prepared message immediately after persisting prepare record record.TransactionManager.Prepared(record.TransactionId, record.Timestamp, thisParticipant, TransactionalStatus.Ok).Ignore(); record.LastSent = DateTime.UtcNow; } }); break; } default: { logger.LogError(777, "internal error: impossible case {CommitRole}", record.Role); throw new NotSupportedException($"{record.Role} is not a supported CommitRole."); } } } catch (Exception e) { logger.Error(666, $"transaction abort due to internal error in {nameof(EnqueueCommit)}: ", e); NotifyOfAbort(record, TransactionalStatus.UnknownException); } }
private void NotifyOfAbort(TransactionRecord <TState> entry, TransactionalStatus status) { switch (entry.Role) { case CommitRole.NotYetDetermined: { // cannot notify anyone. TA will detect broken lock during prepare. break; } case CommitRole.RemoteCommit: { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"aborting RemoteCommitEntry {entry.Timestamp:o} status={status}"); } if (entry.LastSent.HasValue) { return; // cannot abort anymore if we already sent prepare-ok message } entry.TransactionManager.Prepared(entry.TransactionId, entry.Timestamp, thisParticipant, status).Ignore(); break; } case CommitRole.LocalCommit: { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"aborting LocalCommitEntry {entry.Timestamp:o} status={status}"); } // reply to transaction agent entry.PromiseForTA.TrySetResult(status); // tell remote participants foreach (var p in entry.WriteParticipants) { if (p != thisParticipant) { p.Cancel(entry.TransactionId, entry.Timestamp, status).Ignore(); } } break; } case CommitRole.ReadOnly: { if (logger.IsEnabled(LogLevel.Trace)) { logger.Trace($"aborting ReadEntry {entry.Timestamp:o} status={status}"); } // reply to transaction agent entry.PromiseForTA.TrySetResult(status); break; } default: { logger.LogError(777, "internal error: impossible case {CommitRole}", entry.Role); throw new NotSupportedException($"{entry.Role} is not a supported CommitRole."); } } }