Issue #94 [6/x]: cleanup of JavaMonitor
Use `#blockInterrupts` / `#unblockInterrupts` tp atomically set
`owningProcess` and `count` variables. This is not only faster
(as shared lock would block interrupts too) but also correct.
The old Marcel's code somehow worked but still allowed for a race.
"
COPYRIGHT (c) 1996-2015 by Claus Gittinger
New code and modifications done at SWING Research Group [1]:
COPYRIGHT (c) 2010-2015 by Jan Vrany, Jan Kurs and Marcel Hlopko
SWING Research Group, Czech Technical University in Prague
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
[1] Code written at SWING Research Group contains a signature
of one of the above copright owners. For exact set of such code,
see the differences between this version and version stx:libjava
as of 1.9.2010
"
"{ Package: 'stx:libjava' }"
"{ NameSpace: Smalltalk }"
Object subclass:#JavaMonitor
instanceVariableNames:'owningProcess monitorSema count waitingSema waitEnabled
ownerPrintString'
classVariableNames:'instVarAccess'
poolDictionaries:''
category:'Languages-Java-Support'
!
!JavaMonitor class methodsFor:'documentation'!
copyright
"
COPYRIGHT (c) 1996-2015 by Claus Gittinger
New code and modifications done at SWING Research Group [1]:
COPYRIGHT (c) 2010-2015 by Jan Vrany, Jan Kurs and Marcel Hlopko
SWING Research Group, Czech Technical University in Prague
This software is furnished under a license and may be used
only in accordance with the terms of that license and with the
inclusion of the above copyright notice. This software may not
be provided or otherwise made available to, or used by, any
other person. No title to or ownership of the software is
hereby transferred.
[1] Code written at SWING Research Group contains a signature
of one of the above copright owners. For exact set of such code,
see the differences between this version and version stx:libjava
as of 1.9.2010
"
! !
!JavaMonitor class methodsFor:'initialization'!
initialize
instVarAccess := RecursionLock forMutualExclusion.
"Created: / 29-11-2011 / 11:23:56 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified (format): / 09-01-2013 / 16:10:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!JavaMonitor class methodsFor:'instance creation'!
for: owningObject
^ self basicNew initializeFor: owningObject.
"Created: / 30-11-2011 / 20:39:55 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
!
for: owningObject thread: threadOrNil nestedLockCount: count
^ self basicNew initializeFor: owningObject thread: threadOrNil nestedLockCount: count
"Created: / 26-08-2012 / 17:01:43 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!JavaMonitor methodsFor:'accessing'!
count
^ count
!
processesEntered
"Return a alist of processes that entered the monitor. For testing / debugging purposes only!!"
^ owningProcess isNil
ifTrue:[ #() ]
ifFalse:[ (Array with: owningProcess) , monitorSema waitingProcesses ]
"Created: / 20-11-2011 / 13:22:15 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified (format): / 03-06-2017 / 23:10:31 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!JavaMonitor methodsFor:'atomic'!
disableWait
JavaVM monitorTrace ifTrue:[
Logger log: ('Waiting is disabled on monitor for %1' bindWith: ownerPrintString) severity:Logger severityDEBUG facility:#JVM.
].
"/ critical region not needed here
"/ self instVarAccessCritical: [ waitEnabled := false ].
waitEnabled := false.
"Created: / 30-11-2011 / 20:34:40 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 02-03-2015 / 14:06:51 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
enableWait
"/ critical region not needed here
"/ self instVarAccessCritical: [waitEnabled := true].
waitEnabled := true
"Created: / 30-11-2011 / 20:34:31 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified (format): / 11-10-2013 / 11:17:58 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
instVarAccessCritical: aBlock
^ instVarAccess critical: aBlock.
"Created: / 03-06-2017 / 22:09:24 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 03-06-2017 / 23:15:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
waitEnabled
"/ critical region not needed here
"/ self instVarAccessCritical: [ ^ waitEnabled].
^ waitEnabled.
"Created: / 30-11-2011 / 20:34:56 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
! !
!JavaMonitor methodsFor:'initialization'!
initializeFor: owningObject
self initializeFor: owningObject thread: 0 nestedLockCount: 0.
"Created: / 30-11-2011 / 20:39:31 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 26-08-2012 / 17:25:55 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
initializeFor:owningObject thread:threadId nestedLockCount:cnt
"/ self assert: (owningObject isJavaObject or:[owningObject isJavaClass]).
waitEnabled := true.
waitingSema := Semaphore new:0.
ownerPrintString := owningObject class name , '@' , owningObject identityHash printString.
"/Not locked...
threadId == 0 ifTrue:[
monitorSema := Semaphore new:1.
count := 0.
^self.
] ifFalse: [
"threadId is not zero (zero is threadId of scheduler process, which will never try to acquire the monitor)"
"so it means it is possible that the thin lock is already locked for other thread, we must be careful "
owningProcess := ObjectMemory processesKnownInVM detect:[:p|p id == threadId] ifNone:[nil].
self assert: owningProcess notNil.
cnt timesRepeat:[
"/ JavaVM enteredMonitorsOf: owningProcess add: owningObject.
"/ JavaVM acquiredMonitorsOf: owningProcess add: owningObject.
].
monitorSema := Semaphore new: 0.
count := cnt.
].
"Created: / 26-08-2012 / 17:02:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
"Modified: / 03-06-2017 / 23:10:47 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!JavaMonitor methodsFor:'public'!
acquire
| thisProcess wasBlocked hasAcquired |
hasAcquired := nil.
thisProcess := Processor activeProcess.
owningProcess == thisProcess ifTrue:[
"/ Process already ackquired the monitor, increase the
"/ count and continue...
count := count + 1.
^self.
].
wasBlocked := OperatingSystem blockInterrupts.
[
(owningProcess notNil and:[ owningProcess isDead ]) ifTrue:[
"/ Process that acquired the monitor died without releasing it.
"/ This should not happen.
wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
self assert: false description: 'Process that acquired the monitor died without releasing it'.
owningProcess := nil.
count := 0.
].
"/ We need to know that we already waited on and got semaphore
"/ in case the Semaphore >> #wait is prematurely terminated.
"/ Q: Can this actually happen? If so, how?
hasAcquired := monitorSema wait.
owningProcess := thisProcess.
count := 1.
wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
] ifCurtailed:[
hasAcquired notNil ifTrue:[
OperatingSystem blockInterrupts.
count := 0.
owningProcess := nil.
monitorSema signal.
].
wasBlocked ifFalse:[OperatingSystem unblockInterrupts].
]
"Created: / 20-11-2011 / 13:21:46 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 11-08-2017 / 22:08:16 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
enter
self acquire.
"Created: / 20-11-2011 / 13:21:42 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 03-06-2017 / 23:11:03 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
exit
| thisProcess |
"JV@2017-06-03: Question: Is it legal (i.e., is the good reason to) call #exit
on a monitor by a process that does not own it? Shouldn't we rather throw an
error"
thisProcess := Processor activeProcess.
(self isOwnedBy: thisProcess) ifTrue: [ self release. ].
"Created: / 20-11-2011 / 13:21:54 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified (format): / 03-06-2017 / 23:13:39 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
notify
"wakeup one waiting process"
| thisProcess |
thisProcess := Processor activeProcess.
self assert: owningProcess == thisProcess.
JavaVM monitorTrace ifTrue:[
Logger
log: ('%1: notifying %2 processes' bindWith: thisProcess printString
with: waitingSema waitingProcesses size)
severity: Logger severityDEBUG
facility: #JVM.
].
waitingSema signal.
Processor yield.
"Created: / 22-11-2011 / 12:14:23 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 11-08-2017 / 22:25:44 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
notifyAll
"wakeup one waiting process"
| thisProcess |
thisProcess := Processor activeProcess.
self assert: owningProcess == thisProcess.
JavaVM monitorTrace ifTrue:[
Logger
log: ('%1: notifying all %2 processes' bindWith: thisProcess printString
with: waitingSema waitingProcesses size)
severity: Logger severityDEBUG
facility: #JVM.
].
waitingSema signalForAll.
Processor yield.
"Created: / 22-11-2011 / 12:14:36 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 11-08-2017 / 22:26:01 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
release
| wasBlocked |
self assert: (self isOwnedBy: owningProcess).
wasBlocked := OperatingSystem blockInterrupts.
count == 1 ifTrue:[
owningProcess := nil.
count := 0.
monitorSema signal.
] ifFalse:[
count := count - 1.
].
OperatingSystem unblockInterrupts.
"Created: / 20-11-2011 / 13:21:52 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 11-08-2017 / 22:18:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
wait
"make owning process to go to wait"
self waitForMilliseconds: nil.
"Created: / 22-11-2011 / 11:57:56 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
!
waitForMilliseconds: timeOut
"make owning process to go to wait, but not longer than timeout"
| thisProcess countBeforeWait |
thisProcess := Processor activeProcess.
self assert: owningProcess == thisProcess.
self waitEnabled ifFalse: [
JavaVM monitorTrace ifTrue:[
Logger
log: ('%1 wanted to go to sleep, but it cant, this monitor is for %2 which is already dead'
bindWith: thisProcess printString
with: ownerPrintString)
severity: Logger severityDEBUG
facility: #JVM.
].
^ self.
].
JavaVM monitorTrace ifTrue:[
Logger
log: ('%1 is going to wait on %3 for timeout: %2'
bindWith: thisProcess printString
with: timeOut
with: ownerPrintString printString)
severity: #debug
facility: #JVM.
].
countBeforeWait := count.
"/ Release the monitor so other thread can acquire it
"/ and call #notify or #notifyAll
count := 1. "/ note that at this point we still own the monitor
self release.
"/ Monitor released, wait...
"JV@2011-11-25: zero timeout means wait without timeout!!!!!!"
timeOut == 0 ifTrue: [ waitingSema wait ] ifFalse: [
waitingSema waitWithTimeoutMs: timeOut
].
JavaVM monitorTrace ifTrue:[
Logger
log: ('%1 has been notified and is trying to acquire monitor for %2 which is owned by %3'
bindWith: thisProcess printString with: ownerPrintString printString with: owningProcess printString)
severity: #debug
facility: #JVM.
].
"/ We have been notified, re-acquire the monitor
self acquire.
count := countBeforeWait. "/ note that at this point we already own the monitor
"Created: / 22-11-2011 / 12:52:45 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 01-12-2011 / 10:57:52 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
"Modified: / 11-08-2017 / 23:04:45 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !
!JavaMonitor methodsFor:'queries'!
isAcquired
^ self instVarAccessCritical: [ owningProcess notNil ].
"Created: / 20-11-2011 / 13:22:26 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
!
isOwnedBy: aProcess
^ self instVarAccessCritical: [ owningProcess == aProcess ].
"Created: / 20-11-2011 / 13:23:14 / Marcel Hlopko <hlopkmar@fel.cvut.cz>"
! !
!JavaMonitor class methodsFor:'documentation'!
version_CVS
^ '$Header$'
!
version_HG
^ '$Changeset: <not expanded> $'
!
version_SVN
^ '$Id$'
! !
JavaMonitor initialize!