system.util.getGlobals() not persisted across Designer Save

I have the following startup script:

logger = system.util.logger("Scripting.Startup") 
logger.trace("Scripting.Startup --->")

int_1 = system.util.getGlobals().get("int_1")

logger.debug("Scripting.Startup --- int_1 type=" + type(int_1).__name__)

if int_1 is None:
	int_1 = 0
	logger.debug("Scripting.Startup --- global[int_1] not found, set value=" + str(int_1))
else:
	int_1 = int_1 + 1
	logger.debug("Scripting.Startup --- global[int_1] found. set value=" + str(int_1))

logger.debug("Scripting.Startup --- int_1=" + str(int_1))

system.util.getGlobals()['int_1'] = int_1

logger.debug("Scripting.Startup --- len(globals)= " + str(len(system.util.getGlobals())))

for key, val in system.util.getGlobals().items():
	if key == "__builtins__":
		logger.debug("Scripting.Startup --- globals [" + key +  "]=" + "built-in functions")
	else:
		logger.debug("Scripting.Startup --- globals [" + key +  "]=" + str(val))

logger.trace("Scripting.Startup<---")

See the results on the server.

Any reasons why system.gui.getGlobals() is not persisting between Designer saves ?

It might be a side effect of legacy scoping in gateway events. Try putting your code in a script module, and referencing a dummy function therein from the startup event to ensure the module loads.

Consider doing something like g = system.util.getGlobals() at the beginning, and using g everywhere below. Also consider using the python dictionary method .setdefault() to handle the initial value.

Thank you pturmel for replying, however the problem persists.

As you can see from my screenshot, the value int_1 is not increasing each time I press save from the Designer. I moved the code to a project script called test1.onStartup(), replaced get with setdefault() so int_1 will never be None and used g = system.util.getGlobals().

My startup script is

logger = system.util.logger("Scripting.Startup") 
logger.trace("Scripting.Startup --->")

test1.onStartup()

logger.trace("Scripting.Startup<---")

My project script test1

def onStartup():
	logger = system.util.logger("Scripting.Project.test1") 
	logger.trace("Scripting.Project.test1 --->")

	g = system.util.getGlobals()
	#int_1 = g.get("int_1")
	int_1 = g.setdefault("int_1",0)
		
	logger.debug("Scripting.Project.test1 --- int_1 type=" + type(int_1).__name__)
	
	if int_1 is None:
		int_1 = 0
		logger.debug("Scripting.Project.test1 --- global[int_1] not found, value=" + str(int_1))
	else:
		int_1 = int_1 + 1
		logger.debug("Scripting.Project.test1 --- global[int_1] found. value=" + str(int_1))
	
	logger.debug("Scripting.Project.test1 --- int_1=" + str(int_1))
	
	g['int_1'] = int_1
		
	logger.debug("Scripting.Project.test1 --- len(globals)= " + str(len(g)))
	
	# send to log all global items
	for key, val in g.items():
		if key == "__builtins__":
			logger.debug("Scripting.Project.test1 --- globals [" + key +  "]=" + "built-in functions")
		else:
			logger.debug("Scripting.Project.test1 --- globals [" + key +  "]=" + str(val))
	
	logger.trace("Scripting.Project.test1<---")

Any other ideas?

Try using every where system.util.getGlobals()[ā€˜init_1ā€™] in the code directly in any expression as one variable

The globals donā€™t persist across gateway script restarts.

When did that change? They are utterly useless if they don't persist.

I only took a brief look at the code but it looks like in 7.9 and 8.0 any time a new ScriptManager is created a new globals map is created for that ScriptManager.

Perhaps thereā€™s a difference between how often a projectā€™s ScriptManager is created and re-created between the two? I havenā€™t really tested anything.

I donā€™t think the docs have ever claimed that system.util.getGlobals persists, it only offers a workaround to the unintuitive behavior of Jythonā€™s global in some script scopes.

Havenā€™t we always told people to use tags for global state that must persist?

Indeed, but that isn't the globals map that is returned by getGlobals(). getGlobals() returns the globals from legacy scope, which historically has not been reconstructed on script restarts.

You may have. But it is useless for generic objects, like holding a thread reference or a socket reference for safe replacement on script restarts. Tags also (try to) persist across gateway restarts, while getGlobals() does not. Persisting non-serializable generic objects across restarts is not possible or desired. Some of my v7.9 code is dependent on getGlobals() to avoid memory leaks.

If unbreaking this functionality is not possible, I will create a functional alternative in my Sim Aids module.

Hmm. It would be a separate module, in order to use <exportsā€¦/> in module.xml.

Ignition doesnā€™t have a built in scheduler for script execution. I thought Ignitionā€™s BasicExecutionEngine was a solution.

Creating a new BasicExecutionEngine object on each startup loses reference to the old one and the old reference was needed to shutdown the existing scheduled threads. Without the that reference there is no way to stop the old threads and so new threads are duplicated on each gateway restart causing duplicated execution and memory leaks.

My purpose in using getGlobals() was to save a reference to A BasicExecutionEngine as described in this post.

If getGlobals() canā€™t save a reference to BasicExecutionEngine is there another way to schedule script execution ?

What version of Ignition are you using?

The most recent 8.10.

That is the more repliable way. A module with an hashmap to persist data in each scope. Some functions to store and retrieve values. We use this pattern for storing cache data mainly in the client scope. The data are initialized at client startup. As a side note I noticed that in 8.0 all client tags are restored to initial value when client tag is changed in the designer. That make sense because client tags are a single ressource.

1 Like

Ok. New module ā€œLife Cycleā€ w/ system.util.persistent(name) utility function.

For v7.9: https://www.automation-pros.com/ignition/Life%20Cycle-0.9.0.2004121825.modl

For v8.0: https://www.automation-pros.com/ignition/Life%20Cycle-0.9.0.2004121920-v8.modl

Test with a project script like this:

# project script "Life"

g = system.util.persistent('LifeTest')

count = g.setdefault('editCount', -1)
count += 1
g['editCount'] = count

def editCount():
	return count

Print the return from Life.editCount() in a gateway event.

3 Likes

Is there any documentation on system.util.persistent() functions? how do you delete an item, maybe with pop()? Also, is there a way to completely clean a persistent scope, ie system.util.persistent('LifeTest')

I havenā€™t written docs for that yet. But the return value is a python dictionary, so anything you can do with that is allowed. You are probably looking for the .clear() method. Once a named dictionary is created, it persists until gateway/client/designer restart.

I deliberately did not expose any iterator for persistent names that have been created. You supply a name, you get a dictionary permanently attached to that name. A .clear()'d dictionary is indistinguishable from a new one.

1 Like

By the way, the breakage in system.util.getGlobals() has been fixed for v8.1. With the loss of its Ā«raison-d'etreĀ», I'm not planning to update this Life Cycle module for 8.1.

Edit: In v8.1 getGlobals() is isolated to a given script scope. So, not the same as v7.9. I will be releasing LifeCycle for v8.1 and maintaining it going forward. system.util.persistent() in this module has been superceded by system.util.globalVarMap() in my Integration Toolkit module. All users of v8.1+ should migrate.

2 Likes

Hi @pturmel - do you have the code for that LifeCycle version 8.1 module anywhere? In GitHub?

I think itā€™s this one:

https://www.automation-pros.com/ignition/Life%20Cycle-0.9.0.2103311855-v81.modl

The source code is not public.

2 Likes