Skip to content

Commit 9fc77cc

Browse files
committed
Add test_event to util.hpp, a one-shot atomic flag with watchdog to speed up tests, make some mutex checks deterministic and more robust against CPU starving runners.
1 parent b18a87d commit 9fc77cc

3 files changed

Lines changed: 177 additions & 52 deletions

File tree

test/mutex_test_template.hpp

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,14 @@ void lock_and_sleep(void *arg, M &sm)
179179
{
180180
data<M> *pdata = static_cast<data<M>*>(arg);
181181
boost::interprocess::scoped_lock<M> l(sm);
182-
if(pdata->m_msecs){
182+
//Announce the lock is owned, so that the launcher does not need to guess it
183+
//with a sleep, which is unreliable under heavy CPU load
184+
pdata->m_acquired.signal();
185+
if(pdata->m_block){
186+
//Keep the lock until the test says otherwise
187+
BOOST_INTERPROCESS_CHECK(pdata->m_release.wait());
188+
}
189+
else if(pdata->m_msecs){
183190
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(pdata->m_msecs));
184191
}
185192
else{
@@ -210,7 +217,14 @@ void try_lock_and_sleep(void *arg, M &sm)
210217
data<M> *pdata = static_cast<data<M>*>(arg);
211218
boost::interprocess::scoped_lock<M> l(sm, boost::interprocess::defer_lock);
212219
if (l.try_lock()){
213-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(2*BaseMs));
220+
pdata->m_acquired.signal();
221+
if(pdata->m_block){
222+
//Keep the lock until the test says otherwise
223+
BOOST_INTERPROCESS_CHECK(pdata->m_release.wait());
224+
}
225+
else{
226+
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(2*BaseMs));
227+
}
214228
++shared_val;
215229
pdata->m_value = shared_val;
216230
}
@@ -242,7 +256,14 @@ void timed_lock_and_sleep(void *arg, M &sm)
242256
}
243257

244258
if (r){
245-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(2*BaseMs));
259+
pdata->m_acquired.signal();
260+
if(pdata->m_block){
261+
//Keep the lock until the test says otherwise
262+
BOOST_INTERPROCESS_CHECK(pdata->m_release.wait());
263+
}
264+
else{
265+
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(2*BaseMs));
266+
}
246267
++shared_val;
247268
pdata->m_value = shared_val;
248269
}
@@ -262,8 +283,9 @@ void test_mutex_lock()
262283
boost::interprocess::ipcdetail::OS_thread_t tm1;
263284
boost::interprocess::ipcdetail::thread_launch(tm1, thread_adapter<M>(&lock_and_sleep, &d1, mtx));
264285

265-
//Wait 1*BaseMs
266-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
286+
//Wait until tm1 really owns the lock, so that the order in which both
287+
//threads take it is guaranteed no matter how loaded the machine is
288+
BOOST_INTERPROCESS_CHECK(d1.m_acquired.wait());
267289

268290
// Locker two launches, but it won't hold the lock for 2*BaseMs seconds.
269291
boost::interprocess::ipcdetail::OS_thread_t tm2;
@@ -286,22 +308,25 @@ void test_mutex_lock_timeout()
286308

287309
unsigned wait_time_ms = BOOST_INTERPROCESS_TIMEOUT_WHEN_LOCKING_DURATION_MS;
288310

289-
data<M> d1(1, (int)wait_time_ms * 3);
311+
//tm1 keeps the lock until tm2 is done, so that tm2 provably tries to lock
312+
//an owned mutex and the library timeout is what makes it fail
313+
data<M> d1(1, 0, 0, true);
290314
data<M> d2(2, (int)wait_time_ms * 1);
291315

292-
// Locker one launches, and holds the lock for wait_time_ms * 3.
316+
// Locker one launches, and holds the lock until released.
293317
boost::interprocess::ipcdetail::OS_thread_t tm1;
294318
boost::interprocess::ipcdetail::thread_launch(tm1, thread_adapter<M>(&lock_and_sleep, &d1, mtx));
295319

296-
//Wait until tm1 acquires the lock
297-
boost::interprocess::ipcdetail::thread_sleep_ms(wait_time_ms);
320+
//Wait until tm1 really owns the lock
321+
BOOST_INTERPROCESS_CHECK(d1.m_acquired.wait());
298322

299-
// Locker two launches, and attempts to hold the lock for wait_time_ms * 2.
323+
// Locker two launches, and should fail to lock after the library timeout.
300324
boost::interprocess::ipcdetail::OS_thread_t tm2;
301325
boost::interprocess::ipcdetail::thread_launch(tm2, thread_adapter<M>(&lock_and_catch_errors, &d2, mtx));
302326

303-
//Wait completion
327+
//Wait completion. Only once tm2 is done the lock can be released
304328
boost::interprocess::ipcdetail::thread_join(tm2);
329+
d1.m_release.signal();
305330
boost::interprocess::ipcdetail::thread_join(tm1);
306331

307332
BOOST_INTERPROCESS_CHECK(d1.m_value == 1);
@@ -317,23 +342,26 @@ void test_mutex_try_lock()
317342

318343
M mtx;
319344

320-
data<M> d1(1);
345+
//tm1 keeps the lock until tm2 is done, so that tm2 provably tries to lock
346+
//an owned mutex instead of relying on tm1 still sleeping by then
347+
data<M> d1(1, 0, 0, true);
321348
data<M> d2(2);
322349

323-
// Locker one launches, holds the lock for 2*BaseMs seconds.
350+
// Locker one launches, holds the lock until released.
324351
boost::interprocess::ipcdetail::OS_thread_t tm1;
325352
boost::interprocess::ipcdetail::thread_launch(tm1, thread_adapter<M>(&try_lock_and_sleep, &d1, mtx));
326353

327-
//Wait 1*BaseMs
328-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
354+
//Wait until tm1 really owns the lock
355+
BOOST_INTERPROCESS_CHECK(d1.m_acquired.wait());
329356

330357
// Locker two launches, but it should fail acquiring the lock
331358
boost::interprocess::ipcdetail::OS_thread_t tm2;
332359
boost::interprocess::ipcdetail::thread_launch(tm2, thread_adapter<M>(&try_lock_and_sleep, &d2, mtx));
333360

334-
//Wait completion
335-
boost::interprocess::ipcdetail::thread_join(tm1);
361+
//Wait completion. Only once tm2 is done the lock can be released
336362
boost::interprocess::ipcdetail::thread_join(tm2);
363+
d1.m_release.signal();
364+
boost::interprocess::ipcdetail::thread_join(tm1);
337365

338366
//Only the first should succeed locking
339367
BOOST_INTERPROCESS_CHECK(d1.m_value == 1);
@@ -350,15 +378,19 @@ void test_mutex_timed_lock()
350378

351379
M mtx, m2;
352380

381+
//tm1 keeps the lock for 2*BaseMs once acquired. Both lockers must succeed
382+
//here, so tm2 gets a timeout with a wide margin over that hold time: a
383+
//loaded machine must not be able to make the timeout expire
353384
data<M> d1(1, 2*BaseMs, flag);
354-
data<M> d2(2, 2*BaseMs, flag);
385+
data<M> d2(2, 8*BaseMs, flag);
355386

356387
// Locker one launches, holds the lock for 2*BaseMs seconds.
357388
boost::interprocess::ipcdetail::OS_thread_t tm1;
358389
boost::interprocess::ipcdetail::thread_launch(tm1, thread_adapter<M>(&timed_lock_and_sleep, &d1, mtx));
359390

360-
//Wait 1*BaseMs
361-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
391+
//Wait until tm1 really owns the lock, so that the order in which both
392+
//threads take it is guaranteed no matter how loaded the machine is
393+
BOOST_INTERPROCESS_CHECK(d1.m_acquired.wait());
362394

363395
// Locker two launches, holds the lock for 2*BaseMs seconds.
364396
boost::interprocess::ipcdetail::OS_thread_t tm2;

test/sharable_mutex_test_template.hpp

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,27 @@
3535

3636
namespace boost { namespace interprocess { namespace test {
3737

38+
//Once the lock is owned, announce it so that the launcher does not need to
39+
//guess it with a sleep, and either keep it until the test releases it
40+
//(m_block) or for the requested amount of time
41+
template<typename SM>
42+
void hold_lock(data<SM> *pdata, unsigned msecs)
43+
{
44+
pdata->m_acquired.signal();
45+
if(pdata->m_block){
46+
BOOST_INTERPROCESS_CHECK(pdata->m_release.wait());
47+
}
48+
else if(msecs){
49+
boost::interprocess::ipcdetail::thread_sleep_ms(msecs);
50+
}
51+
}
52+
3853
template<typename SM>
3954
void plain_exclusive(void *arg, SM &sm)
4055
{
4156
data<SM> *pdata = static_cast<data<SM>*>(arg);
4257
boost::interprocess::scoped_lock<SM> l(sm);
43-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(3*BaseMs));
58+
hold_lock(pdata, unsigned(3*BaseMs));
4459
shared_val += 10;
4560
pdata->m_value = shared_val;
4661
}
@@ -50,9 +65,7 @@ void plain_shared(void *arg, SM &sm)
5065
{
5166
data<SM> *pdata = static_cast<data<SM>*>(arg);
5267
boost::interprocess::sharable_lock<SM> l(sm);
53-
if(pdata->m_msecs){
54-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(pdata->m_msecs));
55-
}
68+
hold_lock(pdata, unsigned(pdata->m_msecs));
5669
pdata->m_value = shared_val;
5770
}
5871

@@ -62,7 +75,7 @@ void try_exclusive(void *arg, SM &sm)
6275
data<SM> *pdata = static_cast<data<SM>*>(arg);
6376
boost::interprocess::scoped_lock<SM> l(sm, boost::interprocess::defer_lock);
6477
if (l.try_lock()){
65-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(3*BaseMs));
78+
hold_lock(pdata, unsigned(3*BaseMs));
6679
shared_val += 10;
6780
pdata->m_value = shared_val;
6881
}
@@ -74,9 +87,7 @@ void try_shared(void *arg, SM &sm)
7487
data<SM> *pdata = static_cast<data<SM>*>(arg);
7588
boost::interprocess::sharable_lock<SM> l(sm, boost::interprocess::defer_lock);
7689
if (l.try_lock()){
77-
if(pdata->m_msecs){
78-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(pdata->m_msecs));
79-
}
90+
hold_lock(pdata, unsigned(pdata->m_msecs));
8091
pdata->m_value = shared_val;
8192
}
8293
}
@@ -96,8 +107,9 @@ void test_plain_sharable_mutex()
96107
boost::interprocess::ipcdetail::OS_thread_t tw1;
97108
boost::interprocess::ipcdetail::thread_launch(tw1, thread_adapter<SM>(plain_exclusive, &e1, mtx));
98109

99-
//Give time to e1 to grab the mutex
100-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
110+
//Wait until e1 really owns the mutex, so that it is guaranteed to be
111+
//the first writer no matter how loaded the machine is
112+
BOOST_INTERPROCESS_CHECK(e1.m_acquired.wait());
101113

102114
// Writer two launches, tries to grab the lock, "clearly"
103115
// after Writer one will already be holding it.
@@ -139,8 +151,10 @@ void test_plain_sharable_mutex()
139151
boost::interprocess::ipcdetail::OS_thread_t thr2;
140152
boost::interprocess::ipcdetail::thread_launch(thr2, thread_adapter<SM>(plain_shared,&s2, mtx));
141153

142-
//Make sure they try to hold the sharable lock
143-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
154+
//Wait until both readers really own the sharable lock, so that they are
155+
//guaranteed to read the value before any writer changes it
156+
BOOST_INTERPROCESS_CHECK(s1.m_acquired.wait());
157+
BOOST_INTERPROCESS_CHECK(s2.m_acquired.wait());
144158

145159
// We launch two writers, that should block until the readers end
146160
boost::interprocess::ipcdetail::OS_thread_t tw1;
@@ -168,19 +182,22 @@ void test_try_sharable_mutex()
168182
SM mtx;
169183

170184
data<SM> s1(1);
171-
data<SM> e1(2);
185+
//e1 keeps the lock until the others are done, so that they provably try to
186+
//lock an owned mutex instead of relying on e1 still sleeping by then
187+
data<SM> e1(2, 0, 0, true);
172188
data<SM> e2(3);
173189

174190
// We start with some specialized tests for "try" behavior
175191
shared_val = 0;
176192

177-
// Writer one launches, holds the lock for 3*BaseMs seconds.
193+
// Writer one launches, holds the lock until released.
178194
boost::interprocess::ipcdetail::OS_thread_t tw1;
179195
boost::interprocess::ipcdetail::thread_launch(tw1, thread_adapter<SM>(try_exclusive,&e1,mtx));
180196

181-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
197+
//Wait until e1 really owns the mutex
198+
BOOST_INTERPROCESS_CHECK(e1.m_acquired.wait());
182199

183-
// Reader one launches, "clearly" after writer #1 holds the lock
200+
// Reader one launches, after writer #1 holds the lock
184201
// and before it releases the lock.
185202
boost::interprocess::ipcdetail::OS_thread_t thr1;
186203
boost::interprocess::ipcdetail::thread_launch(thr1, thread_adapter<SM>(try_shared,&s1,mtx));
@@ -189,8 +206,10 @@ void test_try_sharable_mutex()
189206
boost::interprocess::ipcdetail::OS_thread_t tw2;
190207
boost::interprocess::ipcdetail::thread_launch(tw2, thread_adapter<SM>(try_exclusive,&e2,mtx));
191208

209+
//Only once both are done the lock can be released
192210
boost::interprocess::ipcdetail::thread_join(tw2);
193211
boost::interprocess::ipcdetail::thread_join(thr1);
212+
e1.m_release.signal();
194213
boost::interprocess::ipcdetail::thread_join(tw1);
195214

196215
BOOST_INTERPROCESS_CHECK(e1.m_value == 10);
@@ -217,7 +236,7 @@ void timed_exclusive(void *arg, SM &sm)
217236
}
218237

219238
if (r){
220-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(3*BaseMs));
239+
hold_lock(pdata, unsigned(3*BaseMs));
221240
shared_val += 10;
222241
pdata->m_value = shared_val;
223242
}
@@ -242,7 +261,7 @@ void timed_shared(void *arg, SM &sm)
242261
}
243262

244263
if (r){
245-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(3*BaseMs));
264+
hold_lock(pdata, unsigned(3*BaseMs));
246265
pdata->m_value = shared_val;
247266
}
248267
}
@@ -253,40 +272,50 @@ void test_timed_sharable_mutex()
253272
for (int flag = 0; flag != (int)ETimedLockFlagsMax; ++flag)
254273
{
255274
SM mtx;
256-
data<SM> e1(3, 3*BaseMs, flag);
275+
//e1 keeps the lock until the lockers that must fail are done. Holding it
276+
//for a fixed time instead is not enough: under heavy CPU load a thread
277+
//can take longer to start than the hold time, find the mutex already
278+
//free and succeed, which would defeat the purpose of the test
279+
data<SM> e1(3, 3*BaseMs, flag, true);
257280
data<SM> e2(4, 1*BaseMs, flag);
258281
data<SM> s1(1, 1*BaseMs, flag);
259-
data<SM> s2(2, 3*BaseMs, flag);
282+
//s2 is the only one that must succeed. It is launched just before
283+
//releasing e1, so its timeout only has to cover that short window
284+
data<SM> s2(2, 9*BaseMs, flag);
260285

261286
// We begin with some specialized tests for "timed" behavior
262287

263288
shared_val = 0;
264289

265-
// Writer one will hold the lock for 3*BaseMs seconds.
290+
// Writer one will hold the lock until released.
266291
boost::interprocess::ipcdetail::OS_thread_t tw1;
267292
boost::interprocess::ipcdetail::thread_launch(tw1, thread_adapter<SM>(timed_exclusive,&e1,mtx));
268293

269-
boost::interprocess::ipcdetail::thread_sleep_ms(unsigned(1*BaseMs));
270-
// Writer two will "clearly" try for the lock after the readers
271-
// have tried for it. Writer will wait up 1*BaseMs seconds for the lock.
272-
// This write will fail.
294+
//Wait until e1 really owns the mutex, so that the others are guaranteed
295+
//to find it locked no matter how loaded the machine is
296+
BOOST_INTERPROCESS_CHECK(e1.m_acquired.wait());
297+
298+
// Writer two tries for the lock while writer one holds it, waiting up to
299+
// 1*BaseMs seconds. This write will fail.
273300
boost::interprocess::ipcdetail::OS_thread_t tw2;
274301
boost::interprocess::ipcdetail::thread_launch(tw2, thread_adapter<SM>(timed_exclusive,&e2,mtx));
275302

276-
// Readers one and two will "clearly" try for the lock after writer
277-
// one already holds it. 1st reader will wait 1*BaseMs seconds, and will fail
278-
// to get the lock. 2nd reader will wait 3*BaseMs seconds, and will get
279-
// the lock.
280-
303+
// Reader one also tries while writer one holds the lock, waiting up to
304+
// 1*BaseMs seconds, and will fail to get it.
281305
boost::interprocess::ipcdetail::OS_thread_t thr1;
282306
boost::interprocess::ipcdetail::thread_launch(thr1, thread_adapter<SM>(timed_shared,&s1,mtx));
283307

308+
// Both must have failed before the lock is released
309+
boost::interprocess::ipcdetail::thread_join(tw2);
310+
boost::interprocess::ipcdetail::thread_join(thr1);
311+
312+
// Reader two is launched and the lock released right after, so it gets
313+
// the lock and reads the value written by writer one
284314
boost::interprocess::ipcdetail::OS_thread_t thr2;
285315
boost::interprocess::ipcdetail::thread_launch(thr2, thread_adapter<SM>(timed_shared,&s2,mtx));
316+
e1.m_release.signal();
286317

287318
boost::interprocess::ipcdetail::thread_join(thr2);
288-
boost::interprocess::ipcdetail::thread_join(thr1);
289-
boost::interprocess::ipcdetail::thread_join(tw2);
290319
boost::interprocess::ipcdetail::thread_join(tw1);
291320

292321
BOOST_INTERPROCESS_CHECK(e1.m_value == 10);

0 commit comments

Comments
 (0)