Skip to content

Commit ce2ef64

Browse files
committed
Make SucceedWithThreeInTrickySequence timing unsensitive
Removed the timing aspect. Check that the internals of the function as implemented does what's expected using a "scripted" mutex. Signed-off-by: Ted Lyngmo <ted@lyncon.se>
1 parent b937276 commit ce2ef64

1 file changed

Lines changed: 79 additions & 15 deletions

File tree

tests/beman/timed_lock_alg/try_lock.test.cpp

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,47 @@ template <class MutexType, std::size_t N>
3535
void 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

Comments
 (0)