-
Notifications
You must be signed in to change notification settings - Fork 368
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
[Optimization] Init with context in background #2151
Conversation
cfe077a
to
806ba1b
Compare
startableService.start() | ||
// schedule to start all startable services in a separate thread | ||
fun scheduleStart() { | ||
coroutineScope.launch { |
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.
Since we only need to run a one off background task can we use a Thread
instead of a CoroutineScope
? This has a bit less overhead.
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.
Sure. By using thread, we can avoid creating coroutineScope.
But I think Coroutine is a simpler version of Thread as it does not use as much resources going down to the OS threads and only take an object in the JVM heap. In my test, the time it takes for the code inside the block to complete by using coroutineScope.launch vs using thread is very similar. Using thread is a tiny bit slower in average. This may be affected by the current os responsiveness as well.
Another reason I used coroutine is that this practice is also used in other places like operationRepo so there is some consistency. Do we think it is worth changing it to using thread?
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.
In this specific case we were creating a newSingleThreadContext
and just running one task, the OperationRepo stays alive forever and continually takes work. In this class we simply need to run something in the background and then we no longer need the thread.
We could optimize this in future by using any existing coroutineScope that is shared with other parts of the SDK, but since we don't have that it is more efficient to use a thread.
In my test, the time it takes for the code inside the block to complete by using coroutineScope.launch vs using thread is very similar.
Did you include CoroutineScope(newSingleThreadContext(name = "StartupService"))
and coroutineScope.launch
in your test vs Thread {}.start()
?
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.
Ok the newest commit has changed to use 'Thread.start()' instead of using 'coroutineScope'. I placed a mark right before the initialization of 'StartupService', a mark right before 'launch'/'Thread.start', and the final mark at the completion. The diff is insignificant in general.
5d227f6
to
a7602dc
Compare
b432a80
to
18dd0d5
Compare
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 looked over all the classes that implement IStartableService
and can't see any issues with their start()
method from being called delayed / called from the background.
// ensure calling requestPermission in a thread right after initWithContext does not crash | ||
ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
@SuppressLint({"NewApi", "LocalSuppress"}) CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { | ||
OneSignal.getNotifications().requestPermission(true, Continue.none()); |
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.
Why not just use a Thread
instead for this example code?
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.
If I check out the second commit and use this piece of code in MainApplication.java, I am not able to reproduce the crash anymore.
// ensure calling requestPermission in a thread right after initWithContext does not crash
new Thread(new Runnable() {
@Override
public void run() {
OneSignal.getNotifications().requestPermission(true, Continue.none());
}
}){}.start();
Reports from user are suggesting that
CoroutineScope(Dispatchers.IO).launch {
OneSignal.Notifications.requestPermission(true)
}
is what causes the crash.
Although both Java Thread and Kotlin CoroutineScope are used for handling concurrency, they seem to operate differently. The only equivalence in Java I found to reproduce the same crash is by calling java8 'CompletableFuture.join()' I guess the reason is that 'CompletableFuture.join()' waits for the thread to complete, which is similar to Kotlin Coroutine that can be suspended.
I added a comment to further explain the reason CompletableFuture and its related annotation is added here.
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.
An alternative is to add a Kotlin object file to the example app that has a function to run
CoroutineScope(Dispatchers.IO).launch {
OneSignal.Notifications.requestPermission(true)
}
and then call it from MainApplication.
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.
Good point. I have tried this approach as well, and they behave the same in my test. I chose the Java code here considering "broader" test coverage from other wrappers that run Java code over the bridge.
…ure no crash in the manual test
02e30e1
to
4112dee
Compare
Force push: Consolidating the commits. Code is unchanged since last review. |
Description
One Line Summary
Move start call of unused services off the main thread to make the initWithContext more efficient.
Details
Motivation
Our SDK initialization is currently taking longer to finish compare to some other SDKs. One of the reason is we construct service components like Location Manager or IAM Manager during the initialization phrase when they are not really needed until later. This PR aims to move the initialization of these services to background thread so they don't slow down the overall SDK initialization.
Scope
The retrieval of services that were changed to initialize in background is not longer from a saved instance in OneSignalImp.kt. Instead, each of these components has a getter that retrieve its instance from the service provider.
Testing
Manual testing
Affected code checklist
Checklist
Overview
Testing
Final pass
This change is