Fixed
Status Update
Comments
su...@google.com <su...@google.com> #2
Yigit, do you have time to fix it?
reemission of the same liveData is racy
reemission of the same liveData is racy
dr...@gmail.com <dr...@gmail.com> #3
yea i'll take it.
ra...@google.com <ra...@google.com> #4
Thanks for the detailed analysis. This may not be an issue anymore since we've started using Main.immediate there but I' not sure; I'll try to create a test case.
dr...@gmail.com <dr...@gmail.com> #5
just emitting same live data reproduces the issue.
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
@Test
fun raceTest() {
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData)
emitSource(subLiveData) //crashes
}
subject.addObserver().apply {
testScope.advanceUntilIdle()
}
}
ra...@google.com <ra...@google.com> #6
With 2.2.0-alpha04 (that use Main.immediate), the issue seems to be still there (I tested it by calling emitSource() twice, like your test case)
dr...@gmail.com <dr...@gmail.com> #7
yea sorry immediate does not fix it.
I actually have a WIP fix for it:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1112186
if your case is the one i found (emitting same LiveData multiple times, as shown in #5) you can work around it by adding a dummy transformation.
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData.map {it })
emitSource(subLiveData.map {it} )
}
I actually have a WIP fix for it:
if your case is the one i found (emitting same LiveData multiple times, as shown in #5) you can work around it by adding a dummy transformation.
val subLiveData = MutableLiveData(1)
val subject = liveData(testScope.coroutineContext) {
emitSource(subLiveData.map {it })
emitSource(subLiveData.map {it} )
}
ra...@google.com <ra...@google.com> #8
Project: platform/frameworks/support
Branch: androidx-master-dev
commit af12e75e6b4110f48e44ca121466943909de8f06
Author: Yigit Boyar <yboyar@google.com>
Date: Tue Sep 03 12:58:11 2019
Fix coroutine livedata race condition
This CL fixes a bug in liveData builder where emitting same
LiveData source twice would make it crash because the second
emission registry could possibly happen before first one is
removed as source.
We fix it by using a suspending dispose function. It does feel
a bit hacky but we cannot make DisposableHandle.dispose async
and we do not want to block there. This does not mean that there
is a problem if developer disposes it manually since our emit
functions take care of making sure it disposes (and there is
no other way to add source to the underlying MediatorLiveData)
Bug: 140249349
Test: BuildLiveDataTest#raceTest_*
Change-Id: I0b464c242a583da4669af195cf2504e2adc4de40
M lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/current.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
M lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
M lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt
https://android-review.googlesource.com/1112186
https://goto.google.com/android-sha1/af12e75e6b4110f48e44ca121466943909de8f06
Branch: androidx-master-dev
commit af12e75e6b4110f48e44ca121466943909de8f06
Author: Yigit Boyar <yboyar@google.com>
Date: Tue Sep 03 12:58:11 2019
Fix coroutine livedata race condition
This CL fixes a bug in liveData builder where emitting same
LiveData source twice would make it crash because the second
emission registry could possibly happen before first one is
removed as source.
We fix it by using a suspending dispose function. It does feel
a bit hacky but we cannot make DisposableHandle.dispose async
and we do not want to block there. This does not mean that there
is a problem if developer disposes it manually since our emit
functions take care of making sure it disposes (and there is
no other way to add source to the underlying MediatorLiveData)
Bug: 140249349
Test: BuildLiveDataTest#raceTest_*
Change-Id: I0b464c242a583da4669af195cf2504e2adc4de40
M lifecycle/lifecycle-livedata-ktx/api/2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/current.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_2.2.0-alpha05.txt
M lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
M lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
M lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt
dr...@gmail.com <dr...@gmail.com> #9
Yeah sorry the OneTimeWorkRequest stuff was there as one way of getting the immediate work done, and mostly it's OK, but certainly for some devices in particular the device delays quite some time before running the work, so it's not immediate enough.
ra...@google.com <ra...@google.com> #10
We introduce some drift due to the way we reschedule ENQUEUED work on init (on process death).
I will fix this.
I will fix this.
ap...@google.com <ap...@google.com> #11
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 39859499e6c1d1502f8dc764360b55a62eaba233
Author: Rahul Ravikumar <rahulrav@google.com>
Date: Mon Jun 17 16:28:19 2019
Fix for drifts in WorkRequests with periodStartTime = 0
* When we have WorkRequests with initial delays (and periodStartTime = 0)
and a process death occurs - we would previously look at all WorkSpecs
in ENQUEUED state and reschedule them.
* This would cause a drift for WorkSpecs with periodStartTime = 0,
because they would be scheduled relative to System.currentTimeInMillis().
* To fix this we move the cleanup step into ForceStopRunnable, given it is the
first Runnable being executed in the TaskExecutor. This way we can also
more accureately target WorkSpecs that were previously RUNNING rather than
having to reschedule all WorkSpecs that were ENQUEUED.
Fixes: b/135272196
Test: Existing unit tests in ForceStopRunnableTests, updated tests in WorkManagerImplTest.
Change-Id: I0fb5477c70a43b751fec1e0c5d9901188e2396f4
M work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
M work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
M work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
M work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
M work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
https://android-review.googlesource.com/984508
https://goto.google.com/android-sha1/39859499e6c1d1502f8dc764360b55a62eaba233
Branch: androidx-master-dev
commit 39859499e6c1d1502f8dc764360b55a62eaba233
Author: Rahul Ravikumar <rahulrav@google.com>
Date: Mon Jun 17 16:28:19 2019
Fix for drifts in WorkRequests with periodStartTime = 0
* When we have WorkRequests with initial delays (and periodStartTime = 0)
and a process death occurs - we would previously look at all WorkSpecs
in ENQUEUED state and reschedule them.
* This would cause a drift for WorkSpecs with periodStartTime = 0,
because they would be scheduled relative to System.currentTimeInMillis().
* To fix this we move the cleanup step into ForceStopRunnable, given it is the
first Runnable being executed in the TaskExecutor. This way we can also
more accureately target WorkSpecs that were previously RUNNING rather than
having to reschedule all WorkSpecs that were ENQUEUED.
Fixes:
Test: Existing unit tests in ForceStopRunnableTests, updated tests in WorkManagerImplTest.
Change-Id: I0fb5477c70a43b751fec1e0c5d9901188e2396f4
M work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
M work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
M work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
M work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
M work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
Description
When I set up a periodic job, I want the first in the series to be performed INSTANTLY (the user expects it... they hit a button and are waiting for an immediate result), and then for subsequent ones to be performed based on the periodic schedule (more vague timings is fine... the user is no longer waiting).
When you set up a periodic work request *without* a flex interval, it can run anytime in the set interval, i.e. from NOW until the end of the interval. If the device is awake when the periodic work request is scheduled, which is the case for me, I find that the first work in the series is generally performed at the START of the interval, i.e. NOW... though "NOW" may not be instantly (it's up to the device to decide... may be delayed a few seconds).
Because it cannot be guaranteed that the first periodic work will be performed *instantly*, instead I do an explicit call to the work-performing function outside of the WorkManager realm, and put a flexInterval on the periodic work of 5 mins so that the first periodic work will certainly not be performed straight away, but instead it will be near the end of the first period, most likely 5 mins before the end. This is not quite ideal (first gap between work runs is not quite a full interval) but it's OK... the user won't care or even notice it runs 5 mins "early" the first time.
Now, I thought that the ability to set an "initialDelay" in 2.1.0 would solve my problems... so that I could:
(a) still make an explicit call to the work-performing function to do the immediate work, guaranteed to be NOW (the user expects that)
(b) set up a periodic work request WITHOUT flexInterval (i.e. use the whole interval) and WITH an initial delay equal to the period "interval"
That way, I get the immediate work done, and then after "interval" the periodic work request is scheduled, and because (let's assume) the device is still awake, the first periodic work will likely perform at the START of the interval, i.e. almost straight away, i.e. pretty close to exactly one "interval" after the user set the schedule going.
BUT I'm not finding that. Let's say that "interval" of the periodic work is 15 mins, and I set an "initial delay" also of 15 mins, and I set this going when the user hits the button. I find that the immediate work is performed at 0 mins (triggered explicitly, not by WorkManager, so that's as expected), then at 15 mins NOTHING happens, and only at 30 mins does the first periodic work actually run.
It seems that either the first periodic work happens at the END of the first interval (15-30 mins) and not the start (like it usually does, if the device is awake and not busy), or WorkManager delayed first periodic interval to be at 30-45 mins, i.e. it waited 30 mins not 15 mins, twice what I asked for.
So, how is "setInitialDelay" for a periodic work request meant to work? It doesn't just wait "delay" (instead of not waiting at all) and then enqueue the periodic work request?