Exception and Retry Mechanism
Abnormal
ClientException
ClientException represents an explicit client error, such as the following error codes:
Error Code | Description |
---|---|
SDK.SDK_INVALID_PARAMETER | An invalid parameter value was used, and an error was detected before the request was sent to the server. |
SDK.SDK_ENDPOINT_RESOLVING_ERROR | Could not find a suitable Endpoint to send the request to the server |
SDK.SDK_INVALID_REQUEST | A Request object is used that the framework cannot handle |
ServerException
For the request result of status_code != 200, the server will provide a clear error_code as the error code during the use of the HTTP API. These failed or incorrect requests are represented by the SDK through ServerException.
For the error code and cause of ServerException, please refer to the server documentation of the HTTP API.
Retry Mechanism
Different business scenarios may require different retry policies. The SDK presets retry policies and allows custom extensions and settings.
HTTP API
Default Mechanism
- No retry by default
- After the number of retries is set, qualified requests will be retried, and unqualified requests will not be retried. The code example to enable retry is as follows:
- Python
- Java
from webullsdkcore.client import ApiClient
# Set the maximum number of retries to 3
client = ApiClient(app_key="<your_app_key>", app_secret="<your_app_secret>", region_id="<region_id>", auto_retry=True,
max_retry_num=3)
HttpApiConfig apiConfig = HttpApiConfig.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// Set the maximum number of retries to 3
.autoRetry(true)
.maxRetryNum(3)
.build();
TradeApiService apiService = new TradeHttpApiService(apiConfig);
The descriptions of judging default retry condition:
- Do not retry when exceeding the maximum number of retries.
- Do no retry when it's not a GET request.
- Client IOError will be retried, and non-specific ClientException will not be retried.
- The specific error code of the server (below) will be retried, and the non-specific error code will not be retried.
- TOO_MANY_REQUESTS
- SERVICE_NOT_AVAILABLE
- GATEWAY_TIMEOUT
- INTERNAL_ERROR
- Do not retry for other errors and exceptions.
Quote Subscription
Default Mechanism
- By default, requests that meet the conditions will be retried, and requests that do not meet the conditions will not be retried. There is no limit to the number of retries. The retry interval is 10 seconds. The code example of custom retry is as follows:
- Python
- Java
from webullsdkmdata.quotes.subscribe.default_client import DefaultQuotesClient
from webullsdkcore.retry.retry_policy import NO_RETRY_POLICY
# No Retry setting
quotes_client = DefaultQuotesClient("<your_app_key>", "<your_app_secret>", "<region_id>", retry_policy=NO_RETRY_POLICY)
QuotesSubsClient client = QuotesSubsClient.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// No Retry setting
.reconnectBy(RetryPolicy.never())
.build();
The default retry condition judgment description:
- Client IOError will be retried, and non-specific ClientException will not be retried.
- The specific error code of the mqtt protocol (as follows) is retried, and the non-specific error code is not retried.
- 3 (Server Unavailable)
- 5 (Authorization error)
- Retry when there are other errors and exceptions.
Trade Events Subscription / Quotes API
Default Mechanism
- By default, requests that meet the conditions will be retried, and requests that do not meet the conditions will not be retried. There is no limit to the number of retries. The retry interval is 10 seconds. The code example of custom retry is as follows:
- Python
- Java
from webullsdktradeeventscore.events_client import EventsClient
from webullsdkcore.retry.retry_policy import NO_RETRY_POLICY
# No Retry setting
client = EventsClient("<your_app_key>", "<your_app_secret>", region_id="<region_id>", retry_policy=NO_RETRY_POLICY)
EventClient client = EventClient.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// No Retry setting
.reconnectBy(RetryPolicy.never())
.build();
The default retry condition judgment description:
- The specific error code of the gRPC protocol (as follows) is retried, and the non-specific error code is not retried.
- 2 (UNKNOWN)
- 13 (INTERNAL)
- 14 (UNAVAILABLE)
- Do not retry for other errors and exceptions.
Custom Retry Strategy
Custom retry policy, mainly implemented by extending RetryPolicy
RetryPolicy
realizes the conditional judgment of whether to retry through RetryCondition
, and realizes the backoff strategy of retry interval through BackoffStrategy.
- Python
- Java
The following code demonstrates the implementation of RetryOnRcCodeCondition
:
# RetryCondition is implemented according to the error code of the MQTT protocol.
class RetryOnRcCodeCondition(RetryCondition):
DEFAULT_RETRYABLE_RC_CODE_LIST = [
3,
5,
]
def __init__(self, retryable_rc_code_list=None):
if retryable_rc_code_list:
self.retryable_rc_code_list = retryable_rc_code_list
else:
self.retryable_rc_code_list = self.DEFAULT_RETRYABLE_RC_CODE_LIST
def should_retry(self, retry_policy_context):
if retry_policy_context.rc_code in self.retryable_rc_code_list:
return RetryCondition.RETRY
else:
return RetryCondition.NO_RETRY
The following code demonstrates the implementation of ExponentialBackoffStrategy
:
# A Simple Exponential Backoff Strategy
class ExponentialBackoffStrategy(BackoffStrategy):
MAX_RETRY_LIMIT = 30
def __init__(self, base_delay_in_milliseconds, max_delay_in_milliseconds):
self.base_delay_in_milliseconds = base_delay_in_milliseconds
self.max_delay_in_milliseconds = max_delay_in_milliseconds
def compute_delay_before_next_retry(self, retry_policy_context):
retries = min(self.MAX_RETRY_LIMIT, retry_policy_context.retries_attempted)
delay = min(self.max_delay_in_milliseconds, self.base_delay_in_milliseconds << retries)
return delay
The following code demonstrates a simple RetryPolicy
implementation:
class MyRetryPolicy(RetryPolicy):
def __init__(self):
RetryPolicy.__init__(self, RetryOnRcCodeCondition(), ExponentialBackoffStrategy())
The following code demonstrates the implementation of QuotesSubsRetryCondition
:
// RetryCondition of streaming market data is implemented according to the MQTT protocol.
import com.webull.openapi.quotes.subsribe.lifecycle.QuotesSubsFailedContext;
import com.webull.openapi.quotes.subsribe.message.ConnAck;
import com.webull.openapi.retry.RetryContext;
import com.webull.openapi.retry.condition.RetryCondition;
import com.webull.openapi.utils.ExceptionUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class QuotesSubsRetryCondition implements RetryCondition {
private QuotesSubsRetryCondition() {
}
private static class InstanceHolder {
private static final QuotesSubsRetryCondition instance = new QuotesSubsRetryCondition();
}
public static QuotesSubsRetryCondition getInstance() {
return QuotesSubsRetryCondition.InstanceHolder.instance;
}
@Override
public boolean shouldRetry(RetryContext context) {
if (!(context instanceof QuotesSubsFailedContext)) {
throw new IllegalArgumentException("Retry context[" + context.getClass().getName() + "] is inappropriate to mqtt retry condition!");
}
QuotesSubsFailedContext quotesCtx = (QuotesSubsFailedContext) context;
if (quotesCtx.userDisconnect()) {
return false;
}
if (quotesCtx.getConnAck().isPresent()) {
int connAck = quotesCtx.getConnAck().get();
if (connAck != ConnAck.SERVER_UNAVAILABLE && connAck != ConnAck.NOT_AUTHORIZED) {
return false;
}
}
Throwable rootCause = ExceptionUtils.getRootCause(quotesCtx.getCause());
return !quotesCtx.clientDisconnect() || rootCause instanceof IOException || rootCause instanceof TimeoutException;
}
}
The following code demonstrates the implementation of ExponentialBackoffStrategy
:
// A Simple Exponential Backoff Strategy
import com.webull.openapi.retry.RetryContext;
import java.util.concurrent.TimeUnit;
public class ExponentialBackoffStrategy implements BackoffStrategy {
private static final int MAX_RETRY_LIMIT = 30;
private final long initialDelayNanos;
private final long maxDelayNanos;
public ExponentialBackoffStrategy() {
this(1, 120, TimeUnit.SECONDS);
}
public ExponentialBackoffStrategy(long initialDelay, long maxDelay, TimeUnit timeUnit) {
this.initialDelayNanos = timeUnit.toNanos(initialDelay);
this.maxDelayNanos = timeUnit.toNanos(maxDelay);
}
@Override
public long nextRetryDelay(RetryContext context, TimeUnit timeUnit) {
int retries = Math.min(ExponentialBackoffStrategy.MAX_RETRY_LIMIT, context.getRetriesAttempted());
long delay = Math.min(this.maxDelayNanos, this.initialDelayNanos << retries);
if (delay < this.initialDelayNanos) {
delay = this.maxDelayNanos;
}
return timeUnit.convert(delay, TimeUnit.NANOSECONDS);
}
}
The following code demonstrates a simple RetryPolicy
implementation:
RetryPolicy retryPolicy = new RetryPolicy(QuotesSubsRetryCondition.getInstance(), new ExponentialBackoffStrategy());