async Task DoRandomAtomicCalls() { for (int callNum = 0; callNum < MigrationModel.NUM_CALLS_PER_MACHINE; callNum++) { SortedDictionary <PrimaryKey, DynamicTableEntity> dump = await peekProxy.DumpReferenceTableAsync(); if (PSharpRuntime.Nondeterministic()) { // Query var query = new TableQuery <DynamicTableEntity>(); query.FilterString = ChainTableUtils.CombineFilters( TableQuery.GenerateFilterCondition( TableConstants.PartitionKey, QueryComparisons.Equal, MigrationModel.SINGLE_PARTITION_KEY), TableOperators.And, NondeterministicUserPropertyFilterString()); await RunQueryAtomicAsync(query); } else { // Batch write int batchSize = PSharpRuntime.Nondeterministic() ? 2 : 1; var batch = new TableBatchOperation(); var rowKeyChoices = new List <string> { "0", "1", "2", "3", "4", "5" }; for (int opNum = 0; opNum < batchSize; opNum++) { int opTypeNum = PSharpNondeterminism.Choice(7); int rowKeyI = PSharpNondeterminism.Choice(rowKeyChoices.Count); string rowKey = rowKeyChoices[rowKeyI]; rowKeyChoices.RemoveAt(rowKeyI); // Avoid duplicate in same batch var primaryKey = new PrimaryKey(MigrationModel.SINGLE_PARTITION_KEY, rowKey); string eTag = null; if (opTypeNum >= 1 && opTypeNum <= 3) { DynamicTableEntity existingEntity; int etagTypeNum = PSharpNondeterminism.Choice( dump.TryGetValue(primaryKey, out existingEntity) ? 3 : 2); switch (etagTypeNum) { case 0: eTag = ChainTable2Constants.ETAG_ANY; break; case 1: eTag = "wrong"; break; case 2: eTag = existingEntity.ETag; break; } } DynamicTableEntity entity = new DynamicTableEntity { PartitionKey = MigrationModel.SINGLE_PARTITION_KEY, RowKey = rowKey, ETag = eTag, Properties = new Dictionary <string, EntityProperty> { // Give us something to see on merge. Might help with tracing too! { string.Format("{0}_c{1}_o{2}", machineId.ToString(), callNum, opNum), new EntityProperty(true) }, // Property with 50%/50% distribution for use in filters. { "isHappy", new EntityProperty(PSharpRuntime.Nondeterministic()) } } }; switch (opTypeNum) { case 0: batch.Insert(entity); break; case 1: batch.Replace(entity); break; case 2: batch.Merge(entity); break; case 3: batch.Delete(entity); break; case 4: batch.InsertOrReplace(entity); break; case 5: batch.InsertOrMerge(entity); break; case 6: entity.ETag = ChainTable2Constants.ETAG_DELETE_IF_EXISTS; batch.Delete(entity); break; } } await RunBatchAsync(batch); } } }
async Task ApplyConfigurationAsync(MTableConfiguration newConfig) { using (await LockAsyncBuggable()) { PrimaryKey continuationKey = InternalGetContinuationPrimaryKey(); // ExecuteQueryStreamedAsync validated that mtableQuery has no select or top. TableQuery <MTableEntity> newTableContinuationQuery = (continuationKey == null) ? null : new TableQuery <MTableEntity> { // As in ExecuteQueryAtomicAsync, we have to retrieve all // potential shadowing rows. // XXX: This pays even a bigger penalty for not keeping // conditions on the primary key. But if we were to simply // keep all such conditions, there's a potential to add // more and more continuation filter conditions due to // layering of IChainTable2s, which would lead to some extra // overhead and push us closer to the limit on number of // comparisons. Either try to parse for this or change // ExecuteQueryStreamedAsync to always take a continuation // primary key? FilterString = outer.IsBugEnabled(MTableOptionalBug.QueryStreamedFilterShadowing) ? ChainTableUtils.CombineFilters( ChainTableUtils.GenerateContinuationFilterCondition(continuationKey), TableOperators.And, mtableQuery.FilterString // Could be empty. ) : ChainTableUtils.GenerateContinuationFilterCondition(continuationKey), }; bool justStartedNewStream = false; // Actually, if the query started in state // USE_OLD_HIDE_METADATA, it is API-compliant to continue // returning data from the old table until the migrator starts // deleting it, at which point we switch to the new table. But // some callers may benefit from fresher data, even if it is // never guaranteed, so we go ahead and start the new stream. // XXX: Is this the right decision? if (newConfig.state > TableClientState.USE_OLD_HIDE_METADATA && currentConfig.state <= TableClientState.USE_OLD_HIDE_METADATA && newTableContinuationQuery != null) { newTableStream = await outer.newTable.ExecuteQueryStreamedAsync(newTableContinuationQuery, requestOptions, operationContext); newTableNext = await newTableStream.ReadRowAsync(); justStartedNewStream = true; } if (newConfig.state >= TableClientState.USE_NEW_HIDE_METADATA && currentConfig.state < TableClientState.USE_NEW_HIDE_METADATA) { oldTableStream.Dispose(); oldTableStream = null; oldTableNext = null; // Stop DetermineNextSide from trying to read the old stream. if (!outer.IsBugEnabled(MTableOptionalBug.QueryStreamedBackUpNewStream) && newTableContinuationQuery != null && !justStartedNewStream) { // The new stream could have gotten ahead of the old // stream if rows had not yet been migrated. This // was OK as long as we still planned to read those // rows from the old stream, but now we have to back // up the new stream to where the old stream was. newTableStream.Dispose(); newTableStream = await outer.newTable.ExecuteQueryStreamedAsync(newTableContinuationQuery, requestOptions, operationContext); newTableNext = await newTableStream.ReadRowAsync(); } } if (!outer.IsBugEnabled(MTableOptionalBug.QueryStreamedSaveNewConfig)) { currentConfig = newConfig; } } }