Disappearing class instance in project library

Hi, I have set up a timer class in the project library. I define the class inside a function that is called on gateway startup. I also instantiate the class as masterTimer in the same function.The timer class has a function minuteCall that is called every minute, checks to see what needs to happen at that minute, and does it.

I do it this way because I know the class will get defined and in instantiated this way. If there is a better way to define/instantiate a class, that would be good to know.

This class instantiation seemingly randomly disappears. Everything will be going along fine, then suddenly masterTimer.minuteCall function can’t be found by the calling function. I check when this happens, and masterTimer class itself also can’t be found.

Everything else continues to work as normal, just none of my timer events occur. Memory and CPU seem normal. Running 8.1.5 in Docker on a Raspberry Pi 4 .

Any ideas?

Thanks

I suspect your problem is that things instantiated during gateway startup aren’t guaranteed to be persistent during the lifetime of the gateway. The Jython interpreter instances may be recycled sometimes, wiping out any globals that aren’t explicitly created by importing the library scripts.

EDIT: Some ways to keep variables/values persistent through the life of the gateway are to store them in memory tags (use system.util.jsonEncode and a string tag for complex data), or possibly the “global” dict that you can fetch via system.util.getGlobal. I’m not fully aware of the pros/cons of each method, but use JSON in a memory tag for some of our data since it also persists around gateway restart.

1 Like

This is a function within the instantiation of a class, so I assume a tag wouldn’t work. It’s not obvious that the system.util.getGlobals() method would work either, but I will try it and see

I just did it, and I was able to put the class instance into the globals, and it worked. Now to see if it stays for a few hours or a day. Thanks for the tip!

I think this solution will have the same issue. From my (limited) understanding, the getGlobals just retrieves a reference to a mutable dictionary that lives under the top level JVM. I suspect if you store a reference to a instance in the global dictionary, the reference may break when the Jython instance that created it is recycled.

I’m really hoping one of the folks around here who understand the Jython lifecycle better can clarify things.

EDIT: The only things I would feel safe storing in a tag or getGlobal would be basic datatypes like strings, ints, floats… Anything more complex like dicts or arrays could be pass-by-reference and have this issue. That’s why I suggested jsonEncode.

Quite possible, I will reply if/when it happens, it usually doesn’t last more than a couple hours.

I also just realized I have another class instantiated in the same way, that I don’t seem to have a problem with it disappearing. But I’m not hitting it every minute like this, so maybe I just hadn’t gotten to the point where I notice.

The jython instance memory that has that code will be leaked in its entirety. Anything you put into persistent objects must be native python objects or you must deliberately replace them when the scripts are reloaded.

Which means, in the gateway, defining long-lived classes in events is crazy talk. Define your classes in script modules. The defining script module should extract and replace any instances you stick in getGlobals() (during its top-level import execution).

BTW, the correct way to do things on a timer in the gateway is to use a timer event. The event can call into a function in a script module to gain access to (somewhat) persistent objects at the top level of that script module. (They will persist until project save or similar triggers a script reload.)

OK, I get it. Talking crazytalk. I’m used to doing that lol. Where should I instantiate it so that I know that it starts on a gateway start, yet isn’t part of the startup script? Do all the project library scripts get run on load, so any classes and instantiations of classes get started then if they are on top level?

Right now I have this, and startTimeSystem() is called on Gateway startup:

def startTimerSystem():
	class timerSystem:
		def __init__(self):
			self.timerSystemStructure = []
.
.
.

	global masterTimer 
	masterTimer = timerSystem()

Should it be instead, and when this project library module is loaded, this all happens as part of the load?

class timerSystem:
	def __init__(self):
		self.timerSystemStructure = []
.
.
.
global masterTimer 
masterTimer = timerSystem()

Also, just to be clear, startTimerSystem() is in the project library, but being called in the Gateway startup event.

Thanks

I am using the gateway Timer to call a fucntion in the script library which then calls this masterTimer.minutecall() function. Sounds like the problem is that the function isn’t persistent as mentioned in the other posts. Thanks for your help.

The gateway startup event runs any time its project (or a parent) is saved. Starting this way is not safe.

Projects script are “imported” (not really, but effectively) at first reference after a scripting restart. Each project in a gateway has its own scope. Since system.util.getGlobals() is shared across scopes, use of .setdefault() is recommended/necessary when placing permanent objects there. (You cannot control order of project reloads.)

You never need the globals keyword at the top level. The globals keyword in a function connects that name to the same-named object at the top level of that script module.

Defining the class at the top level is good. Instanciating it at the top level is good. Replacing any prior copies in .getGlobals() is almost certainly necessary. Or just place the persistent data the class is managing into .getGlobals() directly and skip the class definition.

2 Likes

Some more background that would apply to users of earlier versions of Ignition (at least back to 7.9.11 through all of 8.0.x, possibly 8.1.0):

Does that mean types like dicts and lists and sets are safe, as long as the sub-elements are also native Python types?

Yes.

Strictly speaking, any java objects established by the platform are also safe. Java types from add-on modules are safe until you upgrade/restart the module. (Not easily captured.)

1 Like

This seems to be working now, thanks for the clarifications