@@ -271,21 +271,25 @@ async Task<IEnumerable<MessageData>> DedupeExecutionStartedMessagesAsync(
271271 // Terminology:
272272 // "Local" -> the instance ID info comes from the local copy of the message we're examining
273273 // "Remote" -> the instance ID info comes from the Instances table that we're querying
274- IAsyncEnumerable < OrchestrationState > instances = this . trackingStore . GetStateAsync ( instanceIds , cancellationToken ) ;
275- IDictionary < string , OrchestrationState > remoteOrchestrationsById =
276- await instances . ToDictionaryAsync ( o => o . OrchestrationInstance . InstanceId , cancellationToken ) ;
274+ IAsyncEnumerable < InstanceStatus > instances = this . trackingStore . FetchInstanceStatusAsync ( instanceIds , cancellationToken ) ;
275+ IDictionary < string , InstanceStatus > remoteOrchestrationsById =
276+ await instances . ToDictionaryAsync ( o => o . State . OrchestrationInstance . InstanceId , cancellationToken ) ;
277277
278278 foreach ( MessageData message in executionStartedMessages )
279279 {
280280 OrchestrationInstance localInstance = message . TaskMessage . OrchestrationInstance ;
281281 var expectedGeneration = ( ( ExecutionStartedEvent ) message . TaskMessage . Event ) . Generation ;
282- if ( remoteOrchestrationsById . TryGetValue ( localInstance . InstanceId , out OrchestrationState remoteInstance ) &&
283- ( remoteInstance . OrchestrationInstance . ExecutionId == null || string . Equals ( localInstance . ExecutionId , remoteInstance . OrchestrationInstance . ExecutionId , StringComparison . OrdinalIgnoreCase ) ) )
282+ if ( remoteOrchestrationsById . TryGetValue ( localInstance . InstanceId , out InstanceStatus remoteInstance ) &&
283+ ( remoteInstance . State . OrchestrationInstance . ExecutionId == null || string . Equals ( localInstance . ExecutionId , remoteInstance . State . OrchestrationInstance . ExecutionId , StringComparison . OrdinalIgnoreCase ) ) )
284284 {
285285 // Happy path: The message matches the table status. Alternatively, if the table doesn't have an ExecutionId field (older clients, pre-v1.8.5),
286286 // then we have no way of knowing if it's a duplicate. Either way, allow it to run.
287+ if ( this . settings . UseInstanceTableEtag )
288+ {
289+ message . MessageMetadata = remoteInstance . ETag ;
290+ }
287291 }
288- else if ( expectedGeneration == remoteInstance ? . Generation && this . IsScheduledAfterInstanceUpdate ( message , remoteInstance ) )
292+ else if ( expectedGeneration == remoteInstance ? . State . Generation && this . IsScheduledAfterInstanceUpdate ( message , remoteInstance ? . State ) )
289293 {
290294 // The message was scheduled after the Instances table was updated with the orchestration info.
291295 // We know almost certainly that this is a redundant message and can be safely discarded because
@@ -476,6 +480,10 @@ internal void AddMessageToPendingOrchestration(
476480 if ( targetBatch == null )
477481 {
478482 targetBatch = new PendingMessageBatch ( controlQueue , instanceId , executionId ) ;
483+ if ( this . settings . UseInstanceTableEtag && data . MessageMetadata is ETag instanceEtag )
484+ {
485+ targetBatch . ETags . InstanceETag = instanceEtag ;
486+ }
479487 node = this . pendingOrchestrationMessageBatches . AddLast ( targetBatch ) ;
480488
481489 // Before the batch of messages can be processed, we need to download the latest execution state.
@@ -519,9 +527,20 @@ async Task ScheduleOrchestrationStatePrefetch(
519527 cancellationToken ) ;
520528
521529 batch . OrchestrationState = new OrchestrationRuntimeState ( history . Events ) ;
522- batch . ETag = history . ETag ;
530+ batch . ETags . HistoryETag = history . ETag ;
523531 batch . LastCheckpointTime = history . LastCheckpointTime ;
524532 batch . TrackingStoreContext = history . TrackingStoreContext ;
533+
534+ // Try to get the instance ETag from the tracking store if it wasn't already provided
535+ if ( this . settings . UseInstanceTableEtag && batch . ETags . InstanceETag == null )
536+ {
537+ InstanceStatus ? instanceStatus = await this . trackingStore . FetchInstanceStatusAsync (
538+ batch . OrchestrationInstanceId ,
539+ cancellationToken ) ;
540+ // The instance could not exist in the case that these messages are for the first execution of a suborchestration,
541+ // or an entity-started orchestration, for example
542+ batch . ETags . InstanceETag = instanceStatus ? . ETag ;
543+ }
525544 }
526545
527546 if ( this . settings . UseSeparateQueueForEntityWorkItems
@@ -590,7 +609,7 @@ async Task ScheduleOrchestrationStatePrefetch(
590609 nextBatch . ControlQueue ,
591610 nextBatch . Messages ,
592611 nextBatch . OrchestrationState ,
593- nextBatch . ETag ,
612+ nextBatch . ETags ,
594613 nextBatch . LastCheckpointTime ,
595614 nextBatch . TrackingStoreContext ,
596615 this . settings . ExtendedSessionIdleTimeout ,
@@ -737,8 +756,10 @@ public OrchestrationRuntimeState? OrchestrationState
737756 }
738757 }
739758
740- public ETag ? ETag { get ; set ; }
759+ public OrchestrationETags ETags { get ; } = new OrchestrationETags ( ) ;
760+
741761 public DateTime LastCheckpointTime { get ; set ; }
762+
742763 public object ? TrackingStoreContext { get ; set ; }
743764 }
744765 }
0 commit comments