Repository: badoo/Reaktive (kotlin)

Issue:
`StackOverflowError` is not reported
## Description

I created the following example:

```kotlin
package dev.oleks

import com.badoo.reaktive.single.flatMap
import com.badoo.reaktive.single.subscribe
import com.badoo.reaktive.single.toSingle


fun main() {
    var mapSingle = emptyMap<Int, String>().toSingle()
    repeat(5000) { index ->
        mapSingle = mapSingle.flatMap { map ->
            (map + (index to index.toString())).toSingle()
        }
    }

    var result: Result<Map<Int, String>>? = null
    mapSingle.subscribe(onSuccess = {
        result = Result.success(it)
    }, onError = {
        result = Result.failure(it)
    })
    checkNotNull(result) { "Neither onSuccess nor onError was called" }
    println(result!!.getOrThrow().size)
}
```

Running it with version 2.3.0 results in the following output:

```
Exception in thread "main" java.lang.IllegalStateException: Neither onSuccess nor onError was called
        at dev.oleks.MainKt.main(Main.kt:22)
        at dev.oleks.MainKt.main(Main.kt)
```


Note, that neither the `onError` was called nor the ["onError callback failed"](https://github.com/badoo/Reaktive/blob/e46d432956c4d09841595b2408b4ba53742d5da8/reaktive/src/commonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt#L25) message was printed.

When setting a breakpoint in error handling code, the following original stack trace can be recovered:

```
java.lang.StackOverflowError
	at com.badoo.reaktive.utils.HandleSourceErrorKt.handleReaktiveError(HandleSourceError.kt:9)
	at com.badoo.reaktive.single.FlatMapKt$flatMap$1$1.onSuccess(FlatMap.kt:52)
	at com.badoo.reaktive.single.SingleByEmitterKt$onSubscribeSingle$1.onSuccess(SingleByEmitter.kt:27)
	at com.badoo.reaktive.single.FlatMapKt$flatMap$1$1$onSuccess$1$1.onSuccess(FlatMap.kt)
	at com.badoo.reaktive.single.VariousKt$singleOf$$inlined$singleUnsafe$1.subscribe(Various.kt:21)
	at com.badoo.reaktive.single.VariousKt$singleOf$$inlined$singleUnsafe$1.subscribe(Various.kt:13)
	at com.badoo.reaktive.single.FlatMapKt$flatMap$1$1.onSuccess(FlatMap.kt:24)
	at com.badoo.reaktive.single.SingleByEmitterKt$onSubscribeSingle$1.onSuccess(SingleByEmitter.kt:27)
	at com.badoo.reaktive.single.FlatMapKt$flatMap$1$1$onSuccess$1$1.onSuccess(FlatMap.kt)
	at com.badoo.reaktive.single.VariousKt$singleOf$$inlined$singleUnsafe$1.subscribe(Various.kt:21)
	at com.badoo.reaktive.single.VariousKt$singleOf$$inlined$singleUnsafe$1.subscribe(Various.kt:13)
	at com.badoo.reaktive.single.FlatMapKt$flatMap$1$1.onSuccess(FlatMap.kt:24)
        ....
```

## Additional information

The issue seems similar to https://github.com/ReactiveX/RxJava/issues/748.

Other JVM implementations of Reactive Extensions rethrow `VirtualMachineError` (e.g, `StackOverflowError`), `ThreadDeath` and `LinkageError`[^1][^2] because they cannot always be recovered from.

[^1]: https://github.com/reactor/reactor-core/blob/7259f57d52aafa7f23d6863962ef790ac18f7095/reactor-core/src/main/java/reactor/core/Exceptions.java#L453
[^2]: https://github.com/ReactiveX/RxJava/blob/5c8ddcba3f40f37a24808c17d2cb26d08c370db7/src/main/java/io/reactivex/rxjava3/exceptions/Exceptions.java#L66

Pull request description:
Rethrow fatal exceptions immediately on JVM (`VirtualMachineError`, `ThreadDeath` and `LinkageError`). Couldn't determine any fatal exceptions on other platforms.

Fixes #784.

Patch:
diff --git a/reaktive/api/android/reaktive.api b/reaktive/api/android/reaktive.api
index 9f51343fd..611b69b68 100644
--- a/reaktive/api/android/reaktive.api
+++ b/reaktive/api/android/reaktive.api
@@ -1422,6 +1422,7 @@ public final class com/badoo/reaktive/subject/unicast/UnicastSubjectBuilderKt {
 public final class com/badoo/reaktive/utils/HandleSourceErrorKt {
 	public static final fun handleReaktiveError (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V
 	public static synthetic fun handleReaktiveError$default (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public static final fun throwIfFatal (Ljava/lang/Throwable;)V
 }
 
 public final class com/badoo/reaktive/utils/UncaughtErrorHandlerKt {
diff --git a/reaktive/api/jvm/reaktive.api b/reaktive/api/jvm/reaktive.api
index 9f51343fd..611b69b68 100644
--- a/reaktive/api/jvm/reaktive.api
+++ b/reaktive/api/jvm/reaktive.api
@@ -1422,6 +1422,7 @@ public final class com/badoo/reaktive/subject/unicast/UnicastSubjectBuilderKt {
 public final class com/badoo/reaktive/utils/HandleSourceErrorKt {
 	public static final fun handleReaktiveError (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;)V
 	public static synthetic fun handleReaktiveError$default (Ljava/lang/Throwable;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public static final fun throwIfFatal (Ljava/lang/Throwable;)V
 }
 
 public final class com/badoo/reaktive/utils/UncaughtErrorHandlerKt {
diff --git a/reaktive/src/commonMain/kotlin/com/badoo/reaktive/base/ErrorCallbackExt.kt b/reaktive/src/commonMain/kotlin/com/badoo/reaktive/base/ErrorCallbackExt.kt
index 8b2e5667b..f9bad56db 100644
--- a/reaktive/src/commonMain/kotlin/com/badoo/reaktive/base/ErrorCallbackExt.kt
+++ b/reaktive/src/commonMain/kotlin/com/badoo/reaktive/base/ErrorCallbackExt.kt
@@ -1,6 +1,8 @@
 package com.badoo.reaktive.base
 
+import com.badoo.reaktive.base.exceptions.CompositeException
 import com.badoo.reaktive.utils.handleReaktiveError
+import com.badoo.reaktive.utils.throwIfFatal
 
 inline fun <T> ErrorCallback.tryCatch(
     block: () -> T,
@@ -10,7 +12,18 @@ inline fun <T> ErrorCallback.tryCatch(
     try {
         block()
     } catch (e: Throwable) {
-        handleReaktiveError(errorTransformer(e), ::onError)
+        e.throwIfFatal()
+
+        val transformedError =
+            try {
+                errorTransformer(e)
+            } catch (e2: Throwable) {
+                e2.throwIfFatal()
+                CompositeException(cause1 = e, cause2 = e2)
+            }
+
+        handleReaktiveError(transformedError, ::onError)
+
         return
     }
         .also(onSuccess)
@@ -23,7 +36,17 @@ inline fun ErrorCallback.tryCatch(
     try {
         block()
     } catch (e: Throwable) {
-        handleReaktiveError(errorTransformer(e), ::onError)
+        e.throwIfFatal()
+
+        val transformedError =
+            try {
+                errorTransformer(e)
+            } catch (e2: Throwable) {
+                e2.throwIfFatal()
+                CompositeException(cause1 = e, cause2 = e2)
+            }
+
+        handleReaktiveError(transformedError, ::onError)
     }
 }
 
@@ -34,6 +57,16 @@ internal inline fun tryCatchAndHandle(
     try {
         block()
     } catch (e: Throwable) {
-        handleReaktiveError(errorTransformer(e))
+        e.throwIfFatal()
+
+        val transformedError =
+            try {
+                errorTransformer(e)
+            } catch (e2: Throwable) {
+                e2.throwIfFatal()
+                CompositeException(cause1 = e, cause2 = e2)
+            }
+
+        handleReaktiveError(transformedError)
     }
 }
diff --git a/reaktive/src/commonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt b/reaktive/src/commonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt
index 836f66421..6c1d429de 100644
--- a/reaktive/src/commonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt
+++ b/reaktive/src/commonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt
@@ -3,6 +3,8 @@ package com.badoo.reaktive.utils
 import com.badoo.reaktive.base.exceptions.CompositeException
 
 fun handleReaktiveError(error: Throwable, onError: ((Throwable) -> Unit)? = null) {
+    error.throwIfFatal()
+
     if (onError == null) {
         handleError(error)
     } else {
@@ -14,6 +16,7 @@ private fun handleError(error: Throwable) {
     try {
         reaktiveUncaughtErrorHandler(error)
     } catch (errorDeliveryException: Throwable) {
+        errorDeliveryException.throwIfFatal()
         printErrors("Error delivering uncaught error", error, errorDeliveryException)
     }
 }
@@ -22,11 +25,13 @@ private fun handleError(error: Throwable, onError: (Throwable) -> Unit) {
     try {
         onError(error)
     } catch (errorHandlerException: Throwable) {
+        errorHandlerException.throwIfFatal()
         printErrors("onError callback failed", error, errorHandlerException)
 
         try {
             reaktiveUncaughtErrorHandler(CompositeException(error, errorHandlerException))
         } catch (errorDeliveryException: Throwable) {
+            errorDeliveryException.throwIfFatal()
             printErrors("Error delivering uncaught error", error, errorDeliveryException)
         }
     }
@@ -37,3 +42,11 @@ private fun printErrors(message: String, outerError: Throwable, innerError: Thro
     outerError.printStackTrace()
     innerError.printStackTrace()
 }
+
+fun Throwable.throwIfFatal() {
+    if (isFatal()) {
+        throw this
+    }
+}
+
+internal expect fun Throwable.isFatal(): Boolean
diff --git a/reaktive/src/jsCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt b/reaktive/src/jsCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt
new file mode 100644
index 000000000..2256c81b4
--- /dev/null
+++ b/reaktive/src/jsCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.kt
@@ -0,0 +1,3 @@
+package com.badoo.reaktive.utils
+
+actual fun Throwable.isFatal(): Boolean = false
diff --git a/reaktive/src/jvmCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.jvm.kt b/reaktive/src/jvmCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.jvm.kt
new file mode 100644
index 000000000..6391f60f1
--- /dev/null
+++ b/reaktive/src/jvmCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.jvm.kt
@@ -0,0 +1,6 @@
+package com.badoo.reaktive.utils
+
+internal actual fun Throwable.isFatal(): Boolean =
+    (this is VirtualMachineError) ||
+        (this is ThreadDeath) ||
+        (this is LinkageError)
diff --git a/reaktive/src/nativeCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.native.kt b/reaktive/src/nativeCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.native.kt
new file mode 100644
index 000000000..7a8ce2629
--- /dev/null
+++ b/reaktive/src/nativeCommonMain/kotlin/com/badoo/reaktive/utils/HandleSourceError.native.kt
@@ -0,0 +1,3 @@
+package com.badoo.reaktive.utils
+
+internal actual fun Throwable.isFatal(): Boolean = false


Test patch:
diff --git a/reaktive/src/commonTest/kotlin/com/badoo/reaktive/test/TestErrorCallback.kt b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/test/TestErrorCallback.kt
new file mode 100644
index 000000000..570a5f2f5
--- /dev/null
+++ b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/test/TestErrorCallback.kt
@@ -0,0 +1,15 @@
+package com.badoo.reaktive.test
+
+import com.badoo.reaktive.base.ErrorCallback
+
+class TestErrorCallback(
+    private val onError: (Throwable) -> Unit = {},
+) : ErrorCallback {
+
+    var error: Throwable? = null
+
+    override fun onError(error: Throwable) {
+        onError.invoke(error)
+        this.error = error
+    }
+}
diff --git a/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleTest.kt b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleTest.kt
new file mode 100644
index 000000000..364baeab6
--- /dev/null
+++ b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleTest.kt
@@ -0,0 +1,50 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.exceptions.CompositeException
+import com.badoo.reaktive.base.tryCatchAndHandle
+import com.badoo.reaktive.test.mockUncaughtExceptionHandler
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertIs
+import kotlin.test.assertSame
+
+class TryCatchAndHandleTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_block_thrown_exception() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error = Exception()
+
+        tryCatchAndHandle { throw error }
+
+        assertSame(error, caughtException.value)
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_errorTransformer_thrown_exception() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        tryCatchAndHandle(
+            errorTransformer = { throw error2 },
+            block = { throw error1 },
+        )
+
+        val error = caughtException.value
+        assertIs<CompositeException>(error)
+        assertSame(error1, error.cause1)
+        assertSame(error2, error.cause2)
+    }
+
+    @Test
+    fun swallows_WHEN_reaktiveUncaughtErrorHandler_thrown_exception() {
+        reaktiveUncaughtErrorHandler = { throw Exception() }
+        tryCatchAndHandle { throw Exception() }
+    }
+}
diff --git a/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchTest.kt b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchTest.kt
new file mode 100644
index 000000000..39795917d
--- /dev/null
+++ b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchTest.kt
@@ -0,0 +1,67 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.exceptions.CompositeException
+import com.badoo.reaktive.base.tryCatch
+import com.badoo.reaktive.test.TestErrorCallback
+import com.badoo.reaktive.test.mockUncaughtExceptionHandler
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertIs
+import kotlin.test.assertSame
+
+class TryCatchTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun calls_ErrorCallback_WHEN_block_thrown_exception() {
+        val errorCallback = TestErrorCallback()
+        val error = Exception()
+
+        errorCallback.tryCatch { throw error }
+
+        assertSame(error, errorCallback.error)
+    }
+
+    @Test
+    fun calls_ErrorCallback_WHEN_errorTransformer_thrown_exception() {
+        val errorCallback = TestErrorCallback()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        errorCallback.tryCatch(
+            errorTransformer = { throw error2 },
+            block = { throw error1 },
+        )
+
+        val error = errorCallback.error
+        assertIs<CompositeException>(error)
+        assertSame(error1, error.cause1)
+        assertSame(error2, error.cause2)
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_ErrorCallback_thrown_exception() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        val errorCallback = TestErrorCallback { throw error2 }
+        errorCallback.tryCatch { throw error1 }
+
+        val error = caughtException.value
+        assertIs<CompositeException>(error)
+        assertSame(error1, error.cause1)
+        assertSame(error2, error.cause2)
+    }
+
+    @Test
+    fun swallows_WHEN_reaktiveUncaughtErrorHandler_thrown_exception() {
+        reaktiveUncaughtErrorHandler = { throw Exception() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+        errorCallback.tryCatch { throw Exception() }
+    }
+}
diff --git a/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessTest.kt b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessTest.kt
new file mode 100644
index 000000000..e8cea29d1
--- /dev/null
+++ b/reaktive/src/commonTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessTest.kt
@@ -0,0 +1,79 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.exceptions.CompositeException
+import com.badoo.reaktive.base.tryCatch
+import com.badoo.reaktive.test.TestErrorCallback
+import com.badoo.reaktive.test.mockUncaughtExceptionHandler
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertSame
+
+class TryCatchWithOnSuccessTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun calls_onSuccess_WHEN_block_not_thrown() {
+        val errorCallback = TestErrorCallback()
+        var result: Int? = null
+
+        errorCallback.tryCatch(block = { 0 }, onSuccess = { result = 0 })
+
+        assertEquals(0, result)
+    }
+
+    @Test
+    fun calls_ErrorCallback_WHEN_block_thrown_exception() {
+        val errorCallback = TestErrorCallback()
+        val error = Exception()
+
+        errorCallback.tryCatch(block = { throw error }, onSuccess = {})
+
+        assertSame(error, errorCallback.error)
+    }
+
+    @Test
+    fun calls_ErrorCallback_WHEN_errorTransformer_thrown_exception() {
+        val errorCallback = TestErrorCallback()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        errorCallback.tryCatch(
+            block = { throw error1 },
+            errorTransformer = { throw error2 },
+            onSuccess = {},
+        )
+
+        val error = errorCallback.error
+        assertIs<CompositeException>(error)
+        assertSame(error1, error.cause1)
+        assertSame(error2, error.cause2)
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_ErrorCallback_thrown_exception() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        val errorCallback = TestErrorCallback { throw error2 }
+        errorCallback.tryCatch(block = { throw error1 }, onSuccess = {})
+
+        val error = caughtException.value
+        assertIs<CompositeException>(error)
+        assertSame(error1, error.cause1)
+        assertSame(error2, error.cause2)
+    }
+
+    @Test
+    fun swallows_WHEN_reaktiveUncaughtErrorHandler_thrown_exception() {
+        reaktiveUncaughtErrorHandler = { throw Exception() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+        errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+    }
+}
diff --git a/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/HandleReaktiveErrorJvmTest.kt b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/HandleReaktiveErrorJvmTest.kt
new file mode 100644
index 000000000..22629fb00
--- /dev/null
+++ b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/HandleReaktiveErrorJvmTest.kt
@@ -0,0 +1,127 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.exceptions.CompositeException
+import com.badoo.reaktive.test.mockUncaughtExceptionHandler
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+
+class HandleReaktiveErrorJvmTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun rethrows_WHEN_VirtualMachineError() {
+        assertFailsWith<MyVirtualMachineError> {
+            handleReaktiveError(MyVirtualMachineError())
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ThreadDeath() {
+        assertFailsWith<ThreadDeath> {
+            handleReaktiveError(ThreadDeath())
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_LinkageError() {
+        assertFailsWith<LinkageError> {
+            handleReaktiveError(LinkageError())
+        }
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_exception_and_onError_not_supplied() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error = Exception()
+
+        handleReaktiveError(error)
+
+        assertEquals(error, caughtException.value)
+    }
+
+    @Test
+    fun calls_onError_WHEN_exception_and_onError_supplied() {
+        val error = Exception()
+        var receivedError: Throwable? = null
+
+        handleReaktiveError(error = error, onError = { receivedError = it })
+
+        assertEquals(error, receivedError)
+    }
+
+    @Test
+    fun rethrows_WHEN_onError_thrown_VirtualMachineError() {
+        assertFailsWith<MyVirtualMachineError> {
+            handleReaktiveError(error = Exception(), onError = { throw MyVirtualMachineError() })
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_onError_thrown_ThreadDeath() {
+        assertFailsWith<ThreadDeath> {
+            handleReaktiveError(error = Exception(), onError = { throw ThreadDeath() })
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_onError_thrown_LinkageError() {
+        assertFailsWith<LinkageError> {
+            handleReaktiveError(error = Exception(), onError = { throw LinkageError() })
+        }
+    }
+
+    @Test
+    fun calls_reaktiveUncaughtErrorHandler_WHEN_onError_thrown_exception() {
+        val caughtException = mockUncaughtExceptionHandler()
+        val error1 = Exception()
+        val error2 = Exception()
+
+        handleReaktiveError(error = error1, onError = { throw error2 })
+
+        val error = caughtException.value
+        assertIs<CompositeException>(error)
+        assertEquals(error1, error.cause1)
+        assertEquals(error2, error.cause2)
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_VirtualMachineError() {
+        reaktiveUncaughtErrorHandler = { throw MyVirtualMachineError() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            handleReaktiveError(error = Exception())
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_ThreadDeath() {
+        reaktiveUncaughtErrorHandler = { throw ThreadDeath() }
+
+        assertFailsWith<ThreadDeath> {
+            handleReaktiveError(error = Exception())
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_LinkageError() {
+        reaktiveUncaughtErrorHandler = { throw LinkageError() }
+
+        assertFailsWith<LinkageError> {
+            handleReaktiveError(error = Exception())
+        }
+    }
+
+    @Test
+    fun swallows_WHEN_reaktiveUncaughtErrorHandler_thrown_exception() {
+        reaktiveUncaughtErrorHandler = { throw Exception() }
+
+        handleReaktiveError(error = Exception())
+    }
+}
diff --git a/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/MyVirtualMachineError.kt b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/MyVirtualMachineError.kt
new file mode 100644
index 000000000..08a00d489
--- /dev/null
+++ b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/MyVirtualMachineError.kt
@@ -0,0 +1,3 @@
+package com.badoo.reaktive.utils
+
+class MyVirtualMachineError : VirtualMachineError()
diff --git a/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleJvmTest.kt b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleJvmTest.kt
new file mode 100644
index 000000000..ee3b719c6
--- /dev/null
+++ b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchAndHandleJvmTest.kt
@@ -0,0 +1,92 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.tryCatchAndHandle
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class TryCatchAndHandleJvmTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_VirtualMachineError() {
+        assertFailsWith<MyVirtualMachineError> {
+            tryCatchAndHandle { throw MyVirtualMachineError() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_ThreadDeath() {
+        assertFailsWith<ThreadDeath> {
+            tryCatchAndHandle { throw ThreadDeath() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_LinkageError() {
+        assertFailsWith<LinkageError> {
+            tryCatchAndHandle { throw LinkageError() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_VirtualMachineError() {
+        assertFailsWith<MyVirtualMachineError> {
+            tryCatchAndHandle(
+                errorTransformer = { throw MyVirtualMachineError() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_ThreadDeath() {
+        assertFailsWith<ThreadDeath> {
+            tryCatchAndHandle(
+                errorTransformer = { throw ThreadDeath() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_LinkageError() {
+        assertFailsWith<LinkageError> {
+            tryCatchAndHandle(
+                errorTransformer = { throw LinkageError() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_VirtualMachineError() {
+        reaktiveUncaughtErrorHandler = { throw MyVirtualMachineError() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            tryCatchAndHandle { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_ThreadDeath() {
+        reaktiveUncaughtErrorHandler = { throw ThreadDeath() }
+
+        assertFailsWith<ThreadDeath> {
+            tryCatchAndHandle { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_LinkageError() {
+        reaktiveUncaughtErrorHandler = { throw LinkageError() }
+
+        assertFailsWith<LinkageError> {
+            tryCatchAndHandle { throw Exception() }
+        }
+    }
+}
diff --git a/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchJvmTest.kt b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchJvmTest.kt
new file mode 100644
index 000000000..81adb8a92
--- /dev/null
+++ b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchJvmTest.kt
@@ -0,0 +1,135 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.tryCatch
+import com.badoo.reaktive.test.TestErrorCallback
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class TryCatchJvmTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch { throw MyVirtualMachineError() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch { throw ThreadDeath() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch { throw LinkageError() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch(
+                errorTransformer = { throw MyVirtualMachineError() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch(
+                errorTransformer = { throw ThreadDeath() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch(
+                errorTransformer = { throw LinkageError() },
+                block = { throw Exception() },
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback { throw MyVirtualMachineError() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback { throw ThreadDeath() }
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback { throw LinkageError() }
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_VirtualMachineError() {
+        reaktiveUncaughtErrorHandler = { throw MyVirtualMachineError() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_ThreadDeath() {
+        reaktiveUncaughtErrorHandler = { throw ThreadDeath() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_LinkageError() {
+        reaktiveUncaughtErrorHandler = { throw LinkageError() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch { throw Exception() }
+        }
+    }
+}
diff --git a/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessJvmTest.kt b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessJvmTest.kt
new file mode 100644
index 000000000..fe78c5ff5
--- /dev/null
+++ b/reaktive/src/jvmTest/kotlin/com/badoo/reaktive/utils/TryCatchWithOnSuccessJvmTest.kt
@@ -0,0 +1,138 @@
+package com.badoo.reaktive.utils
+
+import com.badoo.reaktive.base.tryCatch
+import com.badoo.reaktive.test.TestErrorCallback
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class TryCatchWithOnSuccessJvmTest {
+
+    @AfterTest
+    fun after() {
+        resetReaktiveUncaughtErrorHandler()
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch(block = { throw MyVirtualMachineError() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch(block = { throw ThreadDeath() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_block_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch(block = { throw LinkageError() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch(
+                block = { throw Exception() },
+                errorTransformer = { throw MyVirtualMachineError() },
+                onSuccess = {},
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch(
+                block = { throw Exception() },
+                errorTransformer = { throw ThreadDeath() },
+                onSuccess = {},
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_errorTransformer_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback()
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch(
+                block = { throw Exception() },
+                errorTransformer = { throw LinkageError() },
+                onSuccess = {},
+            )
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_VirtualMachineError() {
+        val errorCallback = TestErrorCallback { throw MyVirtualMachineError() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_ThreadDeath() {
+        val errorCallback = TestErrorCallback { throw ThreadDeath() }
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_ErrorCallback_thrown_LinkageError() {
+        val errorCallback = TestErrorCallback { throw LinkageError() }
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_VirtualMachineError() {
+        reaktiveUncaughtErrorHandler = { throw MyVirtualMachineError() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<MyVirtualMachineError> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_ThreadDeath() {
+        reaktiveUncaughtErrorHandler = { throw ThreadDeath() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<ThreadDeath> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+
+    @Test
+    fun rethrows_WHEN_reaktiveUncaughtErrorHandler_thrown_LinkageError() {
+        reaktiveUncaughtErrorHandler = { throw LinkageError() }
+        val errorCallback = TestErrorCallback { throw Exception() }
+
+        assertFailsWith<LinkageError> {
+            errorCallback.tryCatch(block = { throw Exception() }, onSuccess = {})
+        }
+    }
+}