Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow session retries to continue #62

Merged
merged 1 commit into from
Nov 14, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,10 @@ internal data class ApiRequest(
throw IllegalStateException(ex.localizedMessage ?: "", ex)
}
}

/**
* Returns true if the request is a session request. This heuristic should not be widely used
* - it is only used to prioritise session requests over other requests.
*/
fun isSessionRequest(): Boolean = url.toString().endsWith("sessions")
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal class EmbraceDeliveryRetryManager(
*/
override fun scheduleForRetry(request: ApiRequest, payload: ByteArray) {
logger.logDeveloper(TAG, "Scheduling api call for retry")
if (pendingRetriesCount() < MAX_FAILED_CALLS) {
if (pendingRetriesCount() < MAX_FAILED_CALLS || request.isSessionRequest()) {
val scheduleJob = retryQueue.isEmpty()
val cachedPayloadName = cacheManager.savePayload(payload)
val failedApiCall = DeliveryFailedApiCall(request, cachedPayloadName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import io.embrace.android.embracesdk.BuildConfig
import io.embrace.android.embracesdk.ResourceReader
import io.embrace.android.embracesdk.internal.EmbraceSerializer
import io.embrace.android.embracesdk.network.http.HttpMethod
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

internal class ApiRequestTest {
Expand All @@ -28,7 +31,7 @@ internal class ApiRequestTest {

@Test
fun testFullHeaders() {
Assert.assertEquals(
assertEquals(
mapOf(
"Accept" to "application/json",
"User-Agent" to "Embrace/a/1",
Expand All @@ -48,7 +51,7 @@ internal class ApiRequestTest {
@Test
fun testMinimalHeaders() {
val minimal = ApiRequest(url = EmbraceUrl.create("https://google.com"))
Assert.assertEquals(
assertEquals(
mapOf(
"Accept" to "application/json",
"User-Agent" to "Embrace/a/${BuildConfig.VERSION_NAME}",
Expand All @@ -63,14 +66,14 @@ internal class ApiRequestTest {
val expectedInfo = ResourceReader.readResourceAsText("api_request.json")
.filter { !it.isWhitespace() }
val observed = serializer.toJson(request)
Assert.assertEquals(expectedInfo, observed)
assertEquals(expectedInfo, observed)
}

@Test
fun testDeserialization() {
val json = ResourceReader.readResourceAsText("api_request.json")
val obj = serializer.fromJson(json, ApiRequest::class.java)
Assert.assertEquals(
assertEquals(
mapOf(
"Accept" to "application/json",
"User-Agent" to "Embrace/a/1",
Expand All @@ -90,6 +93,14 @@ internal class ApiRequestTest {
@Test
fun testEmptyObject() {
val info = serializer.fromJson("{}", ApiRequest::class.java)
Assert.assertNotNull(info)
assertNotNull(info)
}

@Test
fun testSessionRequest() {
assertFalse(request.isSessionRequest())

val copy = request.copy(url = EmbraceUrl.create("https://example.com/sessions"))
assertTrue(copy.isSessionRequest())
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package io.embrace.android.embracesdk.comms.delivery

import io.embrace.android.embracesdk.EmbraceEvent
import io.embrace.android.embracesdk.capture.connectivity.NetworkConnectivityService
import io.embrace.android.embracesdk.comms.api.ApiRequest
import io.embrace.android.embracesdk.comms.api.ApiRequestMapper
import io.embrace.android.embracesdk.comms.api.EmbraceApiUrlBuilder
import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService
import io.embrace.android.embracesdk.payload.Event
import io.embrace.android.embracesdk.payload.EventMessage
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
Expand Down Expand Up @@ -241,14 +246,55 @@ internal class EmbraceDeliveryRetryManagerTest {

assertEquals(0, deliveryRetryManager.pendingRetriesCount())

val mockApiRequest = mockk<ApiRequest>()
val mockApiRequest = mockk<ApiRequest>(relaxed = true)
repeat(201) {
deliveryRetryManager.scheduleForRetry(mockApiRequest, "{ dummy_payload }".toByteArray())
blockingScheduledExecutorService.runCurrentlyBlocked()
}
assertEquals(200, deliveryRetryManager.pendingRetriesCount())
}

@Test
fun `queue prioritises keeping sessions when saturated`() {
initDeliveryRetryManager(status = NetworkStatus.WIFI, loadFailedRequest = false)

// populate queue with logs up to max of 200 items
val mapper = ApiRequestMapper(
EmbraceApiUrlBuilder(
"https://data.emb-api.com",
"https://config.emb-api.com",
"appId",
lazy { "deviceId" },
lazy { "appVersionName" }
),
lazy { "deviceId" }, "appId"
)
repeat(250) { k ->
val request = mapper.logRequest(
EventMessage(
Event(
type = EmbraceEvent.Type.INFO_LOG,
eventId = "eventId",
messageId = "message_id_$k"
)
)
)
deliveryRetryManager.scheduleForRetry(request, ByteArray(0))
}

// verify logs were added to the queue, and that most recently added requests are dropped
assertEquals(200, failedApiCalls.size)
assertEquals("il:message_id_0", failedApiCalls.first().apiRequest.logId)
assertEquals("il:message_id_199", failedApiCalls.last().apiRequest.logId)

// now add some sessions for retry
deliveryRetryManager.scheduleForRetry(mapper.sessionRequest(), ByteArray(0))
assertEquals(201, failedApiCalls.size)
assertEquals("il:message_id_0", failedApiCalls.first().apiRequest.logId)
val request = failedApiCalls.last().apiRequest
assertTrue(request.url.toString().endsWith("/sessions"))
}

private fun clearApiPipeline() {
clearMocks(mockRetryMethod, answers = false)
failedApiCalls.clear()
Expand Down