Message Handler Dedicated Event Threading - 8.1.5

I have approximately 15 message handlers in one of my projects, I used message handlers so that I could use tag event scripts to call local project scripts. This was a choice made so that I could use a single Ignition project and replicate it without having to create a new project for each instance, I just create a new UDT instance and pass my unique parameters in and the rest is a template with tag event scripts in the UDT.

My issue is with performance, this worked great with 1,5, or even 20 of my instances, but now I have roughly 200 instances and I am seeing performance bottlenecks with my message handlers. I selected “Dedicated” for the threading option in the gateway events, which gives each message handler it’s own dedicated thread so that it doesn’t wait on the others, however I interpreted this incorrectly. I understood it as, every time the message event handler is called, it spawns a new thread for it to execute in, but the reality is that it is a single thread that process all of the events for this specific message handler. So what I end up with is a single congested message handler, while the other “less-used” message handlers can continue to execute.

Is there a way that I can spawn a thread for each call of the message handler rather than have one thread process them all? At this point the project is so large and widely distributed it’s hard to make a large framework change. I guess I could add more message handlers and use a memory tag to index through them for each time it is called to distribute the load to different threads.

Thanks for any help

No, but in your handler you can spawn your own thread using system.util.invokeAsynchronous, but this will only work if you are sending messages and not requests (no return from the handler). You will also run the risk of blowing up your system by spawning too many threads if things get out of hand.

Luckily I don’t need any return from the handlers. Thanks for the help, I will cautiously proceed with that option, is there a way to programmatically get the list of threads via scripting like I can view them in the gateway? My thought is that I could count the threads and only allow spawning new ones if the count is under a specified value.

You can, via Java APIs, doing something like this:

Thread.getAllStackTraces().keySet().size();

But I would not recommend doing this because it’s an expensive call.

Got it, thanks

A cheaper way to get all threads is to enumerate them, like so:

import jarray
from java.lang import Thread

def getAllThreads():
	tcEstimate = Thread.currentThread().threadGroup.activeCount()
	threads = jarray.zeros(tcEstimate*2, Thread)
	threadCount = Thread.enumerate(threads)
	return list(threads)[:threadCount]

Note that retrieves the threads as objects. You’ll probably want to iterate through that list retrieving thread names. And don’t keep that list around longer than necessary–that would be a bad memory leak.

1 Like

Rather than using our invokeAsync directly, you could create your own ExecutorService (cache it in system.util.getGlobals() on the gateway) and immediately delegate from your message handlers to this pool.

3 Likes

Hi Paul,

Thanks for this solution. I was able to successfully create a fixed thread pool named “threadPool” with a maximum thread count of 15. It works like it is supposed to except for when I try to pass variables through to the script I am calling. For example:

Here’s how I am assigning threads to my pool:

system.util.getGlobals()['threadPool'].execute(testRun('hello'))

I have a function named testRun which accepts a value variable as defined below:

def testRun(value):
	print value

This combination gives me a Null Pointer Exception.

However, getting rid of the value and just printing a blank line works fine, like this:

def testRun():
	print ''
system.util.getGlobals()['threadPool'].execute(testRun())

Could you please tell me what I am doing wrong here and how I can pass variables into my thread? Thanks!

When submitting a function to run under another context, you have to pass the bare function name. Parentheses cause function execution. That is, including parentheses and any parameters runs your function on the spot and delivers the return value to run in the Thread Pool or other execution manager. (This is also true for Ignition’s built-in system.util.invokeAsynchronous and .invokeLater.) Your return value is None if you don’t actually return something, which explains your null pointer exception. Your code, as written, tries to execute a null as a task.

Python’s partial() function can assemble a callable (function) and parameters into a single callable object that will do what you want. It would look something like this:

system.util.getGlobals()['threadPool'].execute(partial(testRun, 'hello'))

Also note that Ignition’s built-ins were recently enhanced to do this partial assembly for you. Much easier than closures or default values, which were the most common way to deliver arguments to functions in these cases.

1 Like

Thanks Phil, I think I’m one step closer. I’m now getting a:

TypeError: execute(): 1st arg can't be coerced to java.lang.Runnable

When trying to execute the following:

system.util.getGlobals()['threadPool'].execute(partial(testRun, 'hello'))

Try something like this; Jython isn’t able to perform the single-abstract-method conversion necessary to pass your Python function directly to the Java executor. In a utils (or whatever) project script, add this code:

from java.lang import Runnable
	
class JythonRunnable(Runnable):
	def __init__(self, fn, *args, **kwargs):
		self.fn = fn
		self.args = args
		self.kwargs = kwargs

	def run(self):
		self.fn(*self.args, **self.kwargs)


def invokeAsync(function, *args, **kwargs):
	system.util.getGlobals()["threadPool"].execute(JythonRunnable(function, args, kwargs))

Then in your handlers, just call utils.invokeAsync(testRun, "hello").

For background, everything in Java is a class or interface; Runnable is probably the most common ‘functional’ interface, and it basically just defines a ‘single abstract method’ (named run) that has no arguments and returns no results. By defining a Jython class with Runnable as the superclass, you can do the same thing as partial, but also return the right ‘type’ so the rest of Java just sees a Runnable and doesn’t care it came out of Jython.

2 Likes

Thanks again Paul. This is working perfectly.