Zubin pushed to branch wip/27113-retry at Glasgow Haskell Compiler / GHC
Commits:
-
daa65163
by Zubin Duggal at 2026-05-19T18:46:15+05:30
11 changed files:
- rts/RaiseAsync.c
- rts/Schedule.c
- rts/Schedule.h
- rts/Task.c
- rts/Task.h
- rts/Timer.c
- testsuite/tests/concurrent/should_run/T27113.hs
- testsuite/tests/concurrent/should_run/T27113_c.c
- + testsuite/tests/concurrent/should_run/T27113b.hs
- + testsuite/tests/concurrent/should_run/T27113c.hs
- testsuite/tests/concurrent/should_run/all.T
Changes:
| ... | ... | @@ -36,6 +36,11 @@ static void throwToSendMsg (Capability *cap USED_IF_THREADS, |
| 36 | 36 | Capability *target_cap USED_IF_THREADS,
|
| 37 | 37 | MessageThrowTo *msg USED_IF_THREADS);
|
| 38 | 38 | |
| 39 | +#if defined(THREADED_RTS)
|
|
| 40 | +static void clear_interrupt_pending_if_no_live_throw (Capability *cap,
|
|
| 41 | + StgTSO *target);
|
|
| 42 | +#endif
|
|
| 43 | + |
|
| 39 | 44 | /* -----------------------------------------------------------------------------
|
| 40 | 45 | throwToSingleThreaded
|
| 41 | 46 | |
| ... | ... | @@ -338,7 +343,13 @@ check_target: |
| 338 | 343 | }
|
| 339 | 344 | |
| 340 | 345 | // nobody else can wake up this TSO after we claim the message
|
| 346 | +#if defined(THREADED_RTS)
|
|
| 347 | + StgTSO *m_target = m->target;
|
|
| 348 | +#endif
|
|
| 341 | 349 | doneWithMsgThrowTo(cap, m);
|
| 350 | +#if defined(THREADED_RTS)
|
|
| 351 | + clear_interrupt_pending_if_no_live_throw(cap, m_target);
|
|
| 352 | +#endif
|
|
| 342 | 353 | |
| 343 | 354 | raiseAsync(cap, target, msg->exception, false, NULL);
|
| 344 | 355 | return THROWTO_SUCCESS;
|
| ... | ... | @@ -433,11 +444,12 @@ check_target: |
| 433 | 444 | #if defined(THREADED_RTS)
|
| 434 | 445 | {
|
| 435 | 446 | Task *task = NULL;
|
| 436 | - // walk suspended_ccalls to find the correct worker thread
|
|
| 447 | + InCall *target_incall = NULL;
|
|
| 437 | 448 | InCall *incall;
|
| 438 | 449 | for (incall = cap->suspended_ccalls; incall != NULL; incall = incall->next) {
|
| 439 | 450 | if (incall->suspended_tso == target) {
|
| 440 | 451 | task = incall->task;
|
| 452 | + target_incall = incall;
|
|
| 441 | 453 | break;
|
| 442 | 454 | }
|
| 443 | 455 | }
|
| ... | ... | @@ -446,6 +458,7 @@ check_target: |
| 446 | 458 | if (!((target->flags & TSO_BLOCKEX) &&
|
| 447 | 459 | ((target->flags & TSO_INTERRUPTIBLE) == 0))) {
|
| 448 | 460 | interruptWorkerTask(task);
|
| 461 | + RELEASE_STORE(&target_incall->interrupt_pending, 1);
|
|
| 449 | 462 | }
|
| 450 | 463 | return THROWTO_BLOCKED;
|
| 451 | 464 | } else {
|
| ... | ... | @@ -520,8 +533,14 @@ blockedThrowTo (Capability *cap, StgTSO *target, MessageThrowTo *msg) |
| 520 | 533 | ASSERT(target->cap == cap);
|
| 521 | 534 | |
| 522 | 535 | dirty_TSO(cap,target); // we will modify the blocked_exceptions queue
|
| 536 | +#if defined(THREADED_RTS)
|
|
| 537 | + ACQUIRE_LOCK(&cap->lock);
|
|
| 538 | +#endif
|
|
| 523 | 539 | msg->link = target->blocked_exceptions;
|
| 524 | - target->blocked_exceptions = msg;
|
|
| 540 | + RELEASE_STORE(&target->blocked_exceptions, msg);
|
|
| 541 | +#if defined(THREADED_RTS)
|
|
| 542 | + RELEASE_LOCK(&cap->lock);
|
|
| 543 | +#endif
|
|
| 525 | 544 | }
|
| 526 | 545 | |
| 527 | 546 | /* -----------------------------------------------------------------------------
|
| ... | ... | @@ -664,6 +683,46 @@ removeFromMVarBlockedQueue (StgTSO *tso) |
| 664 | 683 | tso->_link = END_TSO_QUEUE;
|
| 665 | 684 | }
|
| 666 | 685 | |
| 686 | +#if defined(THREADED_RTS)
|
|
| 687 | +static void
|
|
| 688 | +clear_interrupt_pending_if_no_live_throw (Capability *cap, StgTSO *target)
|
|
| 689 | +{
|
|
| 690 | + if (target == NULL) return;
|
|
| 691 | + if (ACQUIRE_LOAD(&target->why_blocked) != BlockedOnCCall_Interruptible)
|
|
| 692 | + return;
|
|
| 693 | + |
|
| 694 | + Capability *target_cap = ACQUIRE_LOAD(&target->cap);
|
|
| 695 | + ACQUIRE_LOCK(&target_cap->lock);
|
|
| 696 | + |
|
| 697 | + // Find the InCall on target_cap. If target has already resumed (or
|
|
| 698 | + // wasn't really suspended on this cap), do nothing.
|
|
| 699 | + InCall *found = NULL;
|
|
| 700 | + for (InCall *ic = target_cap->suspended_ccalls; ic != NULL; ic = ic->next) {
|
|
| 701 | + if (ic->suspended_tso == target) { found = ic; break; }
|
|
| 702 | + }
|
|
| 703 | + if (found == NULL) {
|
|
| 704 | + RELEASE_LOCK(&target_cap->lock);
|
|
| 705 | + return;
|
|
| 706 | + }
|
|
| 707 | + |
|
| 708 | + bool live = false;
|
|
| 709 | + for (MessageThrowTo *t = ACQUIRE_LOAD(&target->blocked_exceptions);
|
|
| 710 | + t != END_BLOCKED_EXCEPTIONS_QUEUE;
|
|
| 711 | + t = (MessageThrowTo*)t->link) {
|
|
| 712 | + if (RELAXED_LOAD((StgWord*)&t->header.info)
|
|
| 713 | + != (StgWord)&stg_MSG_NULL_info) {
|
|
| 714 | + live = true; break;
|
|
| 715 | + }
|
|
| 716 | + }
|
|
| 717 | + if (!live) {
|
|
| 718 | + RELEASE_STORE(&found->interrupt_pending, 0);
|
|
| 719 | + }
|
|
| 720 | + |
|
| 721 | + RELEASE_LOCK(&target_cap->lock);
|
|
| 722 | + (void)cap;
|
|
| 723 | +}
|
|
| 724 | +#endif
|
|
| 725 | + |
|
| 667 | 726 | static void
|
| 668 | 727 | removeFromQueues(Capability *cap, StgTSO *tso)
|
| 669 | 728 | {
|
| ... | ... | @@ -699,8 +758,16 @@ removeFromQueues(Capability *cap, StgTSO *tso) |
| 699 | 758 | // capabilities.
|
| 700 | 759 | // ASSERT(m->header.info == &stg_WHITEHOLE_info);
|
| 701 | 760 | |
| 761 | +#if defined(THREADED_RTS)
|
|
| 762 | + StgTSO *m_target = m->target;
|
|
| 763 | +#endif
|
|
| 764 | + |
|
| 702 | 765 | // unlock and revoke it at the same time
|
| 703 | 766 | doneWithMsgThrowTo(cap, m);
|
| 767 | + |
|
| 768 | +#if defined(THREADED_RTS)
|
|
| 769 | + clear_interrupt_pending_if_no_live_throw(cap, m_target);
|
|
| 770 | +#endif
|
|
| 704 | 771 | break;
|
| 705 | 772 | }
|
| 706 | 773 |
| ... | ... | @@ -2536,6 +2536,25 @@ suspendThread (StgRegTable *reg, bool interruptible) |
| 2536 | 2536 | |
| 2537 | 2537 | suspendTask(cap,task);
|
| 2538 | 2538 | cap->in_haskell = false;
|
| 2539 | + |
|
| 2540 | +#if defined(THREADED_RTS)
|
|
| 2541 | + if (interruptible
|
|
| 2542 | + && !((tso->flags & TSO_BLOCKEX) &&
|
|
| 2543 | + ((tso->flags & TSO_INTERRUPTIBLE) == 0))) {
|
|
| 2544 | + MessageThrowTo *head = ACQUIRE_LOAD(&tso->blocked_exceptions);
|
|
| 2545 | + bool live = false;
|
|
| 2546 | + for (MessageThrowTo *m = head;
|
|
| 2547 | + m != END_BLOCKED_EXCEPTIONS_QUEUE;
|
|
| 2548 | + m = (MessageThrowTo*)m->link) {
|
|
| 2549 | + if (RELAXED_LOAD((StgWord*)&m->header.info)
|
|
| 2550 | + != (StgWord)&stg_MSG_NULL_info) { live = true; break; }
|
|
| 2551 | + }
|
|
| 2552 | + if (live) {
|
|
| 2553 | + RELEASE_STORE(&task->incall->interrupt_pending, 1);
|
|
| 2554 | + }
|
|
| 2555 | + }
|
|
| 2556 | +#endif
|
|
| 2557 | + |
|
| 2539 | 2558 | releaseCapability_(cap,false);
|
| 2540 | 2559 | |
| 2541 | 2560 | RELEASE_LOCK(&cap->lock);
|
| ... | ... | @@ -2574,8 +2593,10 @@ resumeThread (void *task_) |
| 2574 | 2593 | // entry on the suspended_ccalls list will also have been
|
| 2575 | 2594 | // migrated.
|
| 2576 | 2595 | |
| 2577 | - // Remove the thread from the suspended list
|
|
| 2596 | + ACQUIRE_LOCK(&cap->lock);
|
|
| 2597 | + RELEASE_STORE(&incall->interrupt_pending, 0);
|
|
| 2578 | 2598 | recoverSuspendedTask(cap,task);
|
| 2599 | + RELEASE_LOCK(&cap->lock);
|
|
| 2579 | 2600 | |
| 2580 | 2601 | tso = incall->suspended_tso;
|
| 2581 | 2602 | incall->suspended_tso = NULL;
|
| ... | ... | @@ -2614,6 +2635,31 @@ resumeThread (void *task_) |
| 2614 | 2635 | return &cap->r;
|
| 2615 | 2636 | }
|
| 2616 | 2637 | |
| 2638 | +#if defined(THREADED_RTS)
|
|
| 2639 | +void
|
|
| 2640 | +retryInterruptibleSignals(void)
|
|
| 2641 | +{
|
|
| 2642 | + bool any_pending = false;
|
|
| 2643 | + uint32_t n = getNumCapabilities();
|
|
| 2644 | + for (uint32_t i = 0; i < n; i++) {
|
|
| 2645 | + Capability *cap = getCapability(i);
|
|
| 2646 | + if (RELAXED_LOAD(&cap->n_suspended_ccalls) == 0) continue;
|
|
| 2647 | + if (TRY_ACQUIRE_LOCK(&cap->lock) != 0) continue;
|
|
| 2648 | + for (InCall *ic = cap->suspended_ccalls; ic != NULL; ic = ic->next) {
|
|
| 2649 | + if (ACQUIRE_LOAD(&ic->interrupt_pending) == 0) continue;
|
|
| 2650 | + // ic->task is set by newInCall and never cleared.
|
|
| 2651 | + if (ic->task != NULL) {
|
|
| 2652 | + any_pending = true;
|
|
| 2653 | + interruptOSThread(ic->task->id);
|
|
| 2654 | + }
|
|
| 2655 | + }
|
|
| 2656 | + RELEASE_LOCK(&cap->lock);
|
|
| 2657 | + }
|
|
| 2658 | + // Keep the timer alive; handle_tick's idle path would otherwise stop it.
|
|
| 2659 | + if (any_pending) setRecentActivity(ACTIVITY_YES);
|
|
| 2660 | +}
|
|
| 2661 | +#endif
|
|
| 2662 | + |
|
| 2617 | 2663 | /* ---------------------------------------------------------------------------
|
| 2618 | 2664 | * scheduleThread()
|
| 2619 | 2665 | *
|
| ... | ... | @@ -43,6 +43,10 @@ void scheduleThreadOn(Capability *cap, StgWord cpu, StgTSO *tso); |
| 43 | 43 | void wakeUpRts(void);
|
| 44 | 44 | #endif
|
| 45 | 45 | |
| 46 | +#if defined(THREADED_RTS)
|
|
| 47 | +void retryInterruptibleSignals(void);
|
|
| 48 | +#endif
|
|
| 49 | + |
|
| 46 | 50 | /* raiseExceptionHelper */
|
| 47 | 51 | StgWord raiseExceptionHelper (StgRegTable *reg, StgTSO *tso, StgClosure *exception);
|
| 48 | 52 |
| ... | ... | @@ -267,6 +267,7 @@ newInCall (Task *task) |
| 267 | 267 | incall->next = NULL;
|
| 268 | 268 | incall->prev = NULL;
|
| 269 | 269 | incall->prev_stack = task->incall;
|
| 270 | + incall->interrupt_pending = 0;
|
|
| 270 | 271 | task->incall = incall;
|
| 271 | 272 | }
|
| 272 | 273 |
| ... | ... | @@ -106,6 +106,8 @@ typedef struct InCall_ { |
| 106 | 106 | // Links InCalls onto suspended_ccalls, spare_incalls
|
| 107 | 107 | struct InCall_ *prev;
|
| 108 | 108 | struct InCall_ *next;
|
| 109 | + |
|
| 110 | + StgWord interrupt_pending;
|
|
| 109 | 111 | } InCall;
|
| 110 | 112 | |
| 111 | 113 | typedef struct Task_ {
|
| ... | ... | @@ -131,6 +131,9 @@ handle_tick(int unused STG_UNUSED) |
| 131 | 131 | flushEventLog(NULL);
|
| 132 | 132 | }
|
| 133 | 133 | }
|
| 134 | + if (SEQ_CST_LOAD_ALWAYS(&timer_disabled) == 0) {
|
|
| 135 | + retryInterruptibleSignals();
|
|
| 136 | + }
|
|
| 134 | 137 | #endif
|
| 135 | 138 | |
| 136 | 139 | /*
|
| ... | ... | @@ -10,7 +10,7 @@ import System.IO |
| 10 | 10 | import System.Timeout
|
| 11 | 11 | |
| 12 | 12 | foreign import ccall interruptible "cpu_then_pause"
|
| 13 | - c_cpu_then_pause :: CLong -> IO ()
|
|
| 13 | + c_cpu_then_pause :: CLLong -> IO ()
|
|
| 14 | 14 | |
| 15 | 15 | killDelay :: Int
|
| 16 | 16 | killDelay = 200_000
|
| ... | ... | @@ -18,7 +18,7 @@ killDelay = 200_000 |
| 18 | 18 | joinTimeout :: Int
|
| 19 | 19 | joinTimeout = 3_000_000
|
| 20 | 20 | |
| 21 | -runCase :: String -> CLong -> IO ()
|
|
| 21 | +runCase :: String -> CLLong -> IO ()
|
|
| 22 | 22 | runCase name spinNs = do
|
| 23 | 23 | tid <- forkIO $
|
| 24 | 24 | c_cpu_then_pause spinNs
|
| 1 | 1 | #include <errno.h>
|
| 2 | +#include <stdint.h>
|
|
| 2 | 3 | #include <time.h>
|
| 3 | 4 | #include <unistd.h>
|
| 4 | 5 | |
| 5 | -static long now_ns(void) {
|
|
| 6 | +static int64_t now_ns(void) {
|
|
| 6 | 7 | struct timespec t;
|
| 7 | 8 | while (clock_gettime(CLOCK_MONOTONIC, &t) != 0) {
|
| 8 | 9 | if (errno != EINTR) return 0;
|
| 9 | 10 | }
|
| 10 | - return (long)t.tv_sec * 1000000000L + (long)t.tv_nsec;
|
|
| 11 | + return (int64_t)t.tv_sec * 1000000000LL + (int64_t)t.tv_nsec;
|
|
| 11 | 12 | }
|
| 12 | 13 | |
| 13 | -int cpu_then_pause(long spin_ns) {
|
|
| 14 | - long deadline = now_ns() + spin_ns;
|
|
| 14 | +int cpu_then_pause(int64_t spin_ns) {
|
|
| 15 | + int64_t deadline = now_ns() + spin_ns;
|
|
| 15 | 16 | while (now_ns() < deadline) {
|
| 16 | 17 | }
|
| 17 | 18 | return pause();
|
| 1 | +{-# LANGUAGE InterruptibleFFI #-}
|
|
| 2 | +{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 3 | + |
|
| 4 | +module Main where
|
|
| 5 | + |
|
| 6 | +import Control.Concurrent
|
|
| 7 | +import Control.Exception
|
|
| 8 | +import Foreign.C.Types
|
|
| 9 | +import System.IO
|
|
| 10 | +import System.Timeout
|
|
| 11 | + |
|
| 12 | +foreign import ccall interruptible "cpu_then_pause"
|
|
| 13 | + c_cpu_then_pause :: CLLong -> IO ()
|
|
| 14 | + |
|
| 15 | +myCall :: CLLong -> IO ()
|
|
| 16 | +myCall spinNs = mask_ $ do
|
|
| 17 | + allowInterrupt
|
|
| 18 | + c_cpu_then_pause spinNs
|
|
| 19 | + allowInterrupt
|
|
| 20 | + |
|
| 21 | +main :: IO ()
|
|
| 22 | +main = do
|
|
| 23 | + hSetBuffering stdout NoBuffering
|
|
| 24 | + started <- newEmptyMVar
|
|
| 25 | + done <- newEmptyMVar
|
|
| 26 | + tid <- mask_ $ forkIOWithUnmask $ \unmask ->
|
|
| 27 | + flip finally (putMVar done ()) $ do
|
|
| 28 | + putMVar started ()
|
|
| 29 | + unmask (myCall 2_000_000_000)
|
|
| 30 | + `catch` (\(_ :: SomeException) -> return ())
|
|
| 31 | + takeMVar started
|
|
| 32 | + threadDelay 200_000
|
|
| 33 | + result <- timeout 5_000_000 (killThread tid >> takeMVar done)
|
|
| 34 | + case result of
|
|
| 35 | + Just () -> putStrLn "ok"
|
|
| 36 | + Nothing -> do
|
|
| 37 | + hPutStrLn stderr "FAIL: killThread hung"
|
|
| 38 | + error "hung" |
| 1 | +{-# LANGUAGE InterruptibleFFI #-}
|
|
| 2 | +{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 3 | + |
|
| 4 | +module Main where
|
|
| 5 | + |
|
| 6 | +import Control.Concurrent
|
|
| 7 | +import Control.Exception
|
|
| 8 | +import Control.Monad
|
|
| 9 | +import Data.IORef
|
|
| 10 | +import Foreign.C.Types
|
|
| 11 | +import System.Exit
|
|
| 12 | +import System.IO
|
|
| 13 | + |
|
| 14 | +foreign import ccall interruptible "pause" c_pause :: IO CInt
|
|
| 15 | + |
|
| 16 | +postRevokeWindow :: Int
|
|
| 17 | +postRevokeWindow = 2_000_000
|
|
| 18 | + |
|
| 19 | +postRevokeThreshold :: Int
|
|
| 20 | +postRevokeThreshold = 5
|
|
| 21 | + |
|
| 22 | +main :: IO ()
|
|
| 23 | +main = do
|
|
| 24 | + hSetBuffering stdout NoBuffering
|
|
| 25 | + iters <- newIORef (0 :: Int)
|
|
| 26 | + |
|
| 27 | + workerStarted <- newEmptyMVar
|
|
| 28 | + worker <- forkIO $ mask_ $ do
|
|
| 29 | + putMVar workerStarted ()
|
|
| 30 | + (forever $ do
|
|
| 31 | + atomicModifyIORef' iters (\n -> (n + 1, ()))
|
|
| 32 | + _ <- c_pause
|
|
| 33 | + return ())
|
|
| 34 | + `catch` (\(_ :: SomeException) -> return ())
|
|
| 35 | + |
|
| 36 | + takeMVar workerStarted
|
|
| 37 | + threadDelay 100_000
|
|
| 38 | + |
|
| 39 | + killer1Ready <- newEmptyMVar
|
|
| 40 | + killer1 <- forkIO $ do
|
|
| 41 | + putMVar killer1Ready ()
|
|
| 42 | + killThread worker
|
|
| 43 | + |
|
| 44 | + takeMVar killer1Ready
|
|
| 45 | + threadDelay 100_000
|
|
| 46 | + |
|
| 47 | + preRevoke <- readIORef iters
|
|
| 48 | + |
|
| 49 | + killThread killer1
|
|
| 50 | + |
|
| 51 | + threadDelay postRevokeWindow
|
|
| 52 | + |
|
| 53 | + postRevoke <- readIORef iters
|
|
| 54 | + let storm = postRevoke - preRevoke
|
|
| 55 | + if storm > postRevokeThreshold
|
|
| 56 | + then do
|
|
| 57 | + hPutStrLn stderr ("FAIL: post-revoke EINTR storm, iterations="
|
|
| 58 | + ++ show storm
|
|
| 59 | + ++ " (pre=" ++ show preRevoke
|
|
| 60 | + ++ " post=" ++ show postRevoke ++ ")")
|
|
| 61 | + exitFailure
|
|
| 62 | + else
|
|
| 63 | + putStrLn ("ok (post-revoke iterations=" ++ show storm ++ ")") |
| ... | ... | @@ -208,11 +208,24 @@ test('foreignInterruptible', [when(fast(), skip), |
| 208 | 208 | |
| 209 | 209 | test('T27113',
|
| 210 | 210 | [when(opsys('mingw32'), skip),
|
| 211 | + only_threaded_ways,
|
|
| 212 | + req_c],
|
|
| 213 | + compile_and_run, ['T27113_c.c'])
|
|
| 214 | + |
|
| 215 | +test('T27113b',
|
|
| 216 | + [extra_files(['T27113_c.c']),
|
|
| 217 | + when(opsys('mingw32'), skip),
|
|
| 211 | 218 | only_threaded_ways,
|
| 212 | 219 | req_c,
|
| 213 | - expect_broken(27113)],
|
|
| 220 | + ignore_stdout],
|
|
| 214 | 221 | compile_and_run, ['T27113_c.c'])
|
| 215 | 222 | |
| 223 | +test('T27113c',
|
|
| 224 | + [when(opsys('mingw32'), skip),
|
|
| 225 | + only_threaded_ways,
|
|
| 226 | + ignore_stdout],
|
|
| 227 | + compile_and_run, [''])
|
|
| 228 | + |
|
| 216 | 229 | test('conc037', only_ways(['threaded1', 'threaded2', 'nonmoving_thr']), compile_and_run, [''])
|
| 217 | 230 | test('conc038', only_ways(['threaded1', 'threaded2', 'nonmoving_thr']), compile_and_run, [''])
|
| 218 | 231 |