Adds an option to set a timeout for service invocation (#1252)

* Adds http timeout

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds a timeout for the grpc client

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Small updates

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Updates test

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds a timeout example in docs

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds e2e test for http service invocation

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Adds tests for grpc service invocation

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Removes grpc timeout, because it’s not needed. It can be passed directly to the call as shown in the updated tests and docs

Signed-off-by: Elena Kolevska <elena@kolevska.com>

* Update src/Dapr.Client/DaprClientBuilder.cs

Signed-off-by: Elena Kolevska <elena-kolevska@users.noreply.github.com>

---------

Signed-off-by: Elena Kolevska <elena@kolevska.com>
Signed-off-by: Elena Kolevska <elena-kolevska@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip@orst.edu>
This commit is contained in:
Elena Kolevska 2024-04-08 18:23:49 +01:00 committed by GitHub
parent 31af35b6c6
commit bdca3b320b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 112 additions and 3 deletions

View File

@ -21,13 +21,16 @@ The .NET SDK allows you to interface with all of the [Dapr building blocks]({{<
### Invoke a service
#### HTTP
You can either use the `DaprClient` or `System.Net.Http.HttpClient` to invoke your services.
{{< tabs SDK HTTP>}}
{{% codetab %}}
```csharp
using var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().
UseTimeout(TimeSpan.FromSeconds(2)). // Optionally, set a timeout
Build();
// Invokes a POST method named "deposit" that takes input of type "Transaction"
var data = new { id = "17", amount = 99m };
@ -40,15 +43,33 @@ Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance)
```csharp
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");
// To set a timeout on the HTTP client:
client.Timeout = TimeSpan.FromSeconds(2);
var deposit = new Transaction { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
```
{{% /codetab %}}
{{< /tabs >}}
#### gRPC
You can use the `DaprClient` to invoke your services over gRPC.
{{% codetab %}}
```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var invoker = DaprClient.CreateInvocationInvoker(appId: myAppId, daprEndpoint: serviceEndpoint);
var client = new MyService.MyServiceClient(invoker);
var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
await client.MyMethodAsync(new Empty(), options);
Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);
```
{{% /codetab %}}
- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}).
### Save & get application state

View File

@ -57,6 +57,7 @@ namespace Dapr.Client
// property exposed for testing purposes
internal GrpcChannelOptions GrpcChannelOptions { get; private set; }
internal string DaprApiToken { get; private set; }
internal TimeSpan Timeout { get; private set; }
/// <summary>
/// Overrides the HTTP endpoint used by <see cref="DaprClient" /> for communicating with the Dapr runtime.
@ -136,6 +137,17 @@ namespace Dapr.Client
return this;
}
/// <summary>
/// Sets the timeout for the HTTP client used by the <see cref="DaprClient" />.
/// </summary>
/// <param name="timeout"></param>
/// <returns></returns>
public DaprClientBuilder UseTimeout(TimeSpan timeout)
{
this.Timeout = timeout;
return this;
}
/// <summary>
/// Builds a <see cref="DaprClient" /> instance from the properties of the builder.
/// </summary>
@ -162,9 +174,16 @@ namespace Dapr.Client
var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
var client = new Autogenerated.Dapr.DaprClient(channel);
var apiTokenHeader = DaprClient.GetDaprApiTokenHeader(this.DaprApiToken);
var httpClient = HttpClientFactory is object ? HttpClientFactory() : new HttpClient();
if (this.Timeout > TimeSpan.Zero)
{
httpClient.Timeout = this.Timeout;
}
return new DaprClientGrpc(channel, client, httpClient, httpEndpoint, this.JsonSerializerOptions, apiTokenHeader);
}
}

View File

@ -14,6 +14,7 @@
using System;
using System.Text.Json;
using Dapr.Client;
using Grpc.Core;
using Grpc.Net.Client;
using Xunit;
@ -110,5 +111,15 @@ namespace Dapr.AspNetCore.Test
var entry = DaprClient.GetDaprApiTokenHeader(builder.DaprApiToken);
Assert.Equal(default, entry);
}
[Fact]
public void DaprClientBuilder_SetsTimeout()
{
var builder = new DaprClientBuilder();
builder.UseTimeout(TimeSpan.FromSeconds(2));
builder.Build();
Assert.Equal(2, builder.Timeout.Seconds);
}
}
}

View File

@ -25,6 +25,7 @@ service Messager {
rpc GetMessage(GetMessageRequest) returns (MessageResponse);
// Send a series of broadcast messages.
rpc StreamBroadcast(stream Broadcast) returns (stream MessageResponse);
rpc DelayedResponse(google.protobuf.Empty) returns (google.protobuf.Empty);
}
message SendMessageRequest {

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
@ -44,5 +45,11 @@ namespace Dapr.E2E.Test
await responseStream.WriteAsync(new MessageResponse { Message = request.Message });
}
}
public override async Task<Empty> DelayedResponse(Empty request, ServerCallContext context)
{
await Task.Delay(TimeSpan.FromSeconds(2));
return new Empty();
}
}
}

View File

@ -65,5 +65,14 @@ namespace Dapr.E2E.Test
};
return account;
}
[Authorize("Dapr")]
[HttpGet("DelayedResponse")]
public async Task<IActionResult> DelayedResponse()
{
await Task.Delay(TimeSpan.FromSeconds(2));
return Ok();
}
}
}

View File

@ -15,6 +15,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Xunit;
using Xunit.Abstractions;
@ -77,5 +78,21 @@ namespace Dapr.E2E.Test
await responseTask;
}
}
[Fact]
public async Task TestGrpcServiceInvocationWithTimeout()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var invoker = DaprClient.CreateInvocationInvoker(appId: this.AppId, daprEndpoint: this.GrpcEndpoint);
var client = new Messager.MessagerClient(invoker);
var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
var ex = await Assert.ThrowsAsync<RpcException>(async () =>
{
await client.DelayedResponseAsync(new Empty(), options);
});
Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);
}
}
}

View File

@ -13,10 +13,15 @@
namespace Dapr.E2E.Test
{
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Xunit;
public partial class E2ETests
@ -58,6 +63,25 @@ namespace Dapr.E2E.Test
Assert.Equal("1", account.Id);
Assert.Equal(150, account.Balance);
}
[Fact]
public async Task TestHttpServiceInvocationWithTimeout()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
using var client = new DaprClientBuilder()
.UseHttpEndpoint(this.HttpEndpoint)
.UseTimeout(TimeSpan.FromSeconds(1))
.Build();
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
await client.InvokeMethodAsync<HttpResponseMessage>(
appId: this.AppId,
methodName: "DelayedResponse",
httpMethod: new HttpMethod("GET"),
cancellationToken: cts.Token);
});
}
}
internal class Transaction