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"
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+
2449namespace 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
0 commit comments