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() } 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() +}