Skip to content

Commit 03cd5e2

Browse files
author
Mathieu Stefani
committed
Made some refinement to the GcLock. Also added a guard against infinite spinning
1 parent b9d25f5 commit 03cd5e2

2 files changed

Lines changed: 96 additions & 56 deletions

File tree

gc/gc_lock.h

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define __mmap__gc_lock_h__
1111

1212
#define GC_LOCK_DEBUG 0
13+
#define GC_LOCK_SPIN_DEBUG 0
1314

1415
#include "jml/utils/exc_assert.h"
1516
#include "jml/arch/atomic_ops.h"
@@ -21,6 +22,30 @@
2122
# include <iostream>
2223
#endif
2324

25+
#include <chrono>
26+
#include "jml/arch/backtrace.h"
27+
28+
#if GC_LOCK_SPIN_DEBUG
29+
30+
# define GCLOCK_SPINCHECK_DECL \
31+
std::chrono::time_point<std::chrono::system_clock> __start, __end; \
32+
__start = std::chrono::system_clock::now();
33+
34+
# define GCLOCK_SPINCHECK \
35+
do { \
36+
__end = std::chrono::system_clock::now(); \
37+
std::chrono::duration<double> elapsed = __end - __start; \
38+
if (elapsed > std::chrono::seconds(10)) { \
39+
throw ML::Exception("GCLOCK_SPINCHECK: spent more than 10" \
40+
" seconds spinning"); \
41+
} \
42+
} while (0)
43+
#else
44+
# define GCLOCK_SPINCHECK_DECL
45+
# define GCLOCK_SPINCHECK ((void) 0)
46+
#endif
47+
48+
2449
namespace Datacratic {
2550

2651
/*****************************************************************************/
@@ -49,6 +74,7 @@ struct GcLockBase : public boost::noncopyable {
4974
struct ThreadGcInfoEntry {
5075
ThreadGcInfoEntry()
5176
: inEpoch(-1), readLocked(0), writeLocked(0), exclusiveLocked(0),
77+
writeEntered(0),
5278
owner(0)
5379
{
5480
}
@@ -66,6 +92,7 @@ struct GcLockBase : public boost::noncopyable {
6692
int readLocked;
6793
int writeLocked;
6894
int exclusiveLocked;
95+
int writeEntered;
6996

7097
GcLockBase *owner;
7198

@@ -258,46 +285,66 @@ struct GcLockBase : public boost::noncopyable {
258285
void enterWriteShared(GcInfo::PerThreadInfo * info = 0,
259286
RunDefer runDefer = RD_YES)
260287
{
261-
Word oldVal, newVal;
262-
for (;;) {
263-
auto writeLock = data->writeLock;
264-
// We are write locked, continue spinning
265-
if ((writeLock >> (Bits - 1)) == 1)
266-
continue;
267-
268-
oldVal = data->writeLock;
269-
newVal = oldVal + 1;
270-
271-
// If the CAS fails, it surely means someone in
272-
// the meantime set the stop bit, we then retry.
273-
if (ML::cmp_xchg(data->writeLock, oldVal, newVal))
274-
break;
275-
}
276-
288+
ThreadGcInfoEntry & entry = getEntry(info);
289+
if (!entry.writeEntered) {
290+
Word oldVal, newVal;
291+
GCLOCK_SPINCHECK_DECL
277292

278-
enterShared(info, runDefer);
279-
}
293+
for (;;) {
294+
auto writeLock = data->writeLock;
295+
// We are write locked, continue spinning
296+
GCLOCK_SPINCHECK;
297+
298+
if ((writeLock >> (Bits - 1)) == 1) {
299+
sched_yield();
300+
continue;
301+
}
302+
303+
oldVal = data->writeLock;
304+
305+
// When our thread reads current writeLock, it might
306+
// have been locked by an other thread spinning
307+
// right after having been unlocked.
308+
// Thus we could end up in a really weird situation,
309+
// where the linearization point marked by the CAS
310+
// would only be reached after the writeBarrier
311+
// in the locking thread.
312+
//
313+
// To prevent this, we check again if the stop bit
314+
// is set.
315+
if ((oldVal >> (Bits - 1)) == 1) {
316+
sched_yield();
317+
continue;
318+
}
280319

281-
void exitWriteShared(GcInfo::PerThreadInfo * info = 0,
282-
RunDefer runDefer = RD_YES)
283-
{
284-
ExcAssertGreater(data->writeLock, 0);
285-
ML::atomic_dec(data->writeLock);
320+
newVal = oldVal + 1;
321+
322+
323+
// If the CAS fails, someone else in the meantime
324+
// must have entered a CS and incremented the counter
325+
// behind our back. We then retry
326+
if (ML::cmp_xchg(data->writeLock, oldVal, newVal))
327+
break;
328+
}
329+
286330

287-
exitShared(info, runDefer);
331+
enterShared(info, runDefer);
332+
}
333+
++entry.writeEntered;
288334
}
289335

290-
void enterReadShared(GcInfo::PerThreadInfo * info = 0,
291-
RunDefer runDefer = RD_YES)
336+
void exitWriteShared(GcInfo::PerThreadInfo * info = 0,
337+
RunDefer runDefer = RD_YES)
292338
{
293339
ThreadGcInfoEntry & entry = getEntry(info);
340+
ExcAssertGreater(entry.writeEntered, 0);
341+
--entry.writeEntered;
342+
if (!entry.writeEntered) {
343+
ExcAssertGreater(data->writeLock, 0);
344+
ML::atomic_dec(data->writeLock);
294345

295-
entry.enterShared(runDefer);
296-
}
297-
void exitReadShared(GcInfo::PerThreadInfo * info = 0,
298-
RunDefer runDefer = RD_YES)
299-
{
300-
exitShared(info, runDefer);
346+
exitShared(info, runDefer);
347+
}
301348
}
302349

303350
int isLockedShared(GcInfo::PerThreadInfo * info = 0) const
@@ -359,11 +406,20 @@ struct GcLockBase : public boost::noncopyable {
359406
ThreadGcInfoEntry &entry = getEntry();
360407
if (!entry.writeLocked) {
361408
Word oldValue, newValue;
409+
GCLOCK_SPINCHECK_DECL
362410
for (;;) {
411+
GCLOCK_SPINCHECK;
363412
if ((data->writeLock >> (Bits - 1)) == 1)
364413
continue;
365414

366415
oldValue = data->writeLock;
416+
417+
// See enterWriteShared for the reason of this
418+
// double-check.
419+
// TODO: is this really needed ?
420+
if ((oldValue >> (Bits - 1)) == 1)
421+
continue;
422+
367423
newValue = oldValue | StopBitMask;
368424
if (ML::cmp_xchg(data->writeLock, oldValue, newValue))
369425
break;
@@ -428,23 +484,6 @@ struct GcLockBase : public boost::noncopyable {
428484
};
429485

430486

431-
struct ReadSharedGuard {
432-
ReadSharedGuard(GcLockBase & lock, RunDefer runDefer = RD_YES)
433-
: lock(lock),
434-
runDefer(runDefer)
435-
{
436-
lock.enterReadShared(0, runDefer);
437-
}
438-
439-
~ReadSharedGuard()
440-
{
441-
lock.exitReadShared(0, runDefer);
442-
}
443-
444-
GcLockBase & lock;
445-
const RunDefer runDefer;
446-
};
447-
448487
struct WriteSharedGuard {
449488
WriteSharedGuard(GcLockBase & lock, RunDefer runDefer = RD_YES)
450489
: lock(lock),
@@ -638,5 +677,6 @@ struct SharedGcLock : public GcLockBase
638677

639678
} // namespace Datacratic
640679

680+
641681
#endif /* __mmap__gc_lock_h__ */
642682

gc/testing/gc_test.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ BOOST_AUTO_TEST_CASE(test_write_exclusion)
263263
std::atomic<uint64_t> numRead { 0 };
264264
std::atomic<uint64_t> numLockWrite { 0 };
265265

266-
auto doWriteSharedThread = [&]() {
266+
auto doWriteThread = [&]() {
267267
while (!finished.load()) {
268268
GcLock::WriteSharedGuard guard(lock);
269269

@@ -294,9 +294,9 @@ BOOST_AUTO_TEST_CASE(test_write_exclusion)
294294
numLockWrite.fetch_sub(1);
295295
};
296296

297-
auto doReadSharedThread = [&]() {
297+
auto doReadThread = [&]() {
298298
while (!finished.load()) {
299-
GcLock::ReadSharedGuard guard(lock);
299+
GcLock::SharedGuard guard(lock);
300300

301301
numRead.fetch_add(1);
302302
this_thread::sleep_for(chrono::milliseconds(10));
@@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(test_write_exclusion)
309309

310310
boost::thread_group group;
311311
for (size_t i = 0; i < writeThreads; ++i) {
312-
group.create_thread(doWriteSharedThread);
312+
group.create_thread(doWriteThread);
313313
}
314314

315315
this_thread::sleep_for(chrono::milliseconds(500));
@@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE(test_write_exclusion)
332332
finished.store(false);
333333
boost::thread_group group;
334334
for (size_t i = 0; i < writeThreads; ++i) {
335-
group.create_thread(doWriteSharedThread);
335+
group.create_thread(doWriteThread);
336336
}
337337

338338
this_thread::sleep_for(chrono::milliseconds(500));
@@ -358,10 +358,10 @@ BOOST_AUTO_TEST_CASE(test_write_exclusion)
358358
finished.store(false);
359359
boost::thread_group group;
360360
for (size_t i = 0; i < writeThreads; ++i) {
361-
group.create_thread(doWriteSharedThread);
361+
group.create_thread(doWriteThread);
362362
}
363363
for (size_t i = 0; i < readThreads; ++i) {
364-
group.create_thread(doReadSharedThread);
364+
group.create_thread(doReadThread);
365365
}
366366

367367
this_thread::sleep_for(chrono::milliseconds(200));

0 commit comments

Comments
 (0)