public void WithCallTiming_NullSettings() { CallSettings noSettings = null; Assert.Null(noSettings.WithCallTiming(null)); CallTiming timing = CallTiming.FromExpiration(Expiration.None); var result = noSettings.WithCallTiming(timing); Assert.Same(timing, result.Timing); }
/// <summary> /// Updates the call settings with configuration parameters. /// </summary> /// <param name="callSettings">The call settings.</param> /// <param name="config">The configuration.</param> /// <param name="serviceContext">The service context.</param> /// <returns></returns> private CallSettings UpdateCallSettingsWithConfigParameters(CallSettings callSettings, GoogleAdsConfig config, GoogleAdsServiceContext serviceContext) { callSettings = callSettings.WithHeader(GoogleAdsConfig.DEVELOPER_TOKEN_KEYNAME, config.DeveloperToken) .WithResponseMetadataHandler(delegate(Metadata metadata) { GoogleAdsResponseMetadata responseMetadata = new GoogleAdsResponseMetadata(metadata); serviceContext.OnResponseMetadataReceived(responseMetadata); }); if (!string.IsNullOrEmpty(config.LoginCustomerId)) { callSettings = callSettings.WithHeader(GoogleAdsConfig.LOGIN_CUSTOMER_ID_KEYNAME, config.LoginCustomerId); } if (!string.IsNullOrEmpty(config.LibraryIdentifierOverride)) { callSettings = callSettings.WithHeader(GoogleAdsConfig.LIBRARY_IDENTIFIER_KEYNAME, config.LibraryIdentifierOverride); } callSettings = callSettings.WithCallTiming(CallTiming.FromTimeout( TimeSpan.FromMilliseconds(config.Timeout))); return(callSettings); }
/// <summary> /// Asynchronously polls the operation until it is complete, returning the completed operation. /// </summary> /// <remarks> /// <para> /// If this object already represents a completed operation, it is returned with no further RPCs. /// If <paramref name="metadataCallback"/> is non-null, the callback will be called with any metadata /// present before the result is returned. /// </para> /// <para> /// No guarantee is made as to which thread is used for metadata callbacks. However, each callback is /// performed synchronously: this method waits for the callback to complete before the operation is next polled. /// This guarantees that for a single call, metadata callbacks will never occur in parallel. /// </para> /// </remarks> /// <param name="pollSettings">The settings to use for repeated polling, or null /// to use the default poll settings (poll once every 10 seconds, forever).</param> /// <param name="callSettings">The call settings to apply on each call, or null for default settings.</param> /// <param name="metadataCallback">The callback to invoke with the metadata from each poll operation, even if the metadata is null.</param> /// <returns>The completed operation, which can then be checked for errors or a result.</returns> public Task <Operation <TResponse, TMetadata> > PollUntilCompletedAsync( PollSettings pollSettings = null, CallSettings callSettings = null, Action <TMetadata> metadataCallback = null) { if (IsCompleted) { metadataCallback?.Invoke(Metadata); return(Task.FromResult(this)); } // We need to work out the effective timing so that we can truncate any deadline, but anything else // that's in the effective call settings can be left to the normal merging process. In particular, // we don't want to include the header mutation that adds the version header, as otherwise we'll end up // including it twice. var effectiveTiming = Client.GetEffectiveCallSettingsForGetOperation(callSettings)?.Timing; callSettings = callSettings.WithCallTiming(effectiveTiming); Func <DateTime?, Task <Operation <TResponse, TMetadata> > > pollAction = async deadline => { var result = await PollOnceAsync(callSettings.WithEarlierDeadline(deadline, Client.Clock)).ConfigureAwait(false); metadataCallback?.Invoke(result.Metadata); return(result); }; return(Polling.PollRepeatedlyAsync( pollAction, o => o.IsCompleted, Client.Clock, Client.Scheduler, pollSettings ?? Client.DefaultPollSettings ?? s_defaultPollSettings, callSettings?.CancellationToken ?? CancellationToken.None)); }
public void WithCallTiming_NullTiming() { CallTiming timing = CallTiming.FromExpiration(Expiration.None); CancellationToken token = new CancellationTokenSource().Token; var original = new CallSettings(token, null, timing, null, null, null); var result = original.WithCallTiming(null); Assert.Null(result.Timing); Assert.Equal(token, result.CancellationToken); }
public void WithCallTiming_NonNull() { CallTiming timing1 = CallTiming.FromExpiration(Expiration.None); CallTiming timing2 = CallTiming.FromDeadline(DateTime.UtcNow); CancellationToken token = new CancellationTokenSource().Token; var original = new CallSettings(token, null, timing1, null, null, null); var result = original.WithCallTiming(timing2); Assert.Same(timing2, result.Timing); Assert.Equal(token, result.CancellationToken); }
// TODO: This is a modified/simplified version of ApiCallRetryExtensions.WithRetry from Google.Api.Gax.Grpc. Can we combine them somehow? internal static async Task RetryOperationUntilCompleted( Func <Task <bool> > fn, IClock clock, IScheduler scheduler, CallSettings callSettings, RetrySettings retrySettings) { DateTime?overallDeadline = retrySettings.TotalExpiration.CalculateDeadline(clock); TimeSpan retryDelay = retrySettings.RetryBackoff.Delay; TimeSpan callTimeout = retrySettings.TimeoutBackoff.Delay; while (true) { DateTime attemptDeadline = clock.GetCurrentDateTimeUtc() + callTimeout; // Note: this handles a null total deadline due to "<" returning false if overallDeadline is null. DateTime combinedDeadline = overallDeadline < attemptDeadline ? overallDeadline.Value : attemptDeadline; CallSettings attemptCallSettings = callSettings.WithCallTiming(CallTiming.FromDeadline(combinedDeadline)); TimeSpan actualDelay = retrySettings.DelayJitter.GetDelay(retryDelay); DateTime expectedRetryTime; try { bool isResponseOk = await fn().ConfigureAwait(false); if (isResponseOk) { return; } expectedRetryTime = clock.GetCurrentDateTimeUtc() + actualDelay; if (expectedRetryTime > overallDeadline) { // TODO: Can we get this string from somewhere? throw new RpcException(new Status(StatusCode.DeadlineExceeded, "Deadline Exceeded")); } } catch (RpcException e) when(retrySettings.RetryFilter(e)) { expectedRetryTime = clock.GetCurrentDateTimeUtc() + actualDelay; if (expectedRetryTime > overallDeadline) { throw; } } await scheduler.Delay(actualDelay, callSettings.CancellationToken.GetValueOrDefault()).ConfigureAwait(false); retryDelay = retrySettings.RetryBackoff.NextDelay(retryDelay); callTimeout = retrySettings.TimeoutBackoff.NextDelay(callTimeout); } }
internal static async Task <TResponse> Retry <TRequest, TResponse>( Func <TRequest, CallSettings, Task <TResponse> > fn, TRequest request, CallSettings callSettings, IClock clock, IScheduler scheduler) { RetrySettings retrySettings = callSettings.Timing?.Retry; if (retrySettings == null) { return(await fn(request, callSettings).ConfigureAwait(false)); } DateTime?overallDeadline = retrySettings.TotalExpiration.CalculateDeadline(clock); TimeSpan retryDelay = retrySettings.RetryBackoff.Delay; TimeSpan callTimeout = retrySettings.TimeoutBackoff.Delay; while (true) { DateTime attemptDeadline = clock.GetCurrentDateTimeUtc() + callTimeout; // Note: this handles a null total deadline due to "<" returning false if overallDeadline is null. DateTime combinedDeadline = overallDeadline < attemptDeadline ? overallDeadline.Value : attemptDeadline; CallSettings attemptCallSettings = callSettings.WithCallTiming(CallTiming.FromDeadline(combinedDeadline)); try { return(await fn(request, attemptCallSettings).ConfigureAwait(false)); } catch (RpcException e) when(retrySettings.RetryFilter(e)) { TimeSpan actualDelay = retrySettings.DelayJitter.GetDelay(retryDelay); DateTime expectedRetryTime = clock.GetCurrentDateTimeUtc() + actualDelay; if (expectedRetryTime > overallDeadline) { throw; } await scheduler.Delay(actualDelay, callSettings.CancellationToken.GetValueOrDefault()).ConfigureAwait(false); retryDelay = retrySettings.RetryBackoff.NextDelay(retryDelay); callTimeout = retrySettings.TimeoutBackoff.NextDelay(callTimeout); } } }