From 6c789d0d47afc82045aaa8ab22b7fbc54ccc668d Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:58:54 +0100 Subject: [PATCH 1/2] fix: always close retry sleeper --- .../com/openai/core/http/RetryingHttpClient.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt index 317205674..1a431c653 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt @@ -131,7 +131,18 @@ private constructor( } override fun close() { - httpClient.close() + try { + httpClient.close() + } catch (httpClientFailure: Throwable) { + try { + sleeper.close() + } catch (sleeperFailure: Throwable) { + if (sleeperFailure !== httpClientFailure) { + httpClientFailure.addSuppressed(sleeperFailure) + } + } + throw httpClientFailure + } sleeper.close() } From 9120a1259c354f7a58dde903d605a8ee7eeb417a Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:59:12 +0100 Subject: [PATCH 2/2] test: cover RetryingHttpClient close failures --- .../core/http/RetryingHttpClientCloseTest.kt | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 openai-java-core/src/test/kotlin/com/openai/core/http/RetryingHttpClientCloseTest.kt diff --git a/openai-java-core/src/test/kotlin/com/openai/core/http/RetryingHttpClientCloseTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/http/RetryingHttpClientCloseTest.kt new file mode 100644 index 000000000..24ca07f3b --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/core/http/RetryingHttpClientCloseTest.kt @@ -0,0 +1,73 @@ +package com.openai.core.http + +import com.openai.core.Sleeper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +internal class RetryingHttpClientCloseTest { + + @Test + fun closeClosesDelegateAndSleeper() { + val delegate = mock() + val sleeper = mock() + val client = client(delegate, sleeper) + + client.close() + + verify(delegate).close() + verify(sleeper).close() + } + + @Test + fun closeStillClosesSleeperWhenDelegateCloseFails() { + val delegateFailure = IllegalStateException("delegate close failed") + val delegate = mock() + val sleeper = mock() + doThrow(delegateFailure).whenever(delegate).close() + val client = client(delegate, sleeper) + + val thrown = runCatching { client.close() }.exceptionOrNull() + + assertThat(thrown).isSameAs(delegateFailure) + verify(sleeper).close() + } + + @Test + fun closeSuppressesSleeperFailureWhenBothCloseOperationsFail() { + val delegateFailure = IllegalStateException("delegate close failed") + val sleeperFailure = IllegalArgumentException("sleeper close failed") + val delegate = mock() + val sleeper = mock() + doThrow(delegateFailure).whenever(delegate).close() + doThrow(sleeperFailure).whenever(sleeper).close() + val client = client(delegate, sleeper) + + val thrown = runCatching { client.close() }.exceptionOrNull() + + assertThat(thrown).isSameAs(delegateFailure) + assertThat(thrown!!.suppressed).containsExactly(sleeperFailure) + verify(sleeper).close() + } + + @Test + fun closePropagatesSleeperFailureWhenDelegateClosesSuccessfully() { + val sleeperFailure = IllegalStateException("sleeper close failed") + val delegate = mock() + val sleeper = mock() + doThrow(sleeperFailure).whenever(sleeper).close() + val client = client(delegate, sleeper) + + val thrown = runCatching { client.close() }.exceptionOrNull() + + assertThat(thrown).isSameAs(sleeperFailure) + verify(delegate).close() + verify(sleeper).close() + } + + private fun client(delegate: HttpClient, sleeper: Sleeper): HttpClient = + RetryingHttpClient.builder().httpClient(delegate).sleeper(sleeper).build() +}