@@ -81,7 +81,8 @@ protected override async Task ProcessMessage(MessageReceivedEventArgs eventArgs)
8181 lock ( SyncRoot )
8282 {
8383 var exportRequest = eventArgs . Message . ConvertTo < ExportRequestEvent > ( ) ;
84- if ( ExportRequests . ContainsKey ( exportRequest . ExportTaskId ) )
84+ string exportKey = BuildExportKey ( exportRequest . WorkflowInstanceId , exportRequest . ExportTaskId ) ;
85+ if ( ExportRequests . ContainsKey ( exportKey ) )
8586 {
8687 return ;
8788 }
@@ -91,7 +92,7 @@ protected override async Task ProcessMessage(MessageReceivedEventArgs eventArgs)
9192
9293 var exportRequestWithDetails = new ExportRequestEventDetails ( exportRequest ) ;
9394
94- ExportRequests . Add ( exportRequest . ExportTaskId , exportRequestWithDetails ) ;
95+ ExportRequests . Add ( exportKey , exportRequestWithDetails ) ;
9596 if ( ! exportFlow . Post ( exportRequestWithDetails ) )
9697 {
9798 MessageSubscriber . Reject ( eventArgs . Message ) ;
@@ -342,6 +343,62 @@ public async Task DataflowTest_EndToEnd()
342343 _outputDataPlugInEngine . Verify ( p => p . ExecutePlugInsAsync ( It . IsAny < ExportRequestDataMessage > ( ) ) , Times . Exactly ( 5 * 2 ) ) ;
343344 }
344345
346+ [ RetryFact ( 1 , 10 , DisplayName = "Data flow test - same ExportTaskId different WorkflowInstanceId are not deduplicated" ) ]
347+ public async Task DataflowTest_SameExportTaskId_DifferentWorkflowInstanceId_BothProcessed ( )
348+ {
349+ var sharedExportTaskId = Guid . NewGuid ( ) . ToString ( ) ;
350+ var completedCount = 0 ;
351+
352+ _messagePublisherService . Setup ( p => p . Publish ( It . IsAny < string > ( ) , It . IsAny < Message > ( ) ) ) ;
353+ _messageSubscriberService . Setup ( p => p . Acknowledge ( It . IsAny < MessageBase > ( ) ) ) ;
354+ _messageSubscriberService . Setup ( p => p . RequeueWithDelay ( It . IsAny < MessageBase > ( ) ) ) ;
355+ _messageSubscriberService . Setup (
356+ p => p . SubscribeAsync ( It . IsAny < string > ( ) ,
357+ It . IsAny < string > ( ) ,
358+ It . IsAny < Func < MessageReceivedEventArgs , Task > > ( ) ,
359+ It . IsAny < ushort > ( ) ) )
360+ . Callback < string , string , Func < MessageReceivedEventArgs , Task > , ushort > ( async ( topic , queue , messageReceivedCallback , prefetchCount ) =>
361+ {
362+ // Same ExportTaskId, two different WorkflowInstanceIds - must NOT be deduplicated
363+ await messageReceivedCallback ( CreateMessageReceivedEventArgs ( Guid . NewGuid ( ) . ToString ( ) , sharedExportTaskId ) ) ;
364+ await messageReceivedCallback ( CreateMessageReceivedEventArgs ( Guid . NewGuid ( ) . ToString ( ) , sharedExportTaskId ) ) ;
365+ } ) ;
366+
367+ _storageService . Setup ( p => p . GetObjectAsync ( It . IsAny < string > ( ) , It . IsAny < string > ( ) , It . IsAny < CancellationToken > ( ) ) )
368+ . ReturnsAsync ( new MemoryStream ( Encoding . UTF8 . GetBytes ( "test" ) ) ) ;
369+
370+ var countdownEvent = new CountdownEvent ( 2 ) ;
371+ var service = new TestExportService ( _logger . Object , _configuration , _serviceScopeFactory . Object , _dicomToolkit . Object ) ;
372+ service . ReportActionCompleted += ( sender , e ) =>
373+ {
374+ Interlocked . Increment ( ref completedCount ) ;
375+ countdownEvent . Signal ( ) ;
376+ } ;
377+ await service . StartAsync ( _cancellationTokenSource . Token ) ;
378+ Assert . True ( countdownEvent . Wait ( 60000 ) , $ "Expected 2 exports to complete but only { completedCount } completed - second request was incorrectly deduplicated") ;
379+ await StopAndVerify ( service ) ;
380+
381+ _messagePublisherService . Verify (
382+ p => p . Publish ( It . IsAny < string > ( ) ,
383+ It . Is < Message > ( match => ( match . ConvertTo < ExportCompleteEvent > ( ) ) . Status == ExportStatus . Success ) ) , Times . Exactly ( 2 ) ) ;
384+ _messageSubscriberService . Verify ( p => p . Acknowledge ( It . IsAny < MessageBase > ( ) ) , Times . Exactly ( 2 ) ) ;
385+ }
386+
387+ internal static MessageReceivedEventArgs CreateMessageReceivedEventArgs ( string workflowInstanceId , string exportTaskId )
388+ {
389+ var exportRequestEvent = new ExportRequestEvent
390+ {
391+ ExportTaskId = exportTaskId ,
392+ CorrelationId = Guid . NewGuid ( ) . ToString ( ) ,
393+ Destinations = new [ ] { "destination" } ,
394+ Files = new [ ] { "file1" , "file2" } ,
395+ MessageId = Guid . NewGuid ( ) . ToString ( ) ,
396+ WorkflowInstanceId = workflowInstanceId ,
397+ } ;
398+ var jsonMessage = new JsonMessage < ExportRequestEvent > ( exportRequestEvent , MessageBrokerConfiguration . InformaticsGatewayApplicationId , exportRequestEvent . CorrelationId , exportRequestEvent . DeliveryTag ) ;
399+ return new MessageReceivedEventArgs ( jsonMessage . ToMessage ( ) , CancellationToken . None ) ;
400+ }
401+
345402 internal static MessageReceivedEventArgs CreateMessageReceivedEventArgs ( )
346403 {
347404 var exportRequestEvent = new ExportRequestEvent
0 commit comments