[CDX-358] Handle undeliverable RxJava exceptions#160
Conversation
This comment has been minimized.
This comment has been minimized.
This reverts commit c937920.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent host-app crashes caused by RxJava “undeliverable” exceptions (often originating from OkHttp/Retrofit calls after disposal) by installing a global RxJava error handler and standardizing Retrofit Rx subscriptions onto the IO scheduler.
Changes:
- Install a global
RxJavaPlugins.setErrorHandlerduringConstructorIo.init()to handle/log undeliverable exceptions. - Configure Retrofit to use
RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())so subscriptions default to the IO scheduler. - Add unit tests that exercise the error handler against several edge-case exception types.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
library/src/main/java/io/constructor/core/ConstructorIo.kt |
Adds/installs a global RxJava error handler during SDK initialization. |
library/src/main/java/io/constructor/injection/module/NetworkModule.kt |
Switches Retrofit Rx adapter factory to use an IO scheduler by default. |
library/src/test/java/io/constructor/core/ConstructorIoRxErrorHandlerTest.kt |
Adds unit tests to ensure the handler doesn’t throw for key exception cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
This comment has been minimized.
This comment has been minimized.
… set + add comments
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
library/src/test/java/io/constructor/core/ConstructorIoRxErrorHandlerTest.kt
Outdated
Show resolved
Hide resolved
esezen
left a comment
There was a problem hiding this comment.
Looking great. I left some comments
library/src/test/java/io/constructor/core/ConstructorIoRxErrorHandlerTest.kt
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
d75fa30 to
a252f8f
Compare
Code Review SummaryThis PR addresses a real and well-documented RxJava problem: undeliverable exceptions crashing the host app when network errors arrive after stream disposal. The overall approach is sound and the PR description is excellent. The implementation has a few issues worth addressing before merging, ranging from a correctness concern to minor nits. Detailed Feedback[File: The In practice this is low-risk since private val errorHandlerInstalled = AtomicBoolean(false)
internal fun setupRxJavaErrorHandler() {
if (errorHandlerInstalled.getAndSet(true)) return
if (RxJavaPlugins.getErrorHandler() != null) return
RxJavaPlugins.setErrorHandler { throwable -> ... }
}[File: The [File: For unexpected (non-IO, non-Interrupted) exceptions, the handler calls e("Constructor.io: Forwarding unexpected undeliverable exception to uncaught handler: $error")
handler.uncaughtException(Thread.currentThread(), error)[File: If a bug caused the handler to never be invoked, assertNotNull("Uncaught exception handler was never invoked", caughtThrowable)
assertSame(error, caughtThrowable)[File: The test suite covers a raw (non-wrapped) @Test
fun handlesRawInterruptedException() {
Thread.interrupted() // clear any pre-existing flag
RxJavaPlugins.onError(InterruptedException("raw interrupted"))
assertTrue("Interrupt flag should be restored", Thread.interrupted())
}[General] The change from // Use createWithScheduler so Retrofit observables always subscribe on the IO scheduler,
// reducing the window for undeliverable exceptions when subscribers dispose mid-request.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))ConclusionThe PR solves a genuine SDK stability issue with a well-structured approach and solid test coverage. The most significant concern is |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (error is IOException) { | ||
| e("Constructor.io: Non-fatal network error: ${error.javaClass.simpleName} - ${error.message}") | ||
| return@setErrorHandler |
| } else { | ||
| e("Constructor.io: Undeliverable unexpected exception (no uncaught handler): $error") | ||
| } |
Summary:
RxJavaPlugins.setErrorHandlerto catch undeliverableRxJavaexceptions (e.g. network errors that occur after a stream is disposed) and log them instead of crashing the host appRetrofit'sRxJava2CallAdapterFactory.create()tocreateWithScheduler(Schedulers.io())to ensure network calls are consistently subscribed on the IO scheduler, reducing the likelihood of undeliverable exceptionsBackground:
OkHttpcan deliver exceptions toRxJavaafter a stream has already completed or been disposed. When no global error handler is set,RxJava's default behavior is to throw these as unhandled exceptions, which crashes the host app. This is especially problematic for an SDK where internal network errors should never bring down the consumer's application.Why
createWithScheduler(Schedulers.io())?With
RxJava2CallAdapterFactory.create(),Retrofitobservables don't have a default subscription scheduler, meaning the thread that subscribes is the thread that executes the network call. If a subscriber disposes whileOkHttpis mid-request on an uncontrolled thread, the resulting error has no downstream observer and becomes an undeliverable exception. By usingcreateWithScheduler(Schedulers.io()), allRetrofitcalls are consistently subscribed on theRxJavaIO scheduler, which works cooperatively withRxJava's disposal and error-handling mechanisms, significantly reducing the window for undeliverable exceptions.