Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
0a583689
by Sylvain Henry at 2025-07-24T16:20:26-04:00
6 changed files:
- rts/PrimOps.cmm
- rts/RaiseAsync.c
- rts/STM.c
- + testsuite/tests/lib/stm/T26028.hs
- + testsuite/tests/lib/stm/T26028.stdout
- + testsuite/tests/lib/stm/all.T
Changes:
| ... | ... | @@ -1211,16 +1211,27 @@ INFO_TABLE_RET(stg_catch_retry_frame, CATCH_RETRY_FRAME, |
| 1211 | 1211 | gcptr trec, outer, arg;
|
| 1212 | 1212 | |
| 1213 | 1213 | trec = StgTSO_trec(CurrentTSO);
|
| 1214 | - outer = StgTRecHeader_enclosing_trec(trec);
|
|
| 1215 | - (r) = ccall stmCommitNestedTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1216 | - if (r != 0) {
|
|
| 1217 | - // Succeeded (either first branch or second branch)
|
|
| 1218 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1219 | - return (ret);
|
|
| 1220 | - } else {
|
|
| 1221 | - // Did not commit: abort and restart.
|
|
| 1222 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1223 | - jump stg_abort();
|
|
| 1214 | + if (running_alt_code != 1) {
|
|
| 1215 | + // When exiting the lhs code of catchRetry# lhs rhs, we need to cleanup
|
|
| 1216 | + // the nested transaction.
|
|
| 1217 | + // See Note [catchRetry# implementation]
|
|
| 1218 | + outer = StgTRecHeader_enclosing_trec(trec);
|
|
| 1219 | + (r) = ccall stmCommitNestedTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1220 | + if (r != 0) {
|
|
| 1221 | + // Succeeded in first branch
|
|
| 1222 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1223 | + return (ret);
|
|
| 1224 | + } else {
|
|
| 1225 | + // Did not commit: abort and restart.
|
|
| 1226 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1227 | + jump stg_abort();
|
|
| 1228 | + }
|
|
| 1229 | + }
|
|
| 1230 | + else {
|
|
| 1231 | + // nothing to do in the rhs code of catchRetry# lhs rhs, it's already
|
|
| 1232 | + // using the parent transaction (not a nested one).
|
|
| 1233 | + // See Note [catchRetry# implementation]
|
|
| 1234 | + return (ret);
|
|
| 1224 | 1235 | }
|
| 1225 | 1236 | }
|
| 1226 | 1237 | |
| ... | ... | @@ -1453,21 +1464,26 @@ retry_pop_stack: |
| 1453 | 1464 | outer = StgTRecHeader_enclosing_trec(trec);
|
| 1454 | 1465 | |
| 1455 | 1466 | if (frame_type == CATCH_RETRY_FRAME) {
|
| 1456 | - // The retry reaches a CATCH_RETRY_FRAME before the atomic frame
|
|
| 1457 | - ASSERT(outer != NO_TREC);
|
|
| 1458 | - // Abort the transaction attempting the current branch
|
|
| 1459 | - ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1460 | - ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
|
|
| 1467 | + // The retry reaches a CATCH_RETRY_FRAME before the ATOMICALLY_FRAME
|
|
| 1468 | + |
|
| 1461 | 1469 | if (!StgCatchRetryFrame_running_alt_code(frame) != 0) {
|
| 1462 | - // Retry in the first branch: try the alternative
|
|
| 1463 | - ("ptr" trec) = ccall stmStartTransaction(MyCapability() "ptr", outer "ptr");
|
|
| 1464 | - StgTSO_trec(CurrentTSO) = trec;
|
|
| 1470 | + // Retrying in the lhs of catchRetry# lhs rhs, i.e. in a nested
|
|
| 1471 | + // transaction. See Note [catchRetry# implementation]
|
|
| 1472 | + |
|
| 1473 | + // check that we have a parent transaction
|
|
| 1474 | + ASSERT(outer != NO_TREC);
|
|
| 1475 | + |
|
| 1476 | + // Abort the nested transaction
|
|
| 1477 | + ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1478 | + ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
|
|
| 1479 | + |
|
| 1480 | + // As we are retrying in the lhs code, we must now try the rhs code
|
|
| 1481 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1465 | 1482 | StgCatchRetryFrame_running_alt_code(frame) = 1 :: CInt; // true;
|
| 1466 | 1483 | R1 = StgCatchRetryFrame_alt_code(frame);
|
| 1467 | 1484 | jump stg_ap_v_fast [R1];
|
| 1468 | 1485 | } else {
|
| 1469 | - // Retry in the alternative code: propagate the retry
|
|
| 1470 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1486 | + // Retry in the rhs code: propagate the retry
|
|
| 1471 | 1487 | Sp = Sp + SIZEOF_StgCatchRetryFrame;
|
| 1472 | 1488 | goto retry_pop_stack;
|
| 1473 | 1489 | }
|
| ... | ... | @@ -1043,8 +1043,7 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1043 | 1043 | }
|
| 1044 | 1044 | |
| 1045 | 1045 | case CATCH_STM_FRAME:
|
| 1046 | - case CATCH_RETRY_FRAME:
|
|
| 1047 | - // CATCH frames within an atomically block: abort the
|
|
| 1046 | + // CATCH_STM frame within an atomically block: abort the
|
|
| 1048 | 1047 | // inner transaction and continue. Eventually we will
|
| 1049 | 1048 | // hit the outer transaction that will get frozen (see
|
| 1050 | 1049 | // above).
|
| ... | ... | @@ -1056,14 +1055,40 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1056 | 1055 | {
|
| 1057 | 1056 | StgTRecHeader *trec = tso -> trec;
|
| 1058 | 1057 | StgTRecHeader *outer = trec -> enclosing_trec;
|
| 1059 | - debugTraceCap(DEBUG_stm, cap,
|
|
| 1060 | - "found atomically block delivering async exception");
|
|
| 1058 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_STM frame");
|
|
| 1061 | 1059 | stmAbortTransaction(cap, trec);
|
| 1062 | 1060 | stmFreeAbortedTRec(cap, trec);
|
| 1063 | 1061 | tso -> trec = outer;
|
| 1064 | 1062 | break;
|
| 1065 | 1063 | };
|
| 1066 | 1064 | |
| 1065 | + case CATCH_RETRY_FRAME:
|
|
| 1066 | + // CATCH_RETY frame within an atomically block: if we're executing
|
|
| 1067 | + // the lhs code, abort the inner transaction and continue; if we're
|
|
| 1068 | + // executing thr rhs, continue (no nested transaction to abort. See
|
|
| 1069 | + // Note [catchRetry# implementation]). Eventually we will hit the
|
|
| 1070 | + // outer transaction that will get frozen (see above).
|
|
| 1071 | + //
|
|
| 1072 | + // As for the CATCH_STM_FRAME case above, we do not care
|
|
| 1073 | + // whether the transaction is valid or not because its
|
|
| 1074 | + // possible validity cannot have caused the exception
|
|
| 1075 | + // and will not be visible after the abort.
|
|
| 1076 | + {
|
|
| 1077 | + if (!((StgCatchRetryFrame *)frame) -> running_alt_code) {
|
|
| 1078 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_RETRY frame (lhs)");
|
|
| 1079 | + StgTRecHeader *trec = tso -> trec;
|
|
| 1080 | + StgTRecHeader *outer = trec -> enclosing_trec;
|
|
| 1081 | + stmAbortTransaction(cap, trec);
|
|
| 1082 | + stmFreeAbortedTRec(cap, trec);
|
|
| 1083 | + tso -> trec = outer;
|
|
| 1084 | + }
|
|
| 1085 | + else
|
|
| 1086 | + {
|
|
| 1087 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_RETRY frame (rhs)");
|
|
| 1088 | + }
|
|
| 1089 | + break;
|
|
| 1090 | + };
|
|
| 1091 | + |
|
| 1067 | 1092 | default:
|
| 1068 | 1093 | // see Note [Update async masking state on unwind] in Schedule.c
|
| 1069 | 1094 | if (*frame == (W_)&stg_unmaskAsyncExceptionszh_ret_info) {
|
| ... | ... | @@ -1505,3 +1505,30 @@ void stmWriteTVar(Capability *cap, |
| 1505 | 1505 | }
|
| 1506 | 1506 | |
| 1507 | 1507 | /*......................................................................*/
|
| 1508 | + |
|
| 1509 | + |
|
| 1510 | + |
|
| 1511 | +/*
|
|
| 1512 | + |
|
| 1513 | +Note [catchRetry# implementation]
|
|
| 1514 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1515 | +catchRetry# creates a nested transaction for its lhs:
|
|
| 1516 | +- if the lhs transaction succeeds:
|
|
| 1517 | + - the lhs transaction is committed
|
|
| 1518 | + - its read-variables are merged with those of the parent transaction
|
|
| 1519 | + - the rhs code is ignored
|
|
| 1520 | +- if the lhs transaction retries:
|
|
| 1521 | + - the lhs transaction is aborted
|
|
| 1522 | + - its read-variables are merged with those of the parent transaction
|
|
| 1523 | + - the rhs code is executed directly in the parent transaction (see #26028).
|
|
| 1524 | + |
|
| 1525 | +So note that:
|
|
| 1526 | +- lhs code uses a nested transaction
|
|
| 1527 | +- rhs code doesn't use a nested transaction
|
|
| 1528 | + |
|
| 1529 | +We have to take which case we're in into account (using the running_alt_code
|
|
| 1530 | +field of the catchRetry frame) in catchRetry's entry code, in retry#
|
|
| 1531 | +implementation, and also when an async exception is received (to cleanup the
|
|
| 1532 | +right number of transactions).
|
|
| 1533 | + |
|
| 1534 | +*/ |
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import GHC.Conc
|
|
| 4 | + |
|
| 5 | +forever :: IO String
|
|
| 6 | +forever = delay 10 >> forever
|
|
| 7 | + |
|
| 8 | +terminates :: IO String
|
|
| 9 | +terminates = delay 1 >> pure "terminates"
|
|
| 10 | + |
|
| 11 | +delay s = threadDelay (1000000 * s)
|
|
| 12 | + |
|
| 13 | +async :: IO a -> IO (STM a)
|
|
| 14 | +async a = do
|
|
| 15 | + var <- atomically (newTVar Nothing)
|
|
| 16 | + forkIO (a >>= atomically . writeTVar var . Just)
|
|
| 17 | + pure (readTVar var >>= maybe retry pure)
|
|
| 18 | + |
|
| 19 | +main :: IO ()
|
|
| 20 | +main = do
|
|
| 21 | + x <- mapM async $ terminates : replicate 50000 forever
|
|
| 22 | + r <- atomically (foldr1 orElse x)
|
|
| 23 | + print r |
| 1 | +"terminates" |
| 1 | +test('T26028', only_ways(['threaded1']), compile_and_run, ['-O2']) |