diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/impl/PreferencesService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/impl/PreferencesService.kt index 0caa063c52..66caf86a45 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/impl/PreferencesService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/impl/PreferencesService.kt @@ -11,6 +11,7 @@ import com.onesignal.core.internal.startup.IStartableService import com.onesignal.core.internal.time.ITime import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay internal class PreferencesService( @@ -234,6 +235,9 @@ internal class PreferencesService( // wait to be woken up for the next pass waiter.waitForWake() + } catch (e: CancellationException) { + // Swallowing this turns the loop into a hot spin once the scope is gone. + throw e } catch (e: Throwable) { Logging.log(LogLevel.ERROR, "Error with Preference work loop", e) } diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/preferences/PreferencesServiceTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/preferences/PreferencesServiceTests.kt index 52b07f8423..ffdefa1220 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/preferences/PreferencesServiceTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/preferences/PreferencesServiceTests.kt @@ -3,7 +3,9 @@ package com.onesignal.core.internal.preferences import android.content.Context import androidx.test.core.app.ApplicationProvider import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import com.onesignal.common.threading.OneSignalDispatchers import com.onesignal.core.internal.preferences.impl.PreferencesService +import com.onesignal.debug.ILogListener import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.mocks.AndroidMockHelper @@ -12,6 +14,7 @@ import io.kotest.assertions.throwables.shouldThrowUnit import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import kotlinx.coroutines.delay +import java.util.concurrent.atomic.AtomicInteger @RobolectricTest class PreferencesServiceTests : FunSpec({ @@ -228,4 +231,23 @@ class PreferencesServiceTests : FunSpec({ store.contains(mockStringPrefStoreKey) shouldBe false store.contains(mockStringSetPrefStoreKey) shouldBe false } + + test("the write loop stops when its scope is cancelled instead of spinning on the error path") { + val preferencesService = PreferencesService(AndroidMockHelper.applicationService(), MockHelper.time(1000)) + val loopErrors = AtomicInteger() + val listener = ILogListener { if (it.entry.contains("Error with Preference work loop")) loopErrors.incrementAndGet() } + Logging.addListener(listener) + try { + preferencesService.start() + preferencesService.saveBool(mockPrefStoreName, mockBoolPrefStoreKey, true) + delay(300) + + OneSignalDispatchers.resetForTest() + delay(500) + + loopErrors.get() shouldBe 0 + } finally { + Logging.removeListener(listener) + } + } })