Skip to content

Uncaught exception #873

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

Closed
awenger opened this issue Dec 4, 2018 · 5 comments
Closed

Uncaught exception #873

awenger opened this issue Dec 4, 2018 · 5 comments

Comments

@awenger
Copy link

awenger commented Dec 4, 2018

We have a repetitive task in our android app that is implemented with coroutines. At any given point in time, there should be only one of these tasks running. So we serialized it with an actor:


val worker = GlobalScope.actor<Int>(capacity = 128, context = Dispatchers.Default) {
    while(isActive) {
        val job = channel.receive()
        try{
            doWork(job)
        }catch(ex: Throwable) {
            // report but don't crash the app or the worker, just let this one job fail
        }
    }
}
@Throws(IOException::class)
suspend fun doWork(job: Int) {}

With our latest release we are seeing reports of crashes that should have been caught by the try catch in the actor. At first I thought that it must have originated somewhere different, but I could not find any other source. So I started to write a test to see if there is a problem:

    private suspend fun throwDelayed(value: Int) {
        delay(200)
        System.out.println("throwing $value")
        throw java.lang.Exception("for $value")
    }

    private suspend fun secondLevel(value: Int) = coroutineScope {
        (0..9).map { async { throwDelayed(value * 10 + it) } }.forEach { it.await() }
    }

    private suspend fun execute() = coroutineScope {
        val one = async { secondLevel(1) }
        val two = async { secondLevel(2) }
        one.await()
        two.await()
    }

    @Test
    fun actorTest() {
        Thread.setDefaultUncaughtExceptionHandler { t, e -> System.out.println("uncaught $e") }

        runBlocking {
            val actor = GlobalScope.actor<Int> {
                channel.receive()
                try {
                    System.out.println("Actor test")
                    execute()
                    System.out.println("Actor test done")
                } catch (ex: Exception) {
                    System.out.println("Actor caught $ex")
                }
            }
            actor.send(1)
            delay(1000)
        }
        assertTrue(true)
    }

    @Test
    fun asyncTest() {
        Thread.setDefaultUncaughtExceptionHandler { t, e -> System.out.println("uncaught $e") }
        runBlocking {
            val d = GlobalScope.async {
                try {
                    System.out.println("Async test")
                    execute()
                    System.out.println("Async test done")
                } catch (ex: Exception) {
                    System.out.println("Async caught $ex")
                }
            }
            d.await()
            delay(1000)
        }

        assertTrue(true)
    }

    @Test
    fun launchTest() {
        Thread.setDefaultUncaughtExceptionHandler { t, e -> System.out.println("uncaught $e") }
        runBlocking {
            val x = GlobalScope.launch {
                try {
                    System.out.println("Launch test")
                    execute()
                    System.out.println("Launch test done")
                } catch (ex: Exception) {
                    System.out.println("Launch caught $ex")
                }
            }
            delay(1000)
        }
        assertTrue(true)
    }

running actorTest/asyncTest/launchTest separately (running the whole test it seems to work fine) I get every now and then a case were the uncaught exception handler is triggered:

Actor

Actor test
throwing 19
throwing 29
throwing 21
throwing 20
throwing 22
throwing 23
throwing 24
throwing 25
Actor caught java.lang.Exception: for 19
uncaught java.lang.Exception: for 20

Process finished with exit code 0
Actor test
throwing 29
throwing 19
Actor caught java.lang.Exception: for 29

Process finished with exit code 0

Async

Async test
throwing 10
throwing 19
throwing 12
throwing 11
throwing 23
Async caught java.lang.Exception: for 10
uncaught java.lang.Exception: for 10

Process finished with exit code 0
Async test
throwing 23
throwing 13
throwing 29
throwing 19
throwing 10
throwing 11
throwing 12
throwing 14
throwing 15
throwing 16
throwing 17
throwing 18
Async caught java.lang.Exception: for 13

Process finished with exit code 0

Launch

Launch test
throwing 19
throwing 20
throwing 23
throwing 29
Launch caught java.lang.Exception: for 19

Process finished with exit code 0
Launch test
throwing 24
throwing 19
throwing 20
throwing 29
throwing 25
throwing 26
throwing 27
Launch caught java.lang.Exception: for 19
uncaught java.lang.Exception: for 20

Process finished with exit code 0

I tried the different launch/async/actor approaches as I guessed why misunderstood the launc/async error handling concept. Sadly the test don't trigger the behavior reliably. Is this a bug or misunderstanding of the error handling?

@awenger
Copy link
Author

awenger commented Dec 4, 2018

reading #753, I also tried to use the supervisorScope. I still get invocations of the UncaughtExceptionHandler:

        runBlocking {
            val actor = GlobalScope.actor<Int> {
                channel.receive()
                supervisorScope {
                    try {
                        System.out.println("Actor test")
                        execute()
                        System.out.println("Actor test done")
                    } catch (ex: Exception) {
                        System.out.println("Actor caught $ex")
                    }
                }
            }
            actor.send(1)
            delay(400)
        }

@awenger
Copy link
Author

awenger commented Dec 4, 2018

The only thing that worked reliably so far is a custom scope that just swallows such uncaught exceptions:

    @Test
    fun actorTest() {
        System.out.println("----")
        val LoggingExceptionHandler = CoroutineExceptionHandler { _, t -> System.out.println("coroutine exception handler: $t") }
        val customScope = GlobalScope + LoggingExceptionHandler

        Thread.setDefaultUncaughtExceptionHandler { t, e ->
            System.out.println("UNCAUGHT!!!: $e")
        }

        runBlocking {
            val actor = customScope.actor<Int> {
                channel.receive()
                supervisorScope {
                    try {
                        System.out.println("Actor test")
                        execute()
                        System.out.println("Actor test done")
                    } catch (ex: Exception) {
                        System.out.println("Actor caught $ex")
                    }
                }
            }
            actor.send(1)
            delay(400)
        }
        assertTrue(true)
    }

I hope this is a bug and not intentionally.

@awenger
Copy link
Author

awenger commented Dec 4, 2018

on slack I was redirected to #830. It seems the original problem we face is exactly as explained there. The mentioned task, that is triggered via the actor, is executing requests via OkHttp.

However I think the test cases expose another flaw/bug, as they don't use any suspendCancellableCoroutine ?

@elizarov
Copy link
Contributor

elizarov commented Dec 17, 2018

It seems related to #830 or to #893 -- the latter is a minimized example that combines Deferred.await and cancellation. Why you don't explicitly cancel in your, cancellation happens implicitly here due to the shared coroutineScope by two failing coroutines. However, I cannot reproduce your problem on my machine, so I cannot confirm. Let's keep it open, so that you can test it in your environments (where it reproduce) when fixes for #830 and #893 are out.

@qwwdfsad
Copy link
Collaborator

Closing as outdated and most likely fixed.
Please reopen if you still have any issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants