Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Client/Core/PurgeInstancesFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@
DateTimeOffset? CreatedTo = null,
IEnumerable<OrchestrationRuntimeStatus>? Statuses = null)
{
/// <summary>
/// Gets or sets the maximum amount of time to spend purging instances in a single call.
/// If <c>null</c> (default), all matching instances are purged with no time limit.
/// When set, the purge stops accepting new instances after this duration elapses.
/// The value of <see cref="PurgeResult.IsComplete"/> depends on the backend implementation:
/// it may be <c>false</c> if the purge timed out, <c>true</c> if all instances were purged,
/// or <c>null</c> if the backend does not support reporting completion status.
/// Not all backends support this property; those that do not will ignore it.
/// </summary>
public TimeSpan? Timeout { get; init; }

Check warning on line 26 in src/Client/Core/PurgeInstancesFilter.cs

View workflow job for this annotation

GitHub Actions / smoke-tests

The property's documentation summary text should begin with: 'Gets' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1623.md)

Check warning on line 26 in src/Client/Core/PurgeInstancesFilter.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The property's documentation summary text should begin with: 'Gets' (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1623.md)
}
13 changes: 13 additions & 0 deletions src/Client/Grpc/GrpcDurableTaskClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,19 @@ public override Task<PurgeResult> PurgeAllInstancesAsync(
request.PurgeInstanceFilter.RuntimeStatus.AddRange(filter.Statuses.Select(x => x.ToGrpcStatus()));
}

if (filter?.Timeout is not null)
{
if (filter.Timeout.Value <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
nameof(filter),
filter.Timeout.Value,
"Timeout must be a positive TimeSpan.");
}

request.PurgeInstanceFilter.Timeout = Google.Protobuf.WellKnownTypes.Duration.FromTimeSpan(filter.Timeout.Value);
}

return this.PurgeInstancesCoreAsync(request, cancellation);
}

Expand Down
1 change: 1 addition & 0 deletions src/Grpc/orchestrator_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ message PurgeInstanceFilter {
google.protobuf.Timestamp createdTimeFrom = 1;
google.protobuf.Timestamp createdTimeTo = 2;
repeated OrchestrationStatus runtimeStatus = 3;
google.protobuf.Duration timeout = 4;
}

message PurgeInstancesResponse {
Expand Down
39 changes: 39 additions & 0 deletions test/Client/Grpc.Tests/GrpcDurableTaskClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,44 @@ public async Task ScheduleNewOrchestrationInstanceAsync_ValidDedupeStatus_DoesNo
var exception = await act.Should().ThrowAsync<Exception>();
exception.Which.Should().NotBeOfType<ArgumentException>();
}

[Fact]
public async Task PurgeAllInstancesAsync_NegativeTimeout_ThrowsArgumentOutOfRangeException()
{
// Arrange
var client = this.CreateClient();
var filter = new PurgeInstancesFilter { Timeout = TimeSpan.FromSeconds(-1) };

// Act & Assert
Func<Task> act = async () => await client.PurgeAllInstancesAsync(filter);
var exception = await act.Should().ThrowAsync<ArgumentOutOfRangeException>();
exception.Which.Message.Should().Contain("Timeout must be a positive TimeSpan.");
}

[Fact]
public async Task PurgeAllInstancesAsync_ZeroTimeout_ThrowsArgumentOutOfRangeException()
{
// Arrange
var client = this.CreateClient();
var filter = new PurgeInstancesFilter { Timeout = TimeSpan.Zero };

// Act & Assert
Func<Task> act = async () => await client.PurgeAllInstancesAsync(filter);
var exception = await act.Should().ThrowAsync<ArgumentOutOfRangeException>();
exception.Which.Message.Should().Contain("Timeout must be a positive TimeSpan.");
}

[Fact]
public async Task PurgeAllInstancesAsync_PositiveTimeout_DoesNotThrowValidationError()
{
// Arrange
var client = this.CreateClient();
var filter = new PurgeInstancesFilter { Timeout = TimeSpan.FromSeconds(30) };

// Act & Assert - validation should pass; the call will fail at gRPC level, not validation
Func<Task> act = async () => await client.PurgeAllInstancesAsync(filter);
var exception = await act.Should().ThrowAsync<Exception>();
exception.Which.Should().NotBeOfType<ArgumentOutOfRangeException>();
}
}

Loading