internal static byte[] CalculateNextChecksum(SpannerDataReader reader, byte[] currentChecksum, bool readResult) { int size = currentChecksum.Length; size += Protobuf.WellKnownTypes.Value.ForBool(readResult).CalculateSize(); if (readResult) { for (int i = 0; i < reader.FieldCount; i++) { size += reader.GetJsonValue(i).CalculateSize(); } } using var ms = new MemoryStream(size); ms.Write(currentChecksum, 0, currentChecksum.Length); using var cos = new CodedOutputStream(ms); Protobuf.WellKnownTypes.Value.ForBool(readResult).WriteTo(cos); if (readResult) { for (int i = 0; i < reader.FieldCount; i++) { reader.GetJsonValue(i).WriteTo(cos); } } // Flush the protobuf stream so everything is written to the memory stream. cos.Flush(); // Then reset the memory stream to the start so the hash is calculated over all the bytes in the buffer. ms.Position = 0; SHA256 checksum = SHA256.Create(); return(checksum.ComputeHash(ms)); }
internal SpannerDataReaderWithChecksum( SpannerRetriableTransaction transaction, SpannerDataReader spannerDataReader, SpannerCommand command) { Transaction = transaction; _spannerDataReader = spannerDataReader; _spannerCommand = (SpannerCommand)command.Clone(); }
async Task IRetriableStatement.RetryAsync(SpannerRetriableTransaction transaction, CancellationToken cancellationToken, int timeoutSeconds) { _spannerCommand.Transaction = transaction.SpannerTransaction; var reader = await _spannerCommand.ExecuteReaderAsync(cancellationToken); int counter = 0; bool read = true; byte[] newChecksum = new byte[0]; SpannerException newException = null; while (read && counter < _numberOfReadCalls) { try { read = await reader.ReadAsync(cancellationToken); newChecksum = CalculateNextChecksum(reader, newChecksum, read); counter++; } catch (SpannerException e) when(e.ErrorCode == ErrorCode.Aborted) { // Propagate Aborted errors to trigger a new retry. throw; } catch (SpannerException e) { newException = e; counter++; break; } } if (counter == _numberOfReadCalls && newChecksum.SequenceEqual(_currentChecksum) && SpannerRetriableTransaction.SpannerExceptionsEqualForRetry(newException, _firstException)) { // Checksum is ok, we only need to replace the delegate result set if it's still open. if (IsClosed) { reader.Close(); } else { _spannerDataReader = reader; } } else { // The results are not equal, there is an actual concurrent modification, so we cannot // continue the transaction. throw new SpannerAbortedDueToConcurrentModificationException(); } }