Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC

Commits:

17 changed files:

Changes:

  • rts/HeapStackCheck.cmm
    ... ... @@ -703,38 +703,24 @@ stg_block_throwto (P_ tso, P_ exception)
    703 703
     }
    
    704 704
     
    
    705 705
     #if defined(mingw32_HOST_OS)
    
    706
    -INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ ares )
    
    706
    +INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ len, W_ errCode )
    
    707 707
         return ()
    
    708 708
     {
    
    709
    -    W_ len, errC;
    
    710
    -
    
    711
    -    len = TO_W_(StgAsyncIOResult_len(ares));
    
    712
    -    errC = TO_W_(StgAsyncIOResult_errCode(ares));
    
    713
    -    ccall free(ares "ptr");
    
    714
    -    return (len, errC);
    
    709
    +    return (len, errCode);
    
    715 710
     }
    
    716 711
     
    
    717 712
     stg_block_async
    
    718 713
     {
    
    719
    -    Sp_adj(-2);
    
    720
    -    Sp(0) = stg_block_async_info;
    
    721
    -    BLOCK_GENERIC;
    
    722
    -}
    
    714
    +    W_ eintr;
    
    715
    +    (eintr) = ccall rts_EINTR();
    
    723 716
     
    
    724
    -/* Used by threadDelay implementation; it would be desirable to get rid of
    
    725
    - * this free()'ing void return continuation.
    
    726
    - */
    
    727
    -INFO_TABLE_RET ( stg_block_async_void, RET_SMALL, W_ info_ptr, W_ ares )
    
    728
    -    return ()
    
    729
    -{
    
    730
    -    ccall free(ares "ptr");
    
    731
    -    return ();
    
    732
    -}
    
    733
    -
    
    734
    -stg_block_async_void
    
    735
    -{
    
    736
    -    Sp_adj(-2);
    
    737
    -    Sp(0) = stg_block_async_void_info;
    
    717
    +    // Fill the stack frame with values that indicate that the operation
    
    718
    +    // has been interrupted. The IO manager will overwrite these with the
    
    719
    +    // actual results if the async operation completes.
    
    720
    +    Sp_adj(-3);
    
    721
    +    Sp(0) = stg_block_async_info;
    
    722
    +    Sp(1) = -1;                    // len:     -1 indicates error
    
    723
    +    Sp(2) = eintr;                 // errCode: interrupted
    
    738 724
         BLOCK_GENERIC;
    
    739 725
     }
    
    740 726
     
    

  • rts/IOManager.c
    ... ... @@ -633,10 +633,8 @@ void scavengeTSOIOManager(StgTSO *tso)
    633 633
     #endif
    
    634 634
     
    
    635 635
                 /* case IO_MANAGER_WIN32_LEGACY:
    
    636
    -             * BlockedOn{Read,Write,DoProc} uses block_info.async_result
    
    637
    -             * The StgAsyncIOResult async_result is allocated on the C heap.
    
    638
    -             * It'd probably be better if it used the GC heap. If it did we'd
    
    639
    -             * scavenge it here.
    
    636
    +             * BlockedOn{Read,Write,DoProc} uses block_info.async_reqID
    
    637
    +             * which is a plain integer, so nothing to scavenge.
    
    640 638
                  */
    
    641 639
     
    
    642 640
             default:
    
    ... ... @@ -846,7 +844,7 @@ void syncIOCancel(Capability *cap, StgTSO *tso)
    846 844
             case IO_MANAGER_WIN32_LEGACY:
    
    847 845
                 removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
    
    848 846
                                              &cap->iomgr->blocked_queue_tl, tso);
    
    849
    -            abandonWorkRequest(tso->block_info.async_result->reqID);
    
    847
    +            abandonWorkRequest(tso->block_info.async_reqID);
    
    850 848
                 break;
    
    851 849
     #endif
    
    852 850
             default:
    
    ... ... @@ -885,12 +883,7 @@ bool syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay)
    885 883
                  * would make the primops more consistent.
    
    886 884
                  */
    
    887 885
             {
    
    888
    -            StgAsyncIOResult *ares = stgMallocBytes(sizeof(StgAsyncIOResult),
    
    889
    -                                                    "syncDelay");
    
    890
    -            ares->reqID   = addDelayRequest(us_delay);
    
    891
    -            ares->len     = 0;
    
    892
    -            ares->errCode = 0;
    
    893
    -            tso->block_info.async_result = ares;
    
    886
    +            tso->block_info.async_reqID = addDelayRequest(us_delay);
    
    894 887
     
    
    895 888
                 /* Having all async-blocked threads reside on the blocked_queue
    
    896 889
                  * simplifies matters, so set the status to OnDoProc and put the
    

  • rts/PrimOps.cmm
    ... ... @@ -2255,18 +2255,7 @@ stg_delayzh ( W_ us_delay )
    2255 2255
         (ok) = ccall syncDelay(MyCapability() "ptr", CurrentTSO "ptr", us_delay);
    
    2256 2256
     
    
    2257 2257
         if (ok != 0::CBool) (likely: True) {
    
    2258
    -        /* Annoyingly, we cannot be consistent with how we wait and resume the
    
    2259
    -         * blocked thread. The reason is that the win32 legacy I/O manager
    
    2260
    -         * allocates a StgAsyncIOResult struct on the C heap which has to be
    
    2261
    -         * freed when the thread resumes. It's a bit awkward to arrange to
    
    2262
    -         * allocate it on the GC heap instead, so that's how it is for now.
    
    2263
    -         * Sigh.
    
    2264
    -         */
    
    2265
    -#if defined(mingw32_HOST_OS)
    
    2266
    -        jump stg_block_async_void();
    
    2267
    -#else
    
    2268 2258
             jump stg_block_noregs();
    
    2269
    -#endif
    
    2270 2259
         } else {
    
    2271 2260
             jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
    
    2272 2261
         }
    
    ... ... @@ -2276,21 +2265,14 @@ stg_delayzh ( W_ us_delay )
    2276 2265
     #if defined(mingw32_HOST_OS)
    
    2277 2266
     stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf )
    
    2278 2267
     {
    
    2279
    -    W_ ares;
    
    2280 2268
         CInt reqID;
    
    2281 2269
     
    
    2282 2270
     #if defined(THREADED_RTS)
    
    2283 2271
         ccall sbarf("asyncRead# on threaded RTS") never returns;
    
    2284 2272
     #else
    
    2285 2273
     
    
    2286
    -    /* could probably allocate this on the heap instead */
    
    2287
    -    ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
    
    2288
    -                                        "stg_asyncReadzh");
    
    2289 2274
         (reqID) = ccall addIORequest(fd, 0/*FALSE*/,is_sock,len,buf "ptr");
    
    2290
    -    StgAsyncIOResult_reqID(ares)   = reqID;
    
    2291
    -    StgAsyncIOResult_len(ares)     = 0;
    
    2292
    -    StgAsyncIOResult_errCode(ares) = 0;
    
    2293
    -    StgTSO_block_info(CurrentTSO)  = ares;
    
    2275
    +    StgTSO_block_info(CurrentTSO) = reqID;
    
    2294 2276
     
    
    2295 2277
         ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
    
    2296 2278
         %release StgTSO_why_blocked(CurrentTSO) = BlockedOnRead::I32;
    
    ... ... @@ -2302,21 +2284,14 @@ stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf )
    2302 2284
     
    
    2303 2285
     stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf )
    
    2304 2286
     {
    
    2305
    -    W_ ares;
    
    2306 2287
         CInt reqID;
    
    2307 2288
     
    
    2308 2289
     #if defined(THREADED_RTS)
    
    2309 2290
         ccall sbarf("asyncWrite# on threaded RTS") never returns;
    
    2310 2291
     #else
    
    2311 2292
     
    
    2312
    -    ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
    
    2313
    -                                        "stg_asyncWritezh");
    
    2314 2293
         (reqID) = ccall addIORequest(fd, 1/*TRUE*/,is_sock,len,buf "ptr");
    
    2315
    -
    
    2316
    -    StgAsyncIOResult_reqID(ares)   = reqID;
    
    2317
    -    StgAsyncIOResult_len(ares)     = 0;
    
    2318
    -    StgAsyncIOResult_errCode(ares) = 0;
    
    2319
    -    StgTSO_block_info(CurrentTSO)  = ares;
    
    2294
    +    StgTSO_block_info(CurrentTSO) = reqID;
    
    2320 2295
     
    
    2321 2296
         ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
    
    2322 2297
         %release StgTSO_why_blocked(CurrentTSO) = BlockedOnWrite::I32;
    
    ... ... @@ -2328,21 +2303,14 @@ stg_asyncWritezh ( W_ fd, W_ is_sock, W_ len, W_ buf )
    2328 2303
     
    
    2329 2304
     stg_asyncDoProczh ( W_ proc, W_ param )
    
    2330 2305
     {
    
    2331
    -    W_ ares;
    
    2332 2306
         CInt reqID;
    
    2333 2307
     
    
    2334 2308
     #if defined(THREADED_RTS)
    
    2335 2309
         ccall sbarf("asyncDoProc# on threaded RTS") never returns;
    
    2336 2310
     #else
    
    2337 2311
     
    
    2338
    -    /* could probably allocate this on the heap instead */
    
    2339
    -    ("ptr" ares) = ccall stgMallocBytes(SIZEOF_StgAsyncIOResult,
    
    2340
    -                                        "stg_asyncDoProczh");
    
    2341 2312
         (reqID) = ccall addDoProcRequest(proc "ptr",param "ptr");
    
    2342
    -    StgAsyncIOResult_reqID(ares)   = reqID;
    
    2343
    -    StgAsyncIOResult_len(ares)     = 0;
    
    2344
    -    StgAsyncIOResult_errCode(ares) = 0;
    
    2345
    -    StgTSO_block_info(CurrentTSO) = ares;
    
    2313
    +    StgTSO_block_info(CurrentTSO) = reqID;
    
    2346 2314
     
    
    2347 2315
         ASSERT(StgTSO_why_blocked(CurrentTSO) == NotBlocked::I32);
    
    2348 2316
         %release StgTSO_why_blocked(CurrentTSO) = BlockedOnDoProc::I32;
    

  • rts/RtsSymbols.c
    ... ... @@ -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)                        \
    

  • rts/Threads.c
    ... ... @@ -926,7 +926,7 @@ printThreadBlockage(StgTSO *tso)
    926 926
       switch (ACQUIRE_LOAD(&tso->why_blocked)) {
    
    927 927
     #if defined(mingw32_HOST_OS)
    
    928 928
         case BlockedOnDoProc:
    
    929
    -    debugBelch("is blocked on proc (request: %u)", tso->block_info.async_result->reqID);
    
    929
    +    debugBelch("is blocked on proc (request: %" FMT_Word ")", tso->block_info.async_reqID);
    
    930 930
         break;
    
    931 931
     #endif
    
    932 932
     #if !defined(THREADED_RTS)
    

  • rts/include/rts/storage/TSO.h
    ... ... @@ -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;
    
    ... ... @@ -57,7 +48,7 @@ typedef union {
    57 48
       StgAsyncIOOp *aiop;
    
    58 49
       StgTimeoutQueue *timeout;
    
    59 50
     #if defined(mingw32_HOST_OS)
    
    60
    -  StgAsyncIOResult *async_result;
    
    51
    +  StgWord async_reqID;
    
    61 52
     #endif
    
    62 53
     #if !defined(THREADED_RTS)
    
    63 54
       StgWord target;
    

  • rts/include/stg/MiscClosures.h
    ... ... @@ -379,8 +379,6 @@ RTS_RET(stg_block_putmvar);
    379 379
     #if defined(mingw32_HOST_OS)
    
    380 380
     RTS_FUN_DECL(stg_block_async);
    
    381 381
     RTS_RET(stg_block_async);
    
    382
    -RTS_FUN_DECL(stg_block_async_void);
    
    383
    -RTS_RET(stg_block_async_void);
    
    384 382
     #endif
    
    385 383
     RTS_FUN_DECL(stg_block_stmwait);
    
    386 384
     RTS_FUN_DECL(stg_block_throwto);
    

  • rts/win32/AsyncMIO.c
    ... ... @@ -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
    +}

  • rts/win32/AsyncMIO.h
    ... ... @@ -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);

  • testsuite/tests/concurrent/should_run/T26341.hs
    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"

  • testsuite/tests/concurrent/should_run/T26341.stdout
    1
    +"1:thread killed"
    
    2
    +0
    
    3
    +"done"

  • testsuite/tests/concurrent/should_run/T26341a.hs
    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"

  • testsuite/tests/concurrent/should_run/T26341a.stdout
    1
    +caught: thread killed
    
    2
    +re-evaluated ok
    
    3
    +done

  • testsuite/tests/concurrent/should_run/T26341b.hs
    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

  • testsuite/tests/concurrent/should_run/T26341b.stdout
    1
    +stress test passed

  • testsuite/tests/concurrent/should_run/all.T
    ... ... @@ -309,3 +309,19 @@ test('hs_try_putmvar003',
    309 309
     
    
    310 310
     # Check forkIO exception determinism under optimization
    
    311 311
     test('T13330', normal, compile_and_run, ['-O'])
    
    312
    +
    
    313
    +test('T26341', normal, compile_and_run, [''])
    
    314
    +
    
    315
    +# Test EINTR for async I/O interrupted by an exception (#26341)
    
    316
    +test('T26341a'
    
    317
    +    # test uses pipe operations which are not supported by the JS/wasm backends
    
    318
    +    , when(arch('wasm32') or arch('javascript'), skip)
    
    319
    +    , compile_and_run, ['-package process'])
    
    320
    +
    
    321
    +# Stress test: many threads repeatedly interrupt and re-enter async-blocked
    
    322
    +# thunks (#26341). Before the fix, this would crash due to dangling
    
    323
    +# StgAsyncIOResult pointers on the stack.
    
    324
    +test('T26341b'
    
    325
    +    # test uses pipe operations which are not supported by the JS/wasm backends
    
    326
    +    , when(arch('wasm32') or arch('javascript'), skip)
    
    327
    +    , compile_and_run, ['-package process'])

  • utils/deriveConstants/Main.hs
    ... ... @@ -627,12 +627,7 @@ wanteds os = concat
    627 627
                -- Note that this conditional part only affects the C headers.
    
    628 628
                -- That's important, as it means we get the same PlatformConstants
    
    629 629
                -- type on all platforms.
    
    630
    -          ,if os == Just Windows
    
    631
    -           then concat [structSize  C "StgAsyncIOResult"
    
    632
    -                       ,structField C "StgAsyncIOResult" "reqID"
    
    633
    -                       ,structField C "StgAsyncIOResult" "len"
    
    634
    -                       ,structField C "StgAsyncIOResult" "errCode"]
    
    635
    -           else []
    
    630
    +          ,[]
    
    636 631
     
    
    637 632
                -- struct HsIface
    
    638 633
               ,structField C "HsIface" "Z0T_closure"
    
    ... ... @@ -759,9 +754,6 @@ getWanted verbose os tmpdir gccProgram gccFlags nmProgram mobjdumpProgram
    759 754
                          "",
    
    760 755
                          "#define PROFILING",
    
    761 756
                          "#define THREADED_RTS",
    
    762
    -                     -- We need to define this if we want StgAsyncIOResult
    
    763
    -                     -- struct to be present after CPP
    
    764
    -                     --
    
    765 757
                          -- FIXME: rts/PosixSource.h should include ghcplatform.h
    
    766 758
                          -- which should set this. There is a mismatch host/target
    
    767 759
                          -- again...