[IGN-5244]Persist Python Class Object

Umm… sure… I don’t actually know what’s going on here or what you’re trying to do, I didn’t really pay much attention. Just got curious about why the session was wrapped and it’s very intentional.

Yes, that's definitely going to be the easiest. Couldn't you do cleanup in a session shutdown event script?
https://docs.inductiveautomation.com/display/DOC81/Perspective+Session+Event+Scripts#PerspectiveSessionEventScripts-StartupandShutdownEventScripts

I didn’t even realize there was a session shutdown event, how convenient!

This should work then. In general this premise makes OOP work in Perspective much easier.

It would be nice if there was an easier way to pass class references into views without them being converted into their text references, I recognize we’re talking about going from Jython to JavaScript and back, and I’m not sure how possible that even is, but this could ease a ton of projects that rely on lots of scripting.

Awww!

So, A PyObjectAdapter registered to wrap all Session objects with the SessionScriptWrapper$SafetyWrapper, I presume? Defeating that would require a 3rd-party module that can use getSession() and then provide an alternate wrapper. (Jython can't do it because a PyObjectAdapter kicks in to rewrap it wherever it pops out.)

Maybe I'll create a module function that combines .getSession() and a WeakRef. Would still be safe because dereferencing in jython would auto-wrap back to the SafetyWrapper. Hmmm.

This isn't a dismissal - what do you (and @pturmel, for that matter) plan to do with this? I could see us adding something akin to Vision/Swing's putClientProperty mechanism to Perspective; it would obviously stay entirely on the backend model, but it would be easy enough to add. But compelling use cases would definitely make it an easier sell.

That would entirely supplant what I was thinking, yes. Please do. Separate ones for Session, Page, and View would be glorious. Life cycle when scripts restart would be tricky if not restricted to python native objects.

The whole idea is to have class models that carry pure back-end data that can't survive a json round trip. Preferably tolerating jython custom class instances. A design built around weak references saved in a python global can have its objects updated by project script top level actions upon script restart. Yet remain "findable" by scripts in the Perspective session, per session.

Jython collections and lists are fundamentally thread-safe, so the weakref approach provides a way to provide state to sessions that are simultaneously available to non-perspective events.

Hah. Such a good idea we already talked about it a few months ago:

This is exactly the issue, if I spend a lot of "compute" building a class with a lot of custom data and methods, etc. Than rebuilding it every time a user interacts with the screen can be really expensive to the clients.

I am currently working through an issue where I am writing a class that manages a dynamic array of events, and needs to ensure that each event is processed prior to saving some data. Without a class it requires me to pass a json serializable object back and forth from script to view, changing some of the metadata inside the object through each iteration of code. If I was able to use a class, than I could either store the class on the session, or in that page, and just have the popups call a "next_step" type method that exists on that class.

In this case I am going to need to store my "event manager" in the globals, and then have my view tell it to perform each "next step" through the method we have described above. It is fully do-able this way, but not super easy to implement and manage

2 Likes

Another possibility: define “Private” access for custom properties to mean whatever a script/binding assigns retains its native object identity. That would be clean and intuitive all around.

Edit: except for the memory leakage hazard I mentioned in the other discussion. A weak-key dictionary keyed on sessions but stored persistently and managed by a project script is the only safe way I can think of to do this kind of code. Nobody is doing this because it really can’t yet be done. (A bug in a session shutdown script would block cleanup when weak references aren’t used.)

Is there any possible way to identify shutdown at the page level by chance?

If I wanted to have a specific instance of my class per page open, only thing I can think of is storing it with the session id and page id in a compound key, and then to clean it up I would have to iterate through the pages shown in the results from system.perspective.getSessionInfo(), but once again that would have me going through a timer, not a conveniently placed shutdown script

Was curious if there was ever any internal traction on this feature request?

It’s definitely an advanced user feature requirement, but it would enable advanced users to do immensely powerful things.

No movement on it yet.

A per-session python dictionary safely established with weak references is on my todo list after I push out a beta driver that’s had me preoccupied lately.

2 Likes

I figured, just had to remind you guys about how much the advanced users want it :grin:

I read through this thread and I want to make sure I'm implementing this correctly.

Here is the flow. It's all happening in 1 script

When another script wants the data it goes through a getter method

def get_device_list():
	device_list = None
	try:
		device_list = system.util.getGlobals()[key_device_list]
	except KeyError:
		build_device_info()
		device_list = system.util.getGlobals()[key_device_list]
	except:
		raise
	return device_list

SIDE QUESTION: is catching the KeyError the best way? Or is it better to run...

dictGlobals = system.util.getGlobals()
if key_device_list in dictGlobals.keys():
def build_device_info():
    device_list = do.some.stuff.to.build.a.list()
    set_device_list(device_list)
def set_device_list(device_list):
    if device_list == None:
        del system.util.getGlobals()[key_device_list]
    else:
        system.util.getGlobals()[key_device_list] = device_list

Then when the data is no longer needed...

def clear_globals():
	set_device_list(None)

Is this the correct way to do this to prevent memory leaks. I would prefer to avoid using getGlobals() but I am having a bear of time trying to move information around in a clean way. Another reason I am leaning towards using getGlobals() is that the build_device_info() is a big process and ideally it only happens at Vision startup, or when needed.

Thanks!

@Adam_Lyon please post code using the preformatted text buttton </>

Thanks! I was trying to figure out how to do that

You might find the techniques in this topic helpful. The same life cycle considerations for background threads apply to any kind of jython class objects you try to persist. Be aware that you cannot leave a jython class instance in any global scope through a scripting restart. That leaks a big chunk of memory, and your class object keeps running the old code.

I strongly recommend you not place your class object directly into a persistent dictionary, whether from .getGlobals() or my own .persistent(). Instead, segregate the class's data model to store all dynamic data, including nested data, in a standard python dictionary or list or combination thereof. Make your class initialize with the global dictionary, and keep that dictionary updated. In a scripting restart, you simply initialize again with that dictionary.

I think this is starting to make sense. One of the data types of the class object is an ignition dataset. Is that safe to store in .persistent() (inside of a dict) or is it best to only store primitives and use that to build any objects when the class is instantiated?