Skip to content

Commit 9865df2

Browse files
runningcodeclaude
andcommitted
chore(deps): Bump OkHttp from 4.9.2 to 5.5.1
sentry-okhttp and the Spring Boot integrations still compile against okhttp as compileOnly. The okhttp3 members they reference are identical for 4.9.2 and 5.5.1, so OkHttp 4 keeps working at runtime. OkHttp 5 tries IPv6 first and races routes, which surfaced a bug: connectFailed() marked the whole call span INTERNAL_ERROR even when OkHttp then connected on another route. It now only closes the connect timing. callFailed() still reports a call that fails on every route. sentry-apollo and the Android UI tests stay on mockwebserver 4. Apollo 2 passes a null body to Response.Builder.body(), and the UI test APK is pinned to the app's okio 2, while OkHttp 5 needs okio 3. Fixes JAVA-715 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7803c61 commit 9865df2

6 files changed

Lines changed: 56 additions & 13 deletions

File tree

‎gradle/libs.versions.toml‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ log4j2 = "2.26.1"
2525
nopen = "1.0.1"
2626
# see https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html#kotlin-compatibility
2727
# see https://developer.android.com/jetpack/androidx/releases/compose-kotlin
28-
okhttp = "4.9.2"
28+
okhttp = "5.5.1"
2929
openfeature = "1.18.2"
3030
otel = "1.63.0"
3131
otelAlpha = "1.63.0-alpha"
@@ -292,8 +292,10 @@ mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "6.4.
292292
mockito-core = { module = "org.mockito:mockito-core", version = "5.24.0" }
293293
msgpack = { module = "org.msgpack:msgpack-core", version = "0.9.8" }
294294
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
295-
# apollo-runtime 5.x pulls in okhttp 5.x; mockwebserver 4.x is incompatible with that classpath
296-
okhttp-mockwebserver5 = { module = "com.squareup.okhttp3:mockwebserver", version = "5.3.0" }
295+
# For tests that can't run on okhttp 5: apollo-runtime 2.x passes a null body to
296+
# Response.Builder.body(), and the Android UI tests are pinned to leakcanary's okio 2, while okhttp
297+
# 5 needs okio 3.
298+
okhttp-mockwebserver4 = { module = "com.squareup.okhttp3:mockwebserver", version = "4.9.2" }
297299
okio = { module = "com.squareup.okio:okio", version = "1.13.0" }
298300
roboelectric = { module = "org.robolectric:robolectric", version = "4.15" }
299301

‎sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ dependencies {
122122
androidTestImplementation(libs.awaitility3.kotlin)
123123
androidTestImplementation(libs.kotlin.test.junit)
124124
androidTestImplementation(libs.leakcanary.instrumentation)
125-
androidTestImplementation(libs.okhttp.mockwebserver)
125+
androidTestImplementation(libs.okhttp.mockwebserver4)
126126
}
127127

128128
tasks.withType<JavaCompile>().configureEach {

‎sentry-apollo-5/build.gradle.kts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dependencies {
3838
testImplementation(libs.kotlinx.coroutines.test)
3939
testImplementation(libs.mockito.kotlin)
4040
testImplementation(libs.mockito.core)
41-
testImplementation(libs.okhttp.mockwebserver5)
41+
testImplementation(libs.okhttp.mockwebserver)
4242
}
4343

4444
tasks.withType<JavaCompile>().configureEach {

‎sentry-apollo/build.gradle.kts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ dependencies {
3939
testImplementation(libs.kotlinx.coroutines)
4040
testImplementation(libs.mockito.kotlin)
4141
testImplementation(libs.mockito.core)
42-
testImplementation(libs.okhttp.mockwebserver)
42+
testImplementation(libs.okhttp.mockwebserver4)
4343
}
4444

4545
tasks.withType<JavaCompile>().configureEach {

‎sentry-okhttp/src/main/java/io/sentry/okhttp/SentryOkHttpEventListener.kt‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,9 @@ public open class SentryOkHttpEventListener(
245245
}
246246
val okHttpEvent: SentryOkHttpEvent = eventMap[call] ?: return
247247
okHttpEvent.setProtocol(protocol?.name)
248-
okHttpEvent.setError(ioe.message)
249-
okHttpEvent.onEventFinish(CONNECT_EVENT) {
250-
it.throwable = ioe
251-
it.status = SpanStatus.INTERNAL_ERROR
252-
}
248+
// A failed route doesn't fail the call: OkHttp moves on to the next route, and OkHttp 5 races
249+
// routes for Happy Eyeballs. If no route connects, callFailed() reports the error.
250+
okHttpEvent.onEventFinish(CONNECT_EVENT)
253251
}
254252

255253
override fun connectionAcquired(call: Call, connection: Connection) {

‎sentry-okhttp/src/test/java/io/sentry/okhttp/SentryOkHttpEventListenerTest.kt‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.okhttp
22

3+
import com.google.common.truth.Truth.assertThat
34
import io.sentry.BaggageHeader
45
import io.sentry.IScopes
56
import io.sentry.SentryOptions
@@ -9,6 +10,7 @@ import io.sentry.SpanDataConvention
910
import io.sentry.SpanStatus
1011
import io.sentry.TransactionContext
1112
import io.sentry.mockServerRequestTimeoutMillis
13+
import java.io.IOException
1214
import java.util.concurrent.TimeUnit
1315
import kotlin.test.Test
1416
import kotlin.test.assertEquals
@@ -26,7 +28,6 @@ import okhttp3.mockwebserver.MockResponse
2628
import okhttp3.mockwebserver.MockWebServer
2729
import okhttp3.mockwebserver.SocketPolicy
2830
import org.mockito.kotlin.any
29-
import org.mockito.kotlin.anyOrNull
3031
import org.mockito.kotlin.eq
3132
import org.mockito.kotlin.mock
3233
import org.mockito.kotlin.spy
@@ -281,7 +282,7 @@ class SentryOkHttpEventListenerTest {
281282
fun `call span status is not overridden if not null`() {
282283
val mockListener = mock<EventListener>()
283284
lateinit var call: Call
284-
whenever(mockListener.connectStart(any(), anyOrNull(), anyOrNull())).then {
285+
whenever(mockListener.requestHeadersStart(any())).then {
285286
val okHttpEvent = SentryOkHttpEventListener.eventMap[call]
286287
val callSpan = okHttpEvent?.callSpan
287288
assertNotNull(callSpan)
@@ -300,6 +301,48 @@ class SentryOkHttpEventListenerTest {
300301
assertEquals(SpanStatus.UNKNOWN, callSpan.status)
301302
}
302303

304+
@Test
305+
fun `a failed route does not fail the call when the next route connects`() {
306+
val sut = fixture.getSut()
307+
val listener = fixture.sentryOkHttpEventListener
308+
val call = sut.newCall(getRequest())
309+
val response = mock<Response>()
310+
whenever(response.code).thenReturn(200)
311+
whenever(response.protocol).thenReturn(Protocol.HTTP_1_1)
312+
313+
listener.callStart(call)
314+
listener.connectStart(call, mock(), mock())
315+
listener.connectFailed(call, mock(), mock(), null, IOException("Connection refused"))
316+
listener.connectStart(call, mock(), mock())
317+
listener.connectEnd(call, mock(), mock(), Protocol.HTTP_1_1)
318+
listener.responseHeadersStart(call)
319+
listener.responseHeadersEnd(call, response)
320+
val callSpan = SentryOkHttpEventListener.eventMap[call]!!.callSpan!!
321+
listener.callEnd(call)
322+
323+
assertThat(callSpan.status).isEqualTo(SpanStatus.OK)
324+
assertThat(callSpan.throwable).isNull()
325+
assertThat(callSpan.getData("error_message")).isNull()
326+
}
327+
328+
@Test
329+
fun `a call that fails to connect on every route is marked as failed`() {
330+
val sut = fixture.getSut()
331+
val listener = fixture.sentryOkHttpEventListener
332+
val call = sut.newCall(getRequest())
333+
val error = IOException("Connection refused")
334+
335+
listener.callStart(call)
336+
listener.connectStart(call, mock(), mock())
337+
listener.connectFailed(call, mock(), mock(), null, error)
338+
val callSpan = SentryOkHttpEventListener.eventMap[call]!!.callSpan!!
339+
listener.callFailed(call, error)
340+
341+
assertThat(callSpan.status).isEqualTo(SpanStatus.INTERNAL_ERROR)
342+
assertThat(callSpan.throwable).isSameInstanceAs(error)
343+
assertThat(callSpan.getData("error_message")).isEqualTo("Connection refused")
344+
}
345+
303346
private fun verifyDelegation(
304347
listener: SentryOkHttpEventListener,
305348
originalListener: EventListener,

0 commit comments

Comments
 (0)