Exemple #1
0
 public override Task <IQueryStream <TElement> > ExecuteQueryStreamedAsync <TElement>(TableQuery <TElement> query, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
 {
     CallLogging.LogStart(proxyName, nameof(ExecuteQueryStreamedAsync), query, requestOptions, operationContext);
     return(LogOutcomeAsync <IQueryStream <TElement> >(async() => {
         IQueryStream <TElement> origStream = await original.ExecuteQueryStreamedAsync(query, requestOptions, operationContext);
         return new QueryStreamLoggingProxy <TElement>(origStream, string.Format("{0} QueryStream {1}", proxyName, queryStreamCount++));
     },
                                                       (outcome) => CallLogging.LogEnd(proxyName, nameof(ExecuteQueryStreamedAsync), outcome, query, requestOptions, operationContext)));
 }
Exemple #2
0
        async Task CopyAsync(TableRequestOptions requestOptions, OperationContext operationContext)
        {
            // Query all the entities!
            IQueryStream <MTableEntity> oldTableStream = await oldTable.ExecuteQueryStreamedAsync(
                new TableQuery <MTableEntity>(), requestOptions, operationContext);

            MTableEntity oldEntity;
            string       previousPartitionKey = null;

            while ((oldEntity = await oldTableStream.ReadRowAsync()) != null)
            {
                if (RowKeyIsInternal(oldEntity.RowKey))
                {
                    continue;
                }

                if (oldEntity.PartitionKey != previousPartitionKey)
                {
                    previousPartitionKey = oldEntity.PartitionKey;
                    await EnsurePartitionSwitchedAsync(oldEntity.PartitionKey, requestOptions, operationContext);

                    if (IsBugEnabled(MTableOptionalBug.InsertBehindMigrator))
                    {
                        // More reasonable formulation of this bug.  If we're going
                        // to allow CopyAsync while some clients are still writing
                        // to the old table, give the developer credit for realizing
                        // that the stream might be stale by the time the partition
                        // is switched.
                        oldTableStream = await oldTable.ExecuteQueryStreamedAsync(
                            new TableQuery <MTableEntity> {
                            FilterString = TableQuery.GenerateFilterCondition(
                                TableConstants.PartitionKey,
                                QueryComparisons.GreaterThanOrEqual,
                                oldEntity.PartitionKey)
                        }, requestOptions, operationContext);

                        continue;
                    }
                }

                // Can oldEntity be stale by the time we EnsurePartitionSwitchedAsync?
                // Currently no, because we don't start the copy until all clients have
                // entered PREFER_NEW state, meaning that all writes go to the new
                // table.  When we implement the Kstart/Kdone optimization (or similar),
                // then clients will be able to write to the old table in parallel with
                // our scan and we'll need to rescan after EnsurePartitionSwitchedAsync.

                // Should we also stream from the new table and copy only the
                // entities not already copied?
                await TryCopyEntityToNewTableAsync(oldEntity, requestOptions, operationContext);
            }
        }
Exemple #3
0
        public override async Task <IQueryStream <TElement> > ExecuteQueryStreamedAsync <TElement>(TableQuery <TElement> query, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
        {
            // Philosophically, maybe this proxy-making belongs on the host
            // side, but that would require a second custom wrapper because we
            // still need the custom wrapper on the caller side to do the
            // different event types.
            IQueryStream <TElement> remoteStream = await nonannotatableCallProxy.ExecuteQueryStreamedAsync(
                query, requestOptions, operationContext);

            return(new QueryStreamPSharpProxy <TElement>(
                       PSharpRealProxy.MakeTransparentProxy(callerMachineId, hostMachineId, remoteStream,
                                                            null, () => new TableNonannotatableCallEvent())));
        }
Exemple #4
0
        public override async Task <IQueryStream <TElement> > ExecuteQueryStreamedAsync <TElement>(TableQuery <TElement> query, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
        {
            //Trace.TraceInformation("{0} calling {1}.ExecuteQueryStreamedAsync({2})", callerMachineId, debugName, BetterComparer.ToString(query));
            // Philosophically, maybe this proxy-making belongs on the host
            // side, but that would require a second custom wrapper because we
            // still need the custom wrapper on the caller side to do the
            // different event types.
            IQueryStream <TElement> remoteStream = await plainEventProxy.ExecuteQueryStreamedAsync(
                query, requestOptions, operationContext);

            return(new QueryStreamPSharpProxy <TElement>(
                       PSharpRealProxy.MakeTransparentProxy(callerMachineId, hostMachineId, remoteStream,
                                                            string.Format("<{0} QueryStream>", debugName), () => new GenericDispatchableEvent())));
        }
Exemple #5
0
        internal async Task RunQueryStreamedAsync(TableQuery <DynamicTableEntity> query)
        {
            int startRevision = await peekProxy.GetReferenceTableRevisionAsync();

            FilterExpression filterExpr = ChainTableUtils.ParseFilterString(query.FilterString);

            Console.WriteLine("{0} starting streaming query: {1}", machineId, query);
            using (IQueryStream <DynamicTableEntity> stream = await migratingTable.ExecuteQueryStreamedAsync(query))
            {
                PrimaryKey lastKey = ChainTableUtils.FirstValidPrimaryKey;
                for (;;)
                {
                    PrimaryKey returnedContinuationKey = await stream.GetContinuationPrimaryKeyAsync();

                    PSharpRuntime.Assert(returnedContinuationKey == null || returnedContinuationKey.CompareTo(lastKey) >= 0,
                                         "{0}: query stream continuation key is {1}, expected >= {2}",
                                         machineId, returnedContinuationKey, lastKey);

                    DynamicTableEntity row = await stream.ReadRowAsync();  // may be null, meaning end of stream

                    // Must be after ReadRowAsync, otherwise additional rows could become valid
                    // due to a mutation between GetValidStreamReadRows and ReadRowAsync and
                    // we would falsely report a bug if ReadRowAsync returns one of those rows.
                    List <DynamicTableEntity> validRows = await peekProxy.GetValidStreamReadRows(startRevision, filterExpr, lastKey);

                    // Three cheers for automatic use of covariance in overload resolution!
                    PSharpRuntime.Assert(validRows.Contains(row, BetterComparer.Instance),
                                         "{0} query stream returned {1}, which is not one of the valid rows: {2}",
                                         machineId, BetterComparer.ToString(row), BetterComparer.ToString(validRows));
                    Console.WriteLine("{0} query stream returned row {1}, which is valid", machineId, BetterComparer.ToString(row));

                    if (row == null)
                    {
                        // Any returnedContinuationKey (including null) is less or equal to a row of null.
                        break;
                    }
                    else
                    {
                        PSharpRuntime.Assert(returnedContinuationKey != null && returnedContinuationKey.CompareTo(row.GetPrimaryKey()) <= 0,
                                             "{0}: query stream continuation key is {1}, expected <= {2}",
                                             machineId, returnedContinuationKey, row.GetPrimaryKey());
                        lastKey = ChainTableUtils.NextValidPrimaryKeyAfter(row.GetPrimaryKey());
                    }
                }
            }
            Console.WriteLine("{0} finished streaming query", machineId);
        }
            internal async Task StartAsync()
            {
                using (await LockAsyncBuggable())
                {
                    configSubscription = outer.configService.Subscribe(new Subscriber(this), out currentConfig);
                    if (currentConfig.state < TableClientState.USE_NEW_WITH_TOMBSTONES)
                    {
                        oldTableStream = await outer.oldTable.ExecuteQueryStreamedAsync(mtableQuery, requestOptions, operationContext);

                        oldTableNext = await oldTableStream.ReadRowAsync();
                    }
                    if (currentConfig.state > TableClientState.USE_OLD_HIDE_METADATA)
                    {
                        TableQuery <MTableEntity> newTableQuery =
                            outer.IsBugEnabled(MTableOptionalBug.QueryStreamedFilterShadowing) ? mtableQuery
                            // Yes, match everything (!).  See newTableContinuationQuery in ApplyConfigurationAsync.
                            : new TableQuery <MTableEntity>();
                        newTableStream = await outer.newTable.ExecuteQueryStreamedAsync(newTableQuery, requestOptions, operationContext);

                        newTableNext = await newTableStream.ReadRowAsync();
                    }
                }
            }
Exemple #7
0
        async Task DoQueryStreamed()
        {
            int startRevision = await peekProxy.GetReferenceTableRevisionAsync();

            // XXX: Test the filtering?
            var query = new TableQuery <DynamicTableEntity>();

            using (IQueryStream <DynamicTableEntity> stream = await migratingTable.ExecuteQueryStreamedAsync(query))
            {
                PrimaryKey continuationKey = await stream.GetContinuationPrimaryKeyAsync();

                await peekProxy.ValidateQueryStreamGapAsync(startRevision, null, continuationKey);

                do
                {
                    DynamicTableEntity row = await stream.ReadRowAsync();

                    PrimaryKey newContinuationKey = await stream.GetContinuationPrimaryKeyAsync();

                    if (row == null)
                    {
                        PSharpRuntime.Assert(newContinuationKey == null);
                        await peekProxy.ValidateQueryStreamGapAsync(startRevision, continuationKey, null);
                    }
                    else
                    {
                        await peekProxy.ValidateQueryStreamGapAsync(startRevision, continuationKey, row.GetPrimaryKey());

                        await peekProxy.ValidateQueryStreamRowAsync(startRevision, row);

                        await peekProxy.ValidateQueryStreamGapAsync(startRevision,
                                                                    ChainTableUtils.NextValidPrimaryKeyAfter(row.GetPrimaryKey()), newContinuationKey);
                    }
                    continuationKey = newContinuationKey;
                } while (continuationKey != null);
            }
        }
Exemple #8
0
 internal QueryStreamPSharpProxy(IQueryStream <TElement> plainProxy)
 {
     this.plainProxy = plainProxy;
 }
Exemple #9
0
        async Task CleanupAsync(TableRequestOptions requestOptions, OperationContext operationContext)
        {
            // Clean up MTable-specific data from new table.
            // Future: Query only ones with properties we need to clean up.
            IQueryStream <MTableEntity> newTableStream = await newTable.ExecuteQueryStreamedAsync(
                new TableQuery <MTableEntity>(), requestOptions, operationContext);

            MTableEntity newEntity;

            while ((newEntity = await newTableStream.ReadRowAsync()) != null)
            {
                // XXX: Consider factoring out this "query and retry" pattern
                // into a separate method.
Attempt:
                TableOperation cleanupOp = newEntity.deleted ? TableOperation.Delete(newEntity)
                    : TableOperation.Replace(newEntity.Export <DynamicTableEntity>());
                TableResult cleanupResult;
                try
                {
                    cleanupResult = await newTable.ExecuteAsync(cleanupOp, requestOptions, operationContext);
                }
                catch (StorageException ex)
                {
                    if (ex.GetHttpStatusCode() == HttpStatusCode.NotFound)
                    {
                        // Someone else deleted it concurrently.  Nothing to do.
                        await monitor.AnnotateLastBackendCallAsync();

                        continue;
                    }
                    else if (ex.GetHttpStatusCode() == HttpStatusCode.PreconditionFailed)
                    {
                        await monitor.AnnotateLastBackendCallAsync();

                        // Unfortunately we can't assume that anyone who concurrently modifies
                        // the row while the table is in state USE_NEW_HIDE_METADATA will
                        // clean it up, because of InsertOrMerge.  (Consider redesign?)

                        // Re-retrieve row.
                        TableResult retrieveResult = await newTable.ExecuteAsync(
                            TableOperation.Retrieve <MTableEntity>(newEntity.PartitionKey, newEntity.RowKey),
                            requestOptions, operationContext);

                        await monitor.AnnotateLastBackendCallAsync();

                        if ((HttpStatusCode)retrieveResult.HttpStatusCode == HttpStatusCode.NotFound)
                        {
                            continue;
                        }
                        else
                        {
                            newEntity = (MTableEntity)retrieveResult.Result;
                            goto Attempt;
                        }
                    }
                    else
                    {
                        throw ChainTableUtils.GenerateInternalException(ex);
                    }
                }
                await monitor.AnnotateLastBackendCallAsync(
                    spuriousETagChanges :
                    cleanupResult.Etag == null?null : new List <SpuriousETagChange> {
                    new SpuriousETagChange(newEntity.PartitionKey, newEntity.RowKey, cleanupResult.Etag)
                });
            }

            // Delete everything from old table!  No one should be modifying it concurrently.
            IQueryStream <MTableEntity> oldTableStream = await oldTable.ExecuteQueryStreamedAsync(
                new TableQuery <MTableEntity>(), requestOptions, operationContext);

            MTableEntity oldEntity;

            while ((oldEntity = await oldTableStream.ReadRowAsync()) != null)
            {
                await oldTable.ExecuteAsync(TableOperation.Delete(oldEntity), requestOptions, operationContext);

                await monitor.AnnotateLastBackendCallAsync();
            }
        }
            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;
                    }
                }
            }
Exemple #11
0
 internal QueryStreamLoggingProxy(IQueryStream <TElement> original, string proxyName)
 {
     this.original  = original;
     this.proxyName = proxyName;
 }
Exemple #12
0
 internal QueryStreamPSharpProxy(IQueryStream <TElement> nonannotatableProxy)
 {
     this.nonannotatableProxy = nonannotatableProxy;
 }
Exemple #13
0
        Task WalkTableInParallel(IChainTable2 table, TableRequestOptions requestOptions, OperationContext operationContext,
                                 Func <MTableEntity, Task <IQueryStream <MTableEntity> > > rowCallbackAsync)
        {
            return(Task.WhenAll(Enumerable.Range(0, 16).Select(hexNumber => Task.Run(async() =>
            {
                var query = new TableQuery <MTableEntity>();

                if (hexNumber == 0)
                {
                    query.FilterString = TableQuery.GenerateFilterCondition(
                        "PartitionKey",
                        QueryComparisons.LessThanOrEqual,
                        (hexNumber + 1).ToString("x"));
                }
                else if (hexNumber == 15)
                {
                    query.FilterString = TableQuery.GenerateFilterCondition(
                        "PartitionKey",
                        QueryComparisons.GreaterThanOrEqual,
                        hexNumber.ToString("x"));
                }
                else
                {
                    query.FilterString = TableQuery.CombineFilters(
                        TableQuery.GenerateFilterCondition(
                            "PartitionKey",
                            QueryComparisons.GreaterThanOrEqual,
                            hexNumber.ToString("x")),
                        TableOperators.And,
                        TableQuery.GenerateFilterCondition(
                            "PartitionKey",
                            QueryComparisons.LessThanOrEqual,
                            (hexNumber + 1).ToString("x")));
                }

                try
                {
                    IQueryStream <MTableEntity> tableStream = await table.ExecuteQueryStreamedAsync(
                        query, requestOptions, operationContext);

                    MTableEntity entity;
                    while ((entity = await tableStream.ReadRowAsync()) != null)
                    {
                        IQueryStream <MTableEntity> newTableStream = await rowCallbackAsync(entity);
                        if (newTableStream != null)
                        {
                            tableStream = newTableStream;
                        }
                    }
                }
                catch (Exception e)
                {
                    while (e is AggregateException)
                    {
                        e = ((AggregateException)(e)).InnerException;
                    }

                    if (!(e is StorageException && ((StorageException)e).RequestInformation.HttpStatusCode == 404))
                    {
                        throw;
                    }
                }
            }))));
        }