diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ringing/RingStatePollingConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ringing/RingStatePollingConfig.kt index c31a913d209..a09ec123ece 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ringing/RingStatePollingConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ringing/RingStatePollingConfig.kt @@ -23,8 +23,10 @@ package io.getstream.video.android.core.ringing * must be usable, and a non-positive one is rejected rather than quietly disabling the fallback. * * @param startAfterMs how long a ring must go without a participant's ring status changing before - * polling begins. Kept well above the coordinator's websocket ping interval so a merely slow - * answer does not poll. + * polling begins. This is the trade the whole fallback turns on: a shorter quiet period recovers a + * dropped outcome sooner, and also makes rings whose callee is merely slow to answer pay for a read + * they did not need. The default is tuned for the first, because a caller stuck on a ringing screen + * is a visible failure and an extra read is not. * @param intervalMs the gap between reads once polling has begun. Measured from the end of one * read to the start of the next, so a slow read widens the gap rather than being absorbed by it. */ @@ -40,7 +42,7 @@ public data class RingStatePollingConfig( } public companion object { - public const val DEFAULT_START_AFTER_MS: Long = 15_000 + public const val DEFAULT_START_AFTER_MS: Long = 9_000 public const val DEFAULT_INTERVAL_MS: Long = 5_000 /** diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ringing/RingStatePollerTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ringing/RingStatePollerTest.kt index 0c6a8a9bae1..7b377767457 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ringing/RingStatePollerTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ringing/RingStatePollerTest.kt @@ -67,7 +67,7 @@ class RingStatePollerTest { }) poller.start({ sessionId }, { ringTimeout }) - advanceTimeBy(14_999) + advanceTimeBy(8_999) assertThat(reads).isEqualTo(0) poller.stop() @@ -82,7 +82,7 @@ class RingStatePollerTest { }) poller.start({ sessionId }, { ringTimeout }) - advanceTimeBy(15_001) + advanceTimeBy(9_001) assertThat(reads).isEqualTo(1) advanceTimeBy(5_000) @@ -100,15 +100,15 @@ class RingStatePollerTest { }) poller.start({ sessionId }, { ringTimeout }) - advanceTimeBy(10_000) + advanceTimeBy(5_000) // A rejection from one callee does not settle a group ring, so the poller stays armed — // but the socket just proved it is alive, so the wait starts over. poller.onRingParticipantStatusUpdate() - advanceTimeBy(10_000) + advanceTimeBy(5_000) assertThat(reads).isEqualTo(0) - advanceTimeBy(5_001) + advanceTimeBy(4_001) assertThat(reads).isEqualTo(1) poller.stop() @@ -125,9 +125,9 @@ class RingStatePollerTest { poller.start({ sessionId }, { ringTimeout }) advanceTimeBy(60_000) - // 15s quiet period, then reads at 20/25/30s — the read that would land past the + // 9s quiet period, then reads at 9/14/19/24/29s — the read that would land past the // deadline is not issued. - assertThat(reads).isEqualTo(3) + assertThat(reads).isEqualTo(5) poller.stop() } @@ -144,7 +144,7 @@ class RingStatePollerTest { poller.start({ sessionId }, { ringTimeout }) advanceTimeBy(25_001) - assertThat(reads).isEqualTo(3) + assertThat(reads).isEqualTo(4) poller.stop() } @@ -157,7 +157,7 @@ class RingStatePollerTest { }) poller.start({ sessionId }, { ringTimeout }) - advanceTimeBy(15_001) + advanceTimeBy(9_001) assertThat(reads).isEqualTo(1) poller.stop() @@ -207,8 +207,8 @@ class RingStatePollerTest { poller.start({ sessionId }, { 600_000L }) advanceTimeBy(600_000) - // 60s ceiling: quiet period to 15s, then reads at 20..55s. - assertThat(reads).isEqualTo(9) + // 60s ceiling: quiet period to 9s, then reads at 9..59s. + assertThat(reads).isEqualTo(11) poller.stop() } @@ -228,8 +228,8 @@ class RingStatePollerTest { poller.start({ sessionId }, { ringTimeout }) advanceTimeBy(600_000) - // 30s window: quiet period to 15s, then reads at 15, 20 and 25s, and no further. - assertThat(reads).isEqualTo(3) + // 30s window: quiet period to 9s, then reads at 9, 14, 19, 24 and 29s, and no further. + assertThat(reads).isEqualTo(5) poller.stop() } @@ -256,7 +256,7 @@ class RingStatePollerTest { advanceTimeBy(600_000) // The reads of one loop over a 30s window, not two loops interleaved. - assertThat(reads).isEqualTo(3) + assertThat(reads).isEqualTo(5) poller.stop() } @@ -272,7 +272,7 @@ class RingStatePollerTest { poller.start({ sessionId }, { 0L }) advanceTimeBy(600_000) - assertThat(reads).isEqualTo(3) + assertThat(reads).isEqualTo(5) poller.stop() } @@ -287,8 +287,8 @@ class RingStatePollerTest { poller.start({ sessionId }, { null }) advanceTimeBy(600_000) - // 30s default, not the 60s ceiling: quiet period to 15s, then reads at 15, 20 and 25s. - assertThat(reads).isEqualTo(3) + // 30s default, not the 60s ceiling: quiet period to 9s, then reads at 9 through 29s. + assertThat(reads).isEqualTo(5) poller.stop() } @@ -325,8 +325,8 @@ class RingStatePollerTest { }, ) - // A longer window than the default: this exercises three polls, which does not fit in one - // 30s ring. + // A longer window than the default, so the reads this walks through are not also being + // bounded by the ring deadline. poller.start({ current }, { 60_000L }) advanceTimeBy(20_001) assertThat(seen).isEmpty() @@ -350,7 +350,7 @@ class RingStatePollerTest { poller.start({ sessionId }, { ringTimeout }) advanceTimeBy(20_001) - assertThat(seen).hasSize(2) + assertThat(seen).hasSize(3) assertThat(seen.first().sessionId).isEqualTo(sessionId) poller.stop() }