Question
· May 7

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 wait
quit:'$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)

Product version: IRIS 2023.1
$ZV: IRIS for Windows (x86-64) 2023.1.3 (Build 517) Wed Jan 10 2024 13:51:33 EST
Discussion (8)2
Log in or sign up to continue

We can use the %SYS.LockQuery class and its List query 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

ClassMethod LOCK(Lock, Mode)
{
    If '..IsLocked("^A","X") {
        Lock +^A
    }
    Else {
        Write "Locked"
    }
}
// X - Exclusive
// S - Shared
ClassMethod IsLocked(Global As %String, Mode As %String)
{
    #dim status = 0
    Set 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 = 0
	Set 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 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 or multiple 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 = 45
	open myDevice::0
	
	if $t {
	
		// run that critical section
		hang 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 abnormally
ClassMethod UsingGlobal(testTime = 0)
{
	if $i(^X)=1 {
		
		// run that critical section
		hang 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
}

}

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:1 if $test lock +^Test:1 if $test write "It's my resource!"
lock -^Test,-^Test

are pointless. The above locks could be a short version for:
 

(Class)Method MyCrticalSection()
{
  lock +^Test:1
  if '$test quit
  
  // some commands
  do ..MyCriticalSection() // despite the lock, this call enters this section again
  // some more commands
  
  lock -^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.