Commit e55041f
committed
runtime: deliver signals under the threads scheduler when no goroutine sleeps
Under the threads scheduler there is no cooperative idle loop, so
checkSignals() — which resumes the parked os/signal signal_recv goroutine —
was only ever reached from sleepTicks(). A signal was therefore only noticed
while some goroutine happened to be inside time.Sleep, and a program blocked
purely on I/O, channels, mutexes or timers (time.NewTicker uses the timer
queue, not sleepTicks) never observed it at all.
A dedicated signal-watcher thread starts the first time a signal is enabled,
gated to the threads scheduler (!hasScheduler && hasParallelism). It blocks on
signalFutex and calls checkSignals() on wake, mirroring the signal half of
waitForEvents() that the cooperative scheduler runs from its idle loop. Other
schedulers are unaffected: the start is a compile-time no-op for them.
The watcher exists only to serve enabled signals, so that is its lifetime.
enabledSignals tracks the set os/signal wants delivered, the last
signal_disable/signal_ignore stops the thread, and a later signal_enable
starts a fresh one. Without that it blocked on a futex forever, so a program
that had finished with signals kept a thread parked on one for the rest of its
life — nothing observable broke, since the thread is idle and process exit
tears it down, but a loop with no way out is a property worth not having.
Stopping sets the flag, bumps the futex value and wakes ALL waiters. The bump
matters as much as the wake: Wait(0) returns immediately when the futex is
already non-zero, which closes the window between the store and a watcher
about to sleep. WakeAll matters because the watcher is not the only thing
sleeping on signalFutex — sleepTicks and waitForEvents do too — and waking a
single waiter could wake a sleeping goroutine instead, which consumes the
value with its own Swap and leaves the watcher asleep on a futex that is 0
again, never seeing the stop flag. That is the thread leak the stop exists to
prevent. The signal handler already uses WakeAll on this futex for the same
reason. On the way out the watcher resets the futex to 0 so the next one can
block on it.
testdata/signal.go now blocks on the receive rather than on a sleep. The sleep
was doing the delivery rather than waiting for it: sleepTicks waits on the
same futex the signal handler bumps and calls checkSignals on the way out, so
the signal arrived on the back of the sleep whatever else was running, and the
test passed either way — the wrong property for the test guarding this fix.
Blocking on the receive parks the only goroutine there is, so under the
threads scheduler the watcher is the only thing left that can deliver.
Checked by disabling the watcher: with the sleep the test still passes, with
the receive it hangs and is killed. Output is unchanged, so signal.txt stays
as it is.
Verified: a channel/Accept-blocked program with no time.Sleep receives SIGINT,
signal.Stop lets the thread exit, a later signal.Notify starts a new watcher
that delivers again, and the skycoin daemon — previously unkillable with
Ctrl+C under TinyGo — shuts down cleanly on SIGINT, both idle and during
active block sync.1 parent 801bd48 commit e55041f
2 files changed
Lines changed: 111 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
390 | 390 | | |
391 | 391 | | |
392 | 392 | | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
393 | 401 | | |
394 | 402 | | |
395 | 403 | | |
396 | 404 | | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
397 | 493 | | |
398 | 494 | | |
399 | 495 | | |
400 | 496 | | |
401 | 497 | | |
402 | 498 | | |
403 | 499 | | |
| 500 | + | |
404 | 501 | | |
405 | 502 | | |
406 | 503 | | |
| |||
411 | 508 | | |
412 | 509 | | |
413 | 510 | | |
| 511 | + | |
414 | 512 | | |
415 | 513 | | |
416 | 514 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | 11 | | |
13 | 12 | | |
14 | 13 | | |
15 | 14 | | |
16 | 15 | | |
17 | 16 | | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | 17 | | |
30 | 18 | | |
31 | 19 | | |
32 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
0 commit comments