Skip to content

fix: a background job's exit notification crashed the fiber - #52

Merged
webmatze merged 1 commit into
mainfrom
fix/bash-job-wait-overflow
Aug 8, 2026
Merged

fix: a background job's exit notification crashed the fiber#52
webmatze merged 1 commit into
mainfrom
fix/bash-job-wait-overflow

Conversation

@webmatze

@webmatze webmatze commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Fällt beim Testen von #29 auf, ist aber unabhängig davon — der Fehler steckt seit 61780c7 (background bash jobs) auf main.

Jedes gestartete Background-Kommando riss den Fiber ab, der seinen Exit meldet:

⏱️  Background job bash-1 started: printf '...' | base64 -d > pixel.png && ls -la
Unhandled exception in spawn: Arithmetic overflow (OverflowError)
  from time/span.cr:272:21 in '+'
  from crystal/event_loop/polling/fiber_event.cr:11:22 in 'add'
  from channel/select/timeout_action.cr:39:7 in 'wait'

Ursache

bash_jobs.cr:159 wartete mit job.wait(Time::Span::MAX). Das liest sich wie „unbegrenzt warten", ist es aber nicht: der Event-Loop legt den Timer auf now + span, und das läuft über. Isoliert reproduzierbar:

select
when done.receive
when timeout(Time::Span::MAX)   # OverflowError
end

Eine Frist, die sich nicht darstellen lässt, ist keine Frist — das parameterlose wait legt deshalb gar keinen Timer an.

Zweiter Fehler an derselben Stelle

@done trug einen einzelnen Wert, und ein einzelner Wert weckt einen einzelnen Wartenden. Es warten aber regelmäßig zwei: der Tool-Call, der sein Timeout aussitzt, und der Fiber, der on_exit feuert. Je nachdem, wie das Rennen ausging, blieb einer von beiden hängen. Das Schließen des Channels weckt alle.

Aufgefallen ist das bisher nicht, weil der Overflow ohnehin zuerst zuschlug.

Tests

Drei neue Beispiele in bash_jobs_spec.cr: on_exit muss feuern; es muss auch dann feuern, wenn schon ein anderer Fiber auf denselben Job gewartet hat; und ein fristloses wait auf einen bereits beendeten Job muss zurückkehren statt zu blockieren.

Gegen den ungefixten Stand läuft die Suite nicht mehr durch (sie hängt), mit dem Fix in 2 s. crystal spec: 555 Beispiele, 0 Fehler. Format sauber.

🤖 Generated with Claude Code

Starting any background command took down the fiber that reports its
exit with an OverflowError:

    Unhandled exception in spawn: Arithmetic overflow (OverflowError)
      from time/span.cr:272:21 in '+'
      from channel/select/timeout_action.cr:39:7 in 'wait'

`job.wait(Time::Span::MAX)` reads like "wait indefinitely", but the
event loop arms a timer at `now + span`, and that overflows. A deadline
that cannot be represented is not a deadline, so the no-argument `wait`
arms no timer at all.

A second fault at the same spot: `@done` carried a single value, and a
single value releases a single waiter. The tool call waiting out its
timeout and the on_exit fiber are both waiting, so one of them was left
hanging whichever way the race fell. Closing the channel broadcasts.

Both are covered: on_exit has to fire, and it has to fire even after
another fiber already waited on the same job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@webmatze
webmatze merged commit ce66d57 into main Aug 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant