Magnus pushed to branch wip/mangoiv/ghc-9.12-bp at Glasgow Haskell Compiler / GHC
Commits:
-
d99bb8e1
by mangoiv at 2026-05-20T14:52:56+02:00
-
8fde70d7
by mangoiv at 2026-05-20T14:52:56+02:00
-
b4ef6088
by Duncan Coutts at 2026-05-20T14:56:06+02:00
-
3319b7da
by Luite Stegeman at 2026-05-20T14:56:59+02:00
20 changed files:
- rts/HeapStackCheck.cmm
- rts/IOManager.c
- rts/IOManager.h
- rts/Interpreter.c
- rts/PrimOps.cmm
- rts/RtsSymbols.c
- rts/Threads.c
- rts/include/rts/storage/ClosureMacros.h
- rts/include/rts/storage/TSO.h
- rts/include/stg/MiscClosures.h
- rts/win32/AsyncMIO.c
- rts/win32/AsyncMIO.h
- + testsuite/tests/concurrent/should_run/T26341.hs
- + testsuite/tests/concurrent/should_run/T26341.stdout
- + testsuite/tests/concurrent/should_run/T26341a.hs
- + testsuite/tests/concurrent/should_run/T26341a.stdout
- + testsuite/tests/concurrent/should_run/T26341b.hs
- + testsuite/tests/concurrent/should_run/T26341b.stdout
- testsuite/tests/concurrent/should_run/all.T
- utils/deriveConstants/Main.hs
Changes:
| ... | ... | @@ -706,38 +706,24 @@ stg_block_throwto (P_ tso, P_ exception) |
| 706 | 706 | }
|
| 707 | 707 | |
| 708 | 708 | #if defined(mingw32_HOST_OS)
|
| 709 | -INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ ares )
|
|
| 709 | +INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ len, W_ errCode )
|
|
| 710 | 710 | return ()
|
| 711 | 711 | {
|
| 712 | - W_ len, errC;
|
|
| 713 | - |
|
| 714 | - len = TO_W_(StgAsyncIOResult_len(ares));
|
|
| 715 | - errC = TO_W_(StgAsyncIOResult_errCode(ares));
|
|
| 716 | - ccall free(ares "ptr");
|
|
| 717 | - return (len, errC);
|
|
| 712 | + return (len, errCode);
|
|
| 718 | 713 | }
|
| 719 | 714 | |
| 720 | 715 | stg_block_async
|
| 721 | 716 | {
|
| 722 | - Sp_adj(-2);
|
|
| 723 | - Sp(0) = stg_block_async_info;
|
|
| 724 | - BLOCK_GENERIC;
|
|
| 725 | -}
|
|
| 717 | + W_ eintr;
|
|
| 718 | + (eintr) = ccall rts_EINTR();
|
|
| 726 | 719 | |
| 727 | -/* Used by threadDelay implementation; it would be desirable to get rid of
|
|
| 728 | - * this free()'ing void return continuation.
|
|
| 729 | - */
|
|
| 730 | -INFO_TABLE_RET ( stg_block_async_void, RET_SMALL, W_ info_ptr, W_ ares )
|
|
| 731 | - return ()
|
|
| 732 | -{
|
|
| 733 | - ccall free(ares "ptr");
|
|
| 734 | - return ();
|
|
| 735 | -}
|
|
| 736 | - |
|
| 737 | -stg_block_async_void
|
|
| 738 | -{
|
|
| 739 | - Sp_adj(-2);
|
|
| 740 | - Sp(0) = stg_block_async_void_info;
|
|
| 720 | + // Fill the stack frame with values that indicate that the operation
|
|
| 721 | + // has been interrupted. The IO manager will overwrite these with the
|
|
| 722 | + // actual results if the async operation completes.
|
|
| 723 | + Sp_adj(-3);
|
|
| 724 | + Sp(0) = stg_block_async_info;
|
|
| 725 | + Sp(1) = -1; // len: -1 indicates error
|
|
| 726 | + Sp(2) = eintr; // errCode: interrupted
|
|
| 741 | 727 | BLOCK_GENERIC;
|
| 742 | 728 | }
|
| 743 | 729 |
| ... | ... | @@ -561,10 +561,8 @@ void scavengeTSOIOManager(StgTSO *tso) |
| 561 | 561 | */
|
| 562 | 562 | |
| 563 | 563 | /* case IO_MANAGER_WIN32_LEGACY:
|
| 564 | - * BlockedOn{Read,Write,DoProc} uses block_info.async_result
|
|
| 565 | - * The StgAsyncIOResult async_result is allocated on the C heap.
|
|
| 566 | - * It'd probably be better if it used the GC heap. If it did we'd
|
|
| 567 | - * scavenge it here.
|
|
| 564 | + * BlockedOn{Read,Write,DoProc} uses block_info.async_reqID
|
|
| 565 | + * which is a plain integer, so nothing to scavenge.
|
|
| 568 | 566 | */
|
| 569 | 567 | |
| 570 | 568 | default:
|
| ... | ... | @@ -707,7 +705,7 @@ void awaitCompletedTimeoutsOrIO(Capability *cap) |
| 707 | 705 | }
|
| 708 | 706 | |
| 709 | 707 | |
| 710 | -void syncIOWaitReady(Capability *cap,
|
|
| 708 | +bool syncIOWaitReady(Capability *cap,
|
|
| 711 | 709 | StgTSO *tso,
|
| 712 | 710 | IOReadOrWrite rw,
|
| 713 | 711 | HsInt fd)
|
| ... | ... | @@ -724,7 +722,7 @@ void syncIOWaitReady(Capability *cap, |
| 724 | 722 | tso->block_info.fd = fd;
|
| 725 | 723 | RELEASE_STORE(&tso->why_blocked, why_blocked);
|
| 726 | 724 | appendToIOBlockedQueue(cap, tso);
|
| 727 | - break;
|
|
| 725 | + return true;
|
|
| 728 | 726 | }
|
| 729 | 727 | #endif
|
| 730 | 728 | default:
|
| ... | ... | @@ -747,7 +745,7 @@ void syncIOCancel(Capability *cap, StgTSO *tso) |
| 747 | 745 | case IO_MANAGER_WIN32_LEGACY:
|
| 748 | 746 | removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
|
| 749 | 747 | &cap->iomgr->blocked_queue_tl, tso);
|
| 750 | - abandonWorkRequest(tso->block_info.async_result->reqID);
|
|
| 748 | + abandonWorkRequest(tso->block_info.async_reqID);
|
|
| 751 | 749 | break;
|
| 752 | 750 | #endif
|
| 753 | 751 | default:
|
| ... | ... | @@ -761,7 +759,7 @@ static void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime tar |
| 761 | 759 | #endif
|
| 762 | 760 | |
| 763 | 761 | |
| 764 | -void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay)
|
|
| 762 | +bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay)
|
|
| 765 | 763 | {
|
| 766 | 764 | debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id, us_delay);
|
| 767 | 765 | ASSERT(tso->why_blocked == NotBlocked);
|
| ... | ... | @@ -773,7 +771,7 @@ void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 773 | 771 | tso->block_info.target = target;
|
| 774 | 772 | RELEASE_STORE(&tso->why_blocked, BlockedOnDelay);
|
| 775 | 773 | insertIntoSleepingQueue(cap, tso, target);
|
| 776 | - break;
|
|
| 774 | + return true;
|
|
| 777 | 775 | }
|
| 778 | 776 | #endif
|
| 779 | 777 | #if defined(IOMGR_ENABLED_WIN32_LEGACY)
|
| ... | ... | @@ -782,12 +780,7 @@ void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 782 | 780 | * would make the primops more consistent.
|
| 783 | 781 | */
|
| 784 | 782 | {
|
| 785 | - StgAsyncIOResult *ares = stgMallocBytes(sizeof(StgAsyncIOResult),
|
|
| 786 | - "syncDelay");
|
|
| 787 | - ares->reqID = addDelayRequest(us_delay);
|
|
| 788 | - ares->len = 0;
|
|
| 789 | - ares->errCode = 0;
|
|
| 790 | - tso->block_info.async_result = ares;
|
|
| 783 | + tso->block_info.async_reqID = addDelayRequest(us_delay);
|
|
| 791 | 784 | |
| 792 | 785 | /* Having all async-blocked threads reside on the blocked_queue
|
| 793 | 786 | * simplifies matters, so set the status to OnDoProc and put the
|
| ... | ... | @@ -795,7 +788,7 @@ void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay) |
| 795 | 788 | */
|
| 796 | 789 | RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
|
| 797 | 790 | appendToIOBlockedQueue(cap, tso);
|
| 798 | - break;
|
|
| 791 | + return true;
|
|
| 799 | 792 | }
|
| 800 | 793 | #endif
|
| 801 | 794 | default:
|
| ... | ... | @@ -282,13 +282,20 @@ typedef enum { IORead, IOWrite } IOReadOrWrite; |
| 282 | 282 | /* Synchronous operations: I/O and delays. As synchronous operations they
|
| 283 | 283 | * necessarily operate on threads. The thread is suspended until the operation
|
| 284 | 284 | * completes.
|
| 285 | + *
|
|
| 286 | + * These are called from CMM primops. The ones returing int can perform heap
|
|
| 287 | + * allocation, which might fail. They return 0 on success, or n > 0 on heap
|
|
| 288 | + * allocation failure, needing n words. The CMM primops should invoke the
|
|
| 289 | + * GC to free up at least n words and then retry the operation.
|
|
| 285 | 290 | */
|
| 286 | 291 | |
| 287 | -void syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
|
|
| 292 | +/* Result is true on success, or false on allocation failure. */
|
|
| 293 | +bool syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
|
|
| 288 | 294 | |
| 289 | 295 | void syncIOCancel(Capability *cap, StgTSO *tso);
|
| 290 | 296 | |
| 291 | -void syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay);
|
|
| 297 | +/* Result is true on success, or false on allocation failure. */
|
|
| 298 | +bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay);
|
|
| 292 | 299 | |
| 293 | 300 | void syncDelayCancel(Capability *cap, StgTSO *tso);
|
| 294 | 301 |
| ... | ... | @@ -468,7 +468,7 @@ slow_spw(void *Sp, StgStack *cur_stack, StgWord offset_words){ |
| 468 | 468 | }
|
| 469 | 469 | // 2b. Access the element if there is no underflow frame, it must be right
|
| 470 | 470 | // at the top of the stack.
|
| 471 | - else if(Sp_plusW(offset_words) < (StgPtr)(cur_stack->stack + cur_stack->stack_size)) {
|
|
| 471 | + else if((StgPtr)Sp_plusW(offset_words) < (StgPtr)(cur_stack->stack + cur_stack->stack_size)) {
|
|
| 472 | 472 | // Still inside the stack chunk
|
| 473 | 473 | return Sp_plusW(offset_words);
|
| 474 | 474 | } else {
|
| ... | ... | @@ -1832,7 +1832,7 @@ run_BCO: |
| 1832 | 1832 | threadStackUnderflow(cap, cap->r.rCurrentTSO);
|
| 1833 | 1833 | LOAD_STACK_POINTERS;
|
| 1834 | 1834 | by -= sp_to_uf;
|
| 1835 | - } else if (Sp_plusW(by) < (StgPtr)(stk->stack + stk->stack_size)) {
|
|
| 1835 | + } else if ((StgPtr)Sp_plusW(by) < (StgPtr)(stk->stack + stk->stack_size)) {
|
|
| 1836 | 1836 | // we're within the first stack chunk, this chunk has
|
| 1837 | 1837 | // no underflow frame
|
| 1838 | 1838 | break;
|
| ... | ... | @@ -2561,54 +2561,55 @@ stg_whereFromzh (P_ clos, W_ buf) |
| 2561 | 2561 | |
| 2562 | 2562 | stg_waitReadzh ( W_ fd )
|
| 2563 | 2563 | {
|
| 2564 | - ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2565 | - /* IORead */ 0::I32, fd);
|
|
| 2566 | - jump stg_block_noregs();
|
|
| 2564 | + CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2565 | + |
|
| 2566 | + (ok) = ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2567 | + /* IORead */ 0::I32, fd);
|
|
| 2568 | + if (ok != 0::CBool) (likely: True) {
|
|
| 2569 | + jump stg_block_noregs();
|
|
| 2570 | + } else {
|
|
| 2571 | + jump stg_raisezh(ghczminternal_GHCziInternalziIOziException_heapOverflow_closure);
|
|
| 2572 | + }
|
|
| 2567 | 2573 | }
|
| 2568 | 2574 | |
| 2569 | 2575 | stg_waitWritezh ( W_ fd )
|
| 2570 | 2576 | {
|
| 2571 | - ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2572 | - /* IOWrite */ 1::I32, fd);
|
|
| 2573 | - jump stg_block_noregs();
|
|
| 2577 | + CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2578 | + |
|
| 2579 | + (ok) = ccall syncIOWaitReady(MyCapability() "ptr", CurrentTSO "ptr",
|
|
| 2580 | + /* IOWrite */ 1::I32, fd);
|
|
| 2581 | + if (ok != 0::CBool) (likely: True) {
|
|
| 2582 | + jump stg_block_noregs();
|
|
| 2583 | + } else {
|
|
| 2584 | + jump stg_raisezh(ghczminternal_GHCziInternalziIOziException_heapOverflow_closure);
|
|
| 2585 | + }
|
|
| 2574 | 2586 | }
|
| 2575 | 2587 | |
| 2576 | 2588 | stg_delayzh ( W_ us_delay )
|
| 2577 | 2589 | {
|
| 2578 | - ccall syncDelay(MyCapability() "ptr", CurrentTSO "ptr", us_delay);
|
|
| 2590 | + CBool ok; /* Ok, or heap alloc failure. */
|
|
| 2579 | 2591 | |
| 2580 | - /* Annoyingly, we cannot be consistent with how we wait and resume the
|
|
| 2581 | - * blocked thread. The reason is that the win32 legacy I/O manager
|
|
| 2582 | - * allocates a StgAsyncIOResult struct on the C heap which has to be
|
|
| 2583 | - * freed when the thread resumes. It's a bit awkward to arrange to
|
|
| 2584 | - * allocate it on the GC heap instead, so that's how it is for now. Sigh.
|
|
| 2585 | - */
|
|
| 2586 | -#if defined(mingw32_HOST_OS)
|
|
| 2587 | - jump stg_block_async_void();
|
|
| 2588 | -#else
|
|
| 2589 | - jump stg_block_noregs();
|
|
| 2590 | -#endif
|
|
| 2592 | + (ok) = ccall syncDelay(MyCapability() "ptr", CurrentTSO "ptr", us_delay);
|
|
| 2593 | + |
|
| 2594 | + if (ok != 0::CBool) (likely: True) {
|
|
| 2595 | + jump stg_block_noregs();
|
|
| 2596 | + } else {
|
|
| 2597 | + jump stg_raisezh(ghczminternal_GHCziInternalziIOziException_heapOverflow_closure);
|
|
| 2598 | + }
|
|
| 2591 | 2599 | }
|
| 2592 | 2600 | |
| 2593 | 2601 | |
| 2594 | 2602 | #if defined(mingw32_HOST_OS)
|
| 2595 | 2603 | stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf )
|
| 2596 | 2604 | {
|
| 2597 | - W_ ares;
|
|
| 2598 | 2605 | CInt reqID;
|
| 2599 | 2606 | |
| 2600 | 2607 | #if defined(THREADED_RTS)
|
| 2601 | 2608 | ccall barf("asyncRead# on threaded RTS") never returns;
|
| 2602 | 2609 | #else
|
| 2603 | 2610 | |
| 2604 | - /* could probably allocate this on the heap instead */
|
|
| 2605 | - ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
|
|
| 2606 | - "stg_asyncReadzh");
|
|
| 2607 | 2611 | (reqID) = ccall addIORequest(fd, 0/*FALSE*/,is_sock,len,buf "ptr");
|
| 2608 | - StgAsyncIOResult_reqID(ares) = reqID;
|
|
| 2609 | - StgAsyncIOResult_len(ares) = 0;
|
|
| 2610 | - StgAsyncIOResult_errCode(ares) = 0;
|
|
| 2611 | - StgTSO_block_info(CurrentTSO) = ares;
|
|
| 2612 | + StgTSO_block_info(CurrentTSO) = reqID;
|
|
| 2612 | 2613 | |
| 2613 | 2614 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2614 | 2615 | %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32;
|
| ... | ... | @@ -2620,21 +2621,14 @@ stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2620 | 2621 | |
| 2621 | 2622 | stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf )
|
| 2622 | 2623 | {
|
| 2623 | - W_ ares;
|
|
| 2624 | 2624 | CInt reqID;
|
| 2625 | 2625 | |
| 2626 | 2626 | #if defined(THREADED_RTS)
|
| 2627 | 2627 | ccall barf("asyncWrite# on threaded RTS") never returns;
|
| 2628 | 2628 | #else
|
| 2629 | 2629 | |
| 2630 | - ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
|
|
| 2631 | - "stg_asyncWritezh");
|
|
| 2632 | 2630 | (reqID) = ccall addIORequest(fd, 1/*TRUE*/,is_sock,len,buf "ptr");
|
| 2633 | - |
|
| 2634 | - StgAsyncIOResult_reqID(ares) = reqID;
|
|
| 2635 | - StgAsyncIOResult_len(ares) = 0;
|
|
| 2636 | - StgAsyncIOResult_errCode(ares) = 0;
|
|
| 2637 | - StgTSO_block_info(CurrentTSO) = ares;
|
|
| 2631 | + StgTSO_block_info(CurrentTSO) = reqID;
|
|
| 2638 | 2632 | |
| 2639 | 2633 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2640 | 2634 | %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32;
|
| ... | ... | @@ -2646,21 +2640,14 @@ stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf ) |
| 2646 | 2640 | |
| 2647 | 2641 | stg_asyncDoProczh ( W_ proc, W_ param )
|
| 2648 | 2642 | {
|
| 2649 | - W_ ares;
|
|
| 2650 | 2643 | CInt reqID;
|
| 2651 | 2644 | |
| 2652 | 2645 | #if defined(THREADED_RTS)
|
| 2653 | 2646 | ccall barf("asyncDoProc# on threaded RTS") never returns;
|
| 2654 | 2647 | #else
|
| 2655 | 2648 | |
| 2656 | - /* could probably allocate this on the heap instead */
|
|
| 2657 | - ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
|
|
| 2658 | - "stg_asyncDoProczh");
|
|
| 2659 | 2649 | (reqID) = ccall addDoProcRequest(proc "ptr",param "ptr");
|
| 2660 | - StgAsyncIOResult_reqID(ares) = reqID;
|
|
| 2661 | - StgAsyncIOResult_len(ares) = 0;
|
|
| 2662 | - StgAsyncIOResult_errCode(ares) = 0;
|
|
| 2663 | - StgTSO_block_info(CurrentTSO) = ares;
|
|
| 2650 | + StgTSO_block_info(CurrentTSO) = reqID;
|
|
| 2664 | 2651 | |
| 2665 | 2652 | ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
|
| 2666 | 2653 | %release StgTSO_why_blocked(CurrentTSO) = BlockedOnDoProc::I32;
|
| ... | ... | @@ -30,6 +30,7 @@ |
| 30 | 30 | #include <shfolder.h> /* SHGetFolderPathW */
|
| 31 | 31 | #include "IOManager.h"
|
| 32 | 32 | #include "win32/AsyncWinIO.h"
|
| 33 | +#include "win32/AsyncMIO.h"
|
|
| 33 | 34 | #endif
|
| 34 | 35 | |
| 35 | 36 | #if defined(openbsd_HOST_OS)
|
| ... | ... | @@ -168,6 +169,7 @@ extern char **environ; |
| 168 | 169 | SymI_HasProto(__stdio_common_vswprintf_s) \
|
| 169 | 170 | SymI_HasProto(__stdio_common_vswprintf) \
|
| 170 | 171 | SymI_HasProto(_errno) \
|
| 172 | + SymI_HasProto(rts_EINTR) \
|
|
| 171 | 173 | /* see Note [Symbols for MinGW's printf] */ \
|
| 172 | 174 | SymI_HasProto(_lock_file) \
|
| 173 | 175 | SymI_HasProto(_unlock_file) \
|
| ... | ... | @@ -928,7 +928,7 @@ printThreadBlockage(StgTSO *tso) |
| 928 | 928 | switch (ACQUIRE_LOAD(&tso->why_blocked)) {
|
| 929 | 929 | #if defined(mingw32_HOST_OS)
|
| 930 | 930 | case BlockedOnDoProc:
|
| 931 | - debugBelch("is blocked on proc (request: %u)", tso->block_info.async_result->reqID);
|
|
| 931 | + debugBelch("is blocked on proc (request: %" FMT_Word ")", tso->block_info.async_reqID);
|
|
| 932 | 932 | break;
|
| 933 | 933 | #endif
|
| 934 | 934 | #if !defined(THREADED_RTS)
|
| ... | ... | @@ -322,8 +322,10 @@ EXTERN_INLINE bool LOOKS_LIKE_PTR (const void* p) |
| 322 | 322 | EXTERN_INLINE bool LOOKS_LIKE_INFO_PTR_NOT_NULL (StgWord p);
|
| 323 | 323 | EXTERN_INLINE bool LOOKS_LIKE_INFO_PTR_NOT_NULL (StgWord p)
|
| 324 | 324 | {
|
| 325 | +NO_WARN(-Warray-bounds,
|
|
| 325 | 326 | StgInfoTable *info = INFO_PTR_TO_STRUCT((StgInfoTable *)p);
|
| 326 | 327 | return info->type != INVALID_OBJECT && info->type < N_CLOSURE_TYPES;
|
| 328 | +)
|
|
| 327 | 329 | }
|
| 328 | 330 | |
| 329 | 331 | EXTERN_INLINE bool LOOKS_LIKE_INFO_PTR (StgWord p);
|
| ... | ... | @@ -37,15 +37,6 @@ typedef StgWord64 StgThreadID; |
| 37 | 37 | */
|
| 38 | 38 | typedef unsigned int StgThreadReturnCode;
|
| 39 | 39 | |
| 40 | -#if defined(mingw32_HOST_OS)
|
|
| 41 | -/* results from an async I/O request + its request ID. */
|
|
| 42 | -typedef struct {
|
|
| 43 | - unsigned int reqID;
|
|
| 44 | - int len;
|
|
| 45 | - int errCode;
|
|
| 46 | -} StgAsyncIOResult;
|
|
| 47 | -#endif
|
|
| 48 | - |
|
| 49 | 40 | /* Reason for thread being blocked. See comment above struct StgTso_. */
|
| 50 | 41 | typedef union {
|
| 51 | 42 | StgClosure *closure;
|
| ... | ... | @@ -55,7 +46,7 @@ typedef union { |
| 55 | 46 | struct MessageWakeup_ *wakeup;
|
| 56 | 47 | StgInt fd; /* StgInt instead of int, so that it's the same size as the ptrs */
|
| 57 | 48 | #if defined(mingw32_HOST_OS)
|
| 58 | - StgAsyncIOResult *async_result;
|
|
| 49 | + StgWord async_reqID;
|
|
| 59 | 50 | #endif
|
| 60 | 51 | #if !defined(THREADED_RTS)
|
| 61 | 52 | StgWord target;
|
| ... | ... | @@ -430,8 +430,6 @@ RTS_RET(stg_block_putmvar); |
| 430 | 430 | #if defined(mingw32_HOST_OS)
|
| 431 | 431 | RTS_FUN_DECL(stg_block_async);
|
| 432 | 432 | RTS_RET(stg_block_async);
|
| 433 | -RTS_FUN_DECL(stg_block_async_void);
|
|
| 434 | -RTS_RET(stg_block_async_void);
|
|
| 435 | 433 | #endif
|
| 436 | 434 | RTS_FUN_DECL(stg_block_stmwait);
|
| 437 | 435 | RTS_FUN_DECL(stg_block_throwto);
|
| ... | ... | @@ -8,16 +8,19 @@ |
| 8 | 8 | * For the WINIO manager see base in the GHC.Event modules.
|
| 9 | 9 | */
|
| 10 | 10 | |
| 11 | -#if !defined(THREADED_RTS)
|
|
| 12 | 11 | |
| 13 | 12 | #include "Rts.h"
|
| 13 | +#include <errno.h>
|
|
| 14 | +#include "win32/AsyncMIO.h"
|
|
| 15 | + |
|
| 16 | +#if !defined(THREADED_RTS)
|
|
| 17 | + |
|
| 14 | 18 | #include "RtsUtils.h"
|
| 15 | 19 | #include <windows.h>
|
| 16 | 20 | #include <stdio.h>
|
| 17 | 21 | #include "Schedule.h"
|
| 18 | 22 | #include "Capability.h"
|
| 19 | 23 | #include "IOManagerInternals.h"
|
| 20 | -#include "win32/AsyncMIO.h"
|
|
| 21 | 24 | #include "win32/MIOManager.h"
|
| 22 | 25 | |
| 23 | 26 | /*
|
| ... | ... | @@ -299,14 +302,9 @@ start: |
| 299 | 302 | case BlockedOnRead:
|
| 300 | 303 | case BlockedOnWrite:
|
| 301 | 304 | case BlockedOnDoProc:
|
| 302 | - if (tso->block_info.async_result->reqID == rID) {
|
|
| 303 | - // Found the thread blocked waiting on request;
|
|
| 304 | - // stodgily fill
|
|
| 305 | - // in its result block.
|
|
| 306 | - tso->block_info.async_result->len =
|
|
| 307 | - completedTable[i].len;
|
|
| 308 | - tso->block_info.async_result->errCode =
|
|
| 309 | - completedTable[i].errCode;
|
|
| 305 | + if (tso->block_info.async_reqID == rID) {
|
|
| 306 | + HsInt len = completedTable[i].len;
|
|
| 307 | + HsInt errCode = completedTable[i].errCode;
|
|
| 310 | 308 | |
| 311 | 309 | // Drop the matched TSO from blocked_queue
|
| 312 | 310 | if (prev) {
|
| ... | ... | @@ -322,11 +320,14 @@ start: |
| 322 | 320 | // Terminates the run queue + this inner for-loop.
|
| 323 | 321 | tso->_link = END_TSO_QUEUE;
|
| 324 | 322 | tso->why_blocked = NotBlocked;
|
| 325 | - // save the StgAsyncIOResult in the
|
|
| 326 | - // stg_block_async_info stack frame, because
|
|
| 327 | - // the block_info field will be overwritten by
|
|
| 328 | - // pushOnRunQueue().
|
|
| 329 | - tso->stackobj->sp[1] = (W_)tso->block_info.async_result;
|
|
| 323 | + // For stg_block_async frames (read/write/doProc),
|
|
| 324 | + // write len and errCode directly to the stack.
|
|
| 325 | + // For stg_block_noregs frames (delay), nothing
|
|
| 326 | + // to write.
|
|
| 327 | + if (tso->stackobj->sp[0] == (W_)&stg_block_async_info) {
|
|
| 328 | + tso->stackobj->sp[1] = (W_)len;
|
|
| 329 | + tso->stackobj->sp[2] = (W_)errCode;
|
|
| 330 | + }
|
|
| 330 | 331 | pushOnRunQueue(&MainCapability, tso);
|
| 331 | 332 | break;
|
| 332 | 333 | }
|
| ... | ... | @@ -389,3 +390,8 @@ resetAbandonRequestWait( void ) |
| 389 | 390 | }
|
| 390 | 391 | |
| 391 | 392 | #endif /* !defined(THREADED_RTS) */
|
| 393 | + |
|
| 394 | +HsInt rts_EINTR(void)
|
|
| 395 | +{
|
|
| 396 | + return EINTR;
|
|
| 397 | +} |
| ... | ... | @@ -27,3 +27,4 @@ extern int awaitRequests(bool wait); |
| 27 | 27 | |
| 28 | 28 | extern void abandonRequestWait(void);
|
| 29 | 29 | extern void resetAbandonRequestWait(void);
|
| 30 | +extern HsInt rts_EINTR(void); |
| 1 | +{-# OPTIONS_GHC -O -fno-full-laziness #-}
|
|
| 2 | + |
|
| 3 | +import Control.Concurrent (threadDelay, myThreadId, forkIO, killThread)
|
|
| 4 | +import System.IO.Unsafe (unsafePerformIO)
|
|
| 5 | +import Control.Exception
|
|
| 6 | +import GHC.Exts
|
|
| 7 | + |
|
| 8 | +compute :: Int
|
|
| 9 | +compute = noinline unsafePerformIO $ do
|
|
| 10 | + mainThreadID <- myThreadId
|
|
| 11 | + _ <- forkIO $ do
|
|
| 12 | + threadDelay 500000
|
|
| 13 | + killThread mainThreadID
|
|
| 14 | + threadDelay 1000000
|
|
| 15 | + return 0
|
|
| 16 | + |
|
| 17 | +main = do
|
|
| 18 | + catch (print compute) (\(e :: AsyncException) -> print $ "1:" ++ show e)
|
|
| 19 | + catch (print compute) (\(e :: AsyncException) -> print $ "2:" ++ show e)
|
|
| 20 | + print "done" |
| 1 | +"1:thread killed"
|
|
| 2 | +0
|
|
| 3 | +"done" |
| 1 | +-- Test that re-evaluating an AP_STACK from an interrupted async I/O call
|
|
| 2 | +-- does not crash. On Windows non-threaded RTS, re-entry returns EINTR
|
|
| 3 | +-- which readRawBufferPtr converts to IOException Interrupted. On the
|
|
| 4 | +-- threaded RTS (any platform), the blocking read is re-attempted and
|
|
| 5 | +-- succeeds because we write a byte to the pipe between evaluations.
|
|
| 6 | +--
|
|
| 7 | +-- Before the fix for #26341, re-evaluation on Windows would crash or read
|
|
| 8 | +-- uninitialized memory from a freed StgAsyncIOResult.
|
|
| 9 | +{-# OPTIONS_GHC -O -fno-full-laziness #-}
|
|
| 10 | + |
|
| 11 | +import Control.Concurrent (threadDelay, myThreadId, forkIO, killThread, rtsSupportsBoundThreads)
|
|
| 12 | +import Control.Exception
|
|
| 13 | +import Data.IORef
|
|
| 14 | +import Foreign
|
|
| 15 | +import Foreign.C
|
|
| 16 | +import GHC.Exts
|
|
| 17 | +import GHC.IO.Exception (IOErrorType(..), IOException(..))
|
|
| 18 | +import GHC.IO.FD (FD(..), readRawBufferPtr, writeRawBufferPtr)
|
|
| 19 | +import System.Info (os)
|
|
| 20 | +import System.IO.Unsafe (unsafePerformIO)
|
|
| 21 | +import System.Process (createPipeFd)
|
|
| 22 | + |
|
| 23 | +-- Store the write fd so main can feed data into the pipe between
|
|
| 24 | +-- evaluations. On Unix this unblocks the re-entered read; on Windows
|
|
| 25 | +-- stg_block_async returns EINTR regardless.
|
|
| 26 | +{-# NOINLINE writeFdRef #-}
|
|
| 27 | +writeFdRef :: IORef CInt
|
|
| 28 | +writeFdRef = unsafePerformIO $ newIORef (-1)
|
|
| 29 | + |
|
| 30 | +-- | A thunk whose unsafePerformIO blocks on a pipe read. A forked
|
|
| 31 | +-- thread kills the main thread after 200ms, which creates an AP_STACK.
|
|
| 32 | +{-# NOINLINE blockedRead #-}
|
|
| 33 | +blockedRead :: ()
|
|
| 34 | +blockedRead = noinline unsafePerformIO $ do
|
|
| 35 | + (readFd, writeFd) <- createPipeFd
|
|
| 36 | + writeIORef writeFdRef writeFd
|
|
| 37 | + buf <- mallocBytes 1
|
|
| 38 | + mainTid <- myThreadId
|
|
| 39 | + _ <- forkIO $ do
|
|
| 40 | + threadDelay 200000 -- 200ms
|
|
| 41 | + killThread mainTid
|
|
| 42 | + -- readRawBufferPtr dispatches to asyncReadRawBufferPtr on Windows
|
|
| 43 | + -- non-threaded RTS; on Unix it uses threadWaitRead + read().
|
|
| 44 | + _ <- readRawBufferPtr "blockedRead" (FD readFd 0) buf 0 1
|
|
| 45 | + return ()
|
|
| 46 | + |
|
| 47 | +main :: IO ()
|
|
| 48 | +main = do
|
|
| 49 | + -- First evaluation: the thunk blocks on the pipe read, gets killed.
|
|
| 50 | + catch (evaluate blockedRead)
|
|
| 51 | + (\(e :: AsyncException) -> putStrLn $ "caught: " ++ show e)
|
|
| 52 | + |
|
| 53 | + -- Write a byte so the re-entered read can complete on Unix.
|
|
| 54 | + wfd <- readIORef writeFdRef
|
|
| 55 | + buf <- mallocBytes 1
|
|
| 56 | + poke buf 0
|
|
| 57 | + _ <- writeRawBufferPtr "unblock" (FD wfd 0) buf 0 1
|
|
| 58 | + |
|
| 59 | + -- Second evaluation: AP_STACK re-enters.
|
|
| 60 | + -- Non-threaded Windows: asyncRead returns (-1, EINTR) → IOException
|
|
| 61 | + -- Threaded / Unix: read succeeds → returns normally
|
|
| 62 | + let expectEINTR = os == "mingw32" && not rtsSupportsBoundThreads
|
|
| 63 | + result <- try (evaluate blockedRead)
|
|
| 64 | + case result of
|
|
| 65 | + Left e
|
|
| 66 | + | Just ioe <- fromException e
|
|
| 67 | + , ioe_type (ioe :: IOException) == Interrupted
|
|
| 68 | + -> putStrLn "re-evaluated ok"
|
|
| 69 | + | otherwise
|
|
| 70 | + -> putStrLn $ "unexpected: " ++ show e
|
|
| 71 | + Right ()
|
|
| 72 | + | expectEINTR -> putStrLn "unexpected: expected EINTR"
|
|
| 73 | + | otherwise -> putStrLn "re-evaluated ok"
|
|
| 74 | + |
|
| 75 | + putStrLn "done" |
| 1 | +caught: thread killed
|
|
| 2 | +re-evaluated ok
|
|
| 3 | +done |
| 1 | +-- Stress test for #26341: repeatedly interrupt async-blocked threads and
|
|
| 2 | +-- re-enter their AP_STACKs. Before the fix, re-entering a thunk whose
|
|
| 3 | +-- unsafePerformIO was blocked on an async I/O call (Windows non-threaded
|
|
| 4 | +-- RTS) would read uninitialized memory or free a dangling pointer,
|
|
| 5 | +-- because stg_block_async reserved a stack slot for a heap-allocated
|
|
| 6 | +-- StgAsyncIOResult that became invalid after an async exception.
|
|
| 7 | +--
|
|
| 8 | +-- This test spawns many concurrent workers, each of which:
|
|
| 9 | +-- 1. Creates a pipe.
|
|
| 10 | +-- 2. Builds a thunk that blocks on a pipe read via unsafePerformIO.
|
|
| 11 | +-- 3. Evaluates the thunk and kills it with an async exception.
|
|
| 12 | +-- 4. Re-evaluates the thunk (AP_STACK re-entry).
|
|
| 13 | +-- 5. Repeats many times.
|
|
| 14 | +--
|
|
| 15 | +-- On threaded RTS / Unix the re-entered read succeeds (we write a byte
|
|
| 16 | +-- first). On Windows non-threaded RTS the re-entered async call returns
|
|
| 17 | +-- EINTR. Both paths exercise the fixed stack-frame layout.
|
|
| 18 | +{-# OPTIONS_GHC -O -fno-full-laziness #-}
|
|
| 19 | + |
|
| 20 | +import Control.Concurrent
|
|
| 21 | +import Control.Exception
|
|
| 22 | +import Foreign
|
|
| 23 | +import Foreign.C
|
|
| 24 | +import GHC.Exts
|
|
| 25 | +import GHC.IO.Exception (IOErrorType(..), IOException(..))
|
|
| 26 | +import GHC.IO.FD (FD(..), readRawBufferPtr, writeRawBufferPtr)
|
|
| 27 | +import System.IO (hFlush, stdout)
|
|
| 28 | +import System.IO.Unsafe (unsafePerformIO)
|
|
| 29 | +import System.Posix.Internals (c_close)
|
|
| 30 | +import System.Process (createPipeFd)
|
|
| 31 | + |
|
| 32 | +iterations :: Int
|
|
| 33 | +iterations = 200
|
|
| 34 | + |
|
| 35 | +workers :: Int
|
|
| 36 | +workers = 4
|
|
| 37 | + |
|
| 38 | +-- Each worker independently performs `iterations` rounds of:
|
|
| 39 | +-- block on pipe read → interrupt → re-evaluate the AP_STACK.
|
|
| 40 | +worker :: Int -> MVar () -> IO ()
|
|
| 41 | +worker wid done = do
|
|
| 42 | + buf <- mallocBytes 1
|
|
| 43 | + let go 0 = return ()
|
|
| 44 | + go n = do
|
|
| 45 | + (readFd, writeFd) <- createPipeFd
|
|
| 46 | + |
|
| 47 | + -- Build a fresh thunk each iteration so we get a new AP_STACK.
|
|
| 48 | + let {-# NOINLINE blockedThunk #-}
|
|
| 49 | + blockedThunk :: ()
|
|
| 50 | + blockedThunk = noinline unsafePerformIO $ do
|
|
| 51 | + tid <- myThreadId
|
|
| 52 | + _ <- forkIO $ do
|
|
| 53 | + threadDelay 1000 -- 1ms: tight window
|
|
| 54 | + killThread tid
|
|
| 55 | + _ <- readRawBufferPtr "stress" (FD readFd 0) buf 0 1
|
|
| 56 | + return ()
|
|
| 57 | + |
|
| 58 | + -- First evaluation: block and get killed.
|
|
| 59 | + catch (evaluate blockedThunk)
|
|
| 60 | + (\(_ :: SomeException) -> return ())
|
|
| 61 | + |
|
| 62 | + -- Write a byte so the re-entered read can complete on
|
|
| 63 | + -- threaded RTS / Unix.
|
|
| 64 | + poke buf 0
|
|
| 65 | + _ <- writeRawBufferPtr "unblock" (FD writeFd 0) buf 0 1
|
|
| 66 | + |
|
| 67 | + -- Second evaluation: AP_STACK re-entry.
|
|
| 68 | + result <- try (evaluate blockedThunk)
|
|
| 69 | + case result of
|
|
| 70 | + Left e
|
|
| 71 | + | Just ioe <- fromException e
|
|
| 72 | + , ioe_type (ioe :: IOException) == Interrupted
|
|
| 73 | + -> return () -- expected on Windows non-threaded
|
|
| 74 | + | otherwise
|
|
| 75 | + -> throwIO (userError $
|
|
| 76 | + "worker " ++ show wid ++ " iteration " ++ show n ++
|
|
| 77 | + ": unexpected exception: " ++ show e)
|
|
| 78 | + Right () -> return () -- expected on threaded / Unix
|
|
| 79 | + |
|
| 80 | + -- Close the pipe fds.
|
|
| 81 | + _ <- c_close readFd
|
|
| 82 | + _ <- c_close writeFd
|
|
| 83 | + |
|
| 84 | + go (n - 1)
|
|
| 85 | + |
|
| 86 | + go iterations
|
|
| 87 | + putMVar done ()
|
|
| 88 | + |
|
| 89 | +main :: IO ()
|
|
| 90 | +main = do
|
|
| 91 | + dones <- mapM (\wid -> do
|
|
| 92 | + done <- newEmptyMVar
|
|
| 93 | + _ <- forkIO (worker wid done)
|
|
| 94 | + return done
|
|
| 95 | + ) [1..workers]
|
|
| 96 | + |
|
| 97 | + -- Wait for all workers to finish.
|
|
| 98 | + mapM_ takeMVar dones
|
|
| 99 | + |
|
| 100 | + putStrLn "stress test passed"
|
|
| 101 | + hFlush stdout |
| 1 | +stress test passed |
| ... | ... | @@ -298,3 +298,19 @@ test('hs_try_putmvar003', |
| 298 | 298 | |
| 299 | 299 | # Check forkIO exception determinism under optimization
|
| 300 | 300 | test('T13330', normal, compile_and_run, ['-O'])
|
| 301 | + |
|
| 302 | +test('T26341', normal, compile_and_run, [''])
|
|
| 303 | + |
|
| 304 | +# Test EINTR for async I/O interrupted by an exception (#26341)
|
|
| 305 | +test('T26341a'
|
|
| 306 | + # test uses pipe operations which are not supported by the JS/wasm backends
|
|
| 307 | + , when(arch('wasm32') or arch('javascript'), skip)
|
|
| 308 | + , compile_and_run, ['-package process'])
|
|
| 309 | + |
|
| 310 | +# Stress test: many threads repeatedly interrupt and re-enter async-blocked
|
|
| 311 | +# thunks (#26341). Before the fix, this would crash due to dangling
|
|
| 312 | +# StgAsyncIOResult pointers on the stack.
|
|
| 313 | +test('T26341b'
|
|
| 314 | + # test uses pipe operations which are not supported by the JS/wasm backends
|
|
| 315 | + , when(arch('wasm32') or arch('javascript'), skip)
|
|
| 316 | + , compile_and_run, ['-package process']) |
| ... | ... | @@ -646,12 +646,7 @@ wanteds os = concat |
| 646 | 646 | -- Note that this conditional part only affects the C headers.
|
| 647 | 647 | -- That's important, as it means we get the same PlatformConstants
|
| 648 | 648 | -- type on all platforms.
|
| 649 | - ,if os == Just Windows
|
|
| 650 | - then concat [structSize C "StgAsyncIOResult"
|
|
| 651 | - ,structField C "StgAsyncIOResult" "reqID"
|
|
| 652 | - ,structField C "StgAsyncIOResult" "len"
|
|
| 653 | - ,structField C "StgAsyncIOResult" "errCode"]
|
|
| 654 | - else []
|
|
| 649 | + ,[]
|
|
| 655 | 650 | |
| 656 | 651 | -- struct HsIface
|
| 657 | 652 | ,structField C "HsIface" "processRemoteCompletion_closure"
|
| ... | ... | @@ -814,9 +809,6 @@ getWanted verbose os tmpdir gccProgram gccFlags nmProgram mobjdumpProgram |
| 814 | 809 | "",
|
| 815 | 810 | "#define PROFILING",
|
| 816 | 811 | "#define THREADED_RTS",
|
| 817 | - -- We need to define this if we want StgAsyncIOResult
|
|
| 818 | - -- struct to be present after CPP
|
|
| 819 | - --
|
|
| 820 | 812 | -- FIXME: rts/PosixSource.h should include ghcplatform.h
|
| 821 | 813 | -- which should set this. There is a mismatch host/target
|
| 822 | 814 | -- again...
|