Add IDisposable to DaprClient (#563)

Fixes: #559

This change adds disposable support to DaprClient, and updates samples
to dispose it.

I didn't update tests because there are literally hundreds of
non-find-and-replacable cases, and we're not actually doing networking
in our tests so it won't cause an issue.
This commit is contained in:
Ryan Nowak 2021-01-26 15:45:01 -08:00 committed by GitHub
parent 9ca4a43cc7
commit 8a7bac13b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 68 additions and 12 deletions

View File

@ -18,7 +18,7 @@ namespace Samples.Client
// Note: the data types used in this sample are generated from data.proto in GrpcServiceSample
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
Console.WriteLine("Invoking grpc balance");
var request = new GetAccountRequest() { Id = "17", };

View File

@ -17,7 +17,7 @@ namespace Samples.Client
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
// Invokes a POST method named "deposit" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("Invoking deposit");

View File

@ -18,7 +18,7 @@ namespace Samples.Client
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
var eventData = new { Id = "17", Amount = 10m, };
await client.PublishEventAsync(pubsubName, "deposit", eventData, cancellationToken);

View File

@ -19,7 +19,7 @@ namespace Samples.Client
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
// Save state which will create a new etag
await client.SaveStateAsync<Widget>(storeName, stateKeyName, new Widget() { Size = "small", Color = "yellow", }, cancellationToken: cancellationToken);

View File

@ -19,7 +19,7 @@ namespace Samples.Client
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
var state = new Widget() { Size = "small", Color = "yellow", };
await client.SaveStateAsync(storeName, stateKeyName, state, cancellationToken: cancellationToken);

View File

@ -20,7 +20,7 @@ namespace Samples.Client
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
var value = new Widget() { Size = "small", Color = "yellow", };

View File

@ -14,11 +14,21 @@ namespace Dapr.Client
using Google.Protobuf;
/// <summary>
/// <para>
/// Defines client methods for interacting with Dapr endpoints.
/// Use <see cref="DaprClientBuilder"/> to create <see cref="DaprClient"/>
/// Use <see cref="DaprClientBuilder"/> to create <see cref="DaprClient"/>.
/// </para>
/// <para>
/// Implementations of <see cref="DaprClient" /> implement <see cref="IDisposable" /> because the client
/// accesses network resources. For best performance, create a single long-lived client instance and share
/// it for the lifetime of the application. Avoid creating and disposing a client instance for each operation
/// that the application performs - this can lead to socket exhaustion and other problems.
/// </para>
/// </summary>
public abstract class DaprClient
public abstract class DaprClient : IDisposable
{
private bool disposed;
/// <summary>
/// Gets the <see cref="JsonSerializerOptions" /> used for JSON serialization operations.
/// </summary>
@ -684,5 +694,23 @@ namespace Dapr.Client
string storeName,
IReadOnlyDictionary<string, string> metadata = default,
CancellationToken cancellationToken = default);
/// <inheritdoc />
public void Dispose()
{
if (!this.disposed)
{
Dispose(disposing: true);
this.disposed = true;
}
}
/// <summary>
/// Disposes the resources associated with the object.
/// </summary>
/// <param name="disposing"><c>true</c> if called by a call to the <c>Dispose</c> method; otherwise false.</param>
protected virtual void Dispose(bool disposing)
{
}
}
}

View File

@ -134,11 +134,11 @@ namespace Dapr.Client
{
throw new InvalidOperationException("The HTTP endpoint must use http or https.");
}
var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
var client = new Autogenerated.Dapr.DaprClient(channel);
return new DaprClientGrpc(client, new HttpClient(), httpEndpoint, this.JsonSerializerOptions);
return new DaprClientGrpc(channel, client, new HttpClient(), httpEndpoint, this.JsonSerializerOptions);
}
}
}

View File

@ -17,6 +17,7 @@ namespace Dapr.Client
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
using System.Net.Http.Json;
using Google.Protobuf.WellKnownTypes;
using Grpc.Net.Client;
/// <summary>
/// A client for interacting with the Dapr endpoints.
@ -27,6 +28,8 @@ namespace Dapr.Client
private readonly HttpClient httpClient;
private readonly JsonSerializerOptions jsonSerializerOptions;
private readonly GrpcChannel channel;
private readonly Autogenerated.Dapr.DaprClient client;
// property exposed for testing purposes
@ -35,11 +38,13 @@ namespace Dapr.Client
public override JsonSerializerOptions JsonSerializerOptions => jsonSerializerOptions;
internal DaprClientGrpc(
GrpcChannel channel,
Autogenerated.Dapr.DaprClient inner,
HttpClient httpClient,
Uri httpEndpoint,
JsonSerializerOptions jsonSerializerOptions)
{
this.channel = channel;
this.client = inner;
this.httpClient = httpClient;
this.httpEndpoint = httpEndpoint;
@ -825,6 +830,15 @@ namespace Dapr.Client
}
#endregion
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.channel.Dispose();
this.httpClient.Dispose();
}
}
#region Helper Methods
private CallOptions CreateCallOptions(Metadata headers, CancellationToken cancellationToken)

View File

@ -24,7 +24,7 @@ namespace Dapr.Client
/// Initializes a new instance of the <see cref="DaprClientGrpc"/> class.
/// </summary>
internal StateTestClient()
: base(new Autogenerated.Dapr.DaprClient(channel), new HttpClient(), new Uri("http://localhost"), null)
: base(channel, new Autogenerated.Dapr.DaprClient(channel), new HttpClient(), new Uri("http://localhost"), null)
{
}

View File

@ -17,6 +17,7 @@ namespace Dapr.Client.Test
using Dapr.Client.Autogen.Test.Grpc.v1;
using FluentAssertions;
using Grpc.Core;
using Grpc.Net.Client;
using Moq;
using Xunit;
@ -41,6 +42,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -64,6 +66,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -87,6 +90,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -116,6 +120,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -145,6 +150,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -176,6 +182,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -207,6 +214,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -239,6 +247,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -279,6 +288,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -294,6 +304,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -320,6 +331,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),
@ -340,6 +352,7 @@ namespace Dapr.Client.Test
// Configure Client
var httpClient = new TestHttpClient();
var client = new DaprClientGrpc(
GrpcChannel.ForAddress("http://localhost"),
Mock.Of<global::Dapr.Client.Autogen.Grpc.v1.Dapr.DaprClient>(),
httpClient,
new Uri("https://test-endpoint:3501"),

View File

@ -10,6 +10,7 @@ namespace Dapr.Client
using System.Text.Json;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Moq;
public class MockClient
@ -17,7 +18,7 @@ namespace Dapr.Client
public MockClient()
{
Mock = new Mock<Autogen.Grpc.v1.Dapr.DaprClient>(MockBehavior.Strict);
DaprClient = new DaprClientGrpc(Mock.Object, new HttpClient(), new Uri("http://localhost:3500"), new JsonSerializerOptions());
DaprClient = new DaprClientGrpc(GrpcChannel.ForAddress("http://localhost"), Mock.Object, new HttpClient(), new Uri("http://localhost:3500"), new JsonSerializerOptions());
}
public Mock<Autogen.Grpc.v1.Dapr.DaprClient> Mock { get; }