@@ -1454,23 +1454,19 @@ fn many_actors_same_alarm_time() {
14541454
14551455/// Regression test for the alarm-during-sleep-transition race.
14561456///
1457- /// Scenario: an actor schedules an alarm in the near future, then immediately
1458- /// sends a sleep intent. The stop flow may take long enough that the alarm
1459- /// becomes overdue while `handle_stopped` is processing `Decision::Sleep`.
1457+ /// Scenario: an actor schedules an alarm that is already overdue, then
1458+ /// immediately sends a sleep intent. When `handle_stopped` runs `Decision::Sleep`,
1459+ /// `now >= alarm_ts` is true, so the workflow must reallocate and run the
1460+ /// alarm handler instead of clearing `alarm_ts` and sleeping.
14601461///
1461- /// Before the fix in `actor2/runtime.rs`, this window cleared `state.alarm_ts`
1462+ /// Before the fix in `actor2/runtime.rs`, this branch cleared `state.alarm_ts`
14621463/// without handling the overdue alarm, so the scheduled work was silently
14631464/// dropped and the actor went to sleep. The handler would never run.
14641465///
14651466/// After the fix, `Decision::Sleep` detects the overdue alarm, reallocates the
1466- /// actor, and bumps the generation so the alarm handler runs. This test
1467- /// verifies that path by setting a very short alarm offset and checking the
1468- /// actor wakes to generation 1 instead of sleeping forever.
1469- ///
1470- /// Expected: the alarm triggers via reallocation. If the fix is reverted, the
1471- /// alarm will never trigger and this test will time out waiting for the wake.
1467+ /// actor, and bumps the generation so the alarm handler runs. The negative
1468+ /// alarm offset (`-1000`ms) deterministically forces the overdue branch.
14721469#[ test]
1473- #[ ignore = "captures alarm-during-sleep-transition race; times out if the overdue-alarm reallocation path regresses" ]
14741470fn alarm_overdue_during_sleep_transition_fires_via_reallocation ( ) {
14751471 common:: run (
14761472 common:: TestOpts :: new ( 1 ) . with_timeout ( 15 ) ,
@@ -1483,10 +1479,10 @@ fn alarm_overdue_during_sleep_transition_fires_via_reallocation() {
14831479 let runner = common:: setup_runner ( ctx. leader_dc ( ) , & namespace, |builder| {
14841480 builder. with_actor_behavior ( "alarm-actor" , move |_| {
14851481 let ready_tx = ready_tx. clone ( ) ;
1486- // 100ms offset leaves enough time to dispatch the sleep intent
1487- // but is short enough that the alarm is near- overdue by the
1488- // time the workflow reaches `Decision::Sleep` .
1489- Box :: new ( AlarmAndSleepOnceActor :: new ( 100 , ready_tx) )
1482+ // Negative offset guarantees `now >= alarm_ts` when
1483+ // `Decision::Sleep` runs, so the overdue branch is exercised
1484+ // every time instead of racing the workflow scheduler .
1485+ Box :: new ( AlarmAndSleepOnceActor :: new ( - 1000 , ready_tx) )
14901486 } )
14911487 } )
14921488 . await ;
@@ -1504,6 +1500,9 @@ fn alarm_overdue_during_sleep_transition_fires_via_reallocation() {
15041500
15051501 ready_rx. await . expect ( "actor should send ready signal" ) ;
15061502
1503+ // Subscribe before the actor enters sleep so we can't miss the
1504+ // reallocation `Started` event. With the negative offset the wake
1505+ // happens immediately after `handle_stopped` runs.
15071506 let lifecycle_rx = runner. subscribe_lifecycle_events ( ) ;
15081507
15091508 // If the overdue alarm was dropped, the actor would enter sleep and
0 commit comments