Symptom

Nightly bug-hunt flagged ServerBoss as a potential race condition: tasks (a mutableListOf) was iterated inside a scope.launch body that executed after the protecting mutex was already released, and serverJob = null was written in a finally block without holding the mutex.

Root cause

Two races:

  1. Tasks list: start() held the mutex only to call scope.launch { tasks.forEach { … } }. The coroutine body ran after the mutex was released, so a concurrent addTask() could mutate tasks mid-iteration, risking ConcurrentModificationException.

  2. serverJob reset: serverJob = null in the finally block ran without the mutex. A concurrent start() could see a non-null serverJob and skip relaunching, even though the old job had already been cancelled but not yet cleared.

Fix

Prevention