@@ -35,6 +35,47 @@ template <class MutexType, std::size_t N>
3535void unlocker (std::array<MutexType, N>& mtxs) {
3636 std::apply ([](auto &... mts) { return std::scoped_lock (std::adopt_lock, mts...); }, mtxs);
3737}
38+
39+ // Mutex whose try_lock_until/for spins on should_fail instead of timing out,
40+ // allowing deterministic scripting of lock sequences without sleeps.
41+ // wait_count increments each time a thread enters the spin, enabling the
42+ // background thread to synchronize on "algorithm is now blocked here".
43+ // held tracks whether the algorithm currently owns this mutex.
44+ struct ScriptedMutex {
45+ std::atomic<bool > should_fail{false };
46+ std::atomic<int > wait_count{0 };
47+ std::atomic<bool > held{false };
48+
49+ void lock () {
50+ if (should_fail) {
51+ ++wait_count;
52+ while (should_fail)
53+ std::this_thread::yield ();
54+ }
55+ held = true ;
56+ }
57+ bool try_lock () {
58+ if (should_fail)
59+ return false ;
60+ held = true ;
61+ return true ;
62+ }
63+ template <class R , class P >
64+ bool try_lock_for (const std::chrono::duration<R, P>&) {
65+ lock ();
66+ return true ;
67+ }
68+ template <class C , class D >
69+ bool try_lock_until (const std::chrono::time_point<C, D>&) {
70+ lock ();
71+ return true ;
72+ }
73+ void unlock () { held = false ; }
74+ void wait_for_waiter (int n = 1 ) {
75+ while (wait_count < n)
76+ std::this_thread::yield ();
77+ }
78+ };
3879} // namespace
3980
4081// ============================================================================
@@ -149,21 +190,44 @@ TEST(TryLockIntegration, SucceedWithThreeInTrickySequence) {
149190 // The comments in this test are on implementation details.
150191 // A different implementation may behave differently but should
151192 // still succeed in locking all three in time.
152- std::array<std::timed_mutex, 3 > mtxs;
153- auto th = JThread ([&] {
154- std::lock (mtxs[0 ], mtxs[1 ], mtxs[2 ]);
155- std::this_thread::sleep_for (55ms);
156- mtxs[0 ].unlock (); // 5ms after try_lock_for started, 95ms left
157- // try_lock_for gets this and jumps to mtxs[1]
158- std::this_thread::sleep_for (5ms);
159- mtxs[2 ].unlock (); // try_lock_for still hangs on mtxs[1]
160- mtxs[0 ].lock ();
161- mtxs[1 ].unlock (); // try_lock_for gets this and jumps to mtxs[0]
162- // 10ms after try_lock_for started, 90ms left
163- std::this_thread::sleep_for (10ms);
164- mtxs[0 ].unlock (); // try_lock_for should have 80ms left here
193+ //
194+ // Algorithm rotation with 3 mutexes [m0,m1,m2]:
195+ // idx=0 [0,1,2]: try_lock_until(m0), try_lock(m1,m2) → m1 fails → retry idx=1
196+ // idx=1 [1,2,0]: try_lock_until(m1), try_lock(m2,m0) → m2 fails → retry idx=2
197+ // idx=2 [2,0,1]: try_lock_until(m2), try_lock(m0,m1) → m0 fails → retry idx=0
198+ // idx=0 [0,1,2]: try_lock_until(m0), try_lock(m1,m2) → both succeed → done
199+ ScriptedMutex m0, m1, m2;
200+ m0.should_fail = m1.should_fail = m2.should_fail = true ;
201+
202+ auto th = JThread ([&] {
203+ // idx=0: algorithm blocks on m0
204+ m0.wait_for_waiter (1 );
205+ EXPECT_FALSE (m1.held );
206+ EXPECT_FALSE (m2.held );
207+ m0.should_fail = false ; // unblock m0; try_lock(m1) fails → releases m0, retry idx=1
208+
209+ // idx=1: algorithm blocks on m1
210+ m1.wait_for_waiter (1 );
211+ EXPECT_FALSE (m0.held );
212+ EXPECT_FALSE (m2.held );
213+ m0.should_fail = true ; // re-block m0 before algorithm reaches it
214+ m1.should_fail = false ; // unblock m1; try_lock(m2) fails → releases m1, retry idx=2
215+
216+ // idx=2: algorithm blocks on m2
217+ m2.wait_for_waiter (1 );
218+ EXPECT_FALSE (m0.held );
219+ EXPECT_FALSE (m1.held );
220+ m1.should_fail = true ; // re-block m1 before algorithm reaches it
221+ m2.should_fail = false ; // unblock m2; try_lock(m0) fails → releases m2, retry idx=0
222+
223+ // idx=0 again: algorithm blocks on m0
224+ m0.wait_for_waiter (2 );
225+ EXPECT_FALSE (m1.held );
226+ EXPECT_FALSE (m2.held );
227+ m1.should_fail = false ; // all free for final round
228+ m2.should_fail = false ;
229+ m0.should_fail = false ; // unblock m0; try_lock(m1,m2) → both succeed → done
165230 });
166231
167- std::this_thread::sleep_for (50ms);
168- EXPECT_EQ (-1 , std::apply ([](auto &... mts) { return tla::try_lock_for (100ms + extra_grace, mts...); }, mtxs));
232+ EXPECT_EQ (-1 , tla::try_lock_for (no_duration, m0, m1, m2));
169233}
0 commit comments