Skip to content

Commit e972799

Browse files
authored
Merge pull request #743 from Project-MONAI/AC-1914
Ac 1914
2 parents 1c86cef + ff0c754 commit e972799

File tree

6 files changed

+152
-4
lines changed

6 files changed

+152
-4
lines changed

src/Shared/Configuration/MessageBrokerConfigurationKeys.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,23 @@ public class MessageBrokerConfigurationKeys
6464

6565
/// <summary>
6666
/// Gets or sets the topic for publishing task update events.
67-
/// Defaults to `md.tasks.update`.
67+
/// Defaults to `md.tasks.cancellation`.
6868
/// </summary>
6969
[ConfigurationKeyName("taskCancellation")]
7070
public string TaskCancellationRequest { get; set; } = "md.tasks.cancellation";
7171

7272
/// <summary>
7373
/// Gets or sets the topic for publishing clinical review request events.
74-
/// Defaults to `md.tasks.update`.
74+
/// Defaults to `aide.clinical_review.request`.
7575
/// </summary>
7676
[ConfigurationKeyName("aideClinicalReviewRequest")]
7777
public string AideClinicalReviewRequest { get; set; } = "aide.clinical_review.request";
78+
79+
/// <summary>
80+
/// Gets or sets the topic for publishing clinical review cancelation events.
81+
/// Defaults to `aide.clinical_review.cancellation`.
82+
/// </summary>
83+
[ConfigurationKeyName("aideClinicalReviewCancelation")]
84+
public string AideClinicalReviewCancelation { get; set; } = "aide.clinical_review.cancellation";
7885
}
7986
}

src/Shared/Configuration/WorkflowManagerOptions.cs

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
using System;
18+
using System.Collections.Generic;
1819
using Microsoft.Extensions.Configuration;
1920

2021
namespace Monai.Deploy.WorkflowManager.Configuration
@@ -47,6 +48,10 @@ public class WorkflowManagerOptions : PagedOptions
4748
[ConfigurationKeyName("taskTimeoutMinutes")]
4849
public double TaskTimeoutMinutes { get; set; } = 60;
4950

51+
[ConfigurationKeyName("perTaskTypeTimeoutMinutes")]
52+
public Dictionary<string, double> PerTaskTypeTimeoutMinutes { get; set; }
53+
54+
5055
public TimeSpan TaskTimeout { get => TimeSpan.FromMinutes(TaskTimeoutMinutes); }
5156

5257
/// <summary>

src/TaskManager/TaskManager/appsettings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
"exportComplete": "md.export.complete",
7272
"exportRequestPrefix": "md.export.request",
7373
"taskCallback": "md.tasks.callback",
74-
"aideClinicalReviewRequest": "aide.clinical_review.request"
74+
"aideClinicalReviewRequest": "aide.clinical_review.request",
75+
"aideClinicalReviewCancelation": "aide.clinical_review.cancellation"
7576
},
7677
"dicomAgents": {
7778
"dicomWebAgentName": "monaidicomweb",

src/WorkflowManager/WorkflowExecuter/Services/WorkflowExecuterService.cs

+19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
using System.Globalization;
1818
using System.Linq;
19+
using System.Runtime.CompilerServices;
1920
using Ardalis.GuardClauses;
2021
using Microsoft.Extensions.Logging;
2122
using Microsoft.Extensions.Options;
@@ -52,9 +53,11 @@ public class WorkflowExecuterService : IWorkflowExecuterService
5253
private readonly IPayloadService _payloadService;
5354
private readonly StorageServiceConfiguration _storageConfiguration;
5455
private readonly double _defaultTaskTimeoutMinutes;
56+
private readonly Dictionary<string, double> _defaultPerTaskTypeTimeoutMinutes = new Dictionary<string, double>();
5557

5658
private string TaskDispatchRoutingKey { get; }
5759
private string ExportRequestRoutingKey { get; }
60+
private string TaskTimeoutRoutingKey { get; }
5861

5962
public WorkflowExecuterService(
6063
ILogger<WorkflowExecuterService> logger,
@@ -81,7 +84,9 @@ public WorkflowExecuterService(
8184

8285
_storageConfiguration = storageConfiguration.Value;
8386
_defaultTaskTimeoutMinutes = configuration.Value.TaskTimeoutMinutes;
87+
_defaultPerTaskTypeTimeoutMinutes = configuration.Value.PerTaskTypeTimeoutMinutes;
8488
TaskDispatchRoutingKey = configuration.Value.Messaging.Topics.TaskDispatchRequest;
89+
TaskTimeoutRoutingKey = configuration.Value.Messaging.Topics.AideClinicalReviewCancelation;
8590
ExportRequestRoutingKey = $"{configuration.Value.Messaging.Topics.ExportRequestPrefix}.{configuration.Value.Messaging.DicomAgents.ScuAgentName}";
8691

8792
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -275,6 +280,7 @@ public async Task<bool> ProcessTaskUpdate(TaskUpdateEvent message)
275280
if (message.Reason == FailureReason.TimedOut && currentTask.Status == TaskExecutionStatus.Failed)
276281
{
277282
_logger.TaskTimedOut(message.TaskId, message.WorkflowInstanceId, currentTask.Timeout);
283+
await TimeOutEvent(workflowInstance, currentTask, message.CorrelationId);
278284

279285
return false;
280286
}
@@ -750,6 +756,15 @@ private async Task<bool> ExportRequest(WorkflowInstance workflowInstance, TaskEx
750756
return true;
751757
}
752758

759+
private async Task<bool> TimeOutEvent(WorkflowInstance workflowInstance, TaskExecution taskExec, string correlationId)
760+
{
761+
var exportRequestEvent = EventMapper.GenerateTaskCancellationEvent("", taskExec.ExecutionId, workflowInstance.Id, taskExec.TaskId, FailureReason.TimedOut, "Timed out");
762+
var jsonMesssage = new JsonMessage<TaskCancellationEvent>(exportRequestEvent, MessageBrokerConfiguration.WorkflowManagerApplicationId, correlationId, Guid.NewGuid().ToString());
763+
764+
await _messageBrokerPublisherService.Publish(TaskTimeoutRoutingKey, jsonMesssage.ToMessage());
765+
return true;
766+
}
767+
753768
private async Task<WorkflowInstance> CreateWorkflowInstanceAsync(WorkflowRequestEvent message, WorkflowRevision workflow)
754769
{
755770
Guard.Against.Null(message, nameof(message));
@@ -822,6 +837,10 @@ public async Task<TaskExecution> CreateTaskExecutionAsync(TaskObject task,
822837
{
823838
task.TimeoutMinutes = _defaultTaskTimeoutMinutes;
824839
}
840+
if (_defaultPerTaskTypeTimeoutMinutes is not null && _defaultPerTaskTypeTimeoutMinutes.ContainsKey(task.Type))
841+
{
842+
task.TimeoutMinutes = _defaultPerTaskTypeTimeoutMinutes[task.Type];
843+
}
825844

826845
var inputArtifacts = new Dictionary<string, string>();
827846
var artifactFound = true;

src/WorkflowManager/WorkflowManager/appsettings.json

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
"TaskManager": {
3636
"concurrency": 1,
3737
"taskTimeoutMinutes": 60,
38+
"perTaskTypeTimeoutMinutes": {
39+
"aide_clinical_review": 5760
40+
},
3841
"plug-ins": {
3942
"argo": "Monai.Deploy.WorkflowManager.TaskManager.Argo.ArgoPlugin, Monai.Deploy.WorkflowManager.TaskManager.Argo",
4043
"aide_clinical_review": "Monai.Deploy.WorkflowManager.TaskManager.AideClinicalReview.AideClinicalReviewPlugin, Monai.Deploy.WorkflowManager.TaskManager.AideClinicalReview",

tests/UnitTests/WorkflowExecuter.Tests/Services/WorkflowExecuterServiceTests.cs

+114-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public class WorkflowExecuterServiceTests
5858
private readonly Mock<IWorkflowService> _workflowService;
5959
private readonly IOptions<WorkflowManagerOptions> _configuration;
6060
private readonly IOptions<StorageServiceConfiguration> _storageConfiguration;
61+
private readonly int _timeoutForTypeTask = 999;
62+
private readonly int _timeoutForDefault = 966;
6163

6264
public WorkflowExecuterServiceTests()
6365
{
@@ -71,7 +73,7 @@ public WorkflowExecuterServiceTests()
7173
_payloadService = new Mock<IPayloadService>();
7274
_workflowService = new Mock<IWorkflowService>();
7375

74-
_configuration = Options.Create(new WorkflowManagerOptions() { Messaging = new MessageBrokerConfiguration { Topics = new MessageBrokerConfigurationKeys { TaskDispatchRequest = "md.task.dispatch", ExportRequestPrefix = "md.export.request" }, DicomAgents = new DicomAgentConfiguration { DicomWebAgentName = "monaidicomweb" } } });
76+
_configuration = Options.Create(new WorkflowManagerOptions() { TaskTimeoutMinutes = _timeoutForDefault, PerTaskTypeTimeoutMinutes = new Dictionary<string, double> { { "taskType", _timeoutForTypeTask } }, Messaging = new MessageBrokerConfiguration { Topics = new MessageBrokerConfigurationKeys { TaskDispatchRequest = "md.task.dispatch", ExportRequestPrefix = "md.export.request" }, DicomAgents = new DicomAgentConfiguration { DicomWebAgentName = "monaidicomweb" } } });
7577
_storageConfiguration = Options.Create(new StorageServiceConfiguration() { Settings = new Dictionary<string, string> { { "bucket", "testbucket" }, { "endpoint", "localhost" }, { "securedConnection", "False" } } });
7678

7779
var dicom = new Mock<IDicomService>();
@@ -1906,6 +1908,65 @@ public async Task ProcessTaskUpdate_ValidTaskUpdateEventWorkflowDoesNotExist_Ret
19061908
response.Should().BeTrue();
19071909
}
19081910

1911+
[Fact]
1912+
public async Task ProcessTaskUpdate_Timout_Sends_Message_To_TaskTimeoutRoutingKey()
1913+
{
1914+
var workflowInstanceId = Guid.NewGuid().ToString();
1915+
1916+
var metadata = new Dictionary<string, object>();
1917+
metadata.Add("a", "b");
1918+
metadata.Add("c", "d");
1919+
1920+
var updateEvent = new TaskUpdateEvent
1921+
{
1922+
WorkflowInstanceId = workflowInstanceId,
1923+
TaskId = "pizza",
1924+
ExecutionId = Guid.NewGuid().ToString(),
1925+
Status = TaskExecutionStatus.Failed,
1926+
Reason = FailureReason.TimedOut,
1927+
Message = "This is a message",
1928+
Metadata = metadata,
1929+
CorrelationId = Guid.NewGuid().ToString()
1930+
};
1931+
1932+
var workflowId = Guid.NewGuid().ToString();
1933+
1934+
var workflow = new WorkflowRevision
1935+
{
1936+
Id = Guid.NewGuid().ToString(),
1937+
WorkflowId = workflowId,
1938+
Revision = 1,
1939+
Workflow = new Workflow
1940+
{
1941+
Name = "Workflowname2",
1942+
Description = "Workflowdesc2",
1943+
Version = "1",
1944+
}
1945+
};
1946+
1947+
var workflowInstance = new WorkflowInstance
1948+
{
1949+
Id = workflowInstanceId,
1950+
WorkflowId = workflowId,
1951+
WorkflowName = workflow.Workflow.Name,
1952+
PayloadId = Guid.NewGuid().ToString(),
1953+
Status = Status.Created,
1954+
BucketId = "bucket",
1955+
Tasks = new List<TaskExecution>
1956+
{
1957+
new TaskExecution
1958+
{
1959+
TaskId = "pizza",
1960+
Status = TaskExecutionStatus.Failed
1961+
}
1962+
}
1963+
};
1964+
1965+
_workflowInstanceRepository.Setup(w => w.GetByWorkflowInstanceIdAsync(workflowInstance.Id)).ReturnsAsync(workflowInstance);
1966+
var response = await WorkflowExecuterService.ProcessTaskUpdate(updateEvent);
1967+
_messageBrokerPublisherService.Verify(w => w.Publish(_configuration.Value.Messaging.Topics.AideClinicalReviewCancelation, It.IsAny<Message>()), Times.Exactly(1));
1968+
}
1969+
19091970
[Fact]
19101971
public async Task ProcessExportComplete_ValidExportCompleteEventMultipleTaskDestinationsDispatched_ReturnsTrue()
19111972
{
@@ -2259,5 +2320,57 @@ public void AttachPatientMetaData_AtachesDataToTaskExec_TaskExecShouldHavePatien
22592320
taskExec.TaskPluginArguments[PatientKeys.PatientHospitalId].Should().BeSameAs(patientDetails.PatientHospitalId);
22602321
taskExec.TaskPluginArguments[PatientKeys.PatientName].Should().BeSameAs(patientDetails.PatientName);
22612322
}
2323+
2324+
[Fact]
2325+
public async Task TaskExecShouldHaveCorrectTimeout()
2326+
{
2327+
var workflowId = Guid.NewGuid().ToString();
2328+
var payloadId = Guid.NewGuid().ToString();
2329+
var workflowInstanceId = Guid.NewGuid().ToString();
2330+
2331+
var pizzaTask = new TaskObject
2332+
{
2333+
Id = "pizza",
2334+
Type = "taskType",
2335+
Description = "taskdesc",
2336+
};
2337+
2338+
var workflowInstance = new WorkflowInstance
2339+
{
2340+
Id = workflowInstanceId,
2341+
WorkflowId = workflowId,
2342+
};
2343+
var bucket = "bucket";
2344+
2345+
var newPizza = await WorkflowExecuterService.CreateTaskExecutionAsync(pizzaTask, workflowInstance, bucket, payloadId);
2346+
Assert.Equal(_timeoutForTypeTask, newPizza.TimeoutInterval);
2347+
2348+
}
2349+
2350+
[Fact]
2351+
public async Task TaskExecShouldPickUpConfiguredDefaultTimeout()
2352+
{
2353+
var workflowId = Guid.NewGuid().ToString();
2354+
var payloadId = Guid.NewGuid().ToString();
2355+
var workflowInstanceId = Guid.NewGuid().ToString();
2356+
2357+
var pizzaTask = new TaskObject
2358+
{
2359+
Id = "pizza",
2360+
Type = "someothertype",
2361+
Description = "taskdesc",
2362+
};
2363+
2364+
var workflowInstance = new WorkflowInstance
2365+
{
2366+
Id = workflowInstanceId,
2367+
WorkflowId = workflowId,
2368+
};
2369+
var bucket = "bucket";
2370+
2371+
var newPizza = await WorkflowExecuterService.CreateTaskExecutionAsync(pizzaTask, workflowInstance, bucket, payloadId);
2372+
Assert.Equal(_timeoutForDefault, newPizza.TimeoutInterval);
2373+
2374+
}
22622375
}
22632376
}

0 commit comments

Comments
 (0)