If in doubt, go to the source. The code is a bit accurate, but this snippet:
override def run(): Unit = { try { runnable.run() val driftNanos = clock() - getAndAdd(delay.toNanos) if (self.get != null) swap(schedule(executor, this, Duration.fromNanos(Math.max(delay.toNanos - driftNanos, 1)))) } catch { case _: SchedulerException ⇒
}
shows that if your runnable throws, the scheduler does not transfer the next execution (what happens inside swap, as I understand it).
source share