We have identified a memory leak in cybersource-rest-client-java SDK, caused by the way HttpClientFactory caches OkHttpClient instances.
Each time a new ApiClient is created, a new HttpClientFactoryAdditionalSettings is instantiated:
// ApiClient class
.........
private HttpClientFactoryAdditionalSettings additionalSettings = new HttpClientFactoryAdditionalSettings();
.........
public ApiClient() {
.....
additionalSettings.setCustomRetryOnConnectionFailure(true);
additionalSettings.setCustomRetryInterceptor(new RetryInterceptor(this.apiRequestMetrics));
additionalSettings.setCustomNetworkEventListener(new NetworkEventListener(this.getNewRandomId(), System.nanoTime()));
.........
This additionalSettings object contributes to the hash generated in GetHashOfMerchantConfiguration:
// HttpClientFactory
private static int GetHashOfMerchantConfiguration(MerchantConfig merchantConfig, HttpClientFactoryAdditionalSettings additionalSettings) {
return Objects.hash(
merchantConfig.getUserDefinedConnectionTimeout(),
merchantConfig.getUserDefinedReadTimeout(),
merchantConfig.getUserDefinedWriteTimeout(),
merchantConfig.getUserDefinedKeepAliveDuration(),
merchantConfig.getUserDefinedMaxIdleConnections(),
additionalSettings.getCustomLoggingInterceptor(),
additionalSettings.getCustomRetryInterceptor(),
additionalSettings.getCustomSSLSocketFactory(),
additionalSettings.getCustomX509TrustManager(),
additionalSettings.getCustomHostnameVerifier(),
additionalSettings.getCustomRetryOnConnectionFailure(),
additionalSettings.getCustomNetworkEventListener(),
additionalSettings.getCustomProxy(),
additionalSettings.getCustomProxyAuthenticator()
);
}
-
Because additionalSettings is a new object for each ApiClient, the hash is effectively always unique.
-
_httpClientInstances.computeIfAbsent(hash, ...) therefore creates a new OkHttpClient every time and stores it in the static map.
-
These OkHttpClient instances are never garbage collected, leading to memory leak.
Impact:
- Heap usage increases over time.
- Can eventually lead to OutOfMemoryError or exhaustion of file descriptors in long-running applications.
Reproduction steps:
- Create a new
ApiClient instance:
ApiClient apiClient = new ApiClient();
MerchantConfig merchantConfig = new MerchantConfig(merchantProp);
apiClient.merchantConfig = merchantConfig;
CreatePaymentRequest requestObj = new CreatePaymentRequest();
PaymentsApi apiInstance = new PaymentsApi(apiClient);
result = apiInstance.createPayment(requestObj);
- Repeat the multiple times.
- Observe heap growth and accumulation of
OkHttpClient instances in _httpClientInstances.
References
We have identified a memory leak in
cybersource-rest-client-javaSDK, caused by the wayHttpClientFactorycachesOkHttpClientinstances.Each time a new
ApiClientis created, a newHttpClientFactoryAdditionalSettingsis instantiated:This
additionalSettingsobject contributes to the hash generated inGetHashOfMerchantConfiguration:Because
additionalSettingsis a new object for eachApiClient, the hash is effectively always unique._httpClientInstances.computeIfAbsent(hash, ...)therefore creates a new OkHttpClient every time and stores it in the static map.These
OkHttpClientinstances are never garbage collected, leading to memory leak.Impact:
Reproduction steps:
ApiClientinstance:OkHttpClientinstances in_httpClientInstances.References