Issue #94 [6/x]: refactor `RecursionLock` to use thinlocks.
Use thinlocking in `RecursionLock` to speed up the common case
(no contention). The code is clearly not optimal, further
optimization will come later.
--- a/AbstractLock.st Tue Aug 29 10:05:32 2017 +0100
+++ b/AbstractLock.st Wed Aug 30 12:38:37 2017 +0100
@@ -205,34 +205,6 @@
"Created: / 25-08-2017 / 22:55:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
-critical:aBlock
- "Evaluate aBlock as a critical region. Same process may
- enter critical region again, i.e., nesting allowed."
- ^self critical: aBlock timeoutMs: nil ifBlocking: nil
-
- "Modified (comment): / 25-08-2017 / 09:47:36 / Jan Vrany <jan.vrany@fit.cvut.cz>"
-!
-
-critical:aBlock ifBlocking:blockingBlock
- "Like #critical:, but do not block if the lock cannot be acquired.
- Instead, return the value of the second argument, `blockingBlock`."
-
- ^ self critical:aBlock timeoutMs:0 ifBlocking:blockingBlock.
-!
-
-critical:aBlock timeoutMs:timeoutMs ifBlocking:blockingBlock
- "Like #critical:, but do not block if the lock cannot be acquired
- within `timeoutMs` milliseconds. Instead, return the value of `blockingBlock.`"
-
- | acquired |
- acquired := self acquireWithTimeoutMs: timeoutMs.
- acquired ifTrue:[
- ^ aBlock ensure:[ self release ]
- ] ifFalse:[
- ^ blockingBlock value.
- ].
-!
-
release
"
Release the lock. Return true of lock has been released, `false` if
@@ -272,6 +244,44 @@
"Modified: / 29-08-2017 / 09:53:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
+!AbstractLock methodsFor:'synchronized evaluation'!
+
+critical:aBlock
+ "Evaluate aBlock as a critical region. Same process may
+ enter critical region again, i.e., nesting allowed."
+ ^self critical: aBlock timeoutMs: nil ifBlocking: nil
+
+ "Modified (comment): / 25-08-2017 / 09:47:36 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!
+
+critical:aBlock ifBlocking:blockingBlock
+ "Like #critical:, but do not block if the lock cannot be acquired.
+ Instead, return the value of the second argument, `blockingBlock`."
+
+ ^ self critical:aBlock timeoutMs:0 ifBlocking:blockingBlock.
+!
+
+critical:aBlock timeoutMs:timeoutMs ifBlocking:blockingBlock
+ "Like #critical:, but do not block if the lock cannot be acquired
+ within `timeoutMs` milliseconds. Instead, return the value of `blockingBlock.`"
+
+ | acquired retval |
+
+ [
+ acquired := self acquireWithTimeoutMs: timeoutMs.
+ acquired ifTrue:[
+ retval := aBlock value
+ ] ifFalse:[
+ retval := blockingBlock value.
+ ]
+ ] ensure:[
+ acquired ifTrue:[
+ self release.
+ ]
+ ].
+ ^retval
+! !
+
!AbstractLock class methodsFor:'documentation'!
version_HG
--- a/RecursionLock.st Tue Aug 29 10:05:32 2017 +0100
+++ b/RecursionLock.st Wed Aug 30 12:38:37 2017 +0100
@@ -20,6 +20,19 @@
category:'Kernel-Processes'
!
+!RecursionLock primitiveDefinitions!
+%{
+#define THINLOCKING
+#ifdef THINLOCKING
+# include <thinlocks.h>
+static inline unsigned INT* stxGetLockwordPtr(OBJ o) {
+ return (unsigned INT*)(&__OINST(o, process));
+}
+
+#endif
+%}
+! !
+
!RecursionLock class methodsFor:'documentation'!
copyright
@@ -99,17 +112,163 @@
^ self new
! !
+!RecursionLock methodsFor:'accessing'!
+
+count
+ ^ self processAndCount at: 2.
+
+!
+
+owner
+ ^ self processAndCount at: 1.
+
+! !
+
!RecursionLock methodsFor:'acquire & release'!
+acquireWithTimeoutMs: timeout
+ "
+ Acquire the lock:
+
+ * If the lock is not owned by any process, lock it and return immediately.
+ * If the lock is already owned by the calling process, return immediately.
+ * Otherwise, wait until owning process release it (by means of #release)
+ at most `timeout` milliseconds. If `timeout` is nil, wait forever.
+
+ Return `true` if the lock has been acquired or `false` if bot (e.g. wait
+ timed out)
+ "
+%{ /* NOCONTEXT */
+#ifdef THINLOCKING
+ if ( stxThinLock( stxGetLockwordPtr(self) ) == StxThinlockSuccess ) {
+ return (true);
+ }
+#endif
+%}.
+ "/ Inflate the lock if it's not yet inflated.
+ "/
+ "/ Note, that #inflate method checks again if it's inflated or not,
+ "/ it may haopen some other thread inflated the lock in between the check
+ "/ here and code in #inflate.
+ process class == SmallInteger ifTrue:[ self inflate ].
+ ^ super acquireWithTimeoutMs: timeout
+!
+
release
"
- Release the lock.
+ Release the lock. Return true of lock has been released, `false` if
+ not (because calling process does not own it).
"
- super release ifFalse:[
+%{ /* NOCONTEXT */
+#ifdef THINLOCKING
+ if ( stxThinUnlock( stxGetLockwordPtr(self) ) == StxThinlockSuccess ) {
+ return (true);
+ }
+#endif
+%}.
+ "/ Inflate the lock if it's not yet inflated.
+ "/
+ "/ Note that #inflate method checks again if it's inflated or not,
+ "/ it may haopen some other thread inflated the lock in between the check
+ "/ here and code in #inflate
+ "/
+ "/ Note that `someobject class == SmallInteger` is handled as a special
+ "/ case in stc and compiled as `__isSmallInteger(someobject)` and thus
+ "/ very fast - just bitwise and + non-zero test. Don't change!
+ process class == SmallInteger ifTrue:[ self inflate ].
+ super release ifFalse:[
self error: ('Calling process does not own the lock (caller: %1, owner: %2)' bindWith: Processor activeProcess id with: (process isNil ifTrue:['<no owner>'] ifFalse:[process id])).
].
+! !
- "Created: / 03-10-2017 / 13:06:23 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+!RecursionLock methodsFor:'initialization'!
+
+initialize
+ super initialize.
+ process := 0.
+
+ "Modified: / 25-01-1997 / 00:19:15 / cg"
+ "Modified: / 29-08-2017 / 09:53:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
+! !
+
+!RecursionLock methodsFor:'private'!
+
+inflate
+ "Inflates (thin) lock (into fat lock). If the lock is already a fat lock,
+ #inflate is no-op.
+
+ Called by:
+
+ * #acquire* in case of contention or if maximum nesting count
+ is exceeded (unlikely)
+ * #release in case of contention
+
+
+ "
+
+ | processAndCount wasBlocked |
+
+
+ processAndCount := Array new: 2.
+ wasBlocked := OperatingSystem blockInterrupts.
+ "/ Note that `someobject class == SmallInteger` is handled as a special
+ "/ case in stc and compiled as `__isSmallInteger(someobject)` and thus
+ "/ very fast - just bitwise and + non-zero test. Don't change!
+ process class == SmallInteger ifTrue:[
+ self processAndCountInto: processAndCount.
+ process := processAndCount at: 1.
+ count := processAndCount at: 2.
+ sema setCount: 0.
+ ].
+ wasBlocked ifFalse:[ OperatingSystem unblockInterrupts ].
+
+!
+
+processAndCount
+ | processAndCount |
+
+ processAndCount := Array new: 2.
+ self processAndCountInto: processAndCount.
+ ^ processAndCount
+
+!
+
+processAndCountInto: anArray
+ "Fills in `anArray` with owning process and nesting count.
+
+ Note that by the time this method returns, the data in given array may
+ be already obsolete.
+ "
+ | pid cnt proc |
+
+ "/ Note that `someobject class == SmallInteger` is handled as a special
+ "/ case in stc and compiled as `__isSmallInteger(someobject)` and thus
+ "/ very fast - just bitwise and + non-zero test. Don't change!
+ process class == SmallInteger ifTrue:[
+ %{
+#ifdef THINLOCKING
+ unsigned INT _pid = stxLockwordGetPid( *stxGetLockwordPtr(self) );
+ unsigned INT _cnt = stxLockwordGetCnt( *stxGetLockwordPtr(self) );
+
+ if (_pid == INV_PROCESS_ID) {
+ pid = nil;
+ cnt = __MKINT(0);
+ } else {
+ pid = __MKINT(_pid);
+ cnt = __MKINT(_cnt);
+ }
+#endif
+ %}.
+ pid notNil ifTrue:[
+ proc := ObjectMemory processesKnownInVM detect:[:p|p id == pid] ifNone:[nil].
+ ].
+ ] ifFalse:[
+ proc := process.
+ cnt := count.
+ ].
+ anArray at: 1 put: proc.
+ anArray at: 2 put: cnt.
+
! !
!RecursionLock methodsFor:'printing & storing'!
@@ -150,7 +309,7 @@
|p|
- ^ (p := process) notNil and:[Processor activeProcess ~~ p and:[p isDead not]]
+ ^ (p := self owner) notNil and:[Processor activeProcess ~~ p and:[p isDead not]]
! !
!RecursionLock methodsFor:'signaling'!