Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC
Commits:
-
d1c0af25
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
21dc121a
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
92e1c233
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
f84cc4bd
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
4aa7cd1c
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
8f33ef3a
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
7c193c28
by Duncan Coutts at 2026-03-10T22:56:32+00:00
-
1028c963
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
0d1aeb0c
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
83e9ccc5
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
b8a005b8
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
c5d0190e
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
b2d5a452
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
198a9d50
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
67ebecf0
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
bbb1cdba
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
a3c422f3
by Duncan Coutts at 2026-03-10T22:56:33+00:00
-
298694d9
by Duncan Coutts at 2026-03-10T22:59:04+00:00
-
f3684344
by Duncan Coutts at 2026-03-10T22:59:07+00:00
-
0b1fab72
by Duncan Coutts at 2026-03-10T22:59:07+00:00
-
5a5c128e
by Duncan Coutts at 2026-03-10T22:59:07+00:00
-
b29a5ae2
by Duncan Coutts at 2026-03-10T22:59:07+00:00
-
f7a336ee
by Duncan Coutts at 2026-03-10T22:59:07+00:00
-
9e15419e
by Duncan Coutts at 2026-03-10T22:59:07+00:00
29 changed files:
- docs/users_guide/runtime_control.rst
- rts/Capability.c
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/RtsFlags.c
- rts/RtsSignals.h
- rts/RtsStartup.c
- rts/RtsSymbols.c
- rts/Schedule.c
- rts/Schedule.h
- rts/Timer.c
- + rts/posix/FdWakeup.c
- + rts/posix/FdWakeup.h
- + rts/posix/MIO.c
- + rts/posix/MIO.h
- rts/posix/Poll.c
- rts/posix/Poll.h
- rts/posix/Select.c
- rts/posix/Select.h
- rts/posix/Signals.c
- rts/posix/Signals.h
- rts/rts.cabal
- rts/win32/AwaitEvent.c
- rts/win32/ConsoleHandler.c
- rts/win32/ConsoleHandler.h
- + testsuite/tests/rts/T26408.hs
- + testsuite/tests/rts/T26408.stderr
- testsuite/tests/rts/all.T
Changes:
| ... | ... | @@ -739,18 +739,18 @@ performance. |
| 739 | 739 | |
| 740 | 740 | .. rts-flag:: -I ⟨seconds⟩
|
| 741 | 741 | |
| 742 | - :default: 0.3 seconds in the threaded runtime, 0 in the non-threaded runtime
|
|
| 742 | + :default: 0.3 seconds
|
|
| 743 | 743 | |
| 744 | 744 | .. index::
|
| 745 | 745 | single: idle GC
|
| 746 | 746 | |
| 747 | - Set the amount of idle time which must pass before a idle GC is
|
|
| 748 | - performed. Setting ``-I0`` disables the idle GC.
|
|
| 747 | + A major GC is automatically performed if the runtime has been idle (no
|
|
| 748 | + Haskell computation has been running) for a period of time. Set the amount
|
|
| 749 | + of idle time which must pass before a idle GC is performed.
|
|
| 749 | 750 | |
| 750 | - In the threaded and SMP versions of the RTS (see :ghc-flag:`-threaded`,
|
|
| 751 | - :ref:`options-linker`), a major GC is automatically performed if the
|
|
| 752 | - runtime has been idle (no Haskell computation has been running) for a
|
|
| 753 | - period of time.
|
|
| 751 | + Setting ``-I0`` disables the idle GC. This also has the unfortunate side
|
|
| 752 | + effect of disabling thread deadlock detection (the implementation of which
|
|
| 753 | + uses the idle GC).
|
|
| 754 | 754 | |
| 755 | 755 | For an interactive application, it is probably a good idea to use
|
| 756 | 756 | the idle GC, because this will allow finalizers to run and
|
| ... | ... | @@ -767,8 +767,8 @@ performance. |
| 767 | 767 | after the first idle collection is triggered then no more future collections
|
| 768 | 768 | will be scheduled until more work is performed.
|
| 769 | 769 | |
| 770 | - This is an experimental feature, please let us know if it causes
|
|
| 771 | - problems and/or could benefit from further tuning.
|
|
| 770 | + Please let us know if it causes problems and/or could benefit from further
|
|
| 771 | + tuning.
|
|
| 772 | 772 | |
| 773 | 773 | .. rts-flag:: -Iw ⟨seconds⟩
|
| 774 | 774 | |
| ... | ... | @@ -779,7 +779,7 @@ performance. |
| 779 | 779 | |
| 780 | 780 | Set the minimum wait time between runs of the idle GC.
|
| 781 | 781 | |
| 782 | - By default, if idle GC is enabled in the threaded runtime, a major
|
|
| 782 | + By default (and if idle GC is not disabled) a major
|
|
| 783 | 783 | GC will be performed every time the process goes idle for a
|
| 784 | 784 | sufficiently long duration (see :rts-flag:`-I ⟨seconds⟩`). For
|
| 785 | 785 | large server processes accepting regular but infrequent requests
|
| ... | ... | @@ -1280,6 +1280,7 @@ shutdownCapabilities(Task *task, bool safe) |
| 1280 | 1280 | static void
|
| 1281 | 1281 | freeCapability (Capability *cap)
|
| 1282 | 1282 | {
|
| 1283 | + freeCapabilityIOManager(cap->iomgr);
|
|
| 1283 | 1284 | stgFree(cap->mut_lists);
|
| 1284 | 1285 | stgFree(cap->saved_mut_lists);
|
| 1285 | 1286 | if (cap->current_segments) {
|
| ... | ... | @@ -39,7 +39,7 @@ |
| 39 | 39 | #endif
|
| 40 | 40 | |
| 41 | 41 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 42 | -#include "posix/Signals.h"
|
|
| 42 | +#include "posix/MIO.h"
|
|
| 43 | 43 | #include "Prelude.h"
|
| 44 | 44 | #endif
|
| 45 | 45 | |
| ... | ... | @@ -343,9 +343,7 @@ void initCapabilityIOManager(CapIOManager *iomgr) |
| 343 | 343 | switch (iomgr_type) {
|
| 344 | 344 | #if defined(IOMGR_ENABLED_SELECT)
|
| 345 | 345 | case IO_MANAGER_SELECT:
|
| 346 | - iomgr->blocked_queue_hd = END_TSO_QUEUE;
|
|
| 347 | - iomgr->blocked_queue_tl = END_TSO_QUEUE;
|
|
| 348 | - iomgr->sleeping_queue = END_TSO_QUEUE;
|
|
| 346 | + initCapabilityIOManagerSelect(iomgr);
|
|
| 349 | 347 | break;
|
| 350 | 348 | #endif
|
| 351 | 349 | |
| ... | ... | @@ -373,11 +371,31 @@ void initCapabilityIOManager(CapIOManager *iomgr) |
| 373 | 371 | }
|
| 374 | 372 | |
| 375 | 373 | |
| 374 | +void freeCapabilityIOManager(CapIOManager *iomgr)
|
|
| 375 | +{
|
|
| 376 | + switch (iomgr_type) {
|
|
| 377 | +#if defined(IOMGR_ENABLED_SELECT)
|
|
| 378 | + case IO_MANAGER_SELECT:
|
|
| 379 | + freeCapabilityIOManagerSelect(iomgr);
|
|
| 380 | + break;
|
|
| 381 | +#endif
|
|
| 382 | + |
|
| 383 | +#if defined(IOMGR_ENABLED_POLL)
|
|
| 384 | + case IO_MANAGER_POLL:
|
|
| 385 | + freeCapabilityIOManagerPoll(iomgr);
|
|
| 386 | + break;
|
|
| 387 | +#endif
|
|
| 388 | + default:
|
|
| 389 | + break;
|
|
| 390 | + }
|
|
| 391 | +}
|
|
| 392 | + |
|
| 393 | + |
|
| 376 | 394 | /* Called late in the RTS initialisation
|
| 377 | 395 | */
|
| 378 | -void initIOManager(void)
|
|
| 396 | +void startIOManager(void)
|
|
| 379 | 397 | {
|
| 380 | - debugTrace(DEBUG_iomanager, "initialising %s I/O manager", showIOManager());
|
|
| 398 | + debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager());
|
|
| 381 | 399 | |
| 382 | 400 | switch (iomgr_type) {
|
| 383 | 401 | |
| ... | ... | @@ -441,7 +459,7 @@ void initIOManager(void) |
| 441 | 459 | /* Called from forkProcess in the child process on the surviving capability.
|
| 442 | 460 | */
|
| 443 | 461 | void
|
| 444 | -initIOManagerAfterFork(CapIOManager *iomgr, Capability **pcap)
|
|
| 462 | +restartIOManager(CapIOManager *iomgr, Capability **pcap)
|
|
| 445 | 463 | {
|
| 446 | 464 | |
| 447 | 465 | switch (iomgr_type) {
|
| ... | ... | @@ -541,13 +559,25 @@ exitIOManager(bool wait_threads) |
| 541 | 559 | }
|
| 542 | 560 | }
|
| 543 | 561 | |
| 544 | -/* Wakeup hook: called from the scheduler's wakeUpRts (currently only in
|
|
| 545 | - * threaded mode).
|
|
| 562 | +/* Wakeup hook: called from the scheduler's wakeUpRts
|
|
| 546 | 563 | */
|
| 547 | 564 | void wakeupIOManager(void)
|
| 548 | 565 | {
|
| 566 | + debugTrace(DEBUG_iomanager, "Sending wakeup to I/O manager...");
|
|
| 549 | 567 | switch (iomgr_type) {
|
| 550 | 568 | |
| 569 | +#if defined(IOMGR_ENABLED_SELECT)
|
|
| 570 | + case IO_MANAGER_SELECT:
|
|
| 571 | + wakeupIOManagerSelect(MainCapability.iomgr);
|
|
| 572 | + break;
|
|
| 573 | +#endif
|
|
| 574 | + |
|
| 575 | +#if defined(IOMGR_ENABLED_POLL)
|
|
| 576 | + case IO_MANAGER_POLL:
|
|
| 577 | + wakeupIOManagerPoll(MainCapability.iomgr);
|
|
| 578 | + break;
|
|
| 579 | +#endif
|
|
| 580 | + |
|
| 551 | 581 | #if defined(IOMGR_ENABLED_MIO_POSIX)
|
| 552 | 582 | case IO_MANAGER_MIO_POSIX:
|
| 553 | 583 | /* MIO Posix implementation in posix/Signals.c */
|
| ... | ... | @@ -572,8 +602,13 @@ void wakeupIOManager(void) |
| 572 | 602 | #endif
|
| 573 | 603 | break;
|
| 574 | 604 | #endif
|
| 575 | - default:
|
|
| 605 | +#if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
|
| 606 | + case IO_MANAGER_WIN32_LEGACY:
|
|
| 607 | + abandonRequestWait();
|
|
| 576 | 608 | break;
|
| 609 | +#endif
|
|
| 610 | + default:
|
|
| 611 | + barf("wakeupIOManager not implemented");
|
|
| 577 | 612 | }
|
| 578 | 613 | }
|
| 579 | 614 | |
| ... | ... | @@ -661,64 +696,6 @@ setIOManagerControlFd(uint32_t cap_no, int fd) { |
| 661 | 696 | #endif
|
| 662 | 697 | |
| 663 | 698 | |
| 664 | -bool anyPendingTimeoutsOrIO(CapIOManager *iomgr)
|
|
| 665 | -{
|
|
| 666 | - switch (iomgr_type) {
|
|
| 667 | -#if defined(IOMGR_ENABLED_SELECT)
|
|
| 668 | - case IO_MANAGER_SELECT:
|
|
| 669 | - return (iomgr->blocked_queue_hd != END_TSO_QUEUE)
|
|
| 670 | - || (iomgr->sleeping_queue != END_TSO_QUEUE);
|
|
| 671 | -#endif
|
|
| 672 | - |
|
| 673 | -#if defined(IOMGR_ENABLED_POLL)
|
|
| 674 | - case IO_MANAGER_POLL:
|
|
| 675 | - return anyPendingTimeoutsOrIOPoll(iomgr);
|
|
| 676 | -#endif
|
|
| 677 | - |
|
| 678 | -#if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
|
| 679 | - case IO_MANAGER_WIN32_LEGACY:
|
|
| 680 | - return (iomgr->blocked_queue_hd != END_TSO_QUEUE);
|
|
| 681 | -#endif
|
|
| 682 | - |
|
| 683 | - /* For the purpose of the scheduler, the threaded I/O managers never have
|
|
| 684 | - pending I/O or timers. Of course in reality they do, but they're
|
|
| 685 | - managed via other primitives that the scheduler can see into (threads,
|
|
| 686 | - MVars and foreign blocking calls).
|
|
| 687 | - */
|
|
| 688 | -#if defined(IOMGR_ENABLED_MIO_POSIX)
|
|
| 689 | - case IO_MANAGER_MIO_POSIX:
|
|
| 690 | - return false;
|
|
| 691 | -#endif
|
|
| 692 | - |
|
| 693 | -#if defined(IOMGR_ENABLED_MIO_WIN32)
|
|
| 694 | - case IO_MANAGER_MIO_WIN32:
|
|
| 695 | - return false;
|
|
| 696 | -#endif
|
|
| 697 | - |
|
| 698 | -#if defined(IOMGR_ENABLED_WINIO)
|
|
| 699 | -#if defined(THREADED_RTS)
|
|
| 700 | - /* As above, the threaded variants never have pending I/O or timers */
|
|
| 701 | - case IO_MANAGER_WINIO:
|
|
| 702 | - return false;
|
|
| 703 | -#else
|
|
| 704 | - case IO_MANAGER_WINIO:
|
|
| 705 | - return false;
|
|
| 706 | - /* FIXME: But what is this? The WinIO I/O manager *also* returns false
|
|
| 707 | - in the non-threaded case! This is *totally bogus*! In the
|
|
| 708 | - non-threaded RTS the scheduler expects to be able to poll for IO.
|
|
| 709 | - The fact that this gives a wrong and useless answer for WinIO is
|
|
| 710 | - probably the cause of the complication in the scheduler with having
|
|
| 711 | - to call awaitCompletedTimeoutsOrIO() in multiple places (on Windows,
|
|
| 712 | - non-threaded).
|
|
| 713 | - */
|
|
| 714 | -#endif
|
|
| 715 | -#endif
|
|
| 716 | - default:
|
|
| 717 | - barf("anyPendingTimeoutsOrIO not implemented");
|
|
| 718 | - }
|
|
| 719 | -}
|
|
| 720 | - |
|
| 721 | - |
|
| 722 | 699 | void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
|
| 723 | 700 | {
|
| 724 | 701 | debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts");
|
| ... | ... | @@ -782,7 +759,9 @@ void awaitCompletedTimeoutsOrIO(CapIOManager *iomgr) |
| 782 | 759 | default:
|
| 783 | 760 | barf("pollCompletedTimeoutsOrIO not implemented");
|
| 784 | 761 | }
|
| 785 | - ASSERT(!emptyRunQueue(iomgr->cap) || getSchedState() != SCHED_RUNNING);
|
|
| 762 | + // FIXME: the post condition is now more complicated. Await can now simply
|
|
| 763 | + // be interrupted by wakeupIOManager.
|
|
| 764 | + // ASSERT(!emptyRunQueue(iomgr->cap) || getSchedState() != SCHED_RUNNING);
|
|
| 786 | 765 | }
|
| 787 | 766 | |
| 788 | 767 |
| ... | ... | @@ -15,6 +15,11 @@ |
| 15 | 15 | * subsystem implementations are centralised here. Not all implementations use
|
| 16 | 16 | * all hooks.
|
| 17 | 17 | *
|
| 18 | + * I/O manager are responsible for:
|
|
| 19 | + * - threads waiting on I/O
|
|
| 20 | + * - threads waiting on timeouts
|
|
| 21 | + * - signals (unix, and win32 console) starting handlers
|
|
| 22 | + *
|
|
| 18 | 23 | * -------------------------------------------------------------------------*/
|
| 19 | 24 | |
| 20 | 25 | #pragma once
|
| ... | ... | @@ -242,11 +247,33 @@ CapIOManager *allocCapabilityIOManager(Capability *cap); |
| 242 | 247 | */
|
| 243 | 248 | void initCapabilityIOManager(CapIOManager *iomgr);
|
| 244 | 249 | |
| 250 | +/* When shutting down a capability, or after forkProcess, free the resources
|
|
| 251 | + * held by a CapIOManager to put it back into a state in which either it can be
|
|
| 252 | + * re-initialised using initCapabilityIOManager, or the whole structure freed.
|
|
| 253 | + *
|
|
| 254 | + * Note that this does not free the CapIOManager structure itself, just the
|
|
| 255 | + * contents.
|
|
| 256 | + *
|
|
| 257 | + * This is used during capability shutdown, during RTS shutdown. It is not used
|
|
| 258 | + * when reducing the number of capabilities. Capabilities are disabled rather
|
|
| 259 | + * than freed entirely: the I/O manager keeps running but threads that become
|
|
| 260 | + * runnable are migrated away.
|
|
| 261 | + *
|
|
| 262 | + * It is also used after forkProcess.
|
|
| 263 | + */
|
|
| 264 | +void freeCapabilityIOManager(CapIOManager *iomgr);
|
|
| 265 | + |
|
| 266 | +/* CapIOManager life cycle:
|
|
| 267 | + *
|
|
| 268 | + * alloc -> init -> free -> free struct
|
|
| 269 | + * ^ |
|
|
| 270 | + * +--------+
|
|
| 271 | + */
|
|
| 245 | 272 | |
| 246 | 273 | /* Init hook: called from hs_init_ghc, very late in the startup after almost
|
| 247 | 274 | * everything else is done.
|
| 248 | 275 | */
|
| 249 | -void initIOManager(void);
|
|
| 276 | +void startIOManager(void);
|
|
| 250 | 277 | |
| 251 | 278 | |
| 252 | 279 | /* Init hook: called from forkProcess in the child process on the surviving
|
| ... | ... | @@ -255,8 +282,8 @@ void initIOManager(void); |
| 255 | 282 | * This is synchronous and can run Haskell code, so can change the given cap.
|
| 256 | 283 | * TODO: it would make for a cleaner API here if this were made asynchronous.
|
| 257 | 284 | */
|
| 258 | -void initIOManagerAfterFork(CapIOManager *iomgr,
|
|
| 259 | - /* inout */ Capability **pcap);
|
|
| 285 | +void restartIOManager(CapIOManager *iomgr,
|
|
| 286 | + /* inout */ Capability **pcap);
|
|
| 260 | 287 | |
| 261 | 288 | /* TODO: rationalise initIOManager and initIOManagerAfterFork into a single
|
| 262 | 289 | per-capability init function.
|
| ... | ... | @@ -283,19 +310,13 @@ void stopIOManager(void); |
| 283 | 310 | void exitIOManager(bool wait_threads);
|
| 284 | 311 | |
| 285 | 312 | |
| 286 | -/* Wakeup hook: called from the scheduler's wakeUpRts (currently only in
|
|
| 287 | - * threaded mode).
|
|
| 313 | +/* Wakeup hook: called from the scheduler's wakeUpRts().
|
|
| 288 | 314 | *
|
| 289 | 315 | * The I/O manager can be blocked waiting on I/O or timers. Sometimes there are
|
| 290 | 316 | * other external events where we need to wake up the I/O manager and return
|
| 291 | - * to the schedulr.
|
|
| 292 | - *
|
|
| 293 | - * At the moment, all the non-threaded I/O managers will do this automagically
|
|
| 294 | - * since a signal will interrupt any waiting system calls, so at the moment
|
|
| 295 | - * the implementation for the non-threaded I/O managers does nothing.
|
|
| 317 | + * to the scheduler.
|
|
| 296 | 318 | *
|
| 297 | - * For the I/O managers in threaded mode, this arranges to unblock the I/O
|
|
| 298 | - * manager if it waa blocked waiting.
|
|
| 319 | + * This arranges to unblock the I/O manager if it was blocked waiting.
|
|
| 299 | 320 | */
|
| 300 | 321 | void wakeupIOManager(void);
|
| 301 | 322 | |
| ... | ... | @@ -343,35 +364,27 @@ void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso); |
| 343 | 364 | void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso);
|
| 344 | 365 | #endif
|
| 345 | 366 | |
| 346 | -/* Check to see if there are any pending timeouts or I/O operations
|
|
| 347 | - * in progress with the I/O manager.
|
|
| 367 | +/* Poll for any completed I/O operations, expired timers or pending signals
|
|
| 368 | + * with handlers. If there are any, process the completions as appropriate
|
|
| 369 | + * (which will typically unblock some waiting threads).
|
|
| 348 | 370 | *
|
| 349 | - * This is used by the scheduler as part of deadlock-detection, and the
|
|
| 350 | - * "context switch as often as possible" test.
|
|
| 351 | - */
|
|
| 352 | -bool anyPendingTimeoutsOrIO(CapIOManager *iomgr);
|
|
| 353 | - |
|
| 354 | -/* If there are any completed I/O operations or expired timers, process the
|
|
| 355 | - * completions as appropriate (which will typically unblock some waiting
|
|
| 356 | - * threads, but no guarantee). If there are none, return without waiting.
|
|
| 371 | + * This polls, but does not block.
|
|
| 372 | + *
|
|
| 373 | + * No post-condition. It does not guarantee anything such as there being
|
|
| 374 | + * runnable threads, since this does not wait.
|
|
| 357 | 375 | *
|
| 358 | - * Called from schedule() both *before* and *after* scheduleDetectDeadlock().
|
|
| 376 | + * Called from schedule() before scheduleDetectDeadlock().
|
|
| 359 | 377 | */
|
| 360 | 378 | void pollCompletedTimeoutsOrIO(CapIOManager *iomgr);
|
| 361 | 379 | |
| 362 | - /* If there are any completed I/O operations or expired timers, process the
|
|
| 363 | - * completions as appropriate. If there are none, wait until I/O or a timer
|
|
| 364 | - * does complete (or we get a signal with a handler) and process the
|
|
| 365 | - * completions as appropriate.
|
|
| 380 | +/* Wait for completed I/O operations, expired timers or signals and process
|
|
| 381 | + * the completions as appropriate.
|
|
| 366 | 382 | *
|
| 367 | 383 | * Upon return this guarantees that the scheduler run queue is non-empty or
|
| 368 | 384 | * that the scheduler is no longer in the running state. Succinctly, the
|
| 369 | 385 | * post-condition is (!emptyRunQueue(cap) || getSchedState() != SCHED_RUNNING).
|
| 370 | 386 | *
|
| 371 | - * This is only expected to be called if anyPendingTimeoutsOrIO() returns true,
|
|
| 372 | - * i.e. there actually is something to wait for.
|
|
| 373 | - *
|
|
| 374 | - * Called from schedule() both *before* and *after* scheduleDetectDeadlock().
|
|
| 387 | + * Called from schedule() after scheduleDetectDeadlock().
|
|
| 375 | 388 | */
|
| 376 | 389 | void awaitCompletedTimeoutsOrIO(CapIOManager *iomgr);
|
| 377 | 390 |
| ... | ... | @@ -46,6 +46,11 @@ struct _CapIOManager { |
| 46 | 46 | StgTSO *sleeping_queue;
|
| 47 | 47 | #endif
|
| 48 | 48 | |
| 49 | +#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
|
|
| 50 | + /* FDs for waking up the I/O manager when it is blocked waiting */
|
|
| 51 | + int wakeup_fd_r, wakeup_fd_w;
|
|
| 52 | +#endif
|
|
| 53 | + |
|
| 49 | 54 | #if defined(IOMGR_ENABLED_POLL)
|
| 50 | 55 | /* AIOP and timeout collections shared by several I/O manager impls */
|
| 51 | 56 | ClosureTable aiop_table;
|
| ... | ... | @@ -53,8 +58,11 @@ struct _CapIOManager { |
| 53 | 58 | #endif
|
| 54 | 59 | |
| 55 | 60 | #if defined(IOMGR_ENABLED_POLL)
|
| 56 | - /* Auxiliary table with size and indexes matching the aiop_table */
|
|
| 57 | - struct pollfd *aiop_poll_table;
|
|
| 61 | + /* Auxiliary table with size and indexes matching the aiop_table. This is
|
|
| 62 | + * aliased to the tail of the full poll table, which has a head entry for
|
|
| 63 | + * the wakeup_fd_r above, so we can also poll that fd.
|
|
| 64 | + */
|
|
| 65 | + struct pollfd *aiop_poll_table, *full_poll_table;
|
|
| 58 | 66 | #endif
|
| 59 | 67 | |
| 60 | 68 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -173,11 +173,7 @@ void initRtsFlagsDefaults(void) |
| 173 | 173 | RtsFlags.GcFlags.sweep = false;
|
| 174 | 174 | RtsFlags.GcFlags.idleGCDelayTime = USToTime(300000); // 300ms
|
| 175 | 175 | RtsFlags.GcFlags.interIdleGCWait = 0;
|
| 176 | -#if defined(THREADED_RTS)
|
|
| 177 | 176 | RtsFlags.GcFlags.doIdleGC = true;
|
| 178 | -#else
|
|
| 179 | - RtsFlags.GcFlags.doIdleGC = false;
|
|
| 180 | -#endif
|
|
| 181 | 177 | RtsFlags.GcFlags.heapBase = 0; /* means don't care */
|
| 182 | 178 | RtsFlags.GcFlags.allocLimitGrace = (100*1024) / BLOCK_SIZE;
|
| 183 | 179 | RtsFlags.GcFlags.numa = false;
|
| ... | ... | @@ -2,26 +2,15 @@ |
| 2 | 2 | *
|
| 3 | 3 | * (c) The GHC Team, 1998-2005
|
| 4 | 4 | *
|
| 5 | - * Signal processing / handling.
|
|
| 5 | + * Signal processing / handling. This is the shared API to the subsystems for
|
|
| 6 | + * POSIX signals and Win32 console events.
|
|
| 7 | + *
|
|
| 8 | + * Platform specific APIs live in posix/Signals.h and win32/ConsoleHandler.h
|
|
| 6 | 9 | *
|
| 7 | 10 | * ---------------------------------------------------------------------------*/
|
| 8 | 11 | |
| 9 | 12 | #pragma once
|
| 10 | 13 | |
| 11 | -#if !defined(mingw32_HOST_OS) && defined(HAVE_SIGNAL_H)
|
|
| 12 | - |
|
| 13 | -#include "posix/Signals.h"
|
|
| 14 | - |
|
| 15 | -#elif defined(mingw32_HOST_OS)
|
|
| 16 | - |
|
| 17 | -#include "win32/ConsoleHandler.h"
|
|
| 18 | - |
|
| 19 | -#else
|
|
| 20 | - |
|
| 21 | -#define signals_pending() (false)
|
|
| 22 | - |
|
| 23 | -#endif
|
|
| 24 | - |
|
| 25 | 14 | #if defined(RTS_USER_SIGNALS)
|
| 26 | 15 | |
| 27 | 16 | #include "BeginPrivate.h"
|
| ... | ... | @@ -44,39 +33,20 @@ void resetDefaultHandlers(void); |
| 44 | 33 | |
| 45 | 34 | void freeSignalHandlers(void);
|
| 46 | 35 | |
| 47 | -/*
|
|
| 48 | - * Function: awaitUserSignals()
|
|
| 49 | - *
|
|
| 50 | - * Wait for the next console event. Currently a NOP (returns immediately.)
|
|
| 36 | +/* Tear down and shut down user signal processing.
|
|
| 37 | + * This is called *after* freeSignalHandlers, but unconditionally!
|
|
| 38 | + * TODO: unify this and freeSignalHandlers together, and make them make sense!
|
|
| 51 | 39 | */
|
| 52 | -void awaitUserSignals(void);
|
|
| 40 | +void finiUserSignals(void);
|
|
| 53 | 41 | |
| 54 | 42 | /*
|
| 55 | 43 | * Function: startPendingSignalHandlers()
|
| 56 | 44 | *
|
| 57 | - * Start any pending signal handlers. This is used by the scheduler and some
|
|
| 58 | - * in-RTS I/O managers. It does nothing (returns false) in the threaded RTS.
|
|
| 59 | - *
|
|
| 60 | - * Returns true if any signal handlers were pending and thus started.
|
|
| 45 | + * If there are any queued up posix signals or win32 console events, run the
|
|
| 46 | + * handlers associated with them. This is used by some in-RTS I/O managers.
|
|
| 61 | 47 | */
|
| 62 | -INLINE_HEADER bool startPendingSignalHandlers(Capability *cap);
|
|
| 63 | - |
|
| 64 | 48 | #if !defined(THREADED_RTS)
|
| 65 | -INLINE_HEADER bool startPendingSignalHandlers(Capability *cap)
|
|
| 66 | -{
|
|
| 67 | - if (RtsFlags.MiscFlags.install_signal_handlers && signals_pending()) {
|
|
| 68 | - // safe outside the lock
|
|
| 69 | - startSignalHandlers(cap);
|
|
| 70 | - return true;
|
|
| 71 | - } else {
|
|
| 72 | - return false;
|
|
| 73 | - }
|
|
| 74 | -}
|
|
| 75 | -#else
|
|
| 76 | -INLINE_HEADER bool startPendingSignalHandlers(Capability *cap STG_UNUSED)
|
|
| 77 | -{
|
|
| 78 | - return false;
|
|
| 79 | -}
|
|
| 49 | +void startPendingSignalHandlers(Capability *cap);
|
|
| 80 | 50 | #endif
|
| 81 | 51 | |
| 82 | 52 | #include "EndPrivate.h"
|
| ... | ... | @@ -70,6 +70,10 @@ |
| 70 | 70 | #include <locale.h>
|
| 71 | 71 | #endif
|
| 72 | 72 | |
| 73 | +#if !defined(mingw32_HOST_OS) && defined(HAVE_SIGNAL_H)
|
|
| 74 | +#include <signal.h>
|
|
| 75 | +#endif
|
|
| 76 | + |
|
| 73 | 77 | // Count of how many outstanding hs_init()s there have been.
|
| 74 | 78 | static StgWord hs_init_count = 0;
|
| 75 | 79 | static bool rts_shutdown = false;
|
| ... | ... | @@ -427,7 +431,7 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config) |
| 427 | 431 | }
|
| 428 | 432 | #endif
|
| 429 | 433 | |
| 430 | - initIOManager();
|
|
| 434 | + startIOManager();
|
|
| 431 | 435 | |
| 432 | 436 | x86_init_fpu();
|
| 433 | 437 | |
| ... | ... | @@ -619,9 +623,10 @@ hs_exit_(bool wait_foreign) |
| 619 | 623 | #if defined(mingw32_HOST_OS)
|
| 620 | 624 | if (is_io_mng_native_p())
|
| 621 | 625 | hs_restoreConsoleCP();
|
| 626 | +#endif
|
|
| 622 | 627 | |
| 623 | - /* Disable console signal handlers, we're going down!. */
|
|
| 624 | - finiUserSignals ();
|
|
| 628 | +#if defined(RTS_USER_SIGNALS)
|
|
| 629 | + finiUserSignals();
|
|
| 625 | 630 | #endif
|
| 626 | 631 | |
| 627 | 632 | /* tear down statistics subsystem */
|
| ... | ... | @@ -71,7 +71,6 @@ extern char **environ; |
| 71 | 71 | SymI_HasProto(__hscore_get_saved_termios) \
|
| 72 | 72 | SymI_HasProto(__hscore_set_saved_termios) \
|
| 73 | 73 | SymI_HasProto(shutdownHaskellAndSignal) \
|
| 74 | - SymI_HasProto(signal_handlers) \
|
|
| 75 | 74 | SymI_HasProto(stg_sig_install) \
|
| 76 | 75 | SymI_HasProto(rtsTimerSignal) \
|
| 77 | 76 | SymI_NeedsDataProto(nocldstop)
|
| ... | ... | @@ -146,7 +146,6 @@ static void acquireAllCapabilities(Capability *cap, Task *task); |
| 146 | 146 | static void startWorkerTasks (uint32_t from USED_IF_THREADS,
|
| 147 | 147 | uint32_t to USED_IF_THREADS);
|
| 148 | 148 | #endif
|
| 149 | -static void scheduleCheckBlockedThreads (Capability *cap);
|
|
| 150 | 149 | static void scheduleProcessInbox(Capability **cap);
|
| 151 | 150 | static void scheduleDetectDeadlock (Capability **pcap, Task *task);
|
| 152 | 151 | static void schedulePushWork(Capability *cap, Task *task);
|
| ... | ... | @@ -174,6 +173,11 @@ static void deleteAllThreads (void); |
| 174 | 173 | static void deleteThread_(StgTSO *tso);
|
| 175 | 174 | #endif
|
| 176 | 175 | |
| 176 | +#if defined(FORKPROCESS_PRIMOP_SUPPORTED)
|
|
| 177 | +static void truncateRunQueue(Capability *cap);
|
|
| 178 | +#endif
|
|
| 179 | +static StgTSO *popRunQueue (Capability *cap);
|
|
| 180 | + |
|
| 177 | 181 | /* ---------------------------------------------------------------------------
|
| 178 | 182 | Main scheduling loop.
|
| 179 | 183 | |
| ... | ... | @@ -295,21 +299,21 @@ schedule (Capability *initialCapability, Task *task) |
| 295 | 299 | (pushes threads, wakes up idle capabilities for stealing) */
|
| 296 | 300 | schedulePushWork(cap,task);
|
| 297 | 301 | |
| 298 | - scheduleDetectDeadlock(&cap,task);
|
|
| 302 | + if (emptyRunQueue(cap)) {
|
|
| 303 | + /* When we have no threads to run, we *might* have a deadlock. */
|
|
| 304 | + scheduleDetectDeadlock(&cap,task);
|
|
| 305 | + }
|
|
| 299 | 306 | |
| 300 | - // Normally, the only way we can get here with no threads to
|
|
| 301 | - // run is if a keyboard interrupt received during
|
|
| 302 | - // scheduleCheckBlockedThreads() or scheduleDetectDeadlock().
|
|
| 303 | - // Additionally, it is not fatal for the
|
|
| 304 | - // threaded RTS to reach here with no threads to run.
|
|
| 305 | - //
|
|
| 306 | - // Since IOPorts have no deadlock avoidance guarantees you may also reach
|
|
| 307 | - // this point when blocked on an IO Port. If this is the case the only
|
|
| 308 | - // thing that could unblock it is an I/O event.
|
|
| 309 | - //
|
|
| 310 | - // win32: might be here due to awaitCompletedTimeoutsOrIO() being abandoned
|
|
| 311 | - // as a result of a console event having been delivered or as a result of
|
|
| 312 | - // waiting on an async I/O to complete with WinIO.
|
|
| 307 | +#if !defined(THREADED_RTS)
|
|
| 308 | + /* scheduleFindWork checks for completed I/O but does not block. If there
|
|
| 309 | + * is nothing to do now, we block and wait for I/O, timeouts or signals.
|
|
| 310 | + * Importantly, we only block /after/ checking for deadlocks. See #26408.
|
|
| 311 | + */
|
|
| 312 | + if (emptyRunQueue(cap)) {
|
|
| 313 | + awaitCompletedTimeoutsOrIO(cap->iomgr);
|
|
| 314 | + if (emptyRunQueue(cap)) continue; // look for work again
|
|
| 315 | + }
|
|
| 316 | +#endif
|
|
| 313 | 317 | |
| 314 | 318 | #if defined(THREADED_RTS)
|
| 315 | 319 | scheduleYield(&cap,task);
|
| ... | ... | @@ -317,22 +321,6 @@ schedule (Capability *initialCapability, Task *task) |
| 317 | 321 | if (emptyRunQueue(cap)) continue; // look for work again
|
| 318 | 322 | #endif
|
| 319 | 323 | |
| 320 | -#if !defined(THREADED_RTS)
|
|
| 321 | - if ( emptyRunQueue(cap) ) {
|
|
| 322 | -#if defined(mingw32_HOST_OS)
|
|
| 323 | - /* Notify the I/O manager that we have nothing to do. If there are
|
|
| 324 | - any outstanding I/O requests we'll block here. If there are not
|
|
| 325 | - then this is a user error and we will abort soon. */
|
|
| 326 | - /* TODO: see if we can rationalise these two awaitCompletedTimeoutsOrIO
|
|
| 327 | - * calls before and after scheduleDetectDeadlock().
|
|
| 328 | - */
|
|
| 329 | - awaitCompletedTimeoutsOrIO(cap->iomgr);
|
|
| 330 | -#else
|
|
| 331 | - ASSERT(getSchedState() >= SCHED_INTERRUPTING);
|
|
| 332 | -#endif
|
|
| 333 | - }
|
|
| 334 | -#endif
|
|
| 335 | - |
|
| 336 | 324 | //
|
| 337 | 325 | // Get a thread to run
|
| 338 | 326 | //
|
| ... | ... | @@ -403,13 +391,12 @@ schedule (Capability *initialCapability, Task *task) |
| 403 | 391 | }
|
| 404 | 392 | #endif
|
| 405 | 393 | |
| 406 | - /* context switches are initiated by the timer signal, unless
|
|
| 407 | - * the user specified "context switch as often as possible", with
|
|
| 408 | - * +RTS -C0
|
|
| 409 | - */
|
|
| 410 | - if (RtsFlags.ConcFlags.ctxtSwitchTicks == 0 &&
|
|
| 411 | - (!emptyRunQueue(cap) ||
|
|
| 412 | - anyPendingTimeoutsOrIO(cap->iomgr))) {
|
|
| 394 | + // Context switches are normally initiated by the timer signal. If however
|
|
| 395 | + // the user specified "context switch as often as possible", with +RTS -C0
|
|
| 396 | + // then we now arrange for an early context switch. Context switching very
|
|
| 397 | + // often is expensive, so as an optimisation if there's no other threads
|
|
| 398 | + // to run then we don't arrange a context switch.
|
|
| 399 | + if (RtsFlags.ConcFlags.ctxtSwitchTicks == 0 && !emptyRunQueue(cap)) {
|
|
| 413 | 400 | RELAXED_STORE(&cap->context_switch, 1);
|
| 414 | 401 | }
|
| 415 | 402 | |
| ... | ... | @@ -591,42 +578,12 @@ run_thread: |
| 591 | 578 | } /* end of while() */
|
| 592 | 579 | }
|
| 593 | 580 | |
| 594 | -/* -----------------------------------------------------------------------------
|
|
| 595 | - * Run queue operations
|
|
| 596 | - * -------------------------------------------------------------------------- */
|
|
| 597 | - |
|
| 598 | -static void
|
|
| 599 | -removeFromRunQueue (Capability *cap, StgTSO *tso)
|
|
| 600 | -{
|
|
| 601 | - if (tso->block_info.prev == END_TSO_QUEUE) {
|
|
| 602 | - ASSERT(cap->run_queue_hd == tso);
|
|
| 603 | - cap->run_queue_hd = tso->_link;
|
|
| 604 | - } else {
|
|
| 605 | - setTSOLink(cap, tso->block_info.prev, tso->_link);
|
|
| 606 | - }
|
|
| 607 | - if (tso->_link == END_TSO_QUEUE) {
|
|
| 608 | - ASSERT(cap->run_queue_tl == tso);
|
|
| 609 | - cap->run_queue_tl = tso->block_info.prev;
|
|
| 610 | - } else {
|
|
| 611 | - setTSOPrev(cap, tso->_link, tso->block_info.prev);
|
|
| 612 | - }
|
|
| 613 | - tso->_link = tso->block_info.prev = END_TSO_QUEUE;
|
|
| 614 | - cap->n_run_queue--;
|
|
| 615 | - |
|
| 616 | - IF_DEBUG(sanity, checkRunQueue(cap));
|
|
| 617 | -}
|
|
| 618 | - |
|
| 619 | -void
|
|
| 620 | -promoteInRunQueue (Capability *cap, StgTSO *tso)
|
|
| 621 | -{
|
|
| 622 | - removeFromRunQueue(cap, tso);
|
|
| 623 | - pushOnRunQueue(cap, tso);
|
|
| 624 | -}
|
|
| 625 | - |
|
| 626 | 581 | /* -----------------------------------------------------------------------------
|
| 627 | 582 | * scheduleFindWork()
|
| 628 | 583 | *
|
| 629 | 584 | * Search for work to do, and handle messages from elsewhere.
|
| 585 | + *
|
|
| 586 | + * This does *not* block/wait, even in the non-threaded case.
|
|
| 630 | 587 | * -------------------------------------------------------------------------- */
|
| 631 | 588 | |
| 632 | 589 | static void
|
| ... | ... | @@ -635,16 +592,17 @@ scheduleFindWork (Capability **pcap) |
| 635 | 592 | #if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
|
| 636 | 593 | queueIOThread();
|
| 637 | 594 | #endif
|
| 638 | -#if defined(RTS_USER_SIGNALS)
|
|
| 639 | - startPendingSignalHandlers(*pcap);
|
|
| 640 | -#endif
|
|
| 641 | - |
|
| 642 | 595 | scheduleProcessInbox(pcap);
|
| 643 | 596 | |
| 644 | - scheduleCheckBlockedThreads(*pcap);
|
|
| 597 | + /* From here on, the cap can't change. */
|
|
| 598 | + Capability *cap = *pcap;
|
|
| 599 | + |
|
| 600 | +#if !defined(THREADED_RTS)
|
|
| 601 | + pollCompletedTimeoutsOrIO(cap->iomgr);
|
|
| 602 | +#endif
|
|
| 645 | 603 | |
| 646 | 604 | #if defined(THREADED_RTS)
|
| 647 | - if (emptyRunQueue(*pcap)) { scheduleActivateSpark(*pcap); }
|
|
| 605 | + if (emptyRunQueue(cap)) { scheduleActivateSpark(cap); }
|
|
| 648 | 606 | #endif
|
| 649 | 607 | }
|
| 650 | 608 | |
| ... | ... | @@ -889,115 +847,158 @@ schedulePushWork(Capability *cap USED_IF_THREADS, |
| 889 | 847 | |
| 890 | 848 | }
|
| 891 | 849 | |
| 892 | -/* ----------------------------------------------------------------------------
|
|
| 893 | - * Check for blocked threads that can be woken up.
|
|
| 894 | - * ------------------------------------------------------------------------- */
|
|
| 895 | - |
|
| 896 | -static void
|
|
| 897 | -scheduleCheckBlockedThreads(Capability *cap USED_IF_NOT_THREADS)
|
|
| 898 | -{
|
|
| 899 | -#if !defined(THREADED_RTS)
|
|
| 900 | - /* Check whether there is any completed I/O or expired timers. If so,
|
|
| 901 | - * process the competions as appropriate, which will typically cause some
|
|
| 902 | - * waiting threads to be woken up.
|
|
| 903 | - *
|
|
| 904 | - * If the run queue is empty, and there are no other threads running, we
|
|
| 905 | - * can wait indefinitely for something to happen.
|
|
| 906 | - *
|
|
| 907 | - * TODO: see if we can rationalise these two awaitCompletedTimeoutsOrIO
|
|
| 908 | - * calls before and after scheduleDetectDeadlock()
|
|
| 909 | - *
|
|
| 910 | - * TODO: this test anyPendingTimeoutsOrIO does not have a proper
|
|
| 911 | - * implementation the WinIO I/O manager!
|
|
| 912 | - *
|
|
| 913 | - * The select() I/O manager uses the sleeping_queue and the blocked_queue,
|
|
| 914 | - * and the test checks both. The legacy win32 I/O manager only consults
|
|
| 915 | - * the blocked_queue, but then it puts threads waiting on delay# on the
|
|
| 916 | - * blocked_queue too, so that's ok.
|
|
| 917 | - *
|
|
| 918 | - * The WinIO I/O manager does not use either the sleeping_queue or the
|
|
| 919 | - * blocked_queue, but it's implementation of anyPendingTimeoutsOrIO still
|
|
| 920 | - * checks both! Since both queues will _always_ be empty then it will
|
|
| 921 | - * _always_ return false and so awaitCompletedTimeoutsOrIO will _never_ be
|
|
| 922 | - * called here for WinIO. This may explain why there is a second call to
|
|
| 923 | - * awaitCompletedTimeoutsOrIO below for the case of !defined(THREADED_RTS)
|
|
| 924 | - * && defined(mingw32_HOST_OS).
|
|
| 925 | - */
|
|
| 926 | - if (anyPendingTimeoutsOrIO(cap->iomgr))
|
|
| 927 | - {
|
|
| 928 | - if (emptyRunQueue(cap)) {
|
|
| 929 | - // block and wait
|
|
| 930 | - awaitCompletedTimeoutsOrIO(cap->iomgr);
|
|
| 931 | - } else {
|
|
| 932 | - // poll but do not wait
|
|
| 933 | - pollCompletedTimeoutsOrIO(cap->iomgr);
|
|
| 934 | - }
|
|
| 935 | - }
|
|
| 936 | -#endif
|
|
| 937 | -}
|
|
| 938 | - |
|
| 939 | 850 | /* ----------------------------------------------------------------------------
|
| 940 | 851 | * Detect deadlock conditions and attempt to resolve them.
|
| 941 | 852 | * ------------------------------------------------------------------------- */
|
| 942 | 853 | |
| 854 | +/* Note [Deadlock detection]
|
|
| 855 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 856 | + |
|
| 857 | +For the purpose of this explanation we define:
|
|
| 858 | + * a /partial deadlock/ to be a set of threads that are deadlocked; and
|
|
| 859 | + * a /system deadlock/ is when all threads are deadlocked.
|
|
| 860 | + |
|
| 861 | +Obviously, we can have a partial deadlock without having a system
|
|
| 862 | +deadlock. The design goal of deadlock detection is to guarantee to
|
|
| 863 | +detect (and resolve) system deadlock, but to also try to detect (and
|
|
| 864 | +resolve) partial deadlocks.
|
|
| 865 | + |
|
| 866 | +There are two designs that the RTS has used for deadlock detection: a
|
|
| 867 | +simple historical design originally used in the non-threaded RTS and a
|
|
| 868 | +modern design for the threaded RTS. These days we use the modern design
|
|
| 869 | +in both the threaded and non-threaded RTS.
|
|
| 870 | + |
|
| 871 | +A high level way to think about the two designs is as follows:
|
|
| 872 | + 1. the historical design looks for situations in which there *must* be
|
|
| 873 | + a system deadlock; whereas
|
|
| 874 | + 2. the modern design looks for partial deadlocks opportunistically,
|
|
| 875 | + with the guarantee that if the overall system is deadlocked that we
|
|
| 876 | + will *eventually* detect this.
|
|
| 877 | + |
|
| 878 | +An advantage of the historical design is that it will detect system
|
|
| 879 | +deadlock promptly. A disadvantage is that it will never detect a
|
|
| 880 | +partial deadlock (that isn't also a system deadlock).
|
|
| 881 | + |
|
| 882 | +The modern design can detect partial deadlock, but it is not guaranteed
|
|
| 883 | +to detect system deadlock promptly, just eventually.
|
|
| 884 | + |
|
| 885 | +The mechanism for deadlock detection is garbage collection. GC can be
|
|
| 886 | +instructed to look for deadlocked threads and if it finds them to throw
|
|
| 887 | +exceptions to one or more threads involved in the deadlock. This
|
|
| 888 | +mechanism can find partial deadlocks. It is however expensive -- more
|
|
| 889 | +expensive than a normal major GC. So the difference in the historical
|
|
| 890 | +and modern designs is in when we do this expensive GC check.
|
|
| 891 | + |
|
| 892 | +The historical design
|
|
| 893 | +---------------------
|
|
| 894 | + |
|
| 895 | +When there was just one capability, as in the single threaded RTS, it
|
|
| 896 | +is possible to follow a very simple design. When there are no runnable
|
|
| 897 | +threads, and no threads blocked on pending I/O or on timers then there
|
|
| 898 | +*must* be a deadlock. And thus running deadlock detection promptly in
|
|
| 899 | +this situation is guaranteed to find the deadlock and wake up one or
|
|
| 900 | +more threads. Thus we can guarantee afterwards that there are runnable
|
|
| 901 | +threads.
|
|
| 902 | + |
|
| 903 | +There are a couple problems with this design, but the biggest problem
|
|
| 904 | +is that it cannot be extended to multiple capabilities. When there are
|
|
| 905 | +multiple capabilities then the fact that there are no runnable threads
|
|
| 906 | +on the current capability says nothing about runnable threads on other
|
|
| 907 | +capabilities. Runnable threads elsewhere might wake up threads on this
|
|
| 908 | +capability, and so there is no implication that there is a deadlock.
|
|
| 909 | + |
|
| 910 | +The other problems with this design are:
|
|
| 911 | + 1. it cannot find genuine deadlocks when there are any unrelated
|
|
| 912 | + threads blocked on I/O or timers (see issue #26408); and
|
|
| 913 | + 2. it requires treating signals specially.
|
|
| 914 | + |
|
| 915 | +The problem with signals is that they're a weird kind of I/O. Threads
|
|
| 916 | +do not block waiting on signals. Rather signals can have handlers such
|
|
| 917 | +that when a signal arrives, a new thread is started to execute the
|
|
| 918 | +handler. This means it doesn't neatly fit into the condition "no
|
|
| 919 | +threads blocked on pending I/O or on timers". And if we did shoehorn it
|
|
| 920 | +into that definition then we would not look for deadlocks if there were
|
|
| 921 | +any signal handlers registered, and we would still end up with no
|
|
| 922 | +runnable threads after skipping deadlock detection, which violates the
|
|
| 923 | +post-condition that there be runnable threads. So the solution was that
|
|
| 924 | +after deadlock detection, if there are still no runnable threads and
|
|
| 925 | +there are registered signal handlers then we conclude we must wait for
|
|
| 926 | +a signal to be received -- which will start a thread and thus we will
|
|
| 927 | +end up with runnable threads. But of course this is horrible: we have
|
|
| 928 | +entangled two features far too tightly: deadlock detection with a weird
|
|
| 929 | +-- and platform specific -- kind of I/O.
|
|
| 930 | + |
|
| 931 | +The modern design
|
|
| 932 | +-----------------
|
|
| 933 | + |
|
| 934 | +A change of perspective is required. Instead of thinking of conditions
|
|
| 935 | +in which there must be a deadlock, we simply look for deadlocks in such
|
|
| 936 | +a way in which we will eventually find deadlocks if they exist. A
|
|
| 937 | +benefit of this approach is that we can find deadlocks that the simple
|
|
| 938 | +approach cannot. For example we can find deadlocks when there unrelated
|
|
| 939 | +threads blocked on I/O or timers (see issue #26408).
|
|
| 940 | + |
|
| 941 | +The question is when to run GC it its more expensive deadlock detection
|
|
| 942 | +mode. We obviously do not want to do it too frequently. The design
|
|
| 943 | +choice is to do it during idle GC, at least sometimes. Idle GC is only
|
|
| 944 | +run some time after a capability goes idle. This is a good opportunity.
|
|
| 945 | +We know there are no runnable threads on the capability, so there
|
|
| 946 | +*might* be a deadlock, and when there's nothing else to do is also a
|
|
| 947 | +good moment to do a more expensive GC.
|
|
| 948 | + |
|
| 949 | +The idle GC is controlled by the RecentActivity status, which
|
|
| 950 | +progresses through 4 stages: yes, maybe_no, inactive, done_gc. We only
|
|
| 951 | +invoke a deadlock-detecting major GC in the inactive state. We get into
|
|
| 952 | +the inactive state when:
|
|
| 953 | + * the timer tick goes off
|
|
| 954 | + * we were already in the maybe_no state (which itself requires no
|
|
| 955 | + activity on any capability for a whole timer tick)
|
|
| 956 | + * idle GC is enabled
|
|
| 957 | + * it's been long enough since the most recent idle GC.
|
|
| 958 | +This timer tick also wakes up the I/O manager to ensue we get back to
|
|
| 959 | +the scheduler, and thus to scheduleDetectDeadlock.
|
|
| 960 | + |
|
| 961 | +Note that this means that deadlock detection is disabled if users
|
|
| 962 | +disable idle GC (by setting +RTS -I0). Historically, idle GC was not
|
|
| 963 | +used by default in the non-threaded RTS, but the modern design relies
|
|
| 964 | +on it, so it is enabled by default in all cases.
|
|
| 965 | + |
|
| 966 | +But if idle GC is enabled, then if there is a full system deadlock then
|
|
| 967 | +eventually we will run a major GC with deadlock detection and detect
|
|
| 968 | +and resolve the deadlock. It is not prompt. It must wait at least for
|
|
| 969 | +an idle GC, which by default is 0.3s after all capabilities go idle.
|
|
| 970 | + |
|
| 971 | +Furthermore, there is no post-condition for scheduleDetectDeadlock,
|
|
| 972 | +because of the non-prompt "eventually" nature of the deadlock detection
|
|
| 973 | +design. In particular there can still be no runnable threads. In the
|
|
| 974 | +threaded RTS if there's no runnable threads after this we will yield the
|
|
| 975 | +capability, while in the non-threaded we will ask the I/O manager to
|
|
| 976 | +block and wait for I/O, timers or signals.
|
|
| 977 | +*/
|
|
| 978 | + |
|
| 943 | 979 | static void
|
| 944 | 980 | scheduleDetectDeadlock (Capability **pcap, Task *task)
|
| 945 | 981 | {
|
| 946 | - Capability *cap = *pcap;
|
|
| 947 | - /*
|
|
| 948 | - * Detect deadlock: when we have no threads to run, there are no
|
|
| 949 | - * threads blocked, waiting for I/O, or sleeping, and all the
|
|
| 950 | - * other tasks are waiting for work, we must have a deadlock of
|
|
| 951 | - * some description.
|
|
| 952 | - */
|
|
| 953 | - if ( emptyRunQueue(cap) && !anyPendingTimeoutsOrIO(cap->iomgr) )
|
|
| 954 | - {
|
|
| 955 | -#if defined(THREADED_RTS)
|
|
| 956 | - /*
|
|
| 957 | - * In the threaded RTS, we only check for deadlock if there
|
|
| 958 | - * has been no activity in a complete timeslice. This means
|
|
| 959 | - * we won't eagerly start a full GC just because we don't have
|
|
| 960 | - * any threads to run currently.
|
|
| 961 | - */
|
|
| 962 | - if (getRecentActivity() != ACTIVITY_INACTIVE) return;
|
|
| 963 | -#endif
|
|
| 964 | - |
|
| 965 | - debugTrace(DEBUG_sched, "deadlocked, forcing major GC...");
|
|
| 966 | - |
|
| 967 | - // Garbage collection can release some new threads due to
|
|
| 968 | - // either (a) finalizers or (b) threads resurrected because
|
|
| 969 | - // they are unreachable and will therefore be sent an
|
|
| 970 | - // exception. Any threads thus released will be immediately
|
|
| 971 | - // runnable.
|
|
| 972 | - scheduleDoGC (pcap, task, true/*force major GC*/, false /* Whether it is an overflow GC */, true/*deadlock detection*/, false/*nonconcurrent*/);
|
|
| 973 | - cap = *pcap;
|
|
| 974 | - // when force_major == true. scheduleDoGC sets
|
|
| 975 | - // recent_activity to ACTIVITY_DONE_GC and turns off the timer
|
|
| 976 | - // signal.
|
|
| 982 | + /* See Note [Deadlock detection] */
|
|
| 983 | + if (getRecentActivity() == ACTIVITY_INACTIVE) {
|
|
| 977 | 984 | |
| 978 | - if ( !emptyRunQueue(cap) ) return;
|
|
| 985 | + debugTrace(DEBUG_sched, "maybe deadlocked, forcing major GC...");
|
|
| 979 | 986 | |
| 980 | -#if defined(RTS_USER_SIGNALS) && !defined(THREADED_RTS)
|
|
| 981 | - /* If we have user-installed signal handlers, then wait
|
|
| 982 | - * for signals to arrive rather then bombing out with a
|
|
| 983 | - * deadlock.
|
|
| 987 | + /* Garbage collection can release some new threads due to
|
|
| 988 | + * either (a) finalizers or (b) threads resurrected because
|
|
| 989 | + * they are unreachable and will therefore be sent an
|
|
| 990 | + * exception. Any threads thus released will be immediately
|
|
| 991 | + * runnable.
|
|
| 992 | + */
|
|
| 993 | + scheduleDoGC (pcap, task,
|
|
| 994 | + true /* force major GC */,
|
|
| 995 | + false /* Whether it is an overflow GC */,
|
|
| 996 | + true /* deadlock detection */,
|
|
| 997 | + false /* nonconcurrent */);
|
|
| 998 | + /* When force_major == true, scheduleDoGC sets recent activity to
|
|
| 999 | + * getRecentActivity() == ACTIVITY_DONE_GC and turns off the timer
|
|
| 1000 | + * signal.
|
|
| 984 | 1001 | */
|
| 985 | - if ( RtsFlags.MiscFlags.install_signal_handlers && anyUserHandlers() ) {
|
|
| 986 | - debugTrace(DEBUG_sched,
|
|
| 987 | - "still deadlocked, waiting for signals...");
|
|
| 988 | - |
|
| 989 | - awaitUserSignals();
|
|
| 990 | - |
|
| 991 | - if (signals_pending()) {
|
|
| 992 | - startSignalHandlers(cap);
|
|
| 993 | - }
|
|
| 994 | - |
|
| 995 | - // either we have threads to run, or we were interrupted:
|
|
| 996 | - ASSERT(!emptyRunQueue(cap) || getSchedState() >= SCHED_INTERRUPTING);
|
|
| 997 | - |
|
| 998 | - return;
|
|
| 999 | - }
|
|
| 1000 | -#endif
|
|
| 1001 | 1002 | }
|
| 1002 | 1003 | }
|
| 1003 | 1004 | |
| ... | ... | @@ -2191,7 +2192,15 @@ forkProcess(HsStablePtr *entry |
| 2191 | 2192 | // bound threads for which the corresponding Task does not
|
| 2192 | 2193 | // exist.
|
| 2193 | 2194 | truncateRunQueue(cap);
|
| 2194 | - cap->n_run_queue = 0;
|
|
| 2195 | + |
|
| 2196 | + // Reset and re-initialise the capability's I/O manager,
|
|
| 2197 | + // to get the I/O manager ready again.
|
|
| 2198 | + //
|
|
| 2199 | + // Any threads waiting on I/O or timers should have been
|
|
| 2200 | + // removed from I/O manager queues by deleteThread_ above.
|
|
| 2201 | + // TODO: but we could assert that here.
|
|
| 2202 | + freeCapabilityIOManager(cap->iomgr);
|
|
| 2203 | + initCapabilityIOManager(cap->iomgr);
|
|
| 2195 | 2204 | |
| 2196 | 2205 | // Any suspended C-calling Tasks are no more, their OS threads
|
| 2197 | 2206 | // don't exist now:
|
| ... | ... | @@ -2208,7 +2217,7 @@ forkProcess(HsStablePtr *entry |
| 2208 | 2217 | cap->n_returning_tasks = 0;
|
| 2209 | 2218 | #endif
|
| 2210 | 2219 | |
| 2211 | - // Release all caps except 0, we'll use that for starting
|
|
| 2220 | + // Release all caps except 0, we'll use that for restarting
|
|
| 2212 | 2221 | // the IO manager and running the client action below.
|
| 2213 | 2222 | if (cap->no != 0) {
|
| 2214 | 2223 | task->cap = cap;
|
| ... | ... | @@ -2232,7 +2241,7 @@ forkProcess(HsStablePtr *entry |
| 2232 | 2241 | // like startup event, capabilities, process info etc
|
| 2233 | 2242 | traceTaskCreate(task, cap);
|
| 2234 | 2243 | |
| 2235 | - initIOManagerAfterFork(cap->iomgr, &cap);
|
|
| 2244 | + restartIOManager(cap->iomgr, &cap);
|
|
| 2236 | 2245 | |
| 2237 | 2246 | // start timer after the IOManager is initialized
|
| 2238 | 2247 | // (the idle GC may wake up the IOManager)
|
| ... | ... | @@ -2337,6 +2346,10 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS) |
| 2337 | 2346 | // the capability; we don't have to worry about GC data
|
| 2338 | 2347 | // structures, the nursery, etc.
|
| 2339 | 2348 | //
|
| 2349 | + // This approach also handles threads blocked on I/O. Such threads
|
|
| 2350 | + // remain blocked, and when I/O completes and threads become runnable
|
|
| 2351 | + // then they are migrated away.
|
|
| 2352 | + //
|
|
| 2340 | 2353 | for (n = new_n_capabilities; n < enabled_capabilities; n++) {
|
| 2341 | 2354 | getCapability(n)->disabled = true;
|
| 2342 | 2355 | traceCapDisable(getCapability(n));
|
| ... | ... | @@ -2897,9 +2910,7 @@ interruptStgRts(void) |
| 2897 | 2910 | ASSERT(getSchedState() != SCHED_SHUTTING_DOWN);
|
| 2898 | 2911 | setSchedState(SCHED_INTERRUPTING);
|
| 2899 | 2912 | interruptAllCapabilities();
|
| 2900 | -#if defined(THREADED_RTS)
|
|
| 2901 | 2913 | wakeUpRts();
|
| 2902 | -#endif
|
|
| 2903 | 2914 | }
|
| 2904 | 2915 | |
| 2905 | 2916 | /* -----------------------------------------------------------------------------
|
| ... | ... | @@ -2915,15 +2926,13 @@ interruptStgRts(void) |
| 2915 | 2926 | will have interrupted any blocking system call in progress anyway.
|
| 2916 | 2927 | -------------------------------------------------------------------------- */
|
| 2917 | 2928 | |
| 2918 | -#if defined(THREADED_RTS)
|
|
| 2919 | 2929 | void wakeUpRts(void)
|
| 2920 | 2930 | {
|
| 2921 | - // This forces the IO Manager thread to wakeup, which will
|
|
| 2931 | + // This forces the IO Manager to wakeup, which will
|
|
| 2922 | 2932 | // in turn ensure that some OS thread wakes up and runs the
|
| 2923 | 2933 | // scheduler loop, which will cause a GC and deadlock check.
|
| 2924 | 2934 | wakeupIOManager();
|
| 2925 | 2935 | }
|
| 2926 | -#endif
|
|
| 2927 | 2936 | |
| 2928 | 2937 | /* -----------------------------------------------------------------------------
|
| 2929 | 2938 | Deleting threads
|
| ... | ... | @@ -2997,7 +3006,7 @@ pushOnRunQueue (Capability *cap, StgTSO *tso) |
| 2997 | 3006 | cap->n_run_queue++;
|
| 2998 | 3007 | }
|
| 2999 | 3008 | |
| 3000 | -StgTSO *popRunQueue (Capability *cap)
|
|
| 3009 | +static StgTSO *popRunQueue (Capability *cap)
|
|
| 3001 | 3010 | {
|
| 3002 | 3011 | ASSERT(cap->n_run_queue > 0);
|
| 3003 | 3012 | StgTSO *t = cap->run_queue_hd;
|
| ... | ... | @@ -3017,6 +3026,45 @@ StgTSO *popRunQueue (Capability *cap) |
| 3017 | 3026 | return t;
|
| 3018 | 3027 | }
|
| 3019 | 3028 | |
| 3029 | +#if defined(FORKPROCESS_PRIMOP_SUPPORTED)
|
|
| 3030 | +static void truncateRunQueue(Capability *cap)
|
|
| 3031 | +{
|
|
| 3032 | + // Can only be called by the task owning the capability.
|
|
| 3033 | + TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_hd, "truncateRunQueue");
|
|
| 3034 | + TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_tl, "truncateRunQueue");
|
|
| 3035 | + TSAN_ANNOTATE_BENIGN_RACE(&cap->n_run_queue, "truncateRunQueue");
|
|
| 3036 | + cap->run_queue_hd = END_TSO_QUEUE;
|
|
| 3037 | + cap->run_queue_tl = END_TSO_QUEUE;
|
|
| 3038 | + cap->n_run_queue = 0;
|
|
| 3039 | +}
|
|
| 3040 | +#endif
|
|
| 3041 | + |
|
| 3042 | +static void removeFromRunQueue (Capability *cap, StgTSO *tso)
|
|
| 3043 | +{
|
|
| 3044 | + if (tso->block_info.prev == END_TSO_QUEUE) {
|
|
| 3045 | + ASSERT(cap->run_queue_hd == tso);
|
|
| 3046 | + cap->run_queue_hd = tso->_link;
|
|
| 3047 | + } else {
|
|
| 3048 | + setTSOLink(cap, tso->block_info.prev, tso->_link);
|
|
| 3049 | + }
|
|
| 3050 | + if (tso->_link == END_TSO_QUEUE) {
|
|
| 3051 | + ASSERT(cap->run_queue_tl == tso);
|
|
| 3052 | + cap->run_queue_tl = tso->block_info.prev;
|
|
| 3053 | + } else {
|
|
| 3054 | + setTSOPrev(cap, tso->_link, tso->block_info.prev);
|
|
| 3055 | + }
|
|
| 3056 | + tso->_link = tso->block_info.prev = END_TSO_QUEUE;
|
|
| 3057 | + cap->n_run_queue--;
|
|
| 3058 | + |
|
| 3059 | + IF_DEBUG(sanity, checkRunQueue(cap));
|
|
| 3060 | +}
|
|
| 3061 | + |
|
| 3062 | +void promoteInRunQueue (Capability *cap, StgTSO *tso)
|
|
| 3063 | +{
|
|
| 3064 | + removeFromRunQueue(cap, tso);
|
|
| 3065 | + pushOnRunQueue(cap, tso);
|
|
| 3066 | +}
|
|
| 3067 | + |
|
| 3020 | 3068 | |
| 3021 | 3069 | /* -----------------------------------------------------------------------------
|
| 3022 | 3070 | raiseExceptionHelper
|
| ... | ... | @@ -39,9 +39,7 @@ void scheduleThreadOn(Capability *cap, StgWord cpu, StgTSO *tso); |
| 39 | 39 | *
|
| 40 | 40 | * Causes an OS thread to wake up and run the scheduler, if necessary.
|
| 41 | 41 | */
|
| 42 | -#if defined(THREADED_RTS)
|
|
| 43 | 42 | void wakeUpRts(void);
|
| 44 | -#endif
|
|
| 45 | 43 | |
| 46 | 44 | /* raiseExceptionHelper */
|
| 47 | 45 | StgWord raiseExceptionHelper (StgRegTable *reg, StgTSO *tso, StgClosure *exception);
|
| ... | ... | @@ -164,10 +162,6 @@ void appendToRunQueue (Capability *cap, StgTSO *tso); |
| 164 | 162 | */
|
| 165 | 163 | void pushOnRunQueue (Capability *cap, StgTSO *tso);
|
| 166 | 164 | |
| 167 | -/* Pop the first thread off the runnable queue.
|
|
| 168 | - */
|
|
| 169 | -StgTSO *popRunQueue (Capability *cap);
|
|
| 170 | - |
|
| 171 | 165 | INLINE_HEADER StgTSO *
|
| 172 | 166 | peekRunQueue (Capability *cap)
|
| 173 | 167 | {
|
| ... | ... | @@ -184,18 +178,6 @@ emptyRunQueue(Capability *cap) |
| 184 | 178 | return cap->n_run_queue == 0;
|
| 185 | 179 | }
|
| 186 | 180 | |
| 187 | -INLINE_HEADER void
|
|
| 188 | -truncateRunQueue(Capability *cap)
|
|
| 189 | -{
|
|
| 190 | - // Can only be called by the task owning the capability.
|
|
| 191 | - TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_hd, "truncateRunQueue");
|
|
| 192 | - TSAN_ANNOTATE_BENIGN_RACE(&cap->run_queue_tl, "truncateRunQueue");
|
|
| 193 | - TSAN_ANNOTATE_BENIGN_RACE(&cap->n_run_queue, "truncateRunQueue");
|
|
| 194 | - cap->run_queue_hd = END_TSO_QUEUE;
|
|
| 195 | - cap->run_queue_tl = END_TSO_QUEUE;
|
|
| 196 | - cap->n_run_queue = 0;
|
|
| 197 | -}
|
|
| 198 | - |
|
| 199 | 181 | #endif /* !IN_STG_CODE */
|
| 200 | 182 | |
| 201 | 183 | #include "EndPrivate.h" |
| ... | ... | @@ -149,11 +149,9 @@ handle_tick(int unused STG_UNUSED) |
| 149 | 149 | setRecentActivity(ACTIVITY_INACTIVE);
|
| 150 | 150 | inter_gc_ticks_to_gc = RtsFlags.GcFlags.interIdleGCWait /
|
| 151 | 151 | RtsFlags.MiscFlags.tickInterval;
|
| 152 | -#if defined(THREADED_RTS)
|
|
| 153 | 152 | wakeUpRts();
|
| 154 | 153 | // The scheduler will call stopTimer() when it has done
|
| 155 | 154 | // the GC.
|
| 156 | -#endif
|
|
| 157 | 155 | } else {
|
| 158 | 156 | setRecentActivity(ACTIVITY_DONE_GC);
|
| 159 | 157 | // disable timer signals (see #1623, #5991, #9105)
|
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2025
|
|
| 4 | + *
|
|
| 5 | + * Utilities for a simple fd-based cross-thread wakeup mechanism.
|
|
| 6 | + *
|
|
| 7 | + * This is used in I/O managers, to provide a mechanism to wake them when they
|
|
| 8 | + * are blocked waiting on fds and timeouts. The mechanism works by including
|
|
| 9 | + * the read end fd into the set of fds the I/O manager waits on, and when a
|
|
| 10 | + * wake up is needed, the write end fd is used.
|
|
| 11 | + *
|
|
| 12 | + * This is implemented using either eventfd() or pipe().
|
|
| 13 | + *
|
|
| 14 | + * Linux 2.6.22+ and FreeBSD 13+ support eventfd. It is a single fd with a
|
|
| 15 | + * 64bit counter. It uses less resources than a pipe, and is probably a tad
|
|
| 16 | + * faster. Using write() adds to the counter, while read() reads and resets
|
|
| 17 | + * it. This gives us event combining.
|
|
| 18 | + *
|
|
| 19 | + * Otherwise we use a classic unix pipe.
|
|
| 20 | + *
|
|
| 21 | + * -------------------------------------------------------------------------*/
|
|
| 22 | + |
|
| 23 | +#include "rts/PosixSource.h"
|
|
| 24 | +#include "Rts.h"
|
|
| 25 | + |
|
| 26 | +#include "FdWakeup.h"
|
|
| 27 | + |
|
| 28 | +#include <fcntl.h>
|
|
| 29 | +#include <unistd.h>
|
|
| 30 | + |
|
| 31 | +#ifdef HAVE_SYS_EVENTFD_H
|
|
| 32 | +#include <sys/eventfd.h>
|
|
| 33 | +#endif
|
|
| 34 | + |
|
| 35 | +#if !defined(HAVE_EVENTFD) \
|
|
| 36 | + || (defined(HAVE_EVENTFD) && !(defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)))
|
|
| 37 | +static void fcntl_CLOEXEC_NONBLOCK(int fd)
|
|
| 38 | +{
|
|
| 39 | + int res1 = fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
| 40 | + int res2 = fcntl(fd, F_SETFL, O_NONBLOCK);
|
|
| 41 | + if (RTS_UNLIKELY(res1 < 0 || res2 < 0)) {
|
|
| 42 | + sysErrorBelch("newFdWakeup fcntl()");
|
|
| 43 | + stg_exit(EXIT_FAILURE);
|
|
| 44 | + }
|
|
| 45 | +}
|
|
| 46 | +#endif
|
|
| 47 | + |
|
| 48 | +void newFdWakeup(int *wakeup_fd_r, int *wakeup_fd_w)
|
|
| 49 | +{
|
|
| 50 | +#if defined(HAVE_EVENTFD)
|
|
| 51 | + int wakeup_fd;
|
|
| 52 | +#if defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)
|
|
| 53 | + wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
|
| 54 | +#else
|
|
| 55 | + wakeup_fd = eventfd(0, 0);
|
|
| 56 | + if (wakeup_fd >= 0) fcntl_CLOEXEC_NONBLOCK(wakeup_fd);
|
|
| 57 | +#endif
|
|
| 58 | + if (RTS_UNLIKELY(wakeup_fd < 0)) {
|
|
| 59 | + sysErrorBelch("newFdWakeup eventfd()");
|
|
| 60 | + stg_exit(EXIT_FAILURE);
|
|
| 61 | + }
|
|
| 62 | + /* eventfd uses the same fd for each end */
|
|
| 63 | + *wakeup_fd_r = wakeup_fd;
|
|
| 64 | + *wakeup_fd_w = wakeup_fd;
|
|
| 65 | +#else
|
|
| 66 | + int pipefd[2];
|
|
| 67 | + int res;
|
|
| 68 | + res = pipe(pipefd);
|
|
| 69 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 70 | + sysErrorBelch("newFdWakeup pipe");
|
|
| 71 | + stg_exit(EXIT_FAILURE);
|
|
| 72 | + }
|
|
| 73 | + fcntl_CLOEXEC_NONBLOCK(pipefd[0]);
|
|
| 74 | + fcntl_CLOEXEC_NONBLOCK(pipefd[1]);
|
|
| 75 | + *wakeup_fd_r = pipefd[0]; /* read end */
|
|
| 76 | + *wakeup_fd_w = pipefd[1]; /* write end */
|
|
| 77 | +#endif
|
|
| 78 | +}
|
|
| 79 | + |
|
| 80 | +void closeFdWakeup(int wakeup_fd_r, int wakeup_fd_w)
|
|
| 81 | +{
|
|
| 82 | +#if defined(HAVE_EVENTFD)
|
|
| 83 | + ASSERT(wakeup_fd_r == wakeup_fd_w);
|
|
| 84 | + close(wakeup_fd_r);
|
|
| 85 | +#else
|
|
| 86 | + ASSERT(wakeup_fd_r != wakeup_fd_w);
|
|
| 87 | + close(wakeup_fd_r);
|
|
| 88 | + close(wakeup_fd_w);
|
|
| 89 | +#endif
|
|
| 90 | +}
|
|
| 91 | + |
|
| 92 | +void sendFdWakeup(int wakeup_fd_w)
|
|
| 93 | +{
|
|
| 94 | + int res;
|
|
| 95 | +#if defined(HAVE_EVENTFD)
|
|
| 96 | + uint64_t val = 1;
|
|
| 97 | + res = write(wakeup_fd_w, &val, 8);
|
|
| 98 | +#else
|
|
| 99 | + unsigned char buf = 1;
|
|
| 100 | + res = write(wakeup_fd_w, &buf, 1);
|
|
| 101 | +#endif
|
|
| 102 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 103 | + /* Unlikely the pipe buffer will fill, but it would not be an error. */
|
|
| 104 | + if (errno == EAGAIN) return;
|
|
| 105 | + sysErrorBelch("sendFdWakeup write");
|
|
| 106 | + stg_exit(EXIT_FAILURE);
|
|
| 107 | + }
|
|
| 108 | +}
|
|
| 109 | + |
|
| 110 | +void collectFdWakeup(int wakeup_fd_r)
|
|
| 111 | +{
|
|
| 112 | + int res;
|
|
| 113 | +#if defined(HAVE_EVENTFD)
|
|
| 114 | + uint64_t buf;
|
|
| 115 | + /* eventfd combines events into one counter, so a single read is enough */
|
|
| 116 | + res = read(wakeup_fd_r, &buf, 8);
|
|
| 117 | +#else
|
|
| 118 | + /* Drain the pipe buffer. Multiple wakeup notifications could
|
|
| 119 | + * have been sent before we have a chance to collect them.
|
|
| 120 | + */
|
|
| 121 | + uint64_t buf;
|
|
| 122 | + do {
|
|
| 123 | + res = read(wakeup_fd_r, &buf, 8);
|
|
| 124 | + } while (res == 8);
|
|
| 125 | +#endif
|
|
| 126 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 127 | + /* After the first pipe read, it could block */
|
|
| 128 | + if (errno == EAGAIN) return;
|
|
| 129 | + sysErrorBelch("collectFdWakeup read");
|
|
| 130 | + stg_exit(EXIT_FAILURE);
|
|
| 131 | + }
|
|
| 132 | +} |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2025
|
|
| 4 | + *
|
|
| 5 | + * Utilities for a simple fd-based cross-thread wakeup mechanism.
|
|
| 6 | + *
|
|
| 7 | + * This is used in I/O managers, to provide a mechanism to wake them when they
|
|
| 8 | + * are blocked waiting on fds and timeouts. The mechanism works by including
|
|
| 9 | + * the read end fd into the set of fds the I/O manager waits on, and when a
|
|
| 10 | + * wake up is needed, the write end fd is used.
|
|
| 11 | + *
|
|
| 12 | + * Prototypes for functions in FdWakeup.c
|
|
| 13 | + *
|
|
| 14 | + * -------------------------------------------------------------------------*/
|
|
| 15 | + |
|
| 16 | +#pragma once
|
|
| 17 | + |
|
| 18 | +#include "BeginPrivate.h"
|
|
| 19 | + |
|
| 20 | +void newFdWakeup(int *fd_r, int *fd_w);
|
|
| 21 | +void closeFdWakeup(int fd_r, int fd_w);
|
|
| 22 | + |
|
| 23 | +void sendFdWakeup(int fd_w);
|
|
| 24 | +void collectFdWakeup(int fd_r);
|
|
| 25 | + |
|
| 26 | +#include "EndPrivate.h"
|
|
| 27 | + |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team, 1998-2005
|
|
| 4 | + *
|
|
| 5 | + * Signal processing / handling.
|
|
| 6 | + *
|
|
| 7 | + * ---------------------------------------------------------------------------*/
|
|
| 8 | + |
|
| 9 | +#include "rts/PosixSource.h"
|
|
| 10 | +#include "Rts.h"
|
|
| 11 | + |
|
| 12 | +#include "Schedule.h"
|
|
| 13 | +#include "RtsUtils.h"
|
|
| 14 | +#include "Prelude.h"
|
|
| 15 | +#include "ThreadLabels.h"
|
|
| 16 | + |
|
| 17 | +#include "MIO.h"
|
|
| 18 | +#include "IOManager.h"
|
|
| 19 | +#include "IOManagerInternals.h"
|
|
| 20 | + |
|
| 21 | +#if defined(HAVE_ERRNO_H)
|
|
| 22 | +# include <errno.h>
|
|
| 23 | +#endif
|
|
| 24 | + |
|
| 25 | +#include <stdlib.h>
|
|
| 26 | +#include <unistd.h>
|
|
| 27 | + |
|
| 28 | +// Here's the pipe into which we will send our signals
|
|
| 29 | +static int io_manager_wakeup_fd = -1;
|
|
| 30 | +static int timer_manager_control_wr_fd = -1;
|
|
| 31 | +// TODO: Eliminate these globals. Put then into the CapIOManager, but the
|
|
| 32 | +// problem is these are shared across all caps, not per cap.
|
|
| 33 | + |
|
| 34 | +#define IO_MANAGER_WAKEUP 0xff
|
|
| 35 | +#define IO_MANAGER_DIE 0xfe
|
|
| 36 | +#define IO_MANAGER_SYNC 0xfd
|
|
| 37 | + |
|
| 38 | +void setTimerManagerControlFd(int fd) {
|
|
| 39 | + RELAXED_STORE(&timer_manager_control_wr_fd, fd);
|
|
| 40 | +}
|
|
| 41 | + |
|
| 42 | +void
|
|
| 43 | +setIOManagerWakeupFd (int fd)
|
|
| 44 | +{
|
|
| 45 | + // only called when THREADED_RTS, but unconditionally
|
|
| 46 | + // compiled here because GHC.Event.Control depends on it.
|
|
| 47 | + SEQ_CST_STORE(&io_manager_wakeup_fd, fd);
|
|
| 48 | +}
|
|
| 49 | + |
|
| 50 | +#if defined(THREADED_RTS)
|
|
| 51 | +void timerManagerNotifySignal(int sig, siginfo_t *info)
|
|
| 52 | +{
|
|
| 53 | + StgWord8 buf[sizeof(siginfo_t) + 1];
|
|
| 54 | + int r;
|
|
| 55 | + |
|
| 56 | + buf[0] = sig;
|
|
| 57 | + if (info == NULL) {
|
|
| 58 | + // info may be NULL on Solaris (see #3790)
|
|
| 59 | + memset(buf+1, 0, sizeof(siginfo_t));
|
|
| 60 | + } else {
|
|
| 61 | + memcpy(buf+1, info, sizeof(siginfo_t));
|
|
| 62 | + }
|
|
| 63 | + |
|
| 64 | + int timer_control_fd = RELAXED_LOAD(&timer_manager_control_wr_fd);
|
|
| 65 | + if (0 <= timer_control_fd)
|
|
| 66 | + {
|
|
| 67 | + r = write(timer_control_fd, buf, sizeof(siginfo_t)+1);
|
|
| 68 | + if (r == -1 && errno == EAGAIN) {
|
|
| 69 | + errorBelch("lost signal due to full pipe: %d\n", sig);
|
|
| 70 | + }
|
|
| 71 | + }
|
|
| 72 | + |
|
| 73 | + // If the IO manager hasn't told us what the FD of the write end
|
|
| 74 | + // of its pipe is, there's not much we can do here, so just ignore
|
|
| 75 | + // the signal..
|
|
| 76 | +}
|
|
| 77 | +#endif
|
|
| 78 | + |
|
| 79 | + |
|
| 80 | +/* -----------------------------------------------------------------------------
|
|
| 81 | + * Wake up at least one IO or timer manager HS thread.
|
|
| 82 | + * -------------------------------------------------------------------------- */
|
|
| 83 | +void
|
|
| 84 | +ioManagerWakeup (void)
|
|
| 85 | +{
|
|
| 86 | + int r;
|
|
| 87 | + const int wakeup_fd = SEQ_CST_LOAD(&io_manager_wakeup_fd);
|
|
| 88 | + // Wake up the IO Manager thread by sending a byte down its pipe
|
|
| 89 | + if (wakeup_fd >= 0) {
|
|
| 90 | +#if defined(HAVE_EVENTFD)
|
|
| 91 | + StgWord64 n = (StgWord64)IO_MANAGER_WAKEUP;
|
|
| 92 | + r = write(wakeup_fd, (char *) &n, 8);
|
|
| 93 | +#else
|
|
| 94 | + StgWord8 byte = (StgWord8)IO_MANAGER_WAKEUP;
|
|
| 95 | + r = write(wakeup_fd, &byte, 1);
|
|
| 96 | +#endif
|
|
| 97 | + /* N.B. If the TimerManager is shutting down as we run this
|
|
| 98 | + * then there is a possibility that our first read of
|
|
| 99 | + * io_manager_wakeup_fd is non-negative, but before we get to the
|
|
| 100 | + * write the file is closed. If this occurs, io_manager_wakeup_fd
|
|
| 101 | + * will be written into with -1 (GHC.Event.Control does this prior
|
|
| 102 | + * to closing), so checking this allows us to distinguish this case.
|
|
| 103 | + * To ensure we observe the correct ordering, we declare the
|
|
| 104 | + * io_manager_wakeup_fd as volatile.
|
|
| 105 | + * Since this is not an error condition, we do not print the error
|
|
| 106 | + * message in this case.
|
|
| 107 | + */
|
|
| 108 | + if (r == -1 && SEQ_CST_LOAD(&io_manager_wakeup_fd) >= 0) {
|
|
| 109 | + sysErrorBelch("ioManagerWakeup: write");
|
|
| 110 | + }
|
|
| 111 | + }
|
|
| 112 | +}
|
|
| 113 | + |
|
| 114 | +#if defined(THREADED_RTS)
|
|
| 115 | +void
|
|
| 116 | +ioManagerDie (void)
|
|
| 117 | +{
|
|
| 118 | + StgWord8 byte = (StgWord8)IO_MANAGER_DIE;
|
|
| 119 | + uint32_t i;
|
|
| 120 | + int r;
|
|
| 121 | + |
|
| 122 | + {
|
|
| 123 | + // Shut down timer manager
|
|
| 124 | + const int fd = RELAXED_LOAD(&timer_manager_control_wr_fd);
|
|
| 125 | + if (0 <= fd) {
|
|
| 126 | + r = write(fd, &byte, 1);
|
|
| 127 | + if (r == -1) { sysErrorBelch("ioManagerDie: write"); }
|
|
| 128 | + RELAXED_STORE(&timer_manager_control_wr_fd, -1);
|
|
| 129 | + }
|
|
| 130 | + }
|
|
| 131 | + |
|
| 132 | + {
|
|
| 133 | + // Shut down IO managers
|
|
| 134 | + for (i=0; i < getNumCapabilities(); i++) {
|
|
| 135 | + const int fd = RELAXED_LOAD(&getCapability(i)->iomgr->control_fd);
|
|
| 136 | + if (0 <= fd) {
|
|
| 137 | + r = write(fd, &byte, 1);
|
|
| 138 | + if (r == -1) { sysErrorBelch("ioManagerDie: write"); }
|
|
| 139 | + RELAXED_STORE(&getCapability(i)->iomgr->control_fd, -1);
|
|
| 140 | + }
|
|
| 141 | + }
|
|
| 142 | + }
|
|
| 143 | +}
|
|
| 144 | + |
|
| 145 | +void
|
|
| 146 | +ioManagerStartCap (Capability **cap)
|
|
| 147 | +{
|
|
| 148 | + rts_evalIO(cap,ensureIOManagerIsRunning_closure,NULL);
|
|
| 149 | +}
|
|
| 150 | + |
|
| 151 | +void
|
|
| 152 | +ioManagerStart (void)
|
|
| 153 | +{
|
|
| 154 | + // Make sure the IO manager thread is running
|
|
| 155 | + Capability *cap;
|
|
| 156 | + if (SEQ_CST_LOAD(&timer_manager_control_wr_fd) < 0 || SEQ_CST_LOAD(&io_manager_wakeup_fd) < 0) {
|
|
| 157 | + cap = rts_lock();
|
|
| 158 | + ioManagerStartCap(&cap);
|
|
| 159 | + rts_unlock(cap);
|
|
| 160 | + }
|
|
| 161 | +}
|
|
| 162 | +#endif
|
|
| 163 | + |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team, 1998-2005
|
|
| 4 | + *
|
|
| 5 | + * Signal processing / handling.
|
|
| 6 | + *
|
|
| 7 | + * ---------------------------------------------------------------------------*/
|
|
| 8 | + |
|
| 9 | +#pragma once
|
|
| 10 | + |
|
| 11 | +#include "IOManager.h"
|
|
| 12 | + |
|
| 13 | +#if defined(HAVE_SIGNAL_H)
|
|
| 14 | +# include <signal.h>
|
|
| 15 | +#endif
|
|
| 16 | + |
|
| 17 | +#include "BeginPrivate.h"
|
|
| 18 | + |
|
| 19 | +/* Communicating with the IO manager thread (see GHC.Conc).
|
|
| 20 | + */
|
|
| 21 | +void ioManagerWakeup (void);
|
|
| 22 | +#if defined(THREADED_RTS)
|
|
| 23 | +void ioManagerDie (void);
|
|
| 24 | +void ioManagerStart (void);
|
|
| 25 | +void ioManagerStartCap (/* inout */ Capability **cap);
|
|
| 26 | + |
|
| 27 | +void timerManagerNotifySignal(int sig, siginfo_t *info);
|
|
| 28 | +#endif
|
|
| 29 | + |
|
| 30 | +#include "EndPrivate.h" |
| ... | ... | @@ -41,6 +41,7 @@ |
| 41 | 41 | |
| 42 | 42 | #include "IOManagerInternals.h"
|
| 43 | 43 | #include "Timeout.h"
|
| 44 | +#include "FdWakeup.h"
|
|
| 44 | 45 | |
| 45 | 46 | /******************************************************************************
|
| 46 | 47 | |
| ... | ... | @@ -107,8 +108,9 @@ timeout (if any) as the poll() timeout parameter. |
| 107 | 108 | The CapIOManager structure for this I/O manager contains:
|
| 108 | 109 | |
| 109 | 110 | ClosureTable aiop_table;
|
| 110 | - struct pollfd *aiop_poll_table;
|
|
| 111 | + struct pollfd *aiop_poll_table, *full_poll_table;
|
|
| 111 | 112 | StgTimeoutQueue *timeout_queue;
|
| 113 | + int wakeup_fd_r, wakeup_fd_w;
|
|
| 112 | 114 | |
| 113 | 115 | We also support the Linux-specific ppoll API which supports higher resolution
|
| 114 | 116 | time delays -- nanoseconds rather than milliseconds as in classic poll(). It
|
| ... | ... | @@ -117,6 +119,15 @@ also allows the signal mask to be adjusted, but we do not make use of this. |
| 117 | 119 | int ppoll(struct pollfd *fds, nfds_t nfds,
|
| 118 | 120 | const struct timespec *tmo_p, const sigset_t *sigmask);
|
| 119 | 121 | |
| 122 | +We have both aiop_poll_table and full_poll_table. This is to cope with needing
|
|
| 123 | +to wait on the special extra file descriptor wakeup_fd_r. This fd is used to
|
|
| 124 | +support waking the I/O manager when we are blocked in a poll call. This
|
|
| 125 | +requires waiting on an extra fd that has no corresponding entry in the
|
|
| 126 | +aiop_table. To manage this quirk, we alias the aiop_poll_table to be the tail
|
|
| 127 | +of the full_poll_table and have the first entry of the full_poll_table be the
|
|
| 128 | +wakeup_fd_r. This means the aiop_poll_table indicies match up exactly with the
|
|
| 129 | +aiop_table, but still allows the full_poll_table to have an extra entry.
|
|
| 130 | + |
|
| 120 | 131 | ******************************************************************************/
|
| 121 | 132 | |
| 122 | 133 | /* Forward declarations */
|
| ... | ... | @@ -129,8 +140,31 @@ static void reportPollError(int res, nfds_t nfds) STG_NORETURN; |
| 129 | 140 | void initCapabilityIOManagerPoll(CapIOManager *iomgr)
|
| 130 | 141 | {
|
| 131 | 142 | initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
|
| 132 | - iomgr->aiop_poll_table = NULL;
|
|
| 133 | 143 | iomgr->timeout_queue = emptyTimeoutQueue();
|
| 144 | + |
|
| 145 | + newFdWakeup(&iomgr->wakeup_fd_r, &iomgr->wakeup_fd_w);
|
|
| 146 | + |
|
| 147 | + iomgr->full_poll_table = stgMallocBytes(sizeof(struct pollfd) /* size 1 */,
|
|
| 148 | + "initCapabilityIOManagerPoll");
|
|
| 149 | + iomgr->full_poll_table[0] = (struct pollfd) {
|
|
| 150 | + .fd = iomgr->wakeup_fd_r,
|
|
| 151 | + .events = POLLIN,
|
|
| 152 | + .revents = 0
|
|
| 153 | + };
|
|
| 154 | + iomgr->aiop_poll_table = iomgr->full_poll_table+1; /* hence empty */
|
|
| 155 | +}
|
|
| 156 | + |
|
| 157 | + |
|
| 158 | +void freeCapabilityIOManagerPoll(CapIOManager *iomgr)
|
|
| 159 | +{
|
|
| 160 | + stgFree(iomgr->full_poll_table);
|
|
| 161 | + closeFdWakeup(iomgr->wakeup_fd_r, iomgr->wakeup_fd_w);
|
|
| 162 | +}
|
|
| 163 | + |
|
| 164 | + |
|
| 165 | +void wakeupIOManagerPoll(CapIOManager *iomgr)
|
|
| 166 | +{
|
|
| 167 | + sendFdWakeup(iomgr->wakeup_fd_w);
|
|
| 134 | 168 | }
|
| 135 | 169 | |
| 136 | 170 | |
| ... | ... | @@ -227,13 +261,6 @@ static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop) |
| 227 | 261 | }
|
| 228 | 262 | |
| 229 | 263 | |
| 230 | -bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr)
|
|
| 231 | -{
|
|
| 232 | - return !isEmptyTimeoutQueue(iomgr->timeout_queue)
|
|
| 233 | - || !isEmptyClosureTable(&iomgr->aiop_table);
|
|
| 234 | -}
|
|
| 235 | - |
|
| 236 | - |
|
| 237 | 264 | static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
|
| 238 | 265 | {
|
| 239 | 266 | ASSERT(aiop->outcome != IOOpOutcomeInFlight);
|
| ... | ... | @@ -275,7 +302,7 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) |
| 275 | 302 | }
|
| 276 | 303 | |
| 277 | 304 | |
| 278 | -static void processIOCompletions(CapIOManager *iomgr, int ncompletions)
|
|
| 305 | +static bool processIOCompletions(CapIOManager *iomgr, int ncompletions)
|
|
| 279 | 306 | {
|
| 280 | 307 | /* The scheme we use with poll is that we have a dense poll table, and a
|
| 281 | 308 | * corresponding table that maps to the closure table index. The poll
|
| ... | ... | @@ -285,6 +312,19 @@ static void processIOCompletions(CapIOManager *iomgr, int ncompletions) |
| 285 | 312 | */
|
| 286 | 313 | debugTrace(DEBUG_iomanager, "processIOCompletions(ncompletions = %d)",
|
| 287 | 314 | ncompletions);
|
| 315 | + |
|
| 316 | + bool wakeup;
|
|
| 317 | + /* If the wakeup_fd_r is ready, collect it */
|
|
| 318 | + if (iomgr->full_poll_table[0].revents) {
|
|
| 319 | + ASSERT(iomgr->full_poll_table[0].fd == iomgr->wakeup_fd_r);
|
|
| 320 | + collectFdWakeup(iomgr->wakeup_fd_r);
|
|
| 321 | + ncompletions--;
|
|
| 322 | + wakeup = true;
|
|
| 323 | + debugTrace(DEBUG_iomanager, "Received wakeup in poll I/O manager.");
|
|
| 324 | + } else {
|
|
| 325 | + wakeup = false;
|
|
| 326 | + }
|
|
| 327 | + |
|
| 288 | 328 | struct pollfd *aiop_poll_table = iomgr->aiop_poll_table;
|
| 289 | 329 | int n = ncompletions;
|
| 290 | 330 | int i = 0;
|
| ... | ... | @@ -337,11 +377,14 @@ static void processIOCompletions(CapIOManager *iomgr, int ncompletions) |
| 337 | 377 | i++;
|
| 338 | 378 | }
|
| 339 | 379 | }
|
| 380 | + return wakeup;
|
|
| 340 | 381 | }
|
| 341 | 382 | |
| 342 | 383 | |
| 343 | 384 | void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
|
| 344 | 385 | {
|
| 386 | + ASSERT(iomgr->aiop_poll_table == iomgr->full_poll_table+1);
|
|
| 387 | + |
|
| 345 | 388 | if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
|
| 346 | 389 | Time now = getProcessElapsedTime();
|
| 347 | 390 | processTimeoutCompletions(iomgr, now);
|
| ... | ... | @@ -349,20 +392,20 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 349 | 392 | |
| 350 | 393 | if (!isEmptyClosureTable(&iomgr->aiop_table)) {
|
| 351 | 394 | |
| 352 | - nfds_t nfds = sizeClosureTable(&iomgr->aiop_table);
|
|
| 395 | + nfds_t nfds = sizeClosureTable(&iomgr->aiop_table) + 1;
|
|
| 353 | 396 | |
| 354 | 397 | /* Poll for I/O readiness, without waiting. */
|
| 355 | 398 | #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
| 356 | 399 | /* We could use poll here, since we use no timeout, but for
|
| 357 | 400 | consistency we use the same syscall as at the other call site. */
|
| 358 | 401 | struct timespec tv = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 };
|
| 359 | - int res = ppoll(iomgr->aiop_poll_table, nfds, &tv, NULL);
|
|
| 402 | + int res = ppoll(iomgr->full_poll_table, nfds, &tv, NULL);
|
|
| 360 | 403 | |
| 361 | 404 | debugTrace(DEBUG_iomanager,
|
| 362 | 405 | "ppoll(nfds = %d, timeout.sec = 0, timeout.nsec = 0) = %d",
|
| 363 | 406 | nfds, res);
|
| 364 | 407 | #else
|
| 365 | - int res = poll(iomgr->aiop_poll_table, nfds, 0);
|
|
| 408 | + int res = poll(iomgr->full_poll_table, nfds, 0);
|
|
| 366 | 409 | |
| 367 | 410 | debugTrace(DEBUG_iomanager,
|
| 368 | 411 | "poll(nfds = %d, timeout_ms = 0) = %d",
|
| ... | ... | @@ -385,11 +428,19 @@ void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 385 | 428 | reportPollError(res, nfds);
|
| 386 | 429 | }
|
| 387 | 430 | }
|
| 431 | + |
|
| 432 | +#if defined(RTS_USER_SIGNALS)
|
|
| 433 | + startPendingSignalHandlers(iomgr->cap);
|
|
| 434 | +#endif
|
|
| 388 | 435 | }
|
| 389 | 436 | |
| 390 | 437 | |
| 391 | 438 | void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr)
|
| 392 | 439 | {
|
| 440 | + bool wakeup = false; /* got woken up via wakeupIOManager */
|
|
| 441 | + |
|
| 442 | + ASSERT(iomgr->aiop_poll_table == iomgr->full_poll_table+1);
|
|
| 443 | + |
|
| 393 | 444 | /* Loop until we've woken up some threads. This loop is needed because the
|
| 394 | 445 | * poll() timing isn't accurate, we sometimes sleep for a while but not
|
| 395 | 446 | * long enough to wake up a thread in a threadDelay. Or we may need to
|
| ... | ... | @@ -397,9 +448,10 @@ void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 397 | 448 | * that select() supports.
|
| 398 | 449 | */
|
| 399 | 450 | do {
|
| 400 | - /* There is either pending I/O or pending timers. */
|
|
| 401 | - ASSERT(!isEmptyTimeoutQueue(iomgr->timeout_queue) ||
|
|
| 402 | - !isEmptyClosureTable(&iomgr->aiop_table));
|
|
| 451 | + /* We do /not/ require that there be pending I/O or pending timers.
|
|
| 452 | + * If there is neither, it's because the scheduler wants us to wait
|
|
| 453 | + * on signals only.
|
|
| 454 | + */
|
|
| 403 | 455 | |
| 404 | 456 | Time now = getProcessElapsedTime();
|
| 405 | 457 | processTimeoutCompletions(iomgr, now);
|
| ... | ... | @@ -422,9 +474,9 @@ void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 422 | 474 | #endif
|
| 423 | 475 | |
| 424 | 476 | /* Check for I/O readiness, possibly waiting. */
|
| 425 | - nfds_t nfds = sizeClosureTable(&iomgr->aiop_table);
|
|
| 477 | + nfds_t nfds = sizeClosureTable(&iomgr->aiop_table) + 1;
|
|
| 426 | 478 | #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
| 427 | - int res = ppoll(iomgr->aiop_poll_table, nfds, timeout_ns, NULL);
|
|
| 479 | + int res = ppoll(iomgr->full_poll_table, nfds, timeout_ns, NULL);
|
|
| 428 | 480 | |
| 429 | 481 | debugTrace(DEBUG_iomanager,
|
| 430 | 482 | "ppoll(nfds = %d, timeout.sec = %d, timeout.nsec = %d) = %d",
|
| ... | ... | @@ -432,7 +484,7 @@ void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 432 | 484 | timeout_ns == NULL ? 0 : timeout_ns->tv_nsec,
|
| 433 | 485 | res);
|
| 434 | 486 | #else
|
| 435 | - int res = poll(iomgr->aiop_poll_table, nfds, timeout_ms);
|
|
| 487 | + int res = poll(iomgr->full_poll_table, nfds, timeout_ms);
|
|
| 436 | 488 | |
| 437 | 489 | debugTrace(DEBUG_iomanager,
|
| 438 | 490 | "poll(nfds = %d, timeout_ms = %d) = %d",
|
| ... | ... | @@ -454,17 +506,16 @@ void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 454 | 506 | } else if (res > 0) {
|
| 455 | 507 | int ncompletions = res;
|
| 456 | 508 | ASSERT(ncompletions <= (int)nfds);
|
| 457 | - processIOCompletions(iomgr, ncompletions);
|
|
| 509 | + wakeup = processIOCompletions(iomgr, ncompletions);
|
|
| 458 | 510 | |
| 459 | 511 | } else if (errno == EINTR) {
|
| 460 | - /* We got interrupted by a signal. In the non-threaded RTS, if the
|
|
| 461 | - * signal is one of ours we need to return to the scheduler to let
|
|
| 462 | - * it handle it. Otherwise we would loop and keep waiting for I/O
|
|
| 463 | - * or timeouts, meaning we would block for a long time before the
|
|
| 464 | - * signal is serviced.
|
|
| 465 | - */
|
|
| 512 | + /* We got interrupted by a signal. */
|
|
| 513 | + |
|
| 466 | 514 | #if defined(RTS_USER_SIGNALS)
|
| 467 | - if (startPendingSignalHandlers(iomgr->cap)) break;
|
|
| 515 | + /* Start any corresponding user signal handlers. If any, the run
|
|
| 516 | + * queue will become non-empty and we will drop out of the loop.
|
|
| 517 | + */
|
|
| 518 | + startPendingSignalHandlers(iomgr->cap);
|
|
| 468 | 519 | #endif
|
| 469 | 520 | |
| 470 | 521 | /* We can also be interrupted by the shutdown signal handler, which
|
| ... | ... | @@ -479,6 +530,7 @@ void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr) |
| 479 | 530 | }
|
| 480 | 531 | |
| 481 | 532 | } while (emptyRunQueue(iomgr->cap)
|
| 533 | + && !wakeup
|
|
| 482 | 534 | && (getSchedState() == SCHED_RUNNING));
|
| 483 | 535 | }
|
| 484 | 536 | |
| ... | ... | @@ -508,13 +560,17 @@ static bool enlargeTables(CapIOManager *iomgr) |
| 508 | 560 | bool ok = enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity);
|
| 509 | 561 | if (RTS_UNLIKELY(!ok)) return false;
|
| 510 | 562 | |
| 511 | - /* Update the auxiliary aiop_poll_table to match */
|
|
| 512 | - struct pollfd *aiop_poll_table;
|
|
| 513 | - aiop_poll_table = stgReallocBytes(iomgr->aiop_poll_table,
|
|
| 514 | - sizeof(struct pollfd) * newcapacity,
|
|
| 515 | - "Poll.c: enlargeTables");
|
|
| 516 | - iomgr->aiop_poll_table = aiop_poll_table;
|
|
| 563 | + /* Update the auxiliary aiop_poll_table to match. The full_poll_table is
|
|
| 564 | + * one bigger than the aiop_poll_table, since it has an extra entry at the
|
|
| 565 | + * front for wakeup_fd_r, with no corresponding aiop. */
|
|
| 566 | + iomgr->full_poll_table =
|
|
| 567 | + stgReallocBytes(iomgr->full_poll_table,
|
|
| 568 | + sizeof(struct pollfd) * (newcapacity+1),
|
|
| 569 | + "Poll.c: enlargeTables");
|
|
| 570 | + iomgr->aiop_poll_table = iomgr->full_poll_table+1;
|
|
| 571 | + |
|
| 517 | 572 | /* Initialise the new part of the aiop_poll_table */
|
| 573 | + struct pollfd *aiop_poll_table = iomgr->aiop_poll_table;
|
|
| 518 | 574 | for (int i = oldcapacity; i < newcapacity; i++) {
|
| 519 | 575 | aiop_poll_table[i] = (struct pollfd) {
|
| 520 | 576 | .fd = -1,
|
| ... | ... | @@ -17,6 +17,8 @@ |
| 17 | 17 | #if defined(IOMGR_ENABLED_POLL)
|
| 18 | 18 | |
| 19 | 19 | void initCapabilityIOManagerPoll(CapIOManager *iomgr);
|
| 20 | +void freeCapabilityIOManagerPoll(CapIOManager *iomgr);
|
|
| 21 | +void wakeupIOManagerPoll(CapIOManager *iomgr);
|
|
| 20 | 22 | |
| 21 | 23 | /* Synchronous I/O and timer operations */
|
| 22 | 24 | bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
|
| ... | ... | @@ -29,7 +31,6 @@ bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop, |
| 29 | 31 | void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop);
|
| 30 | 32 | |
| 31 | 33 | /* Scheduler operations */
|
| 32 | -bool anyPendingTimeoutsOrIOPoll(CapIOManager *iomgr);
|
|
| 33 | 34 | void pollCompletedTimeoutsOrIOPoll(CapIOManager *iomgr);
|
| 34 | 35 | void awaitCompletedTimeoutsOrIOPoll(CapIOManager *iomgr);
|
| 35 | 36 |
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | #include "rts/PosixSource.h"
|
| 13 | 13 | #include "Rts.h"
|
| 14 | 14 | |
| 15 | -#include "Signals.h"
|
|
| 15 | +#include "RtsSignals.h"
|
|
| 16 | 16 | #include "Schedule.h"
|
| 17 | 17 | #include "Prelude.h"
|
| 18 | 18 | #include "RaiseAsync.h"
|
| ... | ... | @@ -22,6 +22,7 @@ |
| 22 | 22 | #include "IOManagerInternals.h"
|
| 23 | 23 | #include "Stats.h"
|
| 24 | 24 | #include "GetTime.h"
|
| 25 | +#include "FdWakeup.h"
|
|
| 25 | 26 | |
| 26 | 27 | # if defined(HAVE_SYS_SELECT_H)
|
| 27 | 28 | # include <sys/select.h>
|
| ... | ... | @@ -54,6 +55,25 @@ |
| 54 | 55 | #define TimeToLowResTimeRoundUp(t) (t)
|
| 55 | 56 | #endif
|
| 56 | 57 | |
| 58 | +void initCapabilityIOManagerSelect(CapIOManager *iomgr)
|
|
| 59 | +{
|
|
| 60 | + iomgr->blocked_queue_hd = END_TSO_QUEUE;
|
|
| 61 | + iomgr->blocked_queue_tl = END_TSO_QUEUE;
|
|
| 62 | + iomgr->sleeping_queue = END_TSO_QUEUE;
|
|
| 63 | + |
|
| 64 | + newFdWakeup(&iomgr->wakeup_fd_r, &iomgr->wakeup_fd_w);
|
|
| 65 | +}
|
|
| 66 | + |
|
| 67 | +void freeCapabilityIOManagerSelect(CapIOManager *iomgr)
|
|
| 68 | +{
|
|
| 69 | + closeFdWakeup(iomgr->wakeup_fd_r, iomgr->wakeup_fd_w);
|
|
| 70 | +}
|
|
| 71 | + |
|
| 72 | +void wakeupIOManagerSelect(CapIOManager *iomgr)
|
|
| 73 | +{
|
|
| 74 | + sendFdWakeup(iomgr->wakeup_fd_w);
|
|
| 75 | +}
|
|
| 76 | + |
|
| 57 | 77 | /*
|
| 58 | 78 | * Return the time since the program started, in LowResTime,
|
| 59 | 79 | * rounded down.
|
| ... | ... | @@ -225,6 +245,7 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 225 | 245 | bool seen_bad_fd = false;
|
| 226 | 246 | struct timeval tv, *ptv;
|
| 227 | 247 | LowResTime now;
|
| 248 | + bool wakeup = false; /* got woken up via wakeupIOManager */
|
|
| 228 | 249 | |
| 229 | 250 | IF_DEBUG(scheduler,
|
| 230 | 251 | debugBelch("scheduler: checking for threads blocked on I/O");
|
| ... | ... | @@ -252,6 +273,13 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 252 | 273 | FD_ZERO(&rfd);
|
| 253 | 274 | FD_ZERO(&wfd);
|
| 254 | 275 | |
| 276 | + /* We're always interested in our wakeup fd */
|
|
| 277 | + {
|
|
| 278 | + int fd = iomgr->wakeup_fd_r;
|
|
| 279 | + maxfd = (fd > maxfd) ? fd : maxfd;
|
|
| 280 | + FD_SET(fd, &rfd);
|
|
| 281 | + }
|
|
| 282 | + |
|
| 255 | 283 | for(tso = iomgr->blocked_queue_hd;
|
| 256 | 284 | tso != END_TSO_QUEUE;
|
| 257 | 285 | tso = next) {
|
| ... | ... | @@ -346,16 +374,11 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 346 | 374 | }
|
| 347 | 375 | }
|
| 348 | 376 | |
| 349 | - /* We got a signal; could be one of ours. If so, we need
|
|
| 350 | - * to start up the signal handler straight away, otherwise
|
|
| 351 | - * we could block for a long time before the signal is
|
|
| 352 | - * serviced.
|
|
| 353 | - */
|
|
| 354 | 377 | #if defined(RTS_USER_SIGNALS)
|
| 355 | - if (RtsFlags.MiscFlags.install_signal_handlers && signals_pending()) {
|
|
| 356 | - startSignalHandlers(iomgr->cap);
|
|
| 357 | - return; /* still hold the lock */
|
|
| 358 | - }
|
|
| 378 | + /* Start any corresponding user signal handlers. If any, the run
|
|
| 379 | + * queue will become non-empty and we will drop out of the loop.
|
|
| 380 | + */
|
|
| 381 | + startPendingSignalHandlers(iomgr->cap);
|
|
| 359 | 382 | #endif
|
| 360 | 383 | |
| 361 | 384 | /* we were interrupted, return to the scheduler immediately.
|
| ... | ... | @@ -376,6 +399,13 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 376 | 399 | }
|
| 377 | 400 | }
|
| 378 | 401 | |
| 402 | + /* If the wakeup_fd_r is ready, collect it */
|
|
| 403 | + if (FD_ISSET(iomgr->wakeup_fd_r, &rfd)) {
|
|
| 404 | + collectFdWakeup(iomgr->wakeup_fd_r);
|
|
| 405 | + wakeup = true;
|
|
| 406 | + debugTrace(DEBUG_iomanager, "Received wakeup in select I/O manager.");
|
|
| 407 | + }
|
|
| 408 | + |
|
| 379 | 409 | /* Step through the waiting queue, unblocking every thread that now has
|
| 380 | 410 | * a file descriptor in a ready state.
|
| 381 | 411 | */
|
| ... | ... | @@ -458,7 +488,8 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait) |
| 458 | 488 | }
|
| 459 | 489 | |
| 460 | 490 | } while (wait && getSchedState() == SCHED_RUNNING
|
| 461 | - && emptyRunQueue(iomgr->cap));
|
|
| 491 | + && emptyRunQueue(iomgr->cap)
|
|
| 492 | + && !wakeup);
|
|
| 462 | 493 | }
|
| 463 | 494 | |
| 464 | 495 | #endif /* IOMGR_ENABLED_SELECT */ |
| ... | ... | @@ -15,6 +15,10 @@ typedef StgWord LowResTime; |
| 15 | 15 | |
| 16 | 16 | LowResTime getDelayTarget (HsInt us);
|
| 17 | 17 | |
| 18 | +void initCapabilityIOManagerSelect(CapIOManager *iomgr);
|
|
| 19 | +void freeCapabilityIOManagerSelect(CapIOManager *iomgr);
|
|
| 20 | +void wakeupIOManagerSelect(CapIOManager *iomgr);
|
|
| 21 | + |
|
| 18 | 22 | void awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait);
|
| 19 | 23 | |
| 20 | 24 | #include "EndPrivate.h"
|
| ... | ... | @@ -9,21 +9,13 @@ |
| 9 | 9 | #include "rts/PosixSource.h"
|
| 10 | 10 | #include "Rts.h"
|
| 11 | 11 | |
| 12 | -#include "Schedule.h"
|
|
| 13 | 12 | #include "RtsSignals.h"
|
| 14 | -#include "Signals.h"
|
|
| 15 | -#include "IOManager.h"
|
|
| 13 | +#include "posix/Signals.h"
|
|
| 16 | 14 | #include "RtsUtils.h"
|
| 15 | +#include "Schedule.h"
|
|
| 17 | 16 | #include "Prelude.h"
|
| 18 | -#include "Ticker.h"
|
|
| 19 | 17 | #include "ThreadLabels.h"
|
| 20 | -#include "Libdw.h"
|
|
| 21 | - |
|
| 22 | -/* TODO: eliminate this include. This file should be about signals, not be
|
|
| 23 | - * part of an I/O manager implementation. The code here that are really part
|
|
| 24 | - * of an I/O manager should be moved into an appropriate I/O manager impl.
|
|
| 25 | - */
|
|
| 26 | -#include "IOManagerInternals.h"
|
|
| 18 | +#include "MIO.h"
|
|
| 27 | 19 | |
| 28 | 20 | #if defined(alpha_HOST_ARCH)
|
| 29 | 21 | # if defined(linux_HOST_OS)
|
| ... | ... | @@ -45,10 +37,6 @@ |
| 45 | 37 | # include <errno.h>
|
| 46 | 38 | #endif
|
| 47 | 39 | |
| 48 | -#if defined(HAVE_EVENTFD_H)
|
|
| 49 | -# include <sys/eventfd.h>
|
|
| 50 | -#endif
|
|
| 51 | - |
|
| 52 | 40 | #if defined(HAVE_TERMIOS_H)
|
| 53 | 41 | #include <termios.h>
|
| 54 | 42 | #endif
|
| ... | ... | @@ -107,6 +95,11 @@ freeSignalHandlers(void) { |
| 107 | 95 | #endif
|
| 108 | 96 | }
|
| 109 | 97 | |
| 98 | +void finiUserSignals(void)
|
|
| 99 | +{
|
|
| 100 | + /* nothing */
|
|
| 101 | +};
|
|
| 102 | + |
|
| 110 | 103 | /* -----------------------------------------------------------------------------
|
| 111 | 104 | * Allocate/resize the table of signal handlers.
|
| 112 | 105 | * -------------------------------------------------------------------------- */
|
| ... | ... | @@ -134,110 +127,6 @@ more_handlers(int sig) |
| 134 | 127 | nHandlers = sig + 1;
|
| 135 | 128 | }
|
| 136 | 129 | |
| 137 | -// Here's the pipe into which we will send our signals
|
|
| 138 | -static int io_manager_wakeup_fd = -1;
|
|
| 139 | -static int timer_manager_control_wr_fd = -1;
|
|
| 140 | - |
|
| 141 | -#define IO_MANAGER_WAKEUP 0xff
|
|
| 142 | -#define IO_MANAGER_DIE 0xfe
|
|
| 143 | -#define IO_MANAGER_SYNC 0xfd
|
|
| 144 | - |
|
| 145 | -void setTimerManagerControlFd(int fd) {
|
|
| 146 | - RELAXED_STORE(&timer_manager_control_wr_fd, fd);
|
|
| 147 | -}
|
|
| 148 | - |
|
| 149 | -void
|
|
| 150 | -setIOManagerWakeupFd (int fd)
|
|
| 151 | -{
|
|
| 152 | - // only called when THREADED_RTS, but unconditionally
|
|
| 153 | - // compiled here because GHC.Event.Control depends on it.
|
|
| 154 | - SEQ_CST_STORE(&io_manager_wakeup_fd, fd);
|
|
| 155 | -}
|
|
| 156 | - |
|
| 157 | -/* -----------------------------------------------------------------------------
|
|
| 158 | - * Wake up at least one IO or timer manager HS thread.
|
|
| 159 | - * -------------------------------------------------------------------------- */
|
|
| 160 | -void
|
|
| 161 | -ioManagerWakeup (void)
|
|
| 162 | -{
|
|
| 163 | - int r;
|
|
| 164 | - const int wakeup_fd = SEQ_CST_LOAD(&io_manager_wakeup_fd);
|
|
| 165 | - // Wake up the IO Manager thread by sending a byte down its pipe
|
|
| 166 | - if (wakeup_fd >= 0) {
|
|
| 167 | -#if defined(HAVE_EVENTFD)
|
|
| 168 | - StgWord64 n = (StgWord64)IO_MANAGER_WAKEUP;
|
|
| 169 | - r = write(wakeup_fd, (char *) &n, 8);
|
|
| 170 | -#else
|
|
| 171 | - StgWord8 byte = (StgWord8)IO_MANAGER_WAKEUP;
|
|
| 172 | - r = write(wakeup_fd, &byte, 1);
|
|
| 173 | -#endif
|
|
| 174 | - /* N.B. If the TimerManager is shutting down as we run this
|
|
| 175 | - * then there is a possibility that our first read of
|
|
| 176 | - * io_manager_wakeup_fd is non-negative, but before we get to the
|
|
| 177 | - * write the file is closed. If this occurs, io_manager_wakeup_fd
|
|
| 178 | - * will be written into with -1 (GHC.Event.Control does this prior
|
|
| 179 | - * to closing), so checking this allows us to distinguish this case.
|
|
| 180 | - * To ensure we observe the correct ordering, we declare the
|
|
| 181 | - * io_manager_wakeup_fd as volatile.
|
|
| 182 | - * Since this is not an error condition, we do not print the error
|
|
| 183 | - * message in this case.
|
|
| 184 | - */
|
|
| 185 | - if (r == -1 && SEQ_CST_LOAD(&io_manager_wakeup_fd) >= 0) {
|
|
| 186 | - sysErrorBelch("ioManagerWakeup: write");
|
|
| 187 | - }
|
|
| 188 | - }
|
|
| 189 | -}
|
|
| 190 | - |
|
| 191 | -#if defined(THREADED_RTS)
|
|
| 192 | -void
|
|
| 193 | -ioManagerDie (void)
|
|
| 194 | -{
|
|
| 195 | - StgWord8 byte = (StgWord8)IO_MANAGER_DIE;
|
|
| 196 | - uint32_t i;
|
|
| 197 | - int r;
|
|
| 198 | - |
|
| 199 | - {
|
|
| 200 | - // Shut down timer manager
|
|
| 201 | - const int fd = RELAXED_LOAD(&timer_manager_control_wr_fd);
|
|
| 202 | - if (0 <= fd) {
|
|
| 203 | - r = write(fd, &byte, 1);
|
|
| 204 | - if (r == -1) { sysErrorBelch("ioManagerDie: write"); }
|
|
| 205 | - RELAXED_STORE(&timer_manager_control_wr_fd, -1);
|
|
| 206 | - }
|
|
| 207 | - }
|
|
| 208 | - |
|
| 209 | - {
|
|
| 210 | - // Shut down IO managers
|
|
| 211 | - for (i=0; i < getNumCapabilities(); i++) {
|
|
| 212 | - const int fd = RELAXED_LOAD(&getCapability(i)->iomgr->control_fd);
|
|
| 213 | - if (0 <= fd) {
|
|
| 214 | - r = write(fd, &byte, 1);
|
|
| 215 | - if (r == -1) { sysErrorBelch("ioManagerDie: write"); }
|
|
| 216 | - RELAXED_STORE(&getCapability(i)->iomgr->control_fd, -1);
|
|
| 217 | - }
|
|
| 218 | - }
|
|
| 219 | - }
|
|
| 220 | -}
|
|
| 221 | - |
|
| 222 | -void
|
|
| 223 | -ioManagerStartCap (Capability **cap)
|
|
| 224 | -{
|
|
| 225 | - rts_evalIO(cap,ensureIOManagerIsRunning_closure,NULL);
|
|
| 226 | -}
|
|
| 227 | - |
|
| 228 | -void
|
|
| 229 | -ioManagerStart (void)
|
|
| 230 | -{
|
|
| 231 | - // Make sure the IO manager thread is running
|
|
| 232 | - Capability *cap;
|
|
| 233 | - if (SEQ_CST_LOAD(&timer_manager_control_wr_fd) < 0 || SEQ_CST_LOAD(&io_manager_wakeup_fd) < 0) {
|
|
| 234 | - cap = rts_lock();
|
|
| 235 | - ioManagerStartCap(&cap);
|
|
| 236 | - rts_unlock(cap);
|
|
| 237 | - }
|
|
| 238 | -}
|
|
| 239 | -#endif
|
|
| 240 | - |
|
| 241 | 130 | #if !defined(THREADED_RTS)
|
| 242 | 131 | |
| 243 | 132 | #define N_PENDING_HANDLERS 16
|
| ... | ... | @@ -245,6 +134,10 @@ ioManagerStart (void) |
| 245 | 134 | siginfo_t pending_handler_buf[N_PENDING_HANDLERS];
|
| 246 | 135 | siginfo_t *next_pending_handler = pending_handler_buf;
|
| 247 | 136 | |
| 137 | +static inline bool signals_pending(void) {
|
|
| 138 | + return (next_pending_handler != pending_handler_buf);
|
|
| 139 | +}
|
|
| 140 | + |
|
| 248 | 141 | #endif /* THREADED_RTS */
|
| 249 | 142 | |
| 250 | 143 | /* -----------------------------------------------------------------------------
|
| ... | ... | @@ -260,31 +153,9 @@ generic_handler(int sig USED_IF_THREADS, |
| 260 | 153 | void *p STG_UNUSED)
|
| 261 | 154 | {
|
| 262 | 155 | #if defined(THREADED_RTS)
|
| 263 | - |
|
| 264 | - StgWord8 buf[sizeof(siginfo_t) + 1];
|
|
| 265 | - int r;
|
|
| 266 | - |
|
| 267 | - buf[0] = sig;
|
|
| 268 | - if (info == NULL) {
|
|
| 269 | - // info may be NULL on Solaris (see #3790)
|
|
| 270 | - memset(buf+1, 0, sizeof(siginfo_t));
|
|
| 271 | - } else {
|
|
| 272 | - memcpy(buf+1, info, sizeof(siginfo_t));
|
|
| 273 | - }
|
|
| 274 | - |
|
| 275 | - int timer_control_fd = RELAXED_LOAD(&timer_manager_control_wr_fd);
|
|
| 276 | - if (0 <= timer_control_fd)
|
|
| 277 | - {
|
|
| 278 | - r = write(timer_control_fd, buf, sizeof(siginfo_t)+1);
|
|
| 279 | - if (r == -1 && errno == EAGAIN) {
|
|
| 280 | - errorBelch("lost signal due to full pipe: %d\n", sig);
|
|
| 281 | - }
|
|
| 282 | - }
|
|
| 283 | - |
|
| 284 | - // If the IO manager hasn't told us what the FD of the write end
|
|
| 285 | - // of its pipe is, there's not much we can do here, so just ignore
|
|
| 286 | - // the signal..
|
|
| 287 | - |
|
| 156 | + //TODO: This calls MIO directly. We should go via IOManager API.
|
|
| 157 | + // The IOManager API should be extended to cover signals.
|
|
| 158 | + timerManagerNotifySignal(sig, info);
|
|
| 288 | 159 | #else /* not THREADED_RTS */
|
| 289 | 160 | |
| 290 | 161 | /* Can't call allocate from here. Probably can't call malloc
|
| ... | ... | @@ -346,22 +217,6 @@ unblockUserSignals(void) |
| 346 | 217 | sigprocmask(SIG_SETMASK, &savedSignals, NULL);
|
| 347 | 218 | }
|
| 348 | 219 | |
| 349 | -bool
|
|
| 350 | -anyUserHandlers(void)
|
|
| 351 | -{
|
|
| 352 | - return n_haskell_handlers != 0;
|
|
| 353 | -}
|
|
| 354 | - |
|
| 355 | -#if !defined(THREADED_RTS)
|
|
| 356 | -void
|
|
| 357 | -awaitUserSignals(void)
|
|
| 358 | -{
|
|
| 359 | - while (!signals_pending() && getSchedState() == SCHED_RUNNING) {
|
|
| 360 | - pause();
|
|
| 361 | - }
|
|
| 362 | -}
|
|
| 363 | -#endif
|
|
| 364 | - |
|
| 365 | 220 | /* -----------------------------------------------------------------------------
|
| 366 | 221 | * Install a Haskell signal handler.
|
| 367 | 222 | *
|
| ... | ... | @@ -468,11 +323,13 @@ stg_sig_install(int sig, int spi, void *mask) |
| 468 | 323 | |
| 469 | 324 | #if !defined(THREADED_RTS)
|
| 470 | 325 | void
|
| 471 | -startSignalHandlers(Capability *cap)
|
|
| 326 | +startPendingSignalHandlers(Capability *cap)
|
|
| 472 | 327 | {
|
| 473 | 328 | siginfo_t *info;
|
| 474 | 329 | int sig;
|
| 475 | 330 | |
| 331 | + if (!signals_pending()) return;
|
|
| 332 | + |
|
| 476 | 333 | blockUserSignals();
|
| 477 | 334 | |
| 478 | 335 | while (next_pending_handler != pending_handler_buf) {
|
| ... | ... | @@ -484,7 +341,7 @@ startSignalHandlers(Capability *cap) |
| 484 | 341 | continue; // handler has been changed.
|
| 485 | 342 | }
|
| 486 | 343 | |
| 487 | - info = stgMallocBytes(sizeof(siginfo_t), "startSignalHandlers");
|
|
| 344 | + info = stgMallocBytes(sizeof(siginfo_t), "startPendingSignalHandlers");
|
|
| 488 | 345 | // freed by runHandler
|
| 489 | 346 | memcpy(info, next_pending_handler, sizeof(siginfo_t));
|
| 490 | 347 |
| ... | ... | @@ -2,43 +2,19 @@ |
| 2 | 2 | *
|
| 3 | 3 | * (c) The GHC Team, 1998-2005
|
| 4 | 4 | *
|
| 5 | - * Signal processing / handling.
|
|
| 5 | + * POSIX signal processing / handling.
|
|
| 6 | + *
|
|
| 7 | + * Most of the API for this is common between POSIX and Win32 console events.
|
|
| 8 | + * The common part of the API lives in RtsSignals.h.
|
|
| 6 | 9 | *
|
| 7 | 10 | * ---------------------------------------------------------------------------*/
|
| 8 | 11 | |
| 9 | 12 | #pragma once
|
| 10 | 13 | |
| 11 | -#if defined(HAVE_SIGNAL_H)
|
|
| 12 | -# include <signal.h>
|
|
| 13 | -#endif
|
|
| 14 | - |
|
| 15 | 14 | #include "Ticker.h"
|
| 16 | 15 | |
| 17 | 16 | #include "BeginPrivate.h"
|
| 18 | 17 | |
| 19 | -bool anyUserHandlers(void);
|
|
| 20 | - |
|
| 21 | -#if !defined(THREADED_RTS) && defined(RTS_USER_SIGNALS)
|
|
| 22 | -extern siginfo_t pending_handler_buf[];
|
|
| 23 | -extern siginfo_t *next_pending_handler;
|
|
| 24 | -#define signals_pending() (next_pending_handler != pending_handler_buf)
|
|
| 25 | -void startSignalHandlers(Capability *cap);
|
|
| 26 | -#endif
|
|
| 27 | - |
|
| 28 | 18 | void install_vtalrm_handler(int sig, TickProc handle_tick);
|
| 29 | 19 | |
| 30 | -/* Communicating with the IO manager thread (see GHC.Conc).
|
|
| 31 | - *
|
|
| 32 | - * TODO: these I/O manager things are not related to signals and ought to live
|
|
| 33 | - * elsewhere, e.g. in a module specifically for the I/O manager.
|
|
| 34 | - */
|
|
| 35 | -void ioManagerWakeup (void);
|
|
| 36 | -#if defined(THREADED_RTS)
|
|
| 37 | -void ioManagerDie (void);
|
|
| 38 | -void ioManagerStart (void);
|
|
| 39 | -void ioManagerStartCap (/* inout */ Capability **cap);
|
|
| 40 | -#endif
|
|
| 41 | - |
|
| 42 | -extern StgInt *signal_handlers;
|
|
| 43 | - |
|
| 44 | 20 | #include "EndPrivate.h" |
| ... | ... | @@ -569,6 +569,7 @@ library |
| 569 | 569 | wasm/OSThreads.c
|
| 570 | 570 | wasm/JSFFI.c
|
| 571 | 571 | wasm/JSFFIGlobals.c
|
| 572 | + posix/FdWakeup.c
|
|
| 572 | 573 | posix/Select.c
|
| 573 | 574 | posix/Poll.c
|
| 574 | 575 | posix/Timeout.c
|
| ... | ... | @@ -581,6 +582,8 @@ library |
| 581 | 582 | posix/Ticker.c
|
| 582 | 583 | posix/OSMem.c
|
| 583 | 584 | posix/OSThreads.c
|
| 585 | + posix/FdWakeup.c
|
|
| 586 | + posix/MIO.c
|
|
| 584 | 587 | posix/Poll.c
|
| 585 | 588 | posix/Select.c
|
| 586 | 589 | posix/Signals.c
|
| ... | ... | @@ -14,6 +14,7 @@ |
| 14 | 14 | *
|
| 15 | 15 | */
|
| 16 | 16 | #include "Rts.h"
|
| 17 | +#include "RtsSignals.h"
|
|
| 17 | 18 | #include "RtsFlags.h"
|
| 18 | 19 | #include "Schedule.h"
|
| 19 | 20 | #include "IOManager.h"
|
| ... | ... | @@ -41,14 +42,9 @@ awaitCompletedTimeoutsOrIOWin32(Capability *cap, bool wait) |
| 41 | 42 | awaitRequests(wait);
|
| 42 | 43 | workerWaitingForRequests = false;
|
| 43 | 44 | |
| 44 | - // If a signal was raised, we need to service it
|
|
| 45 | - // XXX the scheduler loop really should be calling
|
|
| 46 | - // startSignalHandlers(), but this is the way that posix/Select.c
|
|
| 47 | - // does it and I'm feeling too paranoid to refactor it today --SDM
|
|
| 48 | - if (stg_pending_events != 0) {
|
|
| 49 | - startSignalHandlers(cap);
|
|
| 50 | - return;
|
|
| 51 | - }
|
|
| 45 | + // If a signal was raised, we need to service it. This will typically
|
|
| 46 | + // start a thread, which will cause us to drop out of the loop.
|
|
| 47 | + startPendingSignalHandlers(cap);
|
|
| 52 | 48 | |
| 53 | 49 | // The return value from awaitRequests() is a red herring: ignore
|
| 54 | 50 | // it. Return to the scheduler if !wait, or
|
| ... | ... | @@ -154,29 +154,18 @@ unblockUserSignals(void) |
| 154 | 154 | }
|
| 155 | 155 | |
| 156 | 156 | |
| 157 | -/*
|
|
| 158 | - * Function: awaitUserSignals()
|
|
| 159 | - *
|
|
| 160 | - * Wait for the next console event. Currently a NOP (returns immediately.)
|
|
| 161 | - */
|
|
| 162 | -void awaitUserSignals(void)
|
|
| 163 | -{
|
|
| 164 | - return;
|
|
| 165 | -}
|
|
| 166 | - |
|
| 167 | - |
|
| 168 | 157 | #if !defined(THREADED_RTS)
|
| 169 | 158 | /*
|
| 170 | - * Function: startSignalHandlers()
|
|
| 159 | + * Function: startPendingSignalHandlers()
|
|
| 171 | 160 | *
|
| 172 | - * Run the handlers associated with the stacked up console events. Console
|
|
| 173 | - * event delivery is blocked for the duration of this call.
|
|
| 161 | + * If there are any queued up console events, run the handlers associated with
|
|
| 162 | + * them. Console event delivery is blocked for the duration of this call.
|
|
| 174 | 163 | */
|
| 175 | -void startSignalHandlers(Capability *cap)
|
|
| 164 | +void startPendingSignalHandlers(Capability *cap)
|
|
| 176 | 165 | {
|
| 177 | 166 | StgStablePtr handler;
|
| 178 | 167 | |
| 179 | - if (console_handler < 0) {
|
|
| 168 | + if (stg_pending_events <= 0 || console_handler < 0) {
|
|
| 180 | 169 | return;
|
| 181 | 170 | }
|
| 182 | 171 |
| ... | ... | @@ -23,36 +23,6 @@ |
| 23 | 23 | * thread, which starts up the handler. See ThrIOManager.c.
|
| 24 | 24 | */
|
| 25 | 25 | |
| 26 | -/*
|
|
| 27 | - * Function: signals_pending()
|
|
| 28 | - *
|
|
| 29 | - * Used by the RTS to check whether new signals have been 'recently' reported.
|
|
| 30 | - * If so, the RTS arranges for the delivered signals to be handled by
|
|
| 31 | - * de-queueing them from their table, running the associated Haskell
|
|
| 32 | - * signal handler.
|
|
| 33 | - */
|
|
| 34 | -extern StgInt stg_pending_events;
|
|
| 35 | - |
|
| 36 | -#define signals_pending() ( stg_pending_events > 0)
|
|
| 37 | - |
|
| 38 | -/*
|
|
| 39 | - * Function: anyUserHandlers()
|
|
| 40 | - *
|
|
| 41 | - * Used by the Scheduler to decide whether its worth its while to stick
|
|
| 42 | - * around waiting for an external signal when there are no threads
|
|
| 43 | - * runnable. A console handler is used to handle termination events (Ctrl+C)
|
|
| 44 | - * and isn't considered a 'user handler'.
|
|
| 45 | - */
|
|
| 46 | -#define anyUserHandlers() (false)
|
|
| 47 | - |
|
| 48 | -/*
|
|
| 49 | - * Function: startSignalHandlers()
|
|
| 50 | - *
|
|
| 51 | - * Run the handlers associated with the queued up console events. Console
|
|
| 52 | - * event delivery is blocked for the duration of this call.
|
|
| 53 | - */
|
|
| 54 | -extern void startSignalHandlers(Capability *cap);
|
|
| 55 | - |
|
| 56 | 26 | /*
|
| 57 | 27 | * Function: rts_waitConsoleHandlerCompletion()
|
| 58 | 28 | *
|
| ... | ... | @@ -62,10 +32,3 @@ extern void startSignalHandlers(Capability *cap); |
| 62 | 32 | extern int rts_waitConsoleHandlerCompletion(void);
|
| 63 | 33 | |
| 64 | 34 | #endif /* THREADED_RTS */ |
| 65 | - |
|
| 66 | -/*
|
|
| 67 | - * Function: finiUserSignals()
|
|
| 68 | - *
|
|
| 69 | - * Tear down and shut down user signal processing.
|
|
| 70 | - */
|
|
| 71 | -extern void finiUserSignals(void); |
| 1 | +import Control.Concurrent
|
|
| 2 | +import Control.Concurrent.STM
|
|
| 3 | +import Control.Exception
|
|
| 4 | +import Control.Monad
|
|
| 5 | + |
|
| 6 | +-- | Test to make sure that deadlock detection works even when there are other
|
|
| 7 | +-- unrelated threads that are blocked on I\/O or timeouts.
|
|
| 8 | +-- Historically however this did affect things in the non-threaded RTS which
|
|
| 9 | +-- would only do deadlock detection if there were no runnable threads /and/
|
|
| 10 | +-- no pending I\/O. See <https://gitlab.haskell.org/ghc/ghc/-/issues/26408>
|
|
| 11 | +main :: IO ()
|
|
| 12 | +main = do
|
|
| 13 | + |
|
| 14 | + -- Set up two threads that are deadlocked on each other
|
|
| 15 | + aDone <- newTVarIO False
|
|
| 16 | + bDone <- newTVarIO False
|
|
| 17 | + let blockingThread theirDone ourDone =
|
|
| 18 | + atomically $ do
|
|
| 19 | + done <- readTVar theirDone
|
|
| 20 | + guard done
|
|
| 21 | + writeTVar ourDone True
|
|
| 22 | + _ <- forkIO (blockingThread bDone aDone)
|
|
| 23 | + _ <- forkIO (blockingThread aDone bDone)
|
|
| 24 | + |
|
| 25 | + -- Set up another thread that is blocked on a long timeout.
|
|
| 26 | + --
|
|
| 27 | + -- We use a timeout rather than I/O as it's more portable, whereas I/O waits
|
|
| 28 | + -- are different between posix and windows I/O managers.
|
|
| 29 | + --
|
|
| 30 | + -- One gotcha is that when the timeout completes then the deadlock will be
|
|
| 31 | + -- detected again (since the bug is about I/O or timeouts masking deadlock
|
|
| 32 | + -- detection). So for a reliable test the timeout used here must be longer
|
|
| 33 | + -- than the test framework's own timeout. So we use maxBound, and we adjust
|
|
| 34 | + -- the test framework's timeout to be short (see run_timeout_multiplier).
|
|
| 35 | + _ <- forkIO (threadDelay maxBound)
|
|
| 36 | + |
|
| 37 | + -- Wait on the deadlocked threads to terminate. We now expect that the threads
|
|
| 38 | + -- that are deadlocked are detected as such and an exception is raised.
|
|
| 39 | + -- Note that if this fails, the test itself will effectively deadlock and
|
|
| 40 | + -- will rely on the test framework's timeout.
|
|
| 41 | + atomically $ do
|
|
| 42 | + status <- mapM readTVar [aDone, bDone]
|
|
| 43 | + guard (or status) |
| 1 | +T26408: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
|
|
| 2 | + |
|
| 3 | +thread blocked indefinitely in an STM transaction |
| ... | ... | @@ -657,6 +657,8 @@ test('T22859', |
| 657 | 657 | omit_ways(llvm_ways)],
|
| 658 | 658 | compile_and_run, ['-with-rtsopts -A8K'])
|
| 659 | 659 | |
| 660 | +test('T26408', [exit_code(1), run_timeout_multiplier(0.1)], compile_and_run, [''])
|
|
| 661 | + |
|
| 660 | 662 | # These tests need access to the internal RTS headers.
|
| 661 | 663 | # TODO: there is probably some cleaner way to do this, and it should probably
|
| 662 | 664 | # be guarded for in-tree tests, since it cannot work against an arbitrary
|