async Task RunCallAsync(TableCall originalCall, MirrorTableCall referenceCall) { // TODO: All assertions should show what the call was. // XXX: We currently have no way to detect incorrect interleaving of // backend calls and AnnotateLastOutgoingCall here. Most incorrect // interleavings will cause an error on the TablesMachine, but some // may go undetected. // For now, we're not doing streaming queries at all, so we don't have // to worry about how to handle them. currentReferenceCall = referenceCall; object actualOutcome = await Catching <StorageException> .Task(originalCall(migratingTable)); // Verify that successfulBatchResult was correct if specified. // (Ideally, we'd also catch if it isn't specified when it should // be, but that's less of a risk as it will likely cause ETag // mismatches anyway.) if (successfulBatchResult != null) { var successfulBatchOutcome = new Outcome <object, StorageException>(successfulBatchResult); PSharpRuntime.Assert(BetterComparer.Instance.Equals(successfulBatchOutcome, actualOutcome), "{0} incorrect successfulBatchResult:\n{1}\nExpected:\n{2}\n", machineId, BetterComparer.ToString(successfulBatchOutcome), BetterComparer.ToString(actualOutcome)); } PSharpRuntime.Assert(currentReferenceOutcome != null, "The call completed without reporting a linearization point."); PSharpRuntime.Assert(BetterComparer.Instance.Equals(actualOutcome, currentReferenceOutcome), "{0} call outcome:\n{1}\nExpected:\n{2}\n", machineId, BetterComparer.ToString(actualOutcome), BetterComparer.ToString(currentReferenceOutcome)); // Reset fields currentReferenceCall = null; successfulBatchResult = null; currentReferenceOutcome = null; }
internal async Task RunQueryAtomicAsync(TableQuery <DynamicTableEntity> query) { // async/await pair needed to upcast the return value to object. TableCall originalCall = async table => await table.ExecuteQueryAtomicAsync(query); MirrorTableCall referenceCall = async referenceTable => await referenceTable.ExecuteQueryAtomicAsync(query); Console.WriteLine("{0} starting atomic query: {1}", machineId, query); await RunCallAsync(originalCall, referenceCall); }
internal async Task RunBatchAsync(TableBatchOperation batch) { TableBatchOperation batchCopy = ChainTableUtils.CopyBatch <DynamicTableEntity>(batch); TableCall originalCall = async table => await table.ExecuteBatchAsync(batch); MirrorTableCall referenceCall = async referenceTable => await referenceTable.ExecuteMirrorBatchAsync(batchCopy, successfulBatchResult); Console.WriteLine("{0} starting batch: {1}", machineId, BetterComparer.ToString(batch)); await RunCallAsync(originalCall, referenceCall); }
// These two should do the same thing. Which do we prefer? /* * Task<object> ITablesMachineAnnotation.AnnotateLastOutgoingCallAsync(MirrorTableCall referenceCall) * { * if (referenceCall == null) * return Task.FromResult((object)null); * return referenceCall(referenceTable); * } */ async Task <object> ITablesMachineAnnotation.AnnotateLastBackendCallAsync( MirrorTableCall referenceCall, IList <SpuriousETagChange> spuriousETagChanges) { if (spuriousETagChanges == null) { spuriousETagChanges = new List <SpuriousETagChange>(); } if (referenceCall == null) { if (spuriousETagChanges.Count > 0) { var batch = new TableBatchOperation(); var originalResponse = new List <TableResult>(); foreach (SpuriousETagChange change in spuriousETagChanges) { batch.Merge(new DynamicTableEntity { PartitionKey = change.partitionKey, RowKey = change.rowKey, ETag = ChainTable2Constants.ETAG_ANY }); originalResponse.Add(new TableResult { Etag = change.newETag }); } try { await referenceTable.ExecuteMirrorBatchAsync(batch, originalResponse, null, null); } catch (StorageException ex) { // Make sure this doesn't get swallowed by a generic StorageException catch block. throw new InvalidOperationException("Invalid spurious ETag change annotation.", ex); } } return(null); } else { if (spuriousETagChanges.Count > 0) { throw new ArgumentException("spuriousETagChanges currently not allowed with a reference call"); } return(await referenceCall(referenceTable)); } }
// This method does not log what the call is, since there's no way to // know what's inside the delegates. Use RunBatchAsync or RunQueryAtomicAsync. async Task RunCallAsync(TableCall originalCall, MirrorTableCall referenceCall) { // TODO: All assertions should show what the call was. // XXX: We currently have no way to detect incorrect interleaving of // backend calls and AnnotateLastOutgoingCall here. Most incorrect // interleavings will cause an error on the TablesMachine, but some // may go undetected. // - FIXME: A missing annotation will cause all machines to become blocked, and // P# considers that a success! To fix that, we need to enable liveness checking. currentReferenceCall = referenceCall; object actualOutcome = await Catching <StorageException> .RunAsync(() => originalCall(migratingTable)); // Verify that successfulBatchResult was correct if specified. // (Ideally, we'd also catch if it isn't specified when it should // be, but that's less of a risk as it will likely cause ETag // mismatches anyway.) if (successfulBatchResult != null) { var successfulBatchOutcome = new Outcome <object, StorageException>(successfulBatchResult); PSharpRuntime.Assert(BetterComparer.Instance.Equals(successfulBatchOutcome, actualOutcome), "{0} incorrect successfulBatchResult:\n{1}\nExpected:\n{2}\n", machineId, BetterComparer.ToString(successfulBatchOutcome), BetterComparer.ToString(actualOutcome)); } PSharpRuntime.Assert(currentReferenceOutcome != null, "{0}: The call completed without reporting a linearization point.", machineId); PSharpRuntime.Assert(BetterComparer.Instance.Equals(actualOutcome, currentReferenceOutcome), "{0} table call outcome is incorrect:\n{1}\nExpected:\n{2}\n", machineId, BetterComparer.ToString(actualOutcome), BetterComparer.ToString(currentReferenceOutcome)); Console.WriteLine("{0} table call outcome is correct:\n{1}", machineId, BetterComparer.ToString(actualOutcome)); // Reset fields currentReferenceCall = null; successfulBatchResult = null; currentReferenceOutcome = null; }