public void ExecuteTransaction(Action <MessageExchangeProtocol> protocolHandler)
        {
            var retryInterval = ServiceEndpoint.RetryListeningSleepInterval;

            Exception lastError = null;

            // retryAllowed is also used to indicate if the error occurred before or after the connection was made
            var retryAllowed = true;
            var watch        = Stopwatch.StartNew();

            for (var i = 0; i < ServiceEndpoint.RetryCountLimit && retryAllowed && watch.Elapsed < ServiceEndpoint.ConnectionErrorRetryTimeout; i++)
            {
                if (i > 0)
                {
                    Thread.Sleep(retryInterval);
                    log.Write(EventType.Error, "Retry attempt {0}", i);
                }

                try
                {
                    lastError = null;

                    IConnection connection = null;
                    try
                    {
                        connection = connectionManager.AcquireConnection(new WebSocketConnectionFactory(clientCertificate), serviceEndpoint, log);

                        // Beyond this point, we have no way to be certain that the server hasn't tried to process a request; therefore, we can't retry after this point
                        retryAllowed = false;

                        protocolHandler(connection.Protocol);
                    }
                    catch
                    {
                        connection?.Dispose();
                        throw;
                    }

                    // Only return the connection to the pool if all went well
                    connectionManager.ReleaseConnection(serviceEndpoint, connection);
                }
                catch (AuthenticationException aex)
                {
                    lastError    = aex;
                    retryAllowed = false;
                }
                catch (WebSocketException wse) when(wse.Message == "Unable to connect to the remote server")
                {
                    lastError    = wse;
                    retryAllowed = false;
                }
                catch (WebSocketException wse)
                {
                    lastError = wse;
                    // When the host is not found or reset the connection an immediate retry isn't going to help
                    if ((wse.InnerException?.Message.StartsWith("The remote name could not be resolved:") ?? false) ||
                        (wse.InnerException?.IsSocketConnectionReset() ?? false) ||
                        wse.IsSocketConnectionReset())
                    {
                        retryAllowed = false;
                    }
                    else
                    {
                        log.Write(EventType.Error, $"Socket communication error with connection to  {serviceEndpoint.Format()}");
                    }
                }
                catch (ConnectionInitializationFailedException cex)
                {
                    log.WriteException(EventType.Error, $"Connection initialization failed while connecting to  {serviceEndpoint.Format()}", cex);
                    lastError    = cex;
                    retryAllowed = true;

                    // If this is the second failure, clear the pooled connections as a precaution
                    // against all connections in the pool being bad
                    if (i == 1)
                    {
                        connectionManager.ClearPooledConnections(serviceEndpoint, log);
                    }
                }
                catch (Exception ex)
                {
                    log.WriteException(EventType.Error, "Unexpected exception executing transaction.", ex);
                    lastError = ex;
                }
            }

            HandleError(lastError, retryAllowed);
        }