Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/flags-retry-transport-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'posthog': patch
'posthog-android': patch
'posthog-server': patch
---

Retry feature flag requests that fail with a DNS, TLS, or connection-refused transport error, instead of only timeouts, EOF, and connection resets
6 changes: 6 additions & 0 deletions posthog/src/main/java/com/posthog/internal/PostHogApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ import okio.BufferedSink
import java.io.EOFException
import java.io.IOException
import java.io.OutputStream
import java.net.ConnectException
import java.net.SocketException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLException

/**
* The class that calls the PostHog API
Expand Down Expand Up @@ -306,6 +309,9 @@ public class PostHogApi(
is IOException ->
error is SocketTimeoutException ||
error is EOFException ||
error is UnknownHostException ||
error is SSLException ||
error is ConnectException ||
(error is SocketException && error.message?.contains("reset", ignoreCase = true) == true)
else -> false
}
Expand Down
33 changes: 29 additions & 4 deletions posthog/src/test/java/com/posthog/internal/PostHogApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
import java.io.IOException
import java.net.ConnectException
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.SocketException
import java.net.UnknownHostException
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.net.ssl.SSLException
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand Down Expand Up @@ -401,14 +404,16 @@ internal class PostHogApiTest {
}
}

@Test
fun `flags does not retry connection refused`() {
private fun assertFlagsRetries(
error: IOException,
expectedAttempts: Int,
) {
val attempts = AtomicInteger(0)
val client =
OkHttpClient.Builder()
.addInterceptor {
attempts.incrementAndGet()
throw SocketException("Connection refused")
throw error
}
.build()
val http = mockHttp(response = MockResponse().setBody("{}"))
Expand All @@ -421,13 +426,33 @@ internal class PostHogApiTest {
sut.flags("distinctId", anonymousId = "anonId", groups = emptyMap())
}

assertEquals(1, attempts.get())
assertEquals(expectedAttempts, attempts.get())
assertEquals(0, http.requestCount)
} finally {
http.shutdown()
}
}

@Test
fun `flags retries connection refused`() {
assertFlagsRetries(ConnectException("Connection refused"), expectedAttempts = 2)
}

@Test
fun `flags retries dns resolution failures`() {
assertFlagsRetries(UnknownHostException("app.posthog.com"), expectedAttempts = 2)
}

@Test
fun `flags retries tls transport failures`() {
assertFlagsRetries(SSLException("handshake aborted"), expectedAttempts = 2)
}

@Test
fun `flags does not retry non transport IO failures`() {
assertFlagsRetries(IOException("unexpected end of stream"), expectedAttempts = 1)
}

@Test
fun `remote config returns successful response`() {
val file = File("src/test/resources/json/basic-remote-config.json")
Expand Down
Loading