How to prevent reentrancy inside same process ?
I use the following code to protect some code for being called by multiple processes at same time :
lock +^TEMP("FOO"):0//don't waitquit:'$test//critical section//...lock -^TEMP("FOO")This works between processes but it does not prevent the same process entering critical section twice.
How to do that, is there any lock option ? I would like it to behave as the lock in C# or Java.
It's OK for me to use something else than LOCK instruction (eg : signals)
Comments
One way would be to add a check for the existence of a process-private global (PPG) node. Something like this:
lock +^TEMP("FOO"):0//don't waitquit:'$testquit:$get(^||CRITSECT("FOO"))
set ^||CRITSECT("FOO")=1//critical section//...kill ^||CRITSECT("FOO")
lock -^TEMP("FOO")We can use the %SYS.LockQuery class and its Listquery function to check whether the global is already locked. If it is, we can skip attempting to acquire the lock.
Check for the specific process
ClassMethodLOCK(Lock, Mode)
{
If '..IsLocked("^A","X") {
Lock +^A
}
Else {
Write"Locked"
}
}
// X - Exclusive// S - SharedClassMethod IsLocked(Global As%String, Mode As%String)
{
#dim status = 0Set tResult = ##class(%SYS.LockQuery).ListFunc($J)
While tResult.%Next() {
If tResult.Mode=Mode&&(tResult.LockString=Global) Set status= 1
}
Return status
}However, the above code only checks for a specific process and does not account for other processes with Xclusive or Shared locks. The sample below checks for all acquired locks, returning their status and lock type.
ClassMethod IsLocked(Global As%String, Mode As%String)
{
#dim status = 0Set tResult = ##class(%SYS.LockQuery).ListFunc()
While tResult.%Next() {
If tResult.LockString=Global {
If tResult.Mode?1(1"X",1"S") Set status= 1_","_tResult.Mode
}
}
Return status #; status be like "1,S" or "1,X"
}
I would have say that code that check for lock and then lock (in two steps) is usually not thread safe (there might be race conditions between the two steps) but since here it can only happen within same process (and not two different processes) that might be OK.
I don't entirely understand the problem, you ask for "inside same process" but further you write "protect some code for being called by multiple processes at same time" - same ormultiple processes?
The only way (I know) to reenter a code inside the same process is, using recursion (there are neither threads nor events in ObjectScript). If you don't want reentrace, do not use recursion.
If you talk about multiple processes (running at the same time) then you have to use some kind of semaphore to let in just one process at any given time in a critical code section, see the examples below. I would use the version with device, because in all error cases (abnormal shut down, process error, etc) IRIS closes the device automagically - globals may have leftovers in case of errors.
Class DC.OnlyOne Extends%RegisteredObject
{
/// device numbers 20-46 and 200-223 are routine interlock devices/// Choose one of them/// ClassMethod UsingDevice(testTime = 0)
{
set myDevice = 45open myDevice::0if$t {
// run that critical sectionhang testTime
set ans="Critical section is done"
} else { set ans = "Critical section in use" }
close myDevice
quit ans
}
/// a more meaningful name then ^X would be reasonable/// a KILL ^X in %ZSTART.mac, label SYSTEM is also advisable/// for the case, the system was shut down abnormallyClassMethod UsingGlobal(testTime = 0)
{
if$i(^X)=1 {
// run that critical sectionhang testTime
set ans="Critical section is done"
} else { set ans="Critical section in use" }
if$i(^X,-1)
quit ans
}
ClassMethod UsingLock(tim = 10)
{
// see John's example
}
}
"protect some code for being called by multiple processes at same time" - same or multiple processes?
Both. LOCK in other programming languages (eg: C#, Java, ...) will protect any process (other processes or your own process) to enter the critical section twice.
I'm neither a C# nor a Java guru, but I know definitively that a LOCK in ObjectScript protects YOUR resources to be to be taken by OTHERS. It does NOT prevents you, to use your OWN resource again and again. And yes, I know that you can get the lock count from ^$LOCK SSV. Locks like
lock +^Test:1if$testlock +^Test:1if$testwrite"It's my resource!"lock -^Test,-^Testare pointless. The above locks could be a short version for:
(Class)Method MyCrticalSection()
{
lock +^Test:1if '$testquit// some commandsdo..MyCriticalSection() // despite the lock, this call enters this section again// some more commandslock -^Test
}whereas the above lock guards against OTHER jobs to enter that method, it doesn't locks my own job out.
Maybe I don't understand your problem. People use to say, if there is a problem I do not understand, than either the problem is very simple or extraordinarily complicated...
Hello. I'm afraid objectscript does not have the exact equivalent of the Java/C# LOCK command.
As far as re-entrant in the same process goes: there is no need, as objectscript has no ability to parallel process in one process. It's a "simple", interpreted language with effectively one thread (though the interpreter itself is another matter).
What is worse (maybe) is the way programmers prevent problems with concurrency is "by convention only". Our lock command puts entries in a shared "lock table", and that is then available for other processors to read to see if something else is using it. But it can be ignored, unlike in Java. See https://docs.intersystems.com/iris20251/csp/docbook/DocBook.UI.Page.cls?KEY=GCOS_lockbasics#GCOS_lockbasics_uses_activity_blocking
(By the way, I personally think there's no need to use "device opening" these days as modern systems usually make a good job of cleaning up the lock table when something goes wrong.)
Just to put things into a correct light
1) java came out mid 90es
at the time ObjectScript (or mumps or m) already had it's lock a quarter centuy. That said, one could ask, why didn't implemented Java the lock the same way as M? Maybe they didn't know about mumps, maybe they targeted something else, who knows... On the other hand, imagine, all the programming lanuages have the same commands, functions, syntaxes etc. Then all the time we just had one programming lanuage.
2) as you said, lock is a 'convention'. A convention, as many other things.
It's a convention too, if you take a tramway you buy a ticket - and most people do buy a ticket, but some are fare dodger. And nobody says, that's the problem of the convention.
3) using a device as a program interlock did not targeted the lock command but setting a global.
If you set a global node to mark something as 'occupied' and during this time your process dies, then the global won't be cleaned up by the system - except you set a process-private-global, but that mark is invisible to external processes.