From eded6b252d5dae6210605b30d6ddd1af9cea66dd Mon Sep 17 00:00:00 2001 From: stainless-bot Date: Mon, 28 Oct 2024 22:51:08 +0000 Subject: [PATCH] chore: rebuild project due to codegen change --- .../braintrustdata/api/core/ClientOptions.kt | 13 ++++-- .../api/core/PhantomReachable.kt | 46 +++++++++++++++++++ .../http/PhantomReachableClosingHttpClient.kt | 21 +++++++++ .../api/core/PhantomReachableTest.kt | 27 +++++++++++ 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt create mode 100644 braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingHttpClient.kt create mode 100644 braintrust-java-core/src/test/kotlin/com/braintrustdata/api/core/PhantomReachableTest.kt diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt index 3ccabbe4..77f87fc8 100755 --- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt +++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt @@ -3,6 +3,7 @@ package com.braintrustdata.api.core import com.braintrustdata.api.core.http.HttpClient +import com.braintrustdata.api.core.http.PhantomReachableClosingHttpClient import com.braintrustdata.api.core.http.RetryingHttpClient import com.fasterxml.jackson.databind.json.JsonMapper import com.google.common.collect.ArrayListMultimap @@ -141,11 +142,13 @@ private constructor( return ClientOptions( httpClient!!, - RetryingHttpClient.builder() - .httpClient(httpClient!!) - .clock(clock) - .maxRetries(maxRetries) - .build(), + PhantomReachableClosingHttpClient( + RetryingHttpClient.builder() + .httpClient(httpClient!!) + .clock(clock) + .maxRetries(maxRetries) + .build() + ), jsonMapper ?: jsonMapper(), clock, baseUrl, diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt new file mode 100644 index 00000000..d3f62aab --- /dev/null +++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt @@ -0,0 +1,46 @@ +@file:JvmName("PhantomReachable") + +package com.braintrustdata.api.core + +import com.braintrustdata.api.errors.BraintrustException +import java.lang.reflect.InvocationTargetException + +/** + * Closes [closeable] when [observed] becomes only phantom reachable. + * + * This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions. + */ +@JvmSynthetic +internal fun closeWhenPhantomReachable(observed: Any, closeable: AutoCloseable) { + check(observed !== closeable) { + "`observed` cannot be the same object as `closeable` because it would never become phantom reachable" + } + closeWhenPhantomReachable?.let { it(observed, closeable::close) } +} + +private val closeWhenPhantomReachable: ((Any, AutoCloseable) -> Unit)? by lazy { + try { + val cleanerClass = Class.forName("java.lang.ref.Cleaner") + val cleanerCreate = cleanerClass.getMethod("create") + val cleanerRegister = + cleanerClass.getMethod("register", Any::class.java, Runnable::class.java) + val cleanerObject = cleanerCreate.invoke(null); + + { observed, closeable -> + try { + cleanerRegister.invoke(cleanerObject, observed, Runnable { closeable.close() }) + } catch (e: ReflectiveOperationException) { + if (e is InvocationTargetException) { + when (val cause = e.cause) { + is RuntimeException, + is Error -> throw cause + } + } + throw BraintrustException("Unexpected reflective invocation failure", e) + } + } + } catch (e: ReflectiveOperationException) { + // We're running Java 8, which has no Cleaner. + null + } +} diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingHttpClient.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingHttpClient.kt new file mode 100644 index 00000000..3f4673df --- /dev/null +++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/http/PhantomReachableClosingHttpClient.kt @@ -0,0 +1,21 @@ +package com.braintrustdata.api.core.http + +import com.braintrustdata.api.core.RequestOptions +import com.braintrustdata.api.core.closeWhenPhantomReachable +import java.util.concurrent.CompletableFuture + +internal class PhantomReachableClosingHttpClient(private val httpClient: HttpClient) : HttpClient { + init { + closeWhenPhantomReachable(this, httpClient) + } + + override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse = + httpClient.execute(request, requestOptions) + + override fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions + ): CompletableFuture = httpClient.executeAsync(request, requestOptions) + + override fun close() = httpClient.close() +} diff --git a/braintrust-java-core/src/test/kotlin/com/braintrustdata/api/core/PhantomReachableTest.kt b/braintrust-java-core/src/test/kotlin/com/braintrustdata/api/core/PhantomReachableTest.kt new file mode 100644 index 00000000..adf8a799 --- /dev/null +++ b/braintrust-java-core/src/test/kotlin/com/braintrustdata/api/core/PhantomReachableTest.kt @@ -0,0 +1,27 @@ +package com.braintrustdata.api.core + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class PhantomReachableTest { + + @Test + fun closeWhenPhantomReachable_whenObservedIsGarbageCollected_closesCloseable() { + var closed = false + val closeable = AutoCloseable { closed = true } + + closeWhenPhantomReachable( + // Pass an inline object for the object to observe so that it becomes immediately + // unreachable. + Any(), + closeable + ) + + assertThat(closed).isFalse() + + System.gc() + Thread.sleep(3000) + + assertThat(closed).isTrue() + } +}