-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coroutines: add event flow #91
Conversation
to `LocalStorageRepository`
The logic of validating queue is no longer relevant as discussed internally. This commits removes `QueueManager` completely, and replaces it with a thread-safe `InMemoryBuffer`, which holds newly recorded events and, as soon as possible, adds them to local persistence. The new event not added to persistence right away, because the local storage file might be occupied/locked by, e.g. process/coroutine of sending events. We do not want to make consumer of the library to wait for adding a new event ever. Hence, the `InMemoryRepository#buffer`.
By using mutual exclusion (`Mutex`). Also, remove the `persistEvent` method, as it's no longer needed actually.
Also, add a name for easier identification while debugging
To reduce overhead of constant loop, the `InMemoryBuffer` will check `buffer` list every second.
As `inMemoryBuffer` triggers asynchronous operation, this PR adds a listener that asserts starting flush queue after adding an event.
50 events batches are no longer a thing since we removed `QueueManager`
50 events batches are no longer a thing since we removed `QueueManager`
private val onEventAddedListener: () -> Unit, | ||
) { | ||
|
||
private val mutex = Mutex() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To test if this mutual exclusion works correctly, comment this, usages in init
and add
and then run stressTest
. It should fail with unexpected result - sometimes recorded events are smaller, sometimes bigger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the instructions! 🥇
FYI: I run that stressTest
3 times after removing mutex
and everything was still working as expected on my side (testing on actual device). I then also triggered the whole ./gradlew connectedDebugAndroidTest
from CLI and it still worked as expected, everything was successful. 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UPDATE: I then also removed the mutex
on the repository level and run the stressTest
3 times, along with running the whole ./gradlew connectedDebugAndroidTest
from CLI. Running the stressTest
succeeded every time, but running the whole ./gradlew connectedDebugAndroidTest
from CLI indeed failed (both times I tried):
com.parsely.parselyandroid.FunctionalTests > stressTest[Pixel 4 - 13] FAILED
java.lang.AssertionError:
Expected size: 500 but was: 1000 in:
Tests on Pixel 4 - 13 failed: There was 1 failure(s).
PS:
- I then uncommented the
mutex
on the repository level and run whole./gradlew connectedDebugAndroidTest
from CLI again and it started failing as well. 👍 - I then, uncommented the
mutex
on the buffer level and run whole./gradlew connectedDebugAndroidTest
from CLI again and it now succeeded. ✅
I guess there exist some flakiness but it seems that everything is working as expected after all. Just wanted to document my testing here, just in case it somehow helps you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing! I think for some reason, they're more likely to fail without mutex
on emulator. In any case, one can also remove the delay
to the InMemoryBuffer#init
- this would make the test fail more certain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for some reason, they're more likely to fail without mutex on emulator.
Yea, maybe! 🤷
In any case, one can also remove the delay to the InMemoryBuffer#init - this would make the test fail more certain.
True, good tip! 💯
parsely/src/main/java/com/parsely/parselyandroid/LocalStorageRepository.kt
Show resolved
Hide resolved
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## coroutines #91 +/- ##
==============================================
+ Coverage 53.00% 55.85% +2.84%
==============================================
Files 12 12
Lines 366 376 +10
Branches 57 57
==============================================
+ Hits 194 210 +16
+ Misses 155 148 -7
- Partials 17 18 +1
☔ View full report in Codecov by Sentry. |
All events are now add to `InMemoryBuffer` and then from there to local storage which is our SSOT for events that are about to be sent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👋 @wzieba !
I have reviewed and tested this PR as per the instructions, everything works as expected, awesome job with InMemoryBuffer
and removing the eventQueue
, lots of complexity is now actually gone! 🌟 🎉 🚀
I have left a questions (❓) and a couple of suggestions (💡) for you to consider. I am going to approve this PR anyway, since none is blocking (ish). I am NOT going to merge this PR yet to give you some time to apply any of my suggestions. However, feel free to ignore them and merge the PR yourself.
parsely/src/main/java/com/parsely/parselyandroid/LocalStorageRepository.kt
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/LocalStorageRepository.kt
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/LocalStorageRepository.kt
Show resolved
Hide resolved
ParselyTracker.PLog("Persisting event queue") | ||
persistObject((inMemoryQueue + getStoredQueue()).distinct()) | ||
open suspend fun insertEvents(toInsert: List<Map<String, Any?>?>) = mutex.withLock { | ||
ParselyTracker.PLog("Persisting ${toInsert.size} events") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion (💡): Just like it is done on InMemoryBuffer.add(...)
, maybe this log can be moved to InMemoryBuffer.init { ... }
as well and make the logging closer to source and more consistent, at least with each other. Wdyt? 🤔
private val repository = FakeLocalStorageRepository() | ||
|
||
@Test | ||
fun `when adding a new event, then save it to local storage and run onEventAdded listener`() = runTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion (💡): Consider splitting this test into two:
when adding a new event, then save it to local storage
given a listener, when adding a new event, then run listener
private val onEventAddedListener: () -> Unit, | ||
) { | ||
|
||
private val mutex = Mutex() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the instructions! 🥇
FYI: I run that stressTest
3 times after removing mutex
and everything was still working as expected on my side (testing on actual device). I then also triggered the whole ./gradlew connectedDebugAndroidTest
from CLI and it still worked as expected, everything was successful. 🤷
private val onEventAddedListener: () -> Unit, | ||
) { | ||
|
||
private val mutex = Mutex() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UPDATE: I then also removed the mutex
on the repository level and run the stressTest
3 times, along with running the whole ./gradlew connectedDebugAndroidTest
from CLI. Running the stressTest
succeeded every time, but running the whole ./gradlew connectedDebugAndroidTest
from CLI indeed failed (both times I tried):
com.parsely.parselyandroid.FunctionalTests > stressTest[Pixel 4 - 13] FAILED
java.lang.AssertionError:
Expected size: 500 but was: 1000 in:
Tests on Pixel 4 - 13 failed: There was 1 failure(s).
PS:
- I then uncommented the
mutex
on the repository level and run whole./gradlew connectedDebugAndroidTest
from CLI again and it started failing as well. 👍 - I then, uncommented the
mutex
on the buffer level and run whole./gradlew connectedDebugAndroidTest
from CLI again and it now succeeded. ✅
I guess there exist some flakiness but it seems that everything is working as expected after all. Just wanted to document my testing here, just in case it somehow helps you.
in `InMemoryBuffer`
Description
This PR introduces Kotlin Coroutines for "add an event" flow. It covers
This PR also:
QueueManager
as discussed internally p1699018266041689-slack-C0533SEJ82HHow to test
Persisting n events
, wheren
is the number of events added in 1-second time framem
of events inSending request with m events
log equals the number you counted in step 2