Where is this thread coming from?!?!?!?

I have a lock that is locked with this thread:

'gateway-tagchangescripts-project[PROJECT_NAME]-21'

I don't have any gateway change script that call the function directly. All calls come from a system.util.invokeAsync() and should say 'script-invoke-async'.

Someone knows where is coming from?

Can you elaborate on this? Share the stack trace or thread dump?

This is definitely a thread from a gateway tag change script in project "PROJECT_NAME".

Yeah sorry.

I have some gateway tag changes that all call the same functions with system.util.invokeAsync().

To fix the problem of the multiple executions we have a lock saved in system.util.getGlobals(). Each tag change script has it's own unique id.

This mean that the code will do something like this:

lock_id = id  # Id passed by param
lock = system.util.getGlobals().get(lock_id)

if not isinstance(lock, threading.Lock):
	system.util.getGlobals()[lock_id] = threading.Lock()
	lock = system.util.getGlobals().get(lock_id)

if not lock.acquire(False):
	return

However, sometimes whenever the code it's trying to enter the function the thread mentioned above it's blocking it.

Gateway tag change looks like this:

system.util.invokeAsynchronous(
	main.logic,
	[
		"id1"
	],
	None,
	"id1 Script"
)

Share the stack trace / thread dump.

Also, not sure if it's related yet or not, but this isn't thread safe like you're hoping:

lock_id = id  # Id passed by param
lock = system.util.getGlobals().get(lock_id)

if not isinstance(lock, threading.Lock):
	system.util.getGlobals()[lock_id] = threading.Lock()
	lock = system.util.getGlobals().get(lock_id)

if not lock.acquire(False):
	return

Two threads could trivially arrive inside the isinstance check at the same time and assign different lock instances or retrieve different lock instances depending on how execution played out from there.

2 Likes

Consider not using locks, and not using system.util.invokeAsynchronous(). Put all of the tags that would use the same lock into the same list of tags in a gateway tag change event. Make as many gateway tag change events as you need. Each will run with one thread, with the least possible amount of contention. And you will avoid the cost of thread creation.

If you must use locks, you must .setdefault to set it up in globals. Also, always use with to safely acquire and release locks. (You may need RLock instead of Lock, too.)

It should be as easy as this:

lock = system.util.getGlobals().getMap() \
    .computeIfAbsent(lock_id, lambda _: threading.Lock())

but it's not, because for some reason globals is a PyStringMap (why????) that does not expose its internal ConcurrentMap (though it looks like this changed in 8.3).

edit: it has getMap(), not sure why it doesn't work :confused:

1 Like

Because it must be compatible with Jython code module globals(). Gaining access to the single, JVM-wide legacy scope globals() is what system.util.getGlobals() was originally created to provide.

Anyways, jython code modules require PyStringMaps as identifier dictionaries. (Both globals() and locals().) Those are keyed with jython identifiers, so other types of immutable keys are not acceptable.

Dropping legacy jython scope makes expanding the signature of .getGlobals() possible.

I normally write instantiation like so:

from threading import RLock

# Fast Path
someLock = system.util.getGlobals().get('someLockId')

if not someLock:
	# Slow path.  The RLock instance might be thrown away if threads
	# are racing
	someLock = system.util.getGlobals().setdefault('someLockId', RLock())
3 Likes

Hi guys!

Thanks a lot for all the information and the help.
We found the problem, but I'm going to study all your comments.

Some thing you said we can't really use because of our current project and our needs.

I really appreciate your help.
I'll come back with whatever we end doing.

BR