public CopyStateStream( long upToSequenceNumber, IOperationDataStream copyContext, ProgressVector localProgress, LogFile operationLog, IReplicationNotifier replicationNotifier, Logger logger, Serializer serializer) { this.upToSequenceNumber = upToSequenceNumber; this.copyContext = copyContext; this.localProgress = localProgress; this.logger = logger; this.serializer = serializer; this.log = operationLog; this.replicationNotifier = replicationNotifier; this.records = operationLog.GetAllRecords().GetEnumerator(); }
private async Task <ProgressVector> GetRemoteReplicaProgress() { var result = new ProgressVector(); do { var copyContextCancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token; var context = await this.copyContext.GetNextAsync(copyContextCancellationToken); if (context == null) { break; } foreach (var item in context) { result.Update(this.serializer.Deserialize <ProgressIndicator>(item)); } }while (true); return(result); }
public CopyContextStream(ProgressVector progressVector, Logger logger, Serializer serializer) { this.logger = logger; this.serializer = serializer; this.progressVector = progressVector.GetEnumerator(); }
public async Task<OperationData> GetNextAsync(CancellationToken cancellationToken) { // Get the progress of the remote replica. if (this.catchUpProgressVector == null) { var remoteProgress = await this.GetRemoteReplicaProgress(); this.catchUpProgressVector = new ProgressVector(this.localProgress.Excluding(remoteProgress)); this.logger.Log( "CopyStateStream:\n" + $"Local progress: {this.localProgress} (& up to LSN {this.upToSequenceNumber})\n" + $"Remote progress: {remoteProgress}\n" + $"Delta Progress: {this.catchUpProgressVector}"); } // Nothing to copy. if (this.upToSequenceNumber == 0) { return null; } // Find the first entry which needs to be replicated var result = default(OperationData); var totalSize = 0; // Wait for the required records to become available. this.hasRecord = this.records.MoveNext(); while (this.hasRecord) { var rawRecord = this.records.Current; var record = this.serializer.Deserialize<Record>(rawRecord); var operation = record as OperationCommittedRecord; if (operation == null) { this.logger.Log( $"CopyStateStream: {record} is not an {nameof(OperationCommittedRecord)}, it is an {record.GetType()}."); this.hasRecord = this.records.MoveNext(); continue; } var currentVersion = operation.Version; var currentLsn = currentVersion.LogSequenceNumber; if (currentLsn > this.upToSequenceNumber) { break; } // Only return records which were actually committed. if (!this.catchUpProgressVector.IncludesVersionInPreviousEpoch(currentVersion) && this.catchUpProgressVector.Current.Epoch != currentVersion.Epoch) { #warning we need a bucket load of tests around this to motivate correctness. How is the 'zero' epoch handled? Does such a thing even exist? /*this.logger.Log( $"CopyStateStream: {currentVersion} is not in the catchup vector or current epoch ({this.catchUpProgressVector.Current.Epoch.ToDisplayString()})");*/ this.hasRecord = this.records.MoveNext(); continue; } if (result == null) { this.lowestLsn = currentLsn; } this.highestLsn = this.highestLsn > currentLsn ? this.highestLsn : currentLsn; // Copy the payload into the result. var data = this.serializer.Serialize(record); if (result == null) { result = new OperationData(data); } else { result.Add(data); } this.hasRecord = this.records.MoveNext(); // If a sequence number which has not yet been committed has been requested, wait for that before continuing. if (!this.hasRecord && this.highestLsn != this.upToSequenceNumber) { await this.WaitForCommit(rawRecord.SequenceNumber, cancellationToken); } // Send only a certain amount of data at a time. totalSize += data.Count; if (totalSize > ResultCutoffLength) { break; } } this.logger.Log( result == null ? "Completed copying state" : $"CopyStateStream.GetNextAsync returning {result.Count} records, from LSN {this.lowestLsn} to {this.highestLsn}"); return result; }
private async Task<ProgressVector> GetRemoteReplicaProgress() { var result = new ProgressVector(); do { var copyContextCancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token; var context = await this.copyContext.GetNextAsync(copyContextCancellationToken); if (context == null) { break; } foreach (var item in context) { result.Update(this.serializer.Deserialize<ProgressIndicator>(item)); } } while (true); return result; }
public async Task <OperationData> GetNextAsync(CancellationToken cancellationToken) { // Get the progress of the remote replica. if (this.catchUpProgressVector == null) { var remoteProgress = await this.GetRemoteReplicaProgress(); this.catchUpProgressVector = new ProgressVector(this.localProgress.Excluding(remoteProgress)); this.logger.Log( "CopyStateStream:\n" + $"Local progress: {this.localProgress} (& up to LSN {this.upToSequenceNumber})\n" + $"Remote progress: {remoteProgress}\n" + $"Delta Progress: {this.catchUpProgressVector}"); } // Nothing to copy. if (this.upToSequenceNumber == 0) { return(null); } // Find the first entry which needs to be replicated var result = default(OperationData); var totalSize = 0; // Wait for the required records to become available. this.hasRecord = this.records.MoveNext(); while (this.hasRecord) { var rawRecord = this.records.Current; var record = this.serializer.Deserialize <Record>(rawRecord); var operation = record as OperationCommittedRecord; if (operation == null) { this.logger.Log( $"CopyStateStream: {record} is not an {nameof(OperationCommittedRecord)}, it is an {record.GetType()}."); this.hasRecord = this.records.MoveNext(); continue; } var currentVersion = operation.Version; var currentLsn = currentVersion.LogSequenceNumber; if (currentLsn > this.upToSequenceNumber) { break; } // Only return records which were actually committed. if (!this.catchUpProgressVector.IncludesVersionInPreviousEpoch(currentVersion) && this.catchUpProgressVector.Current.Epoch != currentVersion.Epoch) { #warning we need a bucket load of tests around this to motivate correctness. How is the 'zero' epoch handled? Does such a thing even exist? /*this.logger.Log( * $"CopyStateStream: {currentVersion} is not in the catchup vector or current epoch ({this.catchUpProgressVector.Current.Epoch.ToDisplayString()})");*/ this.hasRecord = this.records.MoveNext(); continue; } if (result == null) { this.lowestLsn = currentLsn; } this.highestLsn = this.highestLsn > currentLsn ? this.highestLsn : currentLsn; // Copy the payload into the result. var data = this.serializer.Serialize(record); if (result == null) { result = new OperationData(data); } else { result.Add(data); } this.hasRecord = this.records.MoveNext(); // If a sequence number which has not yet been committed has been requested, wait for that before continuing. if (!this.hasRecord && this.highestLsn != this.upToSequenceNumber) { await this.WaitForCommit(rawRecord.SequenceNumber, cancellationToken); } // Send only a certain amount of data at a time. totalSize += data.Count; if (totalSize > ResultCutoffLength) { break; } } this.logger.Log( result == null ? "Completed copying state" : $"CopyStateStream.GetNextAsync returning {result.Count} records, from LSN {this.lowestLsn} to {this.highestLsn}"); return(result); }