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); }
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); }
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); }