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

Commits:

6 changed files:

Changes:

  • rts/PrimOps.cmm
    ... ... @@ -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
             }
    

  • rts/RaiseAsync.c
    ... ... @@ -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) {
    

  • rts/STM.c
    ... ... @@ -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
    +*/

  • testsuite/tests/lib/stm/T26028.hs
    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

  • testsuite/tests/lib/stm/T26028.stdout
    1
    +"terminates"

  • testsuite/tests/lib/stm/all.T
    1
    +test('T26028', only_ways(['threaded1']), compile_and_run, ['-O2'])