diff --git a/formula/src/main/java/com/instacart/formula/FormulaRuntime.kt b/formula/src/main/java/com/instacart/formula/FormulaRuntime.kt index d69b0d17..8a2ad4a8 100644 --- a/formula/src/main/java/com/instacart/formula/FormulaRuntime.kt +++ b/formula/src/main/java/com/instacart/formula/FormulaRuntime.kt @@ -4,6 +4,7 @@ import com.instacart.formula.internal.FormulaManager import com.instacart.formula.internal.FormulaManagerImpl import com.instacart.formula.internal.ManagerDelegate import com.instacart.formula.internal.ThreadChecker +import kotlinx.coroutines.Dispatchers import java.util.LinkedList /** @@ -60,7 +61,13 @@ class FormulaRuntime( this.key = formula.key(input) if (initialization) { - manager = FormulaManagerImpl(this, implementation, input, loggingType = formula::class, inspector = inspector) + manager = FormulaManagerImpl( + delegate = this, + formula = implementation, + initialInput = input, + loggingType = formula::class, + inspector = inspector, + ) run() hasInitialFinished = true diff --git a/formula/src/main/java/com/instacart/formula/internal/ChildrenManager.kt b/formula/src/main/java/com/instacart/formula/internal/ChildrenManager.kt index 33c3c368..3fcd9376 100644 --- a/formula/src/main/java/com/instacart/formula/internal/ChildrenManager.kt +++ b/formula/src/main/java/com/instacart/formula/internal/ChildrenManager.kt @@ -110,9 +110,9 @@ internal class ChildrenManager( val childFormulaHolder = children.findOrInit(key) { val implementation = formula.implementation() FormulaManagerImpl( - delegate, - implementation, - input, + delegate = delegate, + formula = implementation, + initialInput = input, loggingType = formula::class, inspector = inspector ) diff --git a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt index 3cf42ca6..59a8d7b9 100644 --- a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt @@ -18,7 +18,7 @@ internal class ListenerImpl(internal var key: Any) : Liste // TODO: log if null listener (it might be due to formula removal or due to callback removal) val manager = manager ?: return - val deferredTransition = DeferredTransition(this, transition, event) + val deferredTransition = DeferredTransition(this@ListenerImpl, transition, event) manager.onPendingTransition(deferredTransition) } diff --git a/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt b/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt index 7a137b28..ae5b7b4f 100644 --- a/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt +++ b/formula/src/test/java/com/instacart/formula/FormulaRuntimeTest.kt @@ -3,6 +3,7 @@ package com.instacart.formula import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import com.instacart.formula.actions.EmptyAction +import com.instacart.formula.actions.EventOnBgThreadAction import com.instacart.formula.internal.ClearPluginsRule import com.instacart.formula.internal.FormulaKey import com.instacart.formula.internal.TestInspector @@ -82,6 +83,7 @@ import org.junit.rules.RuleChain import org.junit.rules.TestName import org.junit.runner.RunWith import org.junit.runners.Parameterized +import java.util.concurrent.TimeUnit import kotlin.reflect.KClass @RunWith(Parameterized::class) @@ -631,6 +633,25 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) { assertThat(eventCallback.values()).containsExactly("a", "b").inOrder() } + @Test + fun `when action returns value on background thread, we emit an error`() { + val bgAction = EventOnBgThreadAction() + val eventCallback = TestEventCallback() + val formula = OnlyUpdateFormula { + bgAction.onEvent { + transition { + eventCallback(it.toString()) + } + } + } + + val observer = runtime.test(formula, Unit) + bgAction.latch.await(10, TimeUnit.MILLISECONDS) + assertThat(bgAction.errors.values().firstOrNull()?.message).contains( + "com.instacart.formula.subjects.OnlyUpdateFormula - Only thread that created it can post transition result Expected:" + ) + } + @Test fun `stream is disposed when evaluation does not contain it`() { DynamicStreamSubject(runtime) .updateStreams(keys = arrayOf("1")) diff --git a/formula/src/test/java/com/instacart/formula/actions/EventOnBgThreadAction.kt b/formula/src/test/java/com/instacart/formula/actions/EventOnBgThreadAction.kt new file mode 100644 index 00000000..e504887a --- /dev/null +++ b/formula/src/test/java/com/instacart/formula/actions/EventOnBgThreadAction.kt @@ -0,0 +1,27 @@ +package com.instacart.formula.actions + +import com.instacart.formula.Action +import com.instacart.formula.Cancelable +import com.instacart.formula.test.TestEventCallback +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors + +class EventOnBgThreadAction : Action { + val errors = TestEventCallback() + val latch = CountDownLatch(1) + + override fun key(): Any? = null + + override fun start(send: (Int) -> Unit): Cancelable? { + Executors.newSingleThreadExecutor().execute { + try { + send(0) + } catch (e: Throwable) { + errors.invoke(e) + } finally { + latch.countDown() + } + } + return null + } +} \ No newline at end of file