Exemplo n.º 1
0
        /// <summary>
        /// <see cref="ProducerThreadMethod"/> works in parallel with RPM's <see cref="RequestProcessingManager.WriteTo"/>.
        /// RPM supplies empty buffers to be filled with data into <see cref="RequestExecutionContext.BuffersRing"/> and consumes them on the other end.
        /// The data ring has very limited number of buffers.
        /// RPM is limited by network throughput and Producer's speed.
        /// Producer is limited by underlying storage driver, local processing speed and RPM's consumption of complete buffers.
        /// The difference between the two: RPM <see cref="RequestProcessingManager.WriteTo"/> is scheduled for execution by service infrastructure (WCF),
        /// whereas <see cref="DataEngine.ProducerThreadMethod"/> is scheduled by RPM itself, when it invokes <see cref="IDataEngine.BeginExecution"/>.
        /// </summary>
        void ProducerThreadMethod(RequestExecutionContext context)
        {
            PqlEngineSecurityContext.Set(new PqlClientSecurityContext(
                                             context.AuthContext.UserId, "dummy", context.AuthContext.TenantId, context.AuthContext.ContextId));

            var executionPending = true;
            IDriverDataEnumerator sourceEnumerator = null;

            try
            {
                // row number is needed for "rownum()" Pql function
                context.ClauseEvaluationContext.RowNumber = 0;

                // Our production is limited by the network throughput.
                // Production will also be aborted if the destination sink stops accepting.
                // In that case, ConsumingEnumerable will either throw or stop yielding.
                bool havePendingDriverRow = false;
                foreach (var buffer in context.BuffersRing.ConsumeProcessingTasks(context.CancellationTokenSource.Token)
                         )
                {
                    buffer.Cleanup();

                    try
                    {
                        if (executionPending)
                        {
                            executionPending = false;

                            // read network protocol message
                            // parse-compile expressions and collect information for execution plan
                            // generate response headers and fetch data from Redis
                            // this place fails most often, because of Pql compilation or storage driver connectivity failures
                            StartProduction(context, buffer, out sourceEnumerator);

                            if (context.Request.ReturnDataset)
                            {
                                // write response headers BEFORE the query processing is completed
                                // records affected and whatever other stats will be zero
                                Serializer.SerializeWithLengthPrefix(buffer.Stream, context.ResponseHeaders, PrefixStyle.Base128);
                            }
                        }

                        // go through retrieved data
                        havePendingDriverRow = ((DataEngine)context.Engine).WriteItems(
                            context, buffer, sourceEnumerator, havePendingDriverRow);

                        // some consistency checks
                        if (context.Request.ReturnDataset)
                        {
                            if (havePendingDriverRow && buffer.RowsOutput == 0)
                            {
                                throw new Exception("Internal error: should not have pending row when no data is produced");
                            }
                        }
                        else
                        {
                            if (havePendingDriverRow)
                            {
                                throw new Exception("Internal error: should not have pending row when no dataset is requested");
                            }

                            if (buffer.Stream.Length > 0)
                            {
                                throw new Exception("Internal error: should not have written anything to stream when no dataset is requested");
                            }
                        }

                        // time to quit?
                        // no dataset requested, don't have any more data, or enough rows accumulated for requested page of results?
                        if (buffer.RowsOutput == 0 || buffer.IsFailed || !context.Request.ReturnDataset)
                        {
                            if (!context.Request.ReturnDataset)
                            {
                                // if there is no dataset sent, write response headers AFTER processing the query
                                // records affected and whatever other stats are meaningful
                                context.ResponseHeaders.RecordsAffected = context.RecordsAffected;
                                Serializer.SerializeWithLengthPrefix(buffer.Stream, context.ResponseHeaders, PrefixStyle.Base128);
                            }

                            break;
                        }
                    }
                    catch (Exception e)
                    {
                        buffer.Error = e;
                        context.TrySetLastError(e);
                        m_tracer.Exception(e);

                        // this will go to client, and overwrite whatever we managed to put into buffer before failure
                        using (var writer = new PqlErrorDataWriter(1, e, false))
                        {
                            buffer.Stream.SetLength(0);
                            writer.WriteTo(buffer.Stream);
                        }

                        return;
                    }
                    finally
                    {
                        // return the buffer back to the ring in any case
                        context.BuffersRing.ReturnCompletedTask(buffer);
                    }
                }
            }
            catch (OperationCanceledException e)
            {
                context.Cancel(e);
            }
            catch (Exception e)
            {
                if (Environment.HasShutdownStarted)
                {
                    // nobody cares now
                    return;
                }

                var cts = context.CancellationTokenSource;
                if (cts != null && !cts.IsCancellationRequested)
                {
                    m_tracer.Exception(e);
                    context.Cancel(e);
                }
            }
            finally
            {
                var ring = context.BuffersRing;
                if (ring != null)
                {
                    ring.CompleteAddingCompletedTasks();
                }

                if (sourceEnumerator != null)
                {
                    // release driver-level resources & locks
                    sourceEnumerator.Dispose();
                }
            }
        }