JavaMonitor.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Fri, 11 Aug 2017 22:37:56 +0100
changeset 3778 2297f9daef8a
parent 3777 971961e59d5e
child 3779 973f2396c750
permissions -rw-r--r--
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!