public ExtendedOrchestrationWorkItem(
     string name,
     OrchestrationInstance instance,
     EventPayloadMap eventPayloadMap)
 {
     this.Name                 = name;
     this.Instance             = instance;
     this.EventPayloadMappings = eventPayloadMap;
 }
        public static SqlParameter AddTaskEventsParameter(
            this SqlParameterCollection commandParameters,
            string paramName,
            IList <TaskMessage> outboundMessages,
            EventPayloadMap eventPayloadMap)
        {
            SqlParameter param = commandParameters.Add(paramName, SqlDbType.Structured);

            param.TypeName = SqlTypeName;
            param.Value    = ToTaskMessagesParameter(outboundMessages, eventPayloadMap);
            return(param);
        }
예제 #3
0
        public static SqlParameter AddHistoryEventsParameter(
            this SqlParameterCollection commandParameters,
            string paramName,
            IEnumerable <HistoryEvent> newEventCollection,
            OrchestrationInstance instance,
            int nextSequenceNumber,
            EventPayloadMap eventPayloadMap)
        {
            SqlParameter param = commandParameters.Add(paramName, SqlDbType.Structured);

            param.TypeName = "dt.HistoryEvents";
            param.Value    = ToHistoryEventsParameter(newEventCollection, instance, nextSequenceNumber, eventPayloadMap);
            return(param);
        }
예제 #4
0
        static IEnumerable <SqlDataRecord> ToHistoryEventsParameter(
            IEnumerable <HistoryEvent> historyEvents,
            OrchestrationInstance instance,
            int nextSequenceNumber,
            EventPayloadMap eventPayloadMap)
        {
            var record = new SqlDataRecord(HistoryEventSchema);

            foreach (HistoryEvent e in historyEvents)
            {
                record.SetSqlInt64(ColumnOrdinals.SequenceNumber, nextSequenceNumber++);
                record.SetSqlString(ColumnOrdinals.InstanceID, instance.InstanceId);
                record.SetSqlString(ColumnOrdinals.ExecutionID, instance.ExecutionId);
                record.SetSqlString(ColumnOrdinals.EventType, e.EventType.ToString());
                record.SetSqlString(ColumnOrdinals.Name, SqlUtils.GetName(e));
                record.SetSqlString(ColumnOrdinals.RuntimeStatus, SqlUtils.GetRuntimeStatus(e));
                record.SetSqlInt32(ColumnOrdinals.TaskID, SqlUtils.GetTaskId(e));
                record.SetDateTime(ColumnOrdinals.Timestamp, e.Timestamp);
                record.SetBoolean(ColumnOrdinals.IsPlayed, e.IsPlayed);
                record.SetDateTime(ColumnOrdinals.VisibleTime, SqlUtils.GetVisibleTime(e));

                if (eventPayloadMap.TryGetPayloadId(e, out Guid existingPayloadId))
                {
                    // We already have a payload saved in the DB for this event. Send only the payload ID.
                    record.SetSqlString(ColumnOrdinals.Reason, SqlString.Null);
                    record.SetSqlString(ColumnOrdinals.PayloadText, SqlString.Null);
                    record.SetSqlGuid(ColumnOrdinals.PayloadID, existingPayloadId);
                }
                else
                {
                    // This path is expected for ExecutionCompleted, possibly others?
                    SqlString reason = SqlUtils.GetReason(e);
                    record.SetSqlString(ColumnOrdinals.Reason, reason);
                    SqlString payload = SqlUtils.GetPayloadText(e);
                    record.SetSqlString(ColumnOrdinals.PayloadText, payload);
                    SqlGuid newPayloadId = reason.IsNull && payload.IsNull ? SqlGuid.Null : new SqlGuid(Guid.NewGuid());
                    record.SetSqlGuid(ColumnOrdinals.PayloadID, newPayloadId);
                }

                yield return(record);
            }
        }
        public static SqlParameter AddOrchestrationEventsParameter(
            this SqlParameterCollection commandParameters,
            string paramName,
            IList <TaskMessage> orchestratorMessages,
            IList <TaskMessage> timerMessages,
            TaskMessage continuedAsNewMessage,
            EventPayloadMap eventPayloadMap)
        {
            SqlParameter param = commandParameters.Add(paramName, SqlDbType.Structured);

            param.TypeName = SqlTypeName;

            IEnumerable <TaskMessage> messages = orchestratorMessages.Union(timerMessages);

            if (continuedAsNewMessage != null)
            {
                messages = messages.Append(continuedAsNewMessage);
            }

            param.Value = ToOrchestrationMessageParameter(messages, eventPayloadMap);
            return(param);
        }
        static IEnumerable <SqlDataRecord>?ToOrchestrationMessageParameter(
            this IEnumerable <TaskMessage> messages,
            EventPayloadMap eventPayloadMap)
        {
            if (!messages.Any())
            {
                // ADO.NET requires a null value instead of an empty enumerator
                // when passing a table-value parameter with zero entries.
                return(null);
            }

            return(GetOrchestrationMessageRecords());

            // Using a local function to support using null and yield syntax in the same method
            IEnumerable <SqlDataRecord> GetOrchestrationMessageRecords()
            {
                var record = new SqlDataRecord(OrchestrationEventSchema);

                foreach (TaskMessage msg in messages)
                {
                    yield return(PopulateOrchestrationMessage(msg, record, eventPayloadMap));
                }
            }
        }
        public override async Task <TaskOrchestrationWorkItem?> LockNextTaskOrchestrationWorkItemAsync(
            TimeSpan receiveTimeout,
            CancellationToken cancellationToken)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();

            do
            {
                using SqlConnection connection = await this.GetAndOpenConnectionAsync(cancellationToken);

                using SqlCommand command = this.GetSprocCommand(connection, "dt._LockNextOrchestration");

                int      batchSize      = this.settings.WorkItemBatchSize;
                DateTime lockExpiration = DateTime.UtcNow.Add(this.settings.WorkItemLockTimeout);

                command.Parameters.Add("@BatchSize", SqlDbType.Int).Value            = batchSize;
                command.Parameters.Add("@LockedBy", SqlDbType.VarChar, 100).Value    = this.lockedByValue;
                command.Parameters.Add("@LockExpiration", SqlDbType.DateTime2).Value = lockExpiration;

                DbDataReader reader;

                try
                {
                    reader = await SqlUtils.ExecuteReaderAsync(
                        command,
                        this.traceHelper,
                        instanceId : null,
                        cancellationToken);
                }
                catch (Exception e)
                {
                    this.traceHelper.ProcessingError(e, new OrchestrationInstance());
                    throw;
                }

                using (reader)
                {
                    // Result #1: The list of control queue messages
                    int longestWaitTime      = 0;
                    var messages             = new List <TaskMessage>(capacity: batchSize);
                    var eventPayloadMappings = new EventPayloadMap(capacity: batchSize);
                    while (await reader.ReadAsync(cancellationToken))
                    {
                        TaskMessage message = reader.GetTaskMessage();
                        messages.Add(message);
                        Guid?payloadId = reader.GetPayloadId();
                        if (payloadId.HasValue)
                        {
                            // TODO: Need to understand what the payload behavior is for retry events
                            eventPayloadMappings.Add(message.Event, payloadId.Value);
                        }

                        // TODO: We're not currently using this value for anything. Ideally it would be included
                        //       in some logging that still needs to be introduced.
                        longestWaitTime = Math.Max(longestWaitTime, reader.GetInt32("WaitTime"));
                    }

                    if (messages.Count == 0)
                    {
                        // TODO: Make this dynamic based on the number of readers
                        await this.orchestrationBackoffHelper.WaitAsync(cancellationToken);

                        continue;
                    }

                    this.orchestrationBackoffHelper.Reset();

                    // Result #2: The full event history for the locked instance
                    IList <HistoryEvent> history;
                    if (await reader.NextResultAsync(cancellationToken))
                    {
                        history = await ReadHistoryEventsAsync(reader, executionIdFilter : null, cancellationToken);
                    }
                    else
                    {
                        this.traceHelper.GenericWarning(
                            details: "Failed to read history from the database!",
                            instanceId: messages.FirstOrDefault(m => m.OrchestrationInstance?.InstanceId != null)?.OrchestrationInstance.InstanceId);
                        history = Array.Empty <HistoryEvent>();
                    }

                    var runtimeState = new OrchestrationRuntimeState(history);

                    string orchestrationName;
                    OrchestrationInstance instance;
                    if (runtimeState.ExecutionStartedEvent != null)
                    {
                        // This is an existing instance
                        orchestrationName = runtimeState.Name;
                        instance          = runtimeState.OrchestrationInstance !;
                    }
                    else if (messages[0].Event is ExecutionStartedEvent startedEvent)
                    {
                        // This is a new manually-created instance
                        orchestrationName = startedEvent.Name;
                        instance          = startedEvent.OrchestrationInstance;
                    }
                    else if (Entities.AutoStart(messages[0].OrchestrationInstance.InstanceId, messages) &&
                             messages[0].Event is ExecutionStartedEvent autoStartedEvent)
                    {
                        // This is a new auto-start instance (e.g. Durable Entities)
                        orchestrationName = autoStartedEvent.Name;
                        instance          = autoStartedEvent.OrchestrationInstance;
                    }
                    else
                    {
                        // Don't know what to do with this message (TODO: Need to confirm behavior)
                        orchestrationName = "(Unknown)";
                        instance          = new OrchestrationInstance();
                    }

                    return(new ExtendedOrchestrationWorkItem(orchestrationName, instance, eventPayloadMappings)
                    {
                        InstanceId = messages[0].OrchestrationInstance.InstanceId,
                        LockedUntilUtc = lockExpiration,
                        NewMessages = messages,
                        OrchestrationRuntimeState = runtimeState,
                    });
                }
            } while (stopwatch.Elapsed < receiveTimeout);

            return(null);
        }