[BUG-5802]Async Tag Event Script loses reference paths

I have a system that is firing off a ton of tag event scripts, but uses a singleton to manage their threads, and so because of this I need to execute my tag event scripts asynchronously.

In my case, I am using threading instead of invokeAsynchronous, but the issue persists either way

When I try to use tag paths in asynchronous scope, it loses the ability to use a reference path. I am assuming because I am passing the script outside of some scope and not realizing it, or something under the hood with how the tag read and write scripts handle tag paths when there is a tag event in their stack trace. Totally guessing here

Any ideas how to get this to work? Long story short, I cannot know what tag path I will need before the async function, I have to determine that dynamically inside the async function as part of a config file.

Essentially this is the script:

# This will print the value of the second tag
system.util.getLogger("Example").info(str(system.tag.readBlocking(["[.]Second Tag"])[0].value))
def writeFunc():
	# This will print None
	system.util.getLogger("Example").info(str(system.tag.readBlocking(["[.]Second Tag"])[0].value))

# The stock ignition way
system.util.invokeAsynchronous(writeFunc)

# Essentially How I am doing this in threading:
# import threading
# new_thread = threading.Thread(target=writeFunc)
# new_thread.start()

You’re going to have to figure out an approach that allows you to derive the full tag path before asynchronous execution begins.

The event scripts execute with information about the default tag provider and relative path (parent path) stored in Java ThreadLocals. This information is lost once you’ve jumped to another thread.

1 Like

I think you might be able to extract what you want from the current thread, then set it in the new thread via ScriptContext: ScriptContext

Ah, but maybe you could store those and set them yourself (danger…)…

ScriptContext is com.inductiveautomation.ignition.common.script.ScriptContext.

Before invokeAsync, store the relative path:

relative_path = ScriptContext.relativeTagPathRoot()

first thing inside the async func:

ScriptContext.setRelativeTagPathRoot(relative_path)
3 Likes

Ooooo! Neat. I wondered where you tucked some of this stuff.

We’re gonna start passing the ScriptContext values into the thread started by invokeAsync, but you’ll have to keep doing it manually for your own custom threads.

2 Likes

This is exactly what I needed, this singleton method is explicitly being called from tag event scripts in this case, so I should always have access to that stuff before the async call.

Script for anyone in the future:

from com.inductiveautomation.ignition.common.script import ScriptContext

relative_path = ScriptContext.relativeTagPathRoot()

system.util.getLogger("Example").info(str(system.tag.readBlocking(["[.]SiblingTag"])[0].value))
def writeFunc():
	ScriptContext.setRelativeTagPathRoot(relative_path)
	system.util.getLogger("Example").info(str(system.tag.readBlocking(["[.]SiblingTag"])[0].value))


system.util.invokeAsynchronous(writeFunc)

# If you would prefer threading instead of invokeAsync
# import threading
# new_thread = threading.Thread(target=writeFunc)
# new_thread.start()

Outside off you guys setting this inside invokeAsync, are there any other special reasons that we should be using invokeAsync instead of threading?

We are using threading so that our code is test compatible with pytest in python 2.7. But if there are strong reasons to use the first party function, I am not opposed to switching.

I wouldn’t use threading because I trust approximately nothing that comes from the Jython stdlib.

4 Likes

invokeAsync also allows you to pass a description of your async thread, which can be useful for diagnosis. You could probably monkey-patch threading to do the same, but at that point, why bother?

In this case we are storing the thread in a WeakKeyDictionary , I would suppose we could just do the same with the thread provided by invokeAsync.

Is it possible to not immediately start the thread with invokeAsync?

Ooh good call, didnt see that!

invokeAsync is just java.lang.Thread and java.lang.Runnable, with a description set on the ScriptContext.

You can use those directly, and start the thread whenever you’d like.

2 Likes