-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Don't call Dispatchers.Main getter during UnconfinedTestDispatcher construction #4297
Comments
I'm seriously considering withdrawing this issue because I no longer feel like I know what I'm talking about. Initially, I thought I found some workarounds that made me believe I knew more or less where the problem came from, but it later turned out that whenever I saw an improvement in one test, I was re-discovering the same kind of failure (often as a suppressed exception) somewhere else. I'm still trying to understand what is happening in this project where I found this problem. I will wait for some feedback on this but feel free to close. |
Hi! As I mentioned in Kotlinlang, this issue can be worked around on our side: we really are doing unnecessary work which theoretically can fail, and the fix should be fairly straightforward. Even if it turns out not to be a problem in practice and it's your project that's doing something strange, we can still eliminate this problem entirely as a class. |
I've looked into this, and here are the specific conditions for this to occur:
To my knowledge, tests are usually not minified, which can explain why we haven't encountered this before. |
Before this change, the following could happen on the JVM: * `kotlinx.coroutines.test` accesses `Dispatchers.Main` before `setMain` is called. * `Dispatchers.Main` attempts to initialize *some other* Main dispatcher in addition to the `kotlinx-coroutines-test` Main dispatcher, if there is one. * If the `kotlinx-coroutines-android` artifact is present + its R8 rules are used to minify the tests, it's illegal to attempt to fail to create a `Main` dispatcher. `SUPPORT_MISSING = false` ensures that attempting to create a Main dispatcher fails immediately, in the call frame that attempts the creation, not waiting for `Dispatchers.Main` to be meaningfully used. In total, `kotlinx-coroutines-test` + `kotlinx-coroutines-android` + R8 minification of tests leads to some patterns of `kotlinx-coroutines-test` usage to crash. Fixes #4297
Before this change, the following could happen on the JVM: * `kotlinx.coroutines.test` accesses `Dispatchers.Main` before `setMain` is called. * `Dispatchers.Main` attempts to initialize *some other* Main dispatcher in addition to the `kotlinx-coroutines-test` Main dispatcher, if there is one. * If the `kotlinx-coroutines-android` artifact is present + its R8 rules are used to minify the tests, it's illegal to attempt to fail to create a `Main` dispatcher. `SUPPORT_MISSING = false` ensures that attempting to create a Main dispatcher fails immediately, in the call frame that attempts the creation, not waiting for `Dispatchers.Main` to be meaningfully used. In total, `kotlinx-coroutines-test` + `kotlinx-coroutines-android` + R8 minification of tests leads to some patterns of `kotlinx-coroutines-test` usage to crash. Fixes #4297
Before this change, the following could happen on the JVM: * `kotlinx.coroutines.test` accesses `Dispatchers.Main` before `setMain` is called. * `Dispatchers.Main` attempts to initialize *some other* Main dispatcher in addition to the `kotlinx-coroutines-test` Main dispatcher, if there is one. * If the `kotlinx-coroutines-android` artifact is present + its R8 rules are used to minify the tests, it's illegal to attempt to fail to create a `Main` dispatcher. `SUPPORT_MISSING = false` ensures that attempting to create a Main dispatcher fails immediately, in the call frame that attempts the creation, not waiting for `Dispatchers.Main` to be meaningfully used. In total, `kotlinx-coroutines-test` + `kotlinx-coroutines-android` + R8 minification of tests leads to some patterns of `kotlinx-coroutines-test` usage to crash. Fixes #4297
In the project where I found this problem, R8 was disabled 🤷♂️ However, I would like to add that I finally identified the real root cause of many problems I encountered in that project. I realized the production code under test was written in a test-unfriendly manner. For example, there were dangling coroutines launched on non-test dispatchers, throwing exceptions in the background and causing failures of the subsequent tests using That being said, I'm unsure if my original request Don't call Dispatchers.Main getter during UnconfinedTestDispatcher construction makes any sense as the problem I encountered was merely a symptom of a coroutines misuse in that project. I'm leaning towards closing this issue unless you want to keep it open despite all I said above. |
Yes, after reading your stacktrace more carefully, I do see that it's because some coroutines run in |
I'm opening this issue as a result of my short conversation with @dkhalanskyjb on Kotlin Slack: https://kotlinlang.slack.com/archives/C1CFAFJSK/p1734007891082299
What do we have now?
In certain situations, creating an instance of
UnconfinedTestDispatcher
by callingUnconfinedTestDispatcher()
just before setting it as the main dispatcher viaDispatchers.setMain
, e.g. in a JUnit rule, may fail.(The same applies to
StandardTestDispatcher
)The execution path of creating the dispatcher was like this:
UnconfinedTestDispatcher
kotlinx.coroutines/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt
Line 83 in 6c6df2b
TestMainDispatcher.currentTestScheduler
kotlinx.coroutines/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt
Line 50 in 6c6df2b
TestMainDispatcher.currentTestDispatcher
kotlinx.coroutines/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt
Line 47 in 6c6df2b
Dispatchers.Main
(getter invoked beforeDispatchers.setMain
!)kotlinx.coroutines/kotlinx-coroutines-core/jvm/src/Dispatchers.kt
Line 20 in 6c6df2b
MainDispatcherLoader.loadMainDispatcher
MainDispatchersKt.tryCreateDispatcher
kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher
Looper.getMainLooper()
⚡ throwsStacktrace
It prevented me from setting the
UnconfinedTestDispatcher
asMain
because it wasn't possible to construct it in the first place. The error also wasn't helpful because it told me to callsetMain
which happened to be the exact thing I was trying to accomplish.To be perfectly honest, I'm not sure how exactly to reproduce these circumstances. The project where I stumbled upon this problem was large and had a complicated setup. I wouldn't rule out the possibility of a user error or a misconfiguration of some sort, such as a version conflict in dependencies or a test classpath pollution (e.g. having
...-android
in the test classpath). Nevertheless, it would be great if this problem could be avoided somehow.What should be instead?
If possible, the code necessary for substituting the main dispatcher (such as the
UnconfinedTestDispatcher()
orStandardTestDispatcher()
function) should not callDispatchers.Main
(getter) because it leads to strange situations like the one I described where the user is told to callsetMain
even though that's exactly what the user is trying to accomplish.Why?
The upsides of your proposal.
Why not?
The downsides of your proposal that you already see.
The text was updated successfully, but these errors were encountered: