Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
5b5d9d47
by Ben Gamari at 2025-08-25T14:29:35-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,27 +1211,16 @@ 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 | - 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);
|
|
| 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();
|
|
| 1235 | 1224 | }
|
| 1236 | 1225 | }
|
| 1237 | 1226 | |
| ... | ... | @@ -1464,26 +1453,21 @@ retry_pop_stack: |
| 1464 | 1453 | outer = StgTRecHeader_enclosing_trec(trec);
|
| 1465 | 1454 | |
| 1466 | 1455 | if (frame_type == CATCH_RETRY_FRAME) {
|
| 1467 | - // The retry reaches a CATCH_RETRY_FRAME before the ATOMICALLY_FRAME
|
|
| 1468 | - |
|
| 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");
|
|
| 1469 | 1461 | if (!StgCatchRetryFrame_running_alt_code(frame) != 0) {
|
| 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;
|
|
| 1462 | + // Retry in the first branch: try the alternative
|
|
| 1463 | + ("ptr" trec) = ccall stmStartTransaction(MyCapability() "ptr", outer "ptr");
|
|
| 1464 | + StgTSO_trec(CurrentTSO) = trec;
|
|
| 1482 | 1465 | StgCatchRetryFrame_running_alt_code(frame) = 1 :: CInt; // true;
|
| 1483 | 1466 | R1 = StgCatchRetryFrame_alt_code(frame);
|
| 1484 | 1467 | jump stg_ap_v_fast [R1];
|
| 1485 | 1468 | } else {
|
| 1486 | - // Retry in the rhs code: propagate the retry
|
|
| 1469 | + // Retry in the alternative code: propagate the retry
|
|
| 1470 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1487 | 1471 | Sp = Sp + SIZEOF_StgCatchRetryFrame;
|
| 1488 | 1472 | goto retry_pop_stack;
|
| 1489 | 1473 | }
|
| ... | ... | @@ -1043,7 +1043,8 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1043 | 1043 | }
|
| 1044 | 1044 | |
| 1045 | 1045 | case CATCH_STM_FRAME:
|
| 1046 | - // CATCH_STM frame within an atomically block: abort the
|
|
| 1046 | + case CATCH_RETRY_FRAME:
|
|
| 1047 | + // CATCH frames within an atomically block: abort the
|
|
| 1047 | 1048 | // inner transaction and continue. Eventually we will
|
| 1048 | 1049 | // hit the outer transaction that will get frozen (see
|
| 1049 | 1050 | // above).
|
| ... | ... | @@ -1055,40 +1056,14 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1055 | 1056 | {
|
| 1056 | 1057 | StgTRecHeader *trec = tso -> trec;
|
| 1057 | 1058 | StgTRecHeader *outer = trec -> enclosing_trec;
|
| 1058 | - debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_STM frame");
|
|
| 1059 | + debugTraceCap(DEBUG_stm, cap,
|
|
| 1060 | + "found atomically block delivering async exception");
|
|
| 1059 | 1061 | stmAbortTransaction(cap, trec);
|
| 1060 | 1062 | stmFreeAbortedTRec(cap, trec);
|
| 1061 | 1063 | tso -> trec = outer;
|
| 1062 | 1064 | break;
|
| 1063 | 1065 | };
|
| 1064 | 1066 | |
| 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 | - |
|
| 1092 | 1067 | default:
|
| 1093 | 1068 | // see Note [Update async masking state on unwind] in Schedule.c
|
| 1094 | 1069 | if (*frame == (W_)&stg_unmaskAsyncExceptionszh_ret_info) {
|
| ... | ... | @@ -1505,30 +1505,3 @@ 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']) |