Skip to content

Commit

Permalink
gRPC client retries (#1187)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Mar 8, 2021
1 parent 978200a commit 3765363
Show file tree
Hide file tree
Showing 68 changed files with 7,230 additions and 380 deletions.
2 changes: 1 addition & 1 deletion src/Grpc.AspNetCore.Server/InterceptorRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal InterceptorRegistration(
{
throw new ArgumentNullException(nameof(arguments));
}
for (int i = 0; i < arguments.Length; i++)
for (var i = 0; i < arguments.Length; i++)
{
if (arguments[i] == null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Grpc.AspNetCore.Web/Internal/GrpcWebProtocolHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private static void WriteTrailersContent(Span<byte> buffer, IHeaderDictionary tr
// gRPC-Web protocol says that names should be lower-case and grpc-web JS client
// will check for 'grpc-status' and 'grpc-message' in trailers with lower-case key.
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2
for (int i = 0; i < kv.Key.Length; i++)
for (var i = 0; i < kv.Key.Length; i++)
{
char c = kv.Key[i];
currentBuffer[i] = (byte)((uint)(c - 'A') <= ('Z' - 'A') ? c | 0x20 : c);
Expand Down
68 changes: 68 additions & 0 deletions src/Grpc.Net.Client/Configuration/ConfigObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System.Collections;
using System.Collections.Generic;
using Grpc.Net.Client.Internal.Configuration;

namespace Grpc.Net.Client.Configuration
{
/// <summary>
/// Represents a configuration object. Implementations provide strongly typed wrappers over
/// collections of untyped values.
/// </summary>
public abstract class ConfigObject : IConfigValue
{
/// <summary>
/// Gets the underlying configuration values.
/// </summary>
public IDictionary<string, object> Inner { get; }

internal ConfigObject() : this(new Dictionary<string, object>())
{
}

internal ConfigObject(IDictionary<string, object> inner)
{
Inner = inner;
}

object IConfigValue.Inner => Inner;

internal T? GetValue<T>(string key)
{
if (Inner.TryGetValue(key, out var value))
{
return (T?)value;
}
return default;
}

internal void SetValue<T>(string key, T? value)
{
if (value == null)
{
Inner.Remove(key);
}
else
{
Inner[key] = value;
}
}
}
}
89 changes: 89 additions & 0 deletions src/Grpc.Net.Client/Configuration/HedgingPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System;
using System.Collections.Generic;
using Grpc.Core;
using Grpc.Net.Client.Internal.Configuration;

namespace Grpc.Net.Client.Configuration
{
/// <summary>
/// The hedging policy for outgoing calls. Hedged calls may execute more than
/// once on the server, so only idempotent methods should specify a hedging
/// policy.
/// </summary>
/// <remarks>
/// <para>
/// Represents the <c>HedgingPolicy</c> message in https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto.
/// </para>
/// </remarks>
public sealed class HedgingPolicy : ConfigObject
{
internal const string MaxAttemptsPropertyName = "maxAttempts";
internal const string HedgingDelayPropertyName = "hedgingDelay";
internal const string NonFatalStatusCodesPropertyName = "nonFatalStatusCodes";

private ConfigProperty<Values<StatusCode, object>, IList<object>> _nonFatalStatusCodes =
new(i => new Values<StatusCode, object>(i ?? new List<object>(), s => ConvertHelpers.ConvertStatusCode(s), s => ConvertHelpers.ConvertStatusCode(s.ToString()!)), NonFatalStatusCodesPropertyName);

/// <summary>
/// Initializes a new instance of the <see cref="HedgingPolicy"/> class.
/// </summary>
public HedgingPolicy() { }
internal HedgingPolicy(IDictionary<string, object> inner) : base(inner) { }

/// <summary>
/// Gets or sets the maximum number of call attempts. This value includes the original attempt.
/// The hedging policy will send up to this number of calls.
///
/// This property is required and must be 2 or greater.
/// This value is limited by <see cref="GrpcChannelOptions.MaxRetryAttempts"/>.
/// </summary>
public int? MaxAttempts
{
get => GetValue<int>(MaxAttemptsPropertyName);
set => SetValue(MaxAttemptsPropertyName, value);
}

/// <summary>
/// Gets or sets the hedging delay.
/// The first call will be sent immediately, but the subsequent
/// hedged call will be sent at intervals of the specified delay.
/// Set this to 0 or <c>null</c> to immediately send all hedged calls.
/// </summary>
public TimeSpan? HedgingDelay
{
get => ConvertHelpers.ConvertDurationText(GetValue<string>(HedgingDelayPropertyName));
set => SetValue(HedgingDelayPropertyName, ConvertHelpers.ToDurationText(value));
}

/// <summary>
/// Gets a collection of status codes which indicate other hedged calls may still
/// succeed. If a non-fatal status code is returned by the server, hedged
/// calls will continue. Otherwise, outstanding requests will be canceled and
/// the error returned to the client application layer.
///
/// Specifying status codes is optional.
/// </summary>
public IList<StatusCode> NonFatalStatusCodes
{
get => _nonFatalStatusCodes.GetValue(this)!;
}
}
}
101 changes: 101 additions & 0 deletions src/Grpc.Net.Client/Configuration/MethodConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System.Collections.Generic;
using Grpc.Net.Client.Internal.Configuration;

namespace Grpc.Net.Client.Configuration
{
/// <summary>
/// Configuration for a method.
/// The <see cref="Names"/> collection is used to determine which methods this configuration applies to.
/// </summary>
/// <remarks>
/// <para>
/// Represents the <c>MethodConfig</c> message in https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto.
/// </para>
/// </remarks>
public sealed class MethodConfig : ConfigObject
{
private const string NamePropertyName = "name";
private const string RetryPolicyPropertyName = "retryPolicy";
private const string HedgingPolicyPropertyName = "hedgingPolicy";

private ConfigProperty<Values<MethodName, object>, IList<object>> _names =
new(i => new Values<MethodName, object>(i ?? new List<object>(), s => s.Inner, s => new MethodName((IDictionary<string, object>)s)), NamePropertyName);

private ConfigProperty<RetryPolicy, IDictionary<string, object>> _retryPolicy =
new(i => i != null ? new RetryPolicy(i) : null, RetryPolicyPropertyName);

private ConfigProperty<HedgingPolicy, IDictionary<string, object>> _hedgingPolicy =
new(i => i != null ? new HedgingPolicy(i) : null, HedgingPolicyPropertyName);

/// <summary>
/// Initializes a new instance of the <see cref="MethodConfig"/> class.
/// </summary>
public MethodConfig() { }
internal MethodConfig(IDictionary<string, object> inner) : base(inner) { }

/// <summary>
/// Gets or sets the retry policy for outgoing calls.
/// A retry policy can't be combined with <see cref="HedgingPolicy"/>.
/// </summary>
public RetryPolicy? RetryPolicy
{
get => _retryPolicy.GetValue(this);
set => _retryPolicy.SetValue(this, value);
}

/// <summary>
/// Gets or sets the hedging policy for outgoing calls. Hedged calls may execute
/// more than once on the server, so only idempotent methods should specify a hedging
/// policy. A hedging policy can't be combined with <see cref="RetryPolicy"/>.
/// </summary>
public HedgingPolicy? HedgingPolicy
{
get => _hedgingPolicy.GetValue(this);
set => _hedgingPolicy.SetValue(this, value);
}

/// <summary>
/// Gets a collection of names which determine the calls the method config will apply to.
/// A <see cref="MethodConfig"/> without names won't be used. Each name must be unique
/// across an entire <see cref="ServiceConfig"/>.
/// </summary>
/// <remarks>
/// <para>
/// If a name's <see cref="MethodName.Method"/> property isn't set then the method config is the default
/// for all methods for the specified service.
/// </para>
/// <para>
/// If a name's <see cref="MethodName.Service"/> property isn't set then <see cref="MethodName.Method"/> must also be unset,
/// and the method config is the default for all methods on all services.
/// <see cref="MethodName.Default"/> represents this global default name.
/// </para>
/// <para>
/// When determining which method config to use for a given RPC, the most specific match wins. A method config
/// with a configured <see cref="MethodName"/> that exactly matches a call's method and service will be used
/// instead of a service or global default method config.
/// </para>
/// </remarks>
public IList<MethodName> Names
{
get => _names.GetValue(this)!;
}
}
}
82 changes: 82 additions & 0 deletions src/Grpc.Net.Client/Configuration/MethodName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Grpc.Net.Client.Configuration
{
/// <summary>
/// The name of a method. Used to configure what calls a <see cref="MethodConfig"/> applies to using
/// the <see cref="MethodConfig.Names"/> collection.
/// </summary>
/// <remarks>
/// <para>
/// Represents the <c>Name</c> message in https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto.
/// </para>
/// <para>
/// If a name's <see cref="MethodName.Method"/> property isn't set then the method config is the default
/// for all methods for the specified service.
/// </para>
/// <para>
/// If a name's <see cref="MethodName.Service"/> property isn't set then <see cref="MethodName.Method"/> must also be unset,
/// and the method config is the default for all methods on all services.
/// <see cref="MethodName.Default"/> represents this global default name.
/// </para>
/// <para>
/// When determining which method config to use for a given RPC, the most specific match wins. A method config
/// with a configured <see cref="MethodName"/> that exactly matches a call's method and service will be used
/// instead of a service or global default method config.
/// </para>
/// </remarks>
public sealed class MethodName
: ConfigObject
{
/// <summary>
/// A global default name.
/// </summary>
public static readonly MethodName Default = new MethodName(new ReadOnlyDictionary<string, object>(new Dictionary<string, object>()));

private const string ServicePropertyName = "service";
private const string MethodPropertyName = "method";

/// <summary>
/// Initializes a new instance of the <see cref="MethodName"/> class.
/// </summary>
public MethodName() { }
internal MethodName(IDictionary<string, object> inner) : base(inner) { }

/// <summary>
/// Gets or sets the service name.
/// </summary>
public string? Service
{
get => GetValue<string>(ServicePropertyName);
set => SetValue(ServicePropertyName, value);
}

/// <summary>
/// Gets or sets the method name.
/// </summary>
public string? Method
{
get => GetValue<string>(MethodPropertyName);
set => SetValue(MethodPropertyName, value);
}
}
}
Loading

0 comments on commit 3765363

Please sign in to comment.