3333 LOCK_REGISTRY_STRESS_THREADS = 8 ,
3434 LOCK_REGISTRY_STRESS_ITERATIONS = 160 ,
3535 LOCK_REGISTRY_PARKING_WAITERS = 64 ,
36+ LOCK_REGISTRY_DEADLINE_WAIT_MS = 200 ,
3637};
3738
3839typedef struct {
@@ -1020,7 +1021,8 @@ typedef struct {
10201021 atomic_bool ready ;
10211022 atomic_bool go ;
10221023 atomic_bool finished ;
1023- uint64_t deadline_ms ;
1024+ uint64_t wait_ms ;
1025+ uint64_t started_ms ;
10241026 uint64_t returned_ms ;
10251027 cbm_private_file_lock_status_t status ;
10261028 cbm_lock_lease_t * lease ;
@@ -1033,9 +1035,14 @@ static void *lock_registry_deadline_waiter_run(void *opaque) {
10331035 while (!atomic_load_explicit (& waiter -> go , memory_order_acquire )) {
10341036 lock_registry_test_yield ();
10351037 }
1036- waiter -> status =
1037- cbm_lock_registry_acquire (waiter -> registry , "absolute-deadline" , CBM_PRIVATE_FILE_LOCK_EX ,
1038- waiter -> deadline_ms , & waiter -> cancel_token , & waiter -> lease );
1038+ /* The absolute deadline is anchored here, in the thread it bounds, right
1039+ * before the call it bounds. An anchor taken by the observer is already
1040+ * running while this thread is still waiting to be scheduled, so on a
1041+ * loaded host it can expire before the acquire even starts. */
1042+ waiter -> started_ms = cbm_now_ms ();
1043+ waiter -> status = cbm_lock_registry_acquire (
1044+ waiter -> registry , "absolute-deadline" , CBM_PRIVATE_FILE_LOCK_EX ,
1045+ waiter -> started_ms + waiter -> wait_ms , & waiter -> cancel_token , & waiter -> lease );
10391046 waiter -> returned_ms = cbm_now_ms ();
10401047 atomic_store_explicit (& waiter -> finished , true, memory_order_release );
10411048 return NULL ;
@@ -1066,7 +1073,7 @@ TEST(lock_registry_absolute_deadline_survives_repeated_wakes) {
10661073 bool head_started = holder_status == CBM_PRIVATE_FILE_LOCK_OK &&
10671074 cbm_thread_create (& head_thread , 0 , lock_registry_waiter_run , & head ) == 0 ;
10681075 bool head_attempting = false;
1069- uint64_t head_deadline = cbm_now_ms () + 500 ;
1076+ uint64_t head_deadline = cbm_now_ms () + LOCK_REGISTRY_TEST_TIMEOUT_MS ;
10701077 while (head_started && cbm_now_ms () < head_deadline ) {
10711078 head_attempting = cbm_lock_registry_waiter_count (fixture .registry ) == 1 &&
10721079 cbm_lock_registry_attempting_waiter_count_for_test (fixture .registry ) == 1 ;
@@ -1077,6 +1084,7 @@ TEST(lock_registry_absolute_deadline_survives_repeated_wakes) {
10771084 }
10781085
10791086 lock_registry_deadline_waiter_t tail = {.registry = fixture .registry ,
1087+ .wait_ms = LOCK_REGISTRY_DEADLINE_WAIT_MS ,
10801088 .status = CBM_PRIVATE_FILE_LOCK_IO };
10811089 atomic_init (& tail .cancel_token , false);
10821090 atomic_init (& tail .ready , false);
@@ -1086,31 +1094,20 @@ TEST(lock_registry_absolute_deadline_survives_repeated_wakes) {
10861094 bool tail_started =
10871095 head_attempting &&
10881096 cbm_thread_create (& tail_thread , 0 , lock_registry_deadline_waiter_run , & tail ) == 0 ;
1089- uint64_t ready_deadline = cbm_now_ms () + 500 ;
1097+ uint64_t ready_deadline = cbm_now_ms () + LOCK_REGISTRY_TEST_TIMEOUT_MS ;
10901098 while (tail_started && !atomic_load_explicit (& tail .ready , memory_order_acquire ) &&
10911099 cbm_now_ms () < ready_deadline ) {
10921100 lock_registry_test_yield ();
10931101 }
10941102 bool tail_ready = tail_started && atomic_load_explicit (& tail .ready , memory_order_acquire );
1095- uint64_t deadline_start = cbm_now_ms ();
1096- tail . deadline_ms = deadline_start + 200 ;
1103+ uint64_t enqueues_before_tail =
1104+ cbm_lock_registry_waiter_enqueue_count_for_test ( fixture . registry ) ;
10971105 atomic_store_explicit (& tail .go , true, memory_order_release );
10981106
1099- bool tail_queued = false;
1100- uint64_t queue_deadline = deadline_start + 100 ;
1101- while (tail_ready && cbm_now_ms () < queue_deadline ) {
1102- tail_queued = cbm_lock_registry_waiter_count (fixture .registry ) == 2 &&
1103- cbm_lock_registry_attempting_waiter_count_for_test (fixture .registry ) == 1 ;
1104- if (tail_queued ) {
1105- break ;
1106- }
1107- lock_registry_test_yield ();
1108- }
1109-
11101107 cbm_lock_cancel_token_t unrelated_token ;
11111108 atomic_init (& unrelated_token , false);
11121109 bool broadcasts_ok = true;
1113- uint64_t observe_deadline = deadline_start + 600 ;
1110+ uint64_t observe_deadline = cbm_now_ms () + LOCK_REGISTRY_TEST_TIMEOUT_MS ;
11141111 while (tail_ready && !atomic_load_explicit (& tail .finished , memory_order_acquire ) &&
11151112 cbm_now_ms () < observe_deadline ) {
11161113 broadcasts_ok = cbm_lock_registry_request_cancel (fixture .registry , & unrelated_token ) ==
@@ -1120,7 +1117,13 @@ TEST(lock_registry_absolute_deadline_survives_repeated_wakes) {
11201117 }
11211118 bool returned_at_deadline =
11221119 tail_ready && atomic_load_explicit (& tail .finished , memory_order_acquire );
1123- uint64_t elapsed_ms = returned_at_deadline ? tail .returned_ms - deadline_start : UINT64_MAX ;
1120+ /* The enqueue count only ever grows, so this stays true once the tail has
1121+ * joined the queue behind the attempting head. Reading it after the tail
1122+ * has returned removes the window the observer used to have to catch. */
1123+ uint64_t enqueues_after_tail =
1124+ cbm_lock_registry_waiter_enqueue_count_for_test (fixture .registry );
1125+ bool tail_queued = returned_at_deadline && enqueues_after_tail > enqueues_before_tail ;
1126+ uint64_t elapsed_ms = returned_at_deadline ? tail .returned_ms - tail .started_ms : UINT64_MAX ;
11241127
11251128 if (!returned_at_deadline && tail_started ) {
11261129 (void )cbm_lock_registry_request_cancel (fixture .registry , & tail .cancel_token );
0 commit comments