Skip to content

Commit effd411

Browse files
RUM-11821: Fix crash in KronosTimeProvider
1 parent 88d29ba commit effd411

File tree

4 files changed

+94
-8
lines changed

4 files changed

+94
-8
lines changed

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ internal class CoreFeature(
154154
DefaultFirstPartyHostHeaderTypeResolver(emptyMap())
155155
internal var networkInfoProvider: NetworkInfoProvider = NoOpNetworkInfoProvider()
156156
internal var systemInfoProvider: SystemInfoProvider = NoOpSystemInfoProvider()
157+
158+
@Volatile
157159
internal var timeProvider: TimeProvider = DefaultTimeProvider()
160+
158161
internal var trackingConsentProvider: ConsentProvider = NoOpConsentProvider()
159162
internal var userInfoProvider: MutableUserInfoProvider = NoOpMutableUserInfoProvider()
160163
internal var accountInfoProvider: MutableAccountInfoProvider = NoOpMutableAccountInfoProvider()
@@ -487,7 +490,7 @@ internal class CoreFeature(
487490
}
488491
}
489492

490-
timeProvider = KronosTimeProvider(this)
493+
timeProvider = KronosTimeProvider(this, internalLogger)
491494
}
492495
}
493496

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/time/KronosTimeProvider.kt

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,57 @@
66

77
package com.datadog.android.core.internal.time
88

9+
import com.datadog.android.api.InternalLogger
910
import com.datadog.android.internal.time.TimeProvider
1011
import com.lyft.kronos.Clock
1112
import java.util.concurrent.TimeUnit
1213

1314
internal class KronosTimeProvider(
14-
private val clock: Clock
15+
private val clock: Clock,
16+
private val internalLogger: InternalLogger
1517
) : TimeProvider {
1618

1719
override fun getDeviceTimestamp(): Long {
1820
return System.currentTimeMillis()
1921
}
2022

23+
@Suppress("TooGenericExceptionCaught")
2124
override fun getServerTimestamp(): Long {
22-
return clock.getCurrentTimeMs()
25+
return clock.safeGetCurrentTimeMs()
26+
.getOrElse { System.currentTimeMillis() }
2327
}
2428

29+
@Suppress("TooGenericExceptionCaught")
2530
override fun getServerOffsetMillis(): Long {
26-
val server = clock.getCurrentTimeMs()
27-
val device = System.currentTimeMillis()
28-
val delta = server - device
29-
return delta
31+
return clock.safeGetCurrentTimeMs()
32+
.map { server ->
33+
val device = System.currentTimeMillis()
34+
val delta = server - device
35+
delta
36+
}
37+
.getOrElse { 0 }
3038
}
3139

3240
override fun getServerOffsetNanos(): Long {
3341
return TimeUnit.MILLISECONDS.toNanos(getServerOffsetMillis())
3442
}
43+
44+
private fun Clock.safeGetCurrentTimeMs(): Result<Long> {
45+
return runCatching {
46+
getCurrentTimeMs()
47+
}.onFailure { ex ->
48+
internalLogger.log(
49+
level = InternalLogger.Level.ERROR,
50+
targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
51+
messageBuilder = { FAIL_MESSAGE },
52+
throwable = ex,
53+
onlyOnce = true,
54+
additionalProperties = emptyMap()
55+
)
56+
}
57+
}
58+
59+
companion object {
60+
const val FAIL_MESSAGE = "KronosClock.getCurrentTimeMs failed with an exception"
61+
}
3562
}

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/time/KronosTimeProviderTest.kt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
package com.datadog.android.core.internal.time
88

9+
import com.datadog.android.api.InternalLogger
10+
import com.datadog.android.core.internal.time.KronosTimeProvider.Companion.FAIL_MESSAGE
911
import com.datadog.android.utils.forge.Configurator
12+
import com.datadog.android.utils.verifyLog
13+
import com.datadog.tools.unit.forge.anException
1014
import com.lyft.kronos.Clock
15+
import fr.xgouchet.elmyr.Forge
1116
import fr.xgouchet.elmyr.annotation.Forgery
1217
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
1318
import fr.xgouchet.elmyr.junit5.ForgeExtension
@@ -21,6 +26,7 @@ import org.mockito.Mock
2126
import org.mockito.junit.jupiter.MockitoExtension
2227
import org.mockito.junit.jupiter.MockitoSettings
2328
import org.mockito.kotlin.doReturn
29+
import org.mockito.kotlin.doThrow
2430
import org.mockito.kotlin.whenever
2531
import org.mockito.quality.Strictness
2632
import java.util.Date
@@ -42,10 +48,13 @@ internal class KronosTimeProviderTest {
4248
@Forgery
4349
lateinit var fakeDate: Date
4450

51+
@Mock
52+
lateinit var internalLogger: InternalLogger
53+
4554
@BeforeEach
4655
fun `set up`() {
4756
whenever(mockClock.getCurrentTimeMs()) doReturn fakeDate.time
48-
testedTimeProvider = KronosTimeProvider(mockClock)
57+
testedTimeProvider = KronosTimeProvider(mockClock, internalLogger)
4958
}
5059

5160
@Test
@@ -87,6 +96,49 @@ internal class KronosTimeProviderTest {
8796
assertThat(result).isCloseTo(now, Offset.offset(TEST_OFFSET))
8897
}
8998

99+
@Test
100+
fun `M log and return 0 W getServerOffsetMillis { getCurrentTimeMs throws }`(forge: Forge) {
101+
// Given
102+
val exception = forge.anException()
103+
whenever(mockClock.getCurrentTimeMs()) doThrow exception
104+
105+
// When
106+
val result = testedTimeProvider.getServerOffsetMillis()
107+
108+
// Then
109+
internalLogger.verifyLog(
110+
level = InternalLogger.Level.ERROR,
111+
targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
112+
message = FAIL_MESSAGE,
113+
throwable = exception,
114+
onlyOnce = true,
115+
additionalProperties = emptyMap()
116+
)
117+
assertThat(result).isZero()
118+
}
119+
120+
@Test
121+
fun `M log and return System currentTimeMillis W getServerTimestamp { getCurrentTimeMs throws }`(forge: Forge) {
122+
// Given
123+
val exception = forge.anException()
124+
whenever(mockClock.getCurrentTimeMs()) doThrow exception
125+
126+
// When
127+
val now = System.currentTimeMillis()
128+
val result = testedTimeProvider.getServerTimestamp()
129+
130+
// Then
131+
internalLogger.verifyLog(
132+
level = InternalLogger.Level.ERROR,
133+
targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
134+
message = FAIL_MESSAGE,
135+
throwable = exception,
136+
onlyOnce = true,
137+
additionalProperties = emptyMap()
138+
)
139+
assertThat(result).isCloseTo(now, Offset.offset(TEST_OFFSET))
140+
}
141+
90142
companion object {
91143
const val TEST_OFFSET = 10L
92144
}

detekt_custom_safe_calls.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,9 @@ datadog:
577577
- "kotlin.ReplaceWith.constructor(kotlin.String, kotlin.Array)"
578578
- "kotlin.Result.failure(kotlin.Throwable)"
579579
- "kotlin.Result.fold(kotlin.Function1, kotlin.Function1)"
580+
- "kotlin.Result.getOrElse(kotlin.Function1)"
581+
- "kotlin.Result.map(kotlin.Function1)"
582+
- "kotlin.Result.onFailure(kotlin.Function1)"
580583
# endregion
581584
# region Kotlin Collections
582585
- "kotlin.Array.all(kotlin.Function1)"
@@ -1187,5 +1190,6 @@ datadog:
11871190
- "com.squareup.sqldelight.android.AndroidSqliteDriver.Callback.onCorruption(androidx.sqlite.db.SupportSQLiteDatabase)"
11881191
# endregion
11891192
# region Kronos
1193+
- "com.lyft.kronos.Clock.runCatching(kotlin.Function1)"
11901194
- "com.lyft.kronos.KronosClock.shutdown()"
11911195
# endregion

0 commit comments

Comments
 (0)