Skip to content

Commit 06b6936

Browse files
Dexterp37pocmo
authored andcommitted
Run the Glean API off the main thread by default
This introduces the internal Dispatchers object, which allows to use a predefined scope for launching functions off the main thread. Moreover, this also adds the test only FakeDispatchersInTest class, which allows to block on API calls during tests, ensuring they don't fail due to racing.
1 parent 9d8b08b commit 06b6936

File tree

10 files changed

+159
-19
lines changed

10 files changed

+159
-19
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.service.glean
6+
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.ObsoleteCoroutinesApi
9+
import kotlinx.coroutines.newSingleThreadContext
10+
11+
@ObsoleteCoroutinesApi
12+
internal object Dispatchers {
13+
/**
14+
* A coroutine scope to make it easy to dispatch API calls off the main thread.
15+
* This needs to be a `var` so that our tests can override this.
16+
*/
17+
var API = CoroutineScope(newSingleThreadContext("GleanAPIPool"))
18+
}

components/service/glean/src/main/java/mozilla/components/service/glean/EventMetricType.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package mozilla.components.service.glean
66

7+
import kotlinx.coroutines.launch
78
import mozilla.components.service.glean.storages.EventsStorageEngine
89
import mozilla.components.support.base.log.logger.Logger
910

@@ -101,14 +102,16 @@ data class EventMetricType(
101102
eventKeys
102103
}
103104

104-
// Delegate storing the event to the storage engine.
105-
EventsStorageEngine.record(
106-
stores = getStorageNames(),
107-
category = category,
108-
name = name,
109-
objectId = objectId,
110-
value = truncatedValue,
111-
extra = truncatedExtraKeys
112-
)
105+
Dispatchers.API.launch {
106+
// Delegate storing the event to the storage engine.
107+
EventsStorageEngine.record(
108+
stores = getStorageNames(),
109+
category = category,
110+
name = name,
111+
objectId = objectId,
112+
value = truncatedValue,
113+
extra = truncatedExtraKeys
114+
)
115+
}
113116
}
114117
}

components/service/glean/src/main/java/mozilla/components/service/glean/StringMetricType.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package mozilla.components.service.glean
66

7+
import kotlinx.coroutines.launch
78
import mozilla.components.service.glean.storages.StringsStorageEngine
89
import mozilla.components.support.base.log.logger.Logger
910

@@ -54,10 +55,12 @@ data class StringMetricType(
5455
it
5556
}
5657

57-
// Delegate storing the string to the storage engine.
58-
StringsStorageEngine.record(
59-
this,
60-
value = truncatedValue
61-
)
58+
Dispatchers.API.launch {
59+
// Delegate storing the string to the storage engine.
60+
StringsStorageEngine.record(
61+
this@StringMetricType,
62+
value = truncatedValue
63+
)
64+
}
6265
}
6366
}

components/service/glean/src/main/java/mozilla/components/service/glean/UuidMetricType.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package mozilla.components.service.glean
66

7+
import kotlinx.coroutines.launch
78
import java.util.UUID
89

910
import mozilla.components.service.glean.storages.UuidsStorageEngine
@@ -31,15 +32,26 @@ data class UuidMetricType(
3132

3233
/**
3334
* Generate a new UUID value and set it in the metric store.
35+
*
36+
* @return a [UUID] or [null] if we're not allowed to record.
3437
*/
35-
fun generateAndSet(): UUID {
38+
fun generateAndSet(): UUID? {
39+
// Even if `set` is already checking if we're allowed to record,
40+
// we need to check here as well otherwise we'd return a `UUID`
41+
// that won't be stored anywhere.
42+
if (!shouldRecord(logger)) {
43+
return null
44+
}
45+
3646
val uuid = UUID.randomUUID()
3747
set(uuid)
3848
return uuid
3949
}
4050

4151
/**
4252
* Explicitly set an existing UUID value
53+
*
54+
* @param value a valid [UUID] to set the metric to
4355
*/
4456
fun set(value: UUID) {
4557
// TODO report errors through other special metrics handled by the SDK. See bug 1499761.
@@ -48,10 +60,12 @@ data class UuidMetricType(
4860
return
4961
}
5062

51-
// Delegate storing the event to the storage engine.
52-
UuidsStorageEngine.record(
53-
this,
63+
Dispatchers.API.launch {
64+
// Delegate storing the event to the storage engine.
65+
UuidsStorageEngine.record(
66+
this@UuidMetricType,
5467
value = value
55-
)
68+
)
69+
}
5670
}
5771
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.service.glean
6+
7+
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.runBlocking
9+
import org.junit.Test
10+
11+
import org.junit.Assert.assertEquals
12+
import org.junit.Assert.assertNotSame
13+
import org.junit.Assert.assertSame
14+
15+
class DispatchersTest {
16+
17+
@Test
18+
fun `API scope runs off the main thread`() {
19+
val mainThread = Thread.currentThread()
20+
var threadCanary = false
21+
22+
runBlocking {
23+
Dispatchers.API.launch {
24+
assertNotSame(mainThread, Thread.currentThread())
25+
// Use the canary bool to make sure this is getting called before
26+
// the test completes.
27+
assertEquals(false, threadCanary)
28+
threadCanary = true
29+
}.join()
30+
}
31+
32+
assertEquals(true, threadCanary)
33+
assertSame(mainThread, Thread.currentThread())
34+
}
35+
}

components/service/glean/src/test/java/mozilla/components/service/glean/EventMetricTypeTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,27 @@
55
package mozilla.components.service.glean
66

77
import android.os.SystemClock
8+
import kotlinx.coroutines.ExperimentalCoroutinesApi
9+
import kotlinx.coroutines.ObsoleteCoroutinesApi
810
import mozilla.components.service.glean.storages.EventsStorageEngine
911
import org.junit.Assert.assertEquals
1012
import org.junit.Assert.assertNull
1113
import org.junit.Assert.assertTrue
1214
import org.junit.Test
1315

1416
import org.junit.Before
17+
import org.junit.Rule
1518
import org.junit.runner.RunWith
1619
import org.robolectric.RobolectricTestRunner
1720

21+
@ObsoleteCoroutinesApi
22+
@ExperimentalCoroutinesApi
1823
@RunWith(RobolectricTestRunner::class)
1924
class EventMetricTypeTest {
2025

26+
@get:Rule
27+
val fakeDispatchers = FakeDispatchersInTest()
28+
2129
@Before
2230
fun setUp() {
2331
EventsStorageEngine.clearAllStores()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
package mozilla.components.service.glean
6+
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.ExperimentalCoroutinesApi
9+
import kotlinx.coroutines.ObsoleteCoroutinesApi
10+
import org.junit.rules.ExternalResource
11+
12+
/**
13+
* A class that stubs the coroutine scopes in [Dispatchers] to allow blocking
14+
* on async API calls when running tests.
15+
*
16+
* Note: according to [kotlinx.coroutines.Dispatchers.Unconfined], this will work
17+
* as long as the API doesn't use any suspending function.
18+
*/
19+
@ExperimentalCoroutinesApi @ObsoleteCoroutinesApi
20+
class FakeDispatchersInTest : ExternalResource() {
21+
companion object {
22+
private val testMainScope = CoroutineScope(kotlinx.coroutines.Dispatchers.Unconfined)
23+
private val originalAPIScope = Dispatchers.API
24+
}
25+
26+
override fun before() {
27+
Dispatchers.API = testMainScope
28+
}
29+
30+
override fun after() {
31+
Dispatchers.API = originalAPIScope
32+
}
33+
}

components/service/glean/src/test/java/mozilla/components/service/glean/GleanTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package mozilla.components.service.glean
66

77
import android.content.Context
88
import androidx.test.core.app.ApplicationProvider
9+
import kotlinx.coroutines.ExperimentalCoroutinesApi
10+
import kotlinx.coroutines.ObsoleteCoroutinesApi
911
import kotlinx.coroutines.runBlocking
1012
import mozilla.components.service.glean.net.HttpPingUploader
1113
import mozilla.components.service.glean.storages.EventsStorageEngine
@@ -18,6 +20,7 @@ import org.junit.Assert.assertNotNull
1820
import org.junit.Assert.assertNull
1921
import org.junit.Assert.assertTrue
2022
import org.junit.Before
23+
import org.junit.Rule
2124
import org.junit.Test
2225
import org.junit.runner.RunWith
2326
import org.mockito.ArgumentMatchers.anyString
@@ -27,8 +30,14 @@ import org.robolectric.RobolectricTestRunner
2730
import java.io.File
2831
import java.util.UUID
2932

33+
@ObsoleteCoroutinesApi
34+
@ExperimentalCoroutinesApi
3035
@RunWith(RobolectricTestRunner::class)
3136
class GleanTest {
37+
38+
@get:Rule
39+
val fakeDispatchers = FakeDispatchersInTest()
40+
3241
@Before
3342
fun setup() {
3443
Glean.initialize(

components/service/glean/src/test/java/mozilla/components/service/glean/StringMetricTypeTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,25 @@ package mozilla.components.service.glean
66

77
import android.content.Context
88
import androidx.test.core.app.ApplicationProvider
9+
import kotlinx.coroutines.ExperimentalCoroutinesApi
10+
import kotlinx.coroutines.ObsoleteCoroutinesApi
911
import mozilla.components.service.glean.storages.StringsStorageEngine
1012
import org.junit.Assert.assertEquals
1113
import org.junit.Assert.assertNull
1214
import org.junit.Before
15+
import org.junit.Rule
1316
import org.junit.Test
1417
import org.junit.runner.RunWith
1518
import org.robolectric.RobolectricTestRunner
1619

20+
@ObsoleteCoroutinesApi
21+
@ExperimentalCoroutinesApi
1722
@RunWith(RobolectricTestRunner::class)
1823
class StringMetricTypeTest {
1924

25+
@get:Rule
26+
val fakeDispatchers = FakeDispatchersInTest()
27+
2028
@Before
2129
fun setUp() {
2230
StringsStorageEngine.applicationContext = ApplicationProvider.getApplicationContext()

components/service/glean/src/test/java/mozilla/components/service/glean/UuidMetricTypeTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,26 @@ package mozilla.components.service.glean
66

77
import android.content.Context
88
import androidx.test.core.app.ApplicationProvider
9+
import kotlinx.coroutines.ExperimentalCoroutinesApi
10+
import kotlinx.coroutines.ObsoleteCoroutinesApi
911
import mozilla.components.service.glean.storages.UuidsStorageEngine
1012
import org.junit.Assert.assertEquals
1113
import org.junit.Assert.assertNull
1214
import org.junit.Before
15+
import org.junit.Rule
1316
import org.junit.Test
1417
import org.junit.runner.RunWith
1518
import org.robolectric.RobolectricTestRunner
1619
import java.util.UUID
1720

21+
@ObsoleteCoroutinesApi
22+
@ExperimentalCoroutinesApi
1823
@RunWith(RobolectricTestRunner::class)
1924
class UuidMetricTypeTest {
2025

26+
@get:Rule
27+
val fakeDispatchers = FakeDispatchersInTest()
28+
2129
@Before
2230
fun setUp() {
2331
UuidsStorageEngine.applicationContext = ApplicationProvider.getApplicationContext()
@@ -52,6 +60,7 @@ class UuidMetricTypeTest {
5260

5361
val uuid2 = UUID.fromString("ce2adeb8-843a-4232-87a5-a099ed1e7bb3")
5462
uuidMetric.set(uuid2)
63+
5564
// Check that data was properly recorded.
5665
val snapshot2 = UuidsStorageEngine.getSnapshot(storeName = "store1", clearStore = false)
5766
assertEquals(1, snapshot2!!.size)

0 commit comments

Comments
 (0)