Question
Matjaz Murko · Sep 29

Locking / unlocking

Hi.

What is the best/right way to unlock the object in ObjectScript after the lock was acquired by %OpenId class method? Setting the opened object to null (Set object="") doesn't release the lock.

Regards,

Matjaž

Product version: IRIS 2021.1
00
1 0 19 337
Log in or sign up to continue

Hi.

Tnx for your quick reply.

Here is the class method:

ClassMethod Checking(CardNo As %String) As %String [ Language = objectscript ]
{
Set person=##class(MasterData.Person).CardNoOpen(CardNo,4)
Quit:person="" "null"

Set checking=##class(Production.Checking).%New()
Set checking.Person=person
Set checking.Condition.Type=person.Condition.Type
Set checking.Condition.ValidBy=person.Condition.ValidBy
Set checking.ConditionIsValid=(person.Condition.ValidBy>=+$Horolog)
Do checking.%Save()

Set model=##class(API.Models.Operative.PersonGetData).%New()
Set model.Ident=person.Ident
Set model.FirstName=person.FirstName
Set model.LastName=person.LastName
Set model.Department=person.Department.Name
Set model.IsValid=checking.ConditionIsValid

If ('checking.ConditionIsValid)
{
Set person.Condition.Type="T"
Set person.Condition.ValidBy=+$Horolog+6
Do person.%Save()
}

Do model.%JSONExportToString(.json)

Do ##class(MasterData.Person).%UnlockId(person.%Id())

Quit json
}

I'm using now %UnlockId method instead of setting the object to null.

Regards,
Matjaž

Ok,

Could you test with

Set checking = "" ; due to the reference Set checking.Person
Set person = ""

I guess the lock should be released after leaving the classmethod, but if the class is procedure block or not the behavior could be different.

These are my findings:
- class has ProcedureBlock enabled by default,
- if I run the class method from terminal window, it behaves as a procedure block,
- if I run it from IRIS Native API (C# app) then it behaves as non procedure block and I have to set person & checking instances to null to release the lock.

Interesting.
I don't have environment to test wit Native API, but perhaps you could test explicitly "ProcedureBlock" instead of default behavior. Just by using the class key word "ProcedureBlock"
ex:

Class ZUser.NewClass1 Extends %Persistent [ ProcedureBlock ]

edit : also you can try with [ ProcedureBlock = 1 ] on the classmethod. doc link

I do not know if it is important, but I'm using abstract class. And although I explicitly add ProcedureBlock  to the class definition it doesn't help when I'm calling the method with Native API...

Code snipet to expose the problem.

Class ZUser.NewClass1 Extends %Persistent [ Not ProcedureBlock ]
{

ClassMethod Demo()
{

    Do ..TestLock()
    ; This class is Not ProcedureBlock the record still locked

    ; If the class is ProcedureBlock the record is released.
    ; try by yourself :-)
}

ClassMethod TestLock() As %Status
{
    Set id = $Order(^ZUser.NewClass1D(""))
    If id = "" {
        Set obj = ##class(ZUser.NewClass1).%New()
        Do obj.%Save()
        Kill obj
    }

    Set id = $Order(^ZUser.NewClass1D(""))

    Set obj = ##class(ZUser.NewClass1).%OpenId(id, 4)

    ; in case of usage Not ProcedureBlock you should 
    ; kill obj or set obj="" (and all others variables with this object reference) to release the lock


    Return $$$OK


}
}

And if so?

ClassMethod TestLock() As %Status
{
    New id,obj

    Set id $Order(^ZUser.NewClass1D(""))
    If id "" {
        Set obj ##class(ZUser.NewClass1).%New()
        Do obj.%Save()
    }

    Set id $Order(^ZUser.NewClass1D(""))

    Set obj ##class(ZUser.NewClass1).%OpenId(id, 4)

    ; in case of usage Not ProcedureBlock you should 
    ; kill obj or set obj="" (and all others variables with this object reference) to release the lock

    Return $$$OK
}

Hi.
Tnx for reply.
Regardless that I'm using ProcedureBlock, Native API calls to class methods do not put instances out of scope (and consequently do not release locks).

Regards, Matjaž.

  1. ProcedureBlock does not apply to your issue at all
  2. you didn't initially indicate that the issue is with C#
  3. use ReleaseIRISObjects

    Example:

    IRISConnection db; IRIS iris; IRISObject person;   // ...   using (person = (IRISObject)iris.ClassMethodObject("Sample.Person""%OpenId""1""4")) {   }   /* or   person = (IRISObject)iris.ClassMethodObject("Sample.Person", "%OpenId", "1", "4") person.Dispose(); person = null; */   db.ReleaseIRISObjects();

Sure, with the `New` lock will be released.

I'm not using IRISObjects, just ClassMethodString:

PersonData = JsonSerializer.Deserialize<PersonDataClass>(App.Iris.ClassMethodString("API.Calls.Operative", "Checking", CardNo.Text));
            if (PersonData != null)
            {
                if (!PersonData.IsValid)
                {
                    SystemSounds.Exclamation.Play();
            }

I did a test and that's what found out.
It is assumed that the data has already been generated.

COS

Class dc.test Extends %Persistent
{

ClassMethod Checking(CardNo As %StringAs %String
{
  tmp=..%OpenId(CardNo,4)
  "test"
}

}

If executed from the terminal, the lock is removed automatically immediately after the method is completed:

USER>##class(dc.test).Checking("1")
test

C#

// ...
string json = iris.ClassMethodString("dc.test""Checking""1");
 
// here the lock is still present
 
db.ReleaseIRISObjects(); // or iris.ReleaseAllLocks();
 
// here already more is no lock

I'm setting all referenced instances to null in COS to release locks as workaround. But I'm not sure it is a bug or expected behaviour...

There is one thing you should check, than this could trigger effects observed by you.
Objects are tracked by reference counts, as long as an objects reference count is greater then one, locks won't be released and the object isn't deleted.

set obj = ##class(Some.Class).%OpenId(id, 4) // the obj's ref count is one
... // more commands
    // now, the application does something like this
set tmp = obj    // obj's ref count is now two!
... // more commands

set obj = "" // the application intents to close the object
             // but the object still exists due to the fact that the ref count is one
             // (the object is still referenced by <tmp>)

There are methods to detect such a situation:
- $system.OBJ.ShowObjects(), lists all objects with reference counts
- $system.OBJ.ShowReferences(obj), list all variables which contains a reference to <obj>

A quick and dirty approach:

set filename = "...some file name"
open filename:"nw":0
if $t { use filename
        do $system.ShowObjects()
        do $system.ShowReferences(obj) 
        close filename
      }
set obj = "" 

Give it a try, maybe your object has multiple references which cause the problem

Hi.

As I've mentioned in previous post I'm using workaround, but I think it's a bug in API, because the same method works fine in terminal window without explicite set the objects to null (all used objects are released and consequently all locks are released also - expected behaviour in class with procedure block enabled).

Regards,
Matjaž

Yes, most likely this is a feature of the API.
Here's what I found about it in the "old" documentation:

In some situations, caching may not be desirable. For example, if an object is opened with Concurrency Level 4 (Exclusive Lock), the lock will not be released until the next server call. To destroy the object immediately, you can call Close() with the optional useCache parameter set to false

Closing Proxy Objects

PS: by the way, for ActiveX, Factory.ForceSync() method serves the same purpose.

That's the way the proxy classes are  functioning (caching). But in new Native API for IRIS there are no proxies, just class methods/functions are called from app and I think, it should be pretty the same as calling this methods/functions from terminal window.

But in new Native API for IRIS there are no proxies, just class methods/functions are called from app and I think, it should be pretty the same as calling this methods/functions from terminal window.

Introduction to the Native API

Using .NET Reverse Proxy Objects

The Native API allows you to create instances of ObjectScript classes on InterSystems IRIS and generate Object Gateway proxy objects for them at runtime. Your .NET application can use proxies to work with ObjectScript objects as easily as if they were native .NET objects.