Skip to content

Commit f786365

Browse files
Merge pull request #3014 from DataDog/aleksandr-gringauz/RUM-11821/fix-kronos-crash
RUM-11821: Fix crash in KronosTimeProvider
2 parents c633626 + 7cffdcd commit f786365

File tree

4 files changed

+90
-8
lines changed

4 files changed

+90
-8
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ internal class CoreFeature(
487487
}
488488
}
489489

490-
timeProvider = KronosTimeProvider(this)
490+
timeProvider = KronosTimeProvider(this, internalLogger)
491491
}
492492
}
493493

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,55 @@
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

2123
override fun getServerTimestamp(): Long {
22-
return clock.getCurrentTimeMs()
24+
return clock.safeGetCurrentTimeMs()
25+
.getOrElse { System.currentTimeMillis() }
2326
}
2427

2528
override fun getServerOffsetMillis(): Long {
26-
val server = clock.getCurrentTimeMs()
27-
val device = System.currentTimeMillis()
28-
val delta = server - device
29-
return delta
29+
return clock.safeGetCurrentTimeMs()
30+
.map { server ->
31+
val device = System.currentTimeMillis()
32+
val delta = server - device
33+
delta
34+
}
35+
.getOrDefault(0)
3036
}
3137

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

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.WARN,
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.WARN,
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,10 @@ 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.getOrDefault(kotlin.Long)"
581+
- "kotlin.Result.getOrElse(kotlin.Function1)"
582+
- "kotlin.Result.map(kotlin.Function1)"
583+
- "kotlin.Result.onFailure(kotlin.Function1)"
580584
# endregion
581585
# region Kotlin Collections
582586
- "kotlin.Array.all(kotlin.Function1)"
@@ -1187,5 +1191,6 @@ datadog:
11871191
- "com.squareup.sqldelight.android.AndroidSqliteDriver.Callback.onCorruption(androidx.sqlite.db.SupportSQLiteDatabase)"
11881192
# endregion
11891193
# region Kronos
1194+
- "com.lyft.kronos.Clock.runCatching(kotlin.Function1)"
11901195
- "com.lyft.kronos.KronosClock.shutdown()"
11911196
# endregion

0 commit comments

Comments
 (0)