Skip to content

Composite Key (WorkflowInstanceId + ExportTaskId) for Export Deduplication#544

Open
bluna301 wants to merge 1 commit intomainfrom
bluna301/export-deduplication-key
Open

Composite Key (WorkflowInstanceId + ExportTaskId) for Export Deduplication#544
bluna301 wants to merge 1 commit intomainfrom
bluna301/export-deduplication-key

Conversation

@bluna301
Copy link
Copy Markdown

@bluna301 bluna301 commented Mar 5, 2026

Description

Previously, ExportServiceBase used only ExportTaskId as the key for tracking in-progress export requests. This caused requests with the same ExportTaskId but different WorkflowInstanceIds to be incorrectly deduplicated, silently dropping valid export tasks. This eventually leads to RabbitMQ timeout, Informatics Gateway -- RabbitMQ disconnection, and blocking of the Informatics Gateway due to failure to resolve the dropped valid export tasks.

This issue commonly happens when two workflow instances are executing at the same time, and the Task Ids associated with each workflow are identically named (for example, "id": "export-svrtk-orthanc" for a task that exports SVRTK series to ORTHANC).

Manifestation in MONAI Deploy Express environment:

mdl-ig logs:

# first workflow export task
2026-02-20 14:09:38.1761|532|INFO|Monai.Deploy.InformaticsGateway.Services.Export.ScuExportService||CorrelationId=b8d68125-09e3-4731-b859-b97dca0c998f. MessageId=export-svrtk-orthanc. Export request 703ac500-2c41-47ee-8d89-a545dd9f5a70 received & queued for processing.

# second workflow export task - collision
2026-02-20 14:09:41.0543|502|WARN|Monai.Deploy.InformaticsGateway.Services.Export.ScuExportService||CorrelationId=b8d68125-09e3-4731-b859-b97dca0c998f. The export request export-svrtk-orthanc is already queued for export. 

# RabbitMQ timeout
2026-02-20 14:40:31.7626|10013|ERROR|Monai.Deploy.Messaging.RabbitMQ.RabbitMQConnectionFactory||RabbitMQ connection shutdown: AMQP close-reason, initiated by Peer, code=406, text='PRECONDITION_FAILED - delivery acknowledgement on channel 1 timed out. Timeout value used: 1800000 ms. This timeout value can be configured, see consumers doc guide to learn more', classId=0, methodId=0. 

# after RabbitMQ timeout, task gets re-queued
2026-02-20 14:42:07.4279|532|INFO|Monai.Deploy.InformaticsGateway.Services.Export.ScuExportService||CorrelationId=b8d68125-09e3-4731-b859-b97dca0c998f. MessageId=export-svrtk-orthanc. Export request 7614502a-1988-4f0f-9c3a-9a15d85d0a12 received & queued for processing.

Rabbit MQ UI (Timeout Period):

image

RabbitMQ UI (IG Blocked):

image

Changes:

  • Add BuildExportKey() helper combining WorkflowInstanceId and ExportTaskId as a composite key
  • Apply composite key in ReportingActionBlock lookups and in ExtAppScuExportService.ExportRequests dictionary
  • Add regression test: DataflowTest_SameExportTaskId_DifferentWorkflowInstanceId_BothProcessed verifying both tasks are processed"

Status

Ready

Types of changes

  • Non-breaking change (fix or new feature that would not break existing functionality).
  • Breaking change (fix or new feature that would cause existing functionality to change).
  • New tests added to cover the changes.
  • All tests passed locally.
  • Documentation comments included/updated.
  • User guide updated.
  • I have updated the changelog

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where exports with the same task ID from different workflow instances could be incorrectly handled. The system now properly processes each export independently.
  • Tests

    • Added test coverage verifying that exports with identical task IDs from different workflow instances are both processed successfully without unintended interference.

Signed-off-by: bluna301 <luna.bryanr@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 2026

Walkthrough

The changes introduce a composite key mechanism using WorkflowInstanceId and ExportTaskId to replace the previous ExportTaskId-only deduplication logic across export services. A new BuildExportKey helper method is added, and all dictionary operations are updated to use this composite key pattern, with accompanying test coverage validating the new deduplication behavior.

Changes

Cohort / File(s) Summary
Core Export Service Logic
src/InformaticsGateway/Services/Export/ExportServiceBase.cs, src/InformaticsGateway/Services/Export/ExtAppScuExportService.cs
Added BuildExportKey helper to compose a composite key from WorkflowInstanceId and ExportTaskId. Updated all dictionary operations (contains, add, remove, lookup) to use the composite key instead of ExportTaskId alone for proper deduplication semantics.
Export Service Tests
src/InformaticsGateway/Test/Services/Export/ExportServiceBaseTest.cs
Added new test DataflowTest_SameExportTaskId_DifferentWorkflowInstanceId_BothProcessed and helper method CreateMessageReceivedEventArgs to verify that export requests with identical ExportTaskId but different WorkflowInstanceId are not deduplicated and both process successfully.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With keys of two, no more just one,
WorkflowId and TaskId combined as fun—
Deduplication logic now sings true,
Each pair unique, the export's through!
Composite keys for harmony won! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description comprehensively explains the problem, provides clear examples from logs/screenshots, and details the specific changes made including new tests and helper methods.
Title check ✅ Passed The title accurately summarizes the main change: introducing a composite key combining WorkflowInstanceId and ExportTaskId for export deduplication, which directly addresses the core problem described in the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bluna301/export-deduplication-key

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/InformaticsGateway/Services/Export/ExportServiceBase.cs (1)

593-610: ⚠️ Potential issue | 🔴 Critical

BaseProcessMessage still has unacked-duplicate and stale-key failure modes.

At Line 594–598, duplicate requests are dropped without settling the broker message.
At Line 605–610, if exportFlow.Post(...) fails after Add, the key is never removed.

Suggested fix
 string exportKey = BuildExportKey(exportRequest.WorkflowInstanceId, exportRequest.ExportTaskId);
 if (ExportRequests.ContainsKey(exportKey))
 {
     _logger.ExportRequestAlreadyQueued(exportRequest.CorrelationId, exportRequest.ExportTaskId);
+    MessageSubscriber.Acknowledge(eventArgs.Message);
     return;
 }

 ...
 ExportRequests.Add(exportKey, exportRequestWithDetails);
 if (!exportFlow.Post(exportRequestWithDetails))
 {
+    ExportRequests.Remove(exportKey);
     _logger.ErrorPostingExportJobToQueue(exportRequest.CorrelationId, exportRequest.ExportTaskId);
     MessageSubscriber.Reject(eventArgs.Message);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/InformaticsGateway/Services/Export/ExportServiceBase.cs` around lines 593
- 610, The duplicate-request branch and the failed-post branch leak unacked
messages and stale keys: when BuildExportKey finds an existing key
(ExportRequests.ContainsKey) you must settle the broker message (e.g., ack or
reject via MessageSubscriber) instead of just returning; and when you Add
exportRequestWithDetails to ExportRequests then call exportFlow.Post, ensure
that if Post returns false (or throws) you remove the key from ExportRequests
(use Remove/TryRemove on ExportRequests for the same key) before calling
MessageSubscriber.Reject and logging via _logger.ErrorPostingExportJobToQueue;
also guard the Post call with try/finally to remove the key on exceptions to
avoid stale entries.
src/InformaticsGateway/Services/Export/ExtAppScuExportService.cs (1)

70-87: ⚠️ Potential issue | 🔴 Critical

Settle duplicate messages and clean up key on Post failure.

At Line 71–75, returning on duplicate without Acknowledge/Reject leaves the message unacked.
At Line 82–87, when Post fails, the key remains in ExportRequests, causing stale dedupe state.

Suggested fix
 string exportKey = BuildExportKey(externalAppRequest.WorkflowInstanceId, externalAppRequest.ExportTaskId);
 if (ExportRequests.ContainsKey(exportKey))
 {
     _logger.ExportRequestAlreadyQueued(externalAppRequest.CorrelationId, externalAppRequest.ExportTaskId);
+    MessageSubscriber.Acknowledge(eventArgs.Message);
     return;
 }

 ...
 ExportRequests.Add(exportKey, exportRequestWithDetails);
 if (!exportFlow.Post(exportRequestWithDetails))
 {
+    ExportRequests.Remove(exportKey);
     _logger.ErrorPostingExportJobToQueue(externalAppRequest.CorrelationId, externalAppRequest.ExportTaskId);
     MessageSubscriber.Reject(eventArgs.Message);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/InformaticsGateway/Services/Export/ExtAppScuExportService.cs` around
lines 70 - 87, Duplicate incoming messages are currently returned without
settling the message and when exportFlow.Post fails the dedupe key in
ExportRequests is left behind; fix by calling
MessageSubscriber.Acknowledge(eventArgs.Message) (or Reject as appropriate) in
the duplicate branch where _logger.ExportRequestAlreadyQueued is invoked so the
message is settled, and when exportFlow.Post returns false remove the previously
added ExportRequests entry (the one created with BuildExportKey and
ExportRequestEventDetails) before calling _logger.ErrorPostingExportJobToQueue
and MessageSubscriber.Reject(eventArgs.Message) so the dedupe state is cleaned
up; locate symbols BuildExportKey, ExportRequests,
_logger.ExportRequestAlreadyQueued, externalAppRequest,
ExportRequestEventDetails, exportFlow.Post,
_logger.ErrorPostingExportJobToQueue, and MessageSubscriber.Reject/Acknowledge
to apply the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/InformaticsGateway/Services/Export/ExportServiceBase.cs`:
- Around line 593-610: The duplicate-request branch and the failed-post branch
leak unacked messages and stale keys: when BuildExportKey finds an existing key
(ExportRequests.ContainsKey) you must settle the broker message (e.g., ack or
reject via MessageSubscriber) instead of just returning; and when you Add
exportRequestWithDetails to ExportRequests then call exportFlow.Post, ensure
that if Post returns false (or throws) you remove the key from ExportRequests
(use Remove/TryRemove on ExportRequests for the same key) before calling
MessageSubscriber.Reject and logging via _logger.ErrorPostingExportJobToQueue;
also guard the Post call with try/finally to remove the key on exceptions to
avoid stale entries.

In `@src/InformaticsGateway/Services/Export/ExtAppScuExportService.cs`:
- Around line 70-87: Duplicate incoming messages are currently returned without
settling the message and when exportFlow.Post fails the dedupe key in
ExportRequests is left behind; fix by calling
MessageSubscriber.Acknowledge(eventArgs.Message) (or Reject as appropriate) in
the duplicate branch where _logger.ExportRequestAlreadyQueued is invoked so the
message is settled, and when exportFlow.Post returns false remove the previously
added ExportRequests entry (the one created with BuildExportKey and
ExportRequestEventDetails) before calling _logger.ErrorPostingExportJobToQueue
and MessageSubscriber.Reject(eventArgs.Message) so the dedupe state is cleaned
up; locate symbols BuildExportKey, ExportRequests,
_logger.ExportRequestAlreadyQueued, externalAppRequest,
ExportRequestEventDetails, exportFlow.Post,
_logger.ErrorPostingExportJobToQueue, and MessageSubscriber.Reject/Acknowledge
to apply the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26c9a171-0d89-407b-809f-1c093cde860d

📥 Commits

Reviewing files that changed from the base of the PR and between 89c9218 and af5e15c.

📒 Files selected for processing (3)
  • src/InformaticsGateway/Services/Export/ExportServiceBase.cs
  • src/InformaticsGateway/Services/Export/ExtAppScuExportService.cs
  • src/InformaticsGateway/Test/Services/Export/ExportServiceBaseTest.cs

@bluna301 bluna301 changed the title composite key (WorkflowInstanceId/ExportTaskId) for export deduplication Composite Key (WorkflowInstanceId + ExportTaskId) for Export Deduplication Mar 5, 2026
@bluna301 bluna301 self-assigned this Mar 5, 2026
@bluna301 bluna301 requested review from MMelQin and neildsouth March 5, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant