private static async Task ExecuteQueryImplementation(ClientRuntimeContext clientContext, int retryCount = 10, int delay = 500, string userAgent = null) { await new SynchronizationContextRemover(); // Set the TLS preference. Needed on some server os's to work when Office 365 removes support for TLS 1.0 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; var clientTag = string.Empty; if (clientContext is PnPClientContext) { retryCount = (clientContext as PnPClientContext).RetryCount; delay = (clientContext as PnPClientContext).Delay; clientTag = (clientContext as PnPClientContext).ClientTag; } int retryAttempts = 0; int backoffInterval = delay; int retryAfterInterval = 0; bool retry = false; ClientRequestWrapper wrapper = null; if (retryCount <= 0) { throw new ArgumentException("Provide a retry count greater than zero."); } if (delay <= 0) { throw new ArgumentException("Provide a delay greater than zero."); } // Do while retry attempt is less than retry count while (retryAttempts < retryCount) { try { clientContext.ClientTag = SetClientTag(clientTag); // Make CSOM request more reliable by disabling the return value cache. Given we // often clone context objects and the default value is clientContext.DisableReturnValueCache = true; // Add event handler to "insert" app decoration header to mark the PnP Sites Core library as a known application EventHandler <WebRequestEventArgs> appDecorationHandler = AttachRequestUserAgent(userAgent); clientContext.ExecutingWebRequest += appDecorationHandler; // DO NOT CHANGE THIS TO EXECUTEQUERYRETRY if (!retry) { await clientContext.ExecuteQueryAsync(); } else { if (wrapper != null && wrapper.Value != null) { await clientContext.RetryQueryAsync(wrapper.Value); } } // Remove the app decoration event handler after the executequery clientContext.ExecutingWebRequest -= appDecorationHandler; return; } catch (WebException wex) { var response = wex.Response as HttpWebResponse; // Check if request was throttled - http status code 429 // Check is request failed due to server unavailable - http status code 503 if (response != null && (response.StatusCode == (HttpStatusCode)429 || response.StatusCode == (HttpStatusCode)503 // || response.StatusCode == (HttpStatusCode)500 )) { Log.Warning(Constants.LOGGING_SOURCE, CoreResources.ClientContextExtensions_ExecuteQueryRetry, backoffInterval); wrapper = (ClientRequestWrapper)wex.Data["ClientRequest"]; retry = true; retryAfterInterval = backoffInterval; //Add delay for retry, retry-after header is specified in seconds await Task.Delay(retryAfterInterval); //Add to retry count and increase delay. retryAttempts++; backoffInterval = backoffInterval * 2; } else { var errorSb = new System.Text.StringBuilder(); errorSb.AppendLine(wex.ToString()); if (response != null) { //if(response.Headers["SPRequestGuid"] != null) if (response.Headers.AllKeys.Any(k => string.Equals(k, "SPRequestGuid", StringComparison.InvariantCultureIgnoreCase))) { var spRequestGuid = response.Headers["SPRequestGuid"]; errorSb.AppendLine($"ServerErrorTraceCorrelationId: {spRequestGuid}"); } } Log.Error(Constants.LOGGING_SOURCE, CoreResources.ClientContextExtensions_ExecuteQueryRetryException, errorSb.ToString()); throw; } } catch (Microsoft.SharePoint.Client.ServerException serverEx) { var errorSb = new System.Text.StringBuilder(); errorSb.AppendLine(serverEx.ToString()); errorSb.AppendLine($"ServerErrorCode: {serverEx.ServerErrorCode}"); errorSb.AppendLine($"ServerErrorTypeName: {serverEx.ServerErrorTypeName}"); errorSb.AppendLine($"ServerErrorTraceCorrelationId: {serverEx.ServerErrorTraceCorrelationId}"); errorSb.AppendLine($"ServerErrorValue: {serverEx.ServerErrorValue}"); errorSb.AppendLine($"ServerErrorDetails: {serverEx.ServerErrorDetails}"); Log.Error(Constants.LOGGING_SOURCE, CoreResources.ClientContextExtensions_ExecuteQueryRetryException, errorSb.ToString()); throw; } } throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has be attempted."); }
private static void ExecuteQueryImplementation(ClientRuntimeContext clientContext, int retryCount = 10, int delay = 500, string userAgent = null) #endif { #if !ONPREMISES await new SynchronizationContextRemover(); // Set the TLS preference. Needed on some server os's to work when Office 365 removes support for TLS 1.0 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; #endif var clientTag = string.Empty; if (clientContext is PnPClientContext) { retryCount = (clientContext as PnPClientContext).RetryCount; delay = (clientContext as PnPClientContext).Delay; clientTag = (clientContext as PnPClientContext).ClientTag; } int retryAttempts = 0; int backoffInterval = delay; #if !ONPREMISES int retryAfterInterval = 0; bool retry = false; ClientRequestWrapper wrapper = null; #endif if (retryCount <= 0) { throw new ArgumentException("Provide a retry count greater than zero."); } if (delay <= 0) { throw new ArgumentException("Provide a delay greater than zero."); } // Do while retry attempt is less than retry count while (retryAttempts < retryCount) { try { clientContext.ClientTag = SetClientTag(clientTag); // Make CSOM request more reliable by disabling the return value cache. Given we // often clone context objects and the default value is #if !ONPREMISES || SP2016 || SP2019 clientContext.DisableReturnValueCache = true; #endif // Add event handler to "insert" app decoration header to mark the PnP Sites Core library as a known application EventHandler <WebRequestEventArgs> appDecorationHandler = AttachRequestUserAgent(userAgent); clientContext.ExecutingWebRequest += appDecorationHandler; // DO NOT CHANGE THIS TO EXECUTEQUERYRETRY #if !ONPREMISES if (!retry) { #if !NETSTANDARD2_0 await clientContext.ExecuteQueryAsync(); #else clientContext.ExecuteQuery(); #endif } else { if (wrapper != null && wrapper.Value != null) { #if !NETSTANDARD2_0 await clientContext.RetryQueryAsync(wrapper.Value); #else clientContext.RetryQuery(wrapper.Value); #endif } } #else clientContext.ExecuteQuery(); #endif // Remove the app decoration event handler after the executequery clientContext.ExecutingWebRequest -= appDecorationHandler; return; } catch (WebException wex) { var response = wex.Response as HttpWebResponse; // Check if request was throttled - http status code 429 // Check is request failed due to server unavailable - http status code 503 if (response != null && (response.StatusCode == (HttpStatusCode)429 || response.StatusCode == (HttpStatusCode)503)) { Log.Warning(Constants.LOGGING_SOURCE, CoreResources.ClientContextExtensions_ExecuteQueryRetry, backoffInterval); #if !ONPREMISES wrapper = (ClientRequestWrapper)wex.Data["ClientRequest"]; retry = true; // Determine the retry after value - use the retry-after header when available // Retry-After seems to default to a fixed 120 seconds in most cases, let's revert to our // previous logic //string retryAfterHeader = response.GetResponseHeader("Retry-After"); //if (!string.IsNullOrEmpty(retryAfterHeader)) //{ // if (!Int32.TryParse(retryAfterHeader, out retryAfterInterval)) // { // retryAfterInterval = backoffInterval; // } //} //else //{ retryAfterInterval = backoffInterval; //} //Add delay for retry, retry-after header is specified in seconds await Task.Delay(retryAfterInterval); #else Thread.Sleep(backoffInterval); #endif //Add to retry count and increase delay. retryAttempts++; backoffInterval = backoffInterval * 2; } else { Log.Error(Constants.LOGGING_SOURCE, CoreResources.ClientContextExtensions_ExecuteQueryRetryException, wex.ToString()); throw; } } } throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has been reached."); }
/// <summary> /// Executes the query with incremental retry. /// </summary> /// <param name="clientContext">The client context.</param> /// <param name="retryCount">The retry count.</param> /// <param name="delay">The delay.</param> /// <exception cref="ArgumentException"> /// Provide a retry count greater than zero. /// or /// Provide a delay greater than zero. /// </exception> /// <exception cref="MaximumRetryAttemptedException">Maximum retry attempts {retryCount}</exception> public static void ExecuteQueryWithIncrementalRetry(this ClientContext clientContext, int retryCount, int delay) { int retryAttempts = 0; int backoffInterval = delay; int retryAfterInterval = 0; bool retry = false; ClientRequestWrapper wrapper = null; if (retryCount <= 0) { throw new ArgumentException("Provide a retry count greater than zero."); } if (delay <= 0) { throw new ArgumentException("Provide a delay greater than zero."); } clientContext.RequestTimeout = Timeout.Infinite; // Do while retry attempt is less than retry count while (retryAttempts < retryCount) { try { if (!retry) { clientContext.ExecuteQuery(); return; } else { // retry the previous request if (wrapper != null && wrapper.Value != null) { clientContext.RetryQuery(wrapper.Value); return; } } } catch (WebException ex) { var response = ex.Response as HttpWebResponse; // Check if request was throttled - http status code 429 // Check is request failed due to server unavailable - http status code 503 if (response != null && (response.StatusCode == (HttpStatusCode)429 || response.StatusCode == (HttpStatusCode)503)) { wrapper = (ClientRequestWrapper)ex.Data["ClientRequest"]; retry = true; // Determine the retry after value - use the retry-after header when available string retryAfterHeader = response.GetResponseHeader("Retry-After"); if (!string.IsNullOrEmpty(retryAfterHeader)) { if (!Int32.TryParse(retryAfterHeader, out retryAfterInterval)) { retryAfterInterval = backoffInterval; } } else { retryAfterInterval = backoffInterval; } // Delay for the requested seconds Thread.Sleep(retryAfterInterval * 1000); // Increase counters retryAttempts++; backoffInterval = backoffInterval * 2; } else { throw; } } } throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has be attempted."); }