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