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."); }