I cancelled a coroutine. It kept running for 8 more seconds. It just did not care.

This was a search screen. You type, it fires a network request. Type again, it cancels the old request and fires a new one. Standard stuff. Except the old request kept running, and every so often it finished last and painted stale results right over the fresh ones. I had called cancel. I had watched it run in the debugger. The job was "cancelled." And still it went on.

The culprit was one line I wrote myself.

Cancellation is a message, not a bullet

Here is the thing people miss about Kotlin coroutines. Cancelling one does not reach in and kill it. It cannot. The coroutine has to cooperate. So the machinery throws a CancellationException up through your suspend calls, and that exception is the message: we are done, unwind, release your resources, stop.