Ignition jython, using import for script console compatibility

Hello,

I was developing in Ignition 8.1.38 an object-oriented function in Jython and noticed that importing project modules resolves some seemingly random problems that only occur when testing from the Script Console.

I'd like to ask if you see any issues with leaving these imports enabled in the modules, as it appears to work the same way in the Designer's scripting console and in the Gateway.

Apart from the fact that when Ignition detects changes in the modules, it recompiles and restarts the script instance and everything starts from scratch.

Here's a simple example of what I've observed:
I have a package called modules with the modules modBase, mod1, and mod2.
mod1 and mod2 are identical and access modBase functionalities.

# modBase code
myInternalVar = 0

class myClass():
	pass

def incAndGet():
	modules.modBase.myInternalVar += 1
	return modules.modBase.myInternalVar

logger = system.util.getLogger("TEST")
logger.info("modules.modBase loaded")

# mod1 and mod2 code
#import modules

def incAndGet():
	return modules.modBase.incAndGet()

def getClass():
	return modules.modBase.myClass

# Test code, tested in Script Console and Gateway Event Handler
logger = system.util.getLogger("TEST")


logger.info("mod1.increment and get: " + str(modules.mod1.incAndGet()))
logger.info("mod2.increment and get: " + str(modules.mod2.incAndGet()))


logger.info("myClass id from mod1: " + str(id(modules.mod1.getClass())))
logger.info("myClass id from mod2: " + str(id(modules.mod2.getClass())))
logger.info("myClass id from modBase: " + str(id(modules.modBase.myClass)))

inst1 = modules.mod1.getClass()() # create instance
logger.info(
	"instance of myClass isinstance of myClass: "
	+ str(isinstance(inst1, modules.modBase.myClass))
)

Results After running first time after saving in Designer ("script recompiled and restart")

Without importing

Importing modules

The result does not change at the gateway.

Therefore, to avoid discrepancies between tests performed in Designer and those executed in Gateway, I think it's best to maintain the imports.

An interesting point is that initially, I was importing the exact module I needed:

import modules.modBase

This created a separate entry for each imported module name in sys.modules.

But then I accidentally discovered that importing the package is sufficient to prevent this from happening. This also prevents the fully qualified module names from being loaded into sys.modules.

So let me know if you see any problems with this.

Thank you in advance

That should be

def incAndGet():
	global myInternalVar
	myInternalVar += 1
	return myInternalVar

{Seriously racy, btw. I hope you aren't trying to control parallelism with this.}

But anyways, the designer script console has very custom library module loading, in order to encapsulate the REPL properly, IIUC. import bypasses some of this and can lead to strange behavior.

(The designer script console is not suitable for testing gateway scope scripts for many other reasons.)

1 Like

This is somewhat of a digression from the main topic at hand, but I'd love to know what leads folks to these OOP-heavy approaches to scripting within Ignition and exactly what they're (trying) to do with them. For better and for worse you usually have a much better time in Ignition if you embrace the state management we provide first party (e.g. tags) instead of trying to wrangle all your state in code...at least, that seems to be what people are ultimately after.

3 Likes

Unless you need locking/mutual exclusion across parallel tasks in an algorithm. That's about it.

Software engineers who are used to reasoning about code state and less used to reasoning about tag state, is my guess?

Yeah, that seems to be the kind of folks who go for this, but I'll be the first to admit: Ignition is a pretty terrible environment to purely write software in.
The whole point is the layers of abstraction we provide that gloss over lots of it. If you're trying to redo it all in code, you're swimming upstream.

A primary part of the value proposition of Ignition (in comparison to just writing your own bespoke software, if you have the capacity to do so) is that if something is broken, it's IA's problem. The more you're reinventing in code, the more you're making it your problem again.

All that aside...
For the OP: You don't want to import someProjectLibrary or anything like that. It's a bad habit to get into for a variety of reasons, the most significant of which is a fairly catastrophic memory leak.
If you're careful, you can store persistent state via system.util.globals (or Phil's Toolkit modules' global var map), but it must be pure data, not arbitrary custom classes, which you can "rehydrate" into stateful classes if you want to encapsulate logic.

I'd also recommend reading through this topic which describes a related known issue that will not be fixed in 8.1 but is already fixed in 8.3: [IGN-6503]Project Library Script Initialization Races

5 Likes