httpClient Selector Manager Threads

Hey all,
We've currently got a system running. This isn't a huge system, allocated only around 3/4GB of memory.

This server sends out data to a backend server fairly often using HTTP post/get requests.

Something we've noticed is a large number of threads in the system using the name:

HttpClient-xxxx-SelectorManager, id= xyz

They use 0.0 CPU but with the number of them we have open (around 300 threads in total, a lot of them this thread type) I worry we have some kind of memory leak.

To do these calls we use system.net.httpClient(), we have a few instances of these set up for various scripting modules. But only around 4 to 5 across all project scripts.

Make sure you are calling the httpClient outside of your functions and use them globally. You only need to make one client per port you are using. If you are calling system.net.httpClient in a function, it's going to grab a socket from your pool every time it is called. Current version of java in ignition does not close these automatically, so they stay open indefinitely or until the timeout occurs (if you set one).

1 Like

I know this thread is a little older, but I was curious if someone had an example of how to use the system.net.httpClient() method as a "shared" object so that new threads are not constantly created.

Right now, I have a number of tags that call a global script that has the httpClient() method in it to do httpClient().post(). But, since there are many tags, I believe or understand that every time it is told to call the shared script, a new httpClient is being generated. How can I go about trying to use a "shared" httpClient for a process like this? Eventually if the httpClient is not used, it sounds like there is built-in timeout that will force the close after inactivity. If/When that close happens, how can that shared httpClient be created again to be re-used.

I am relatively new to using system.net.httpClient() and I see similar behavior with my threads where there are many showing up. After time some clear out (assuming they're hitting that close timeout) but new ones are always added back in too.

Thanks in advance to anyone that reaches out.

I mgiht be doing it wrong, but we get a good improvement when I create a script module of tools/utilities.

Define the client = system.net.httpClient() somewhere in there. In all of your funtions that will be use a client you can from utilities import client this should then be reused.

I also define a generic client post/get function that is then called from 99% of all my API stuff.

Not just anywhere. At the "top" level, outside any function or class. That makes client a persistent global in that script.

Don't import Ignition script modules. Ever. Not in any form. This can create subtle bugs. Just use the full name utilities.client wherever you need it.

Thank you both for the extra info here, Hayden and Phil.

So, if I were to do something like below, that would work to treat httpClient as a shared object correct?

Assume this is in my script library and my tags are referencing it via tag change event. Also assume the appropriate variables are being passed in, just trying to make the concept simple for discussion.

client = system.net.httpClient()

def apiScriptA():
	res = client.post()
	
	return res
	
def apiScriptB():
	res = client.post()
	
	return res

That being said I have a few questions.

How or when does a new httpClient get created now? I am just a little unclear on how this method ensures the httpClient will be available when by nature it will timeout the session and close it. Sorry if that question seems elementary, just trying to better grasp how this functions.

If you put a little print statement next to it and watch the wrapper.log file in tail/follow mode you can see when it executes which is the first time something external tries to use anything in that script file (module) after you last made and saved gateway script changes.

print "Initializing httpClient object..."
client = system.net.httpClient()

I'm not clear on how long the system.net.httpClient() object will persist and what you might need to do to check that its still valid. Following to see responses from others.

What timeout might that be? I believe the timeout that you can set when constructing the httpClient object relates to get/post actions, not a timeout for the object existence.

Integer timeout - A value, in milliseconds, to set the client’s connect timeout . Defaults to 60000.

Client instances live until they're freed up by garbage collection (no longer referenced anywhere).
There's no explicit startup or shutdown available until we upgrade Ignition to Java 21, which won't happen until Ignition 8.3, at least.

Thanks for the input, Tyler. I will give this a shot and see how it reacts.

That is essentially the 2nd half of my questions or the leading point of the questions. Basically, should I check to see if that client is available before running my script? Or do I even need to? Would it just open another one for me if the original client was gone as soon as I called that script again? These are rhetorical questions at this point, I am not expecting an answer unless someone felt the need to respond.

Either way, I'll run some tests here and report back so that this forum post can have even more substance for the next programmer.

Thank you Paul.

Based on the basic setup I have outlined above, does this implementation look adequate or more closely algin to best practices then for using system.net.httpClient()?

Basically, with the script you have above there will be one client instance (created lazily, I believe, as in only if it's actually used) per "script manager" - an "under the hood" concept you don't directly interact with. Generally speaking, there's one script manager per live project, plus one for the gateway scripting project.
As you make updates to your project(s), a new script manager is created and the old one is destroyed, leading to it any associated resources (such as http client threads) being released.

So in general I would expect, if you're following the pattern outlined above, an average number of selector threads that correlates strongly with the number of active projects you have actually using system.net.httpClient.

If you're seeing dramatically higher numbers, it suggests a leak somewhere, either on our side or yours.

Ok, that adds some context, thank you.

When I had the script like below, it was creating a lot of httpClient threads and I want to narrow that down since these tags fire on a 60 second interval. So as the old threads are closing out or just before, the new ones are already coming in.

I have been having some odd issues on my gateway with memory and thread activity and I have been working with IA Support to narrow this down. One suggestion was to reduce how many httpClient threads I am creating. Another suggestion was to try system.net.httpPost() if it provided what I needed as a return.

def apiScriptA():
	client = system.net.httpClient()
	res = client.post()
	
	return res
	
def apiScriptB():
	client = system.net.httpClient()
	res = client.post()
	
	return res

I wouldn't bother with httpPost. It's not prone to socket/thread leaks, but it's severely limited in functionality, and significant less efficient for repeated calls to the same endpoint because it's not able to reuse HTTP/TCP sessions at all.

1 Like

As an update for anyone reading this, I opted for the suggested change to initialize the httpClient in my script library that then uses the object when each script is called.

I can confirm that this client is created one time and essentially remains available. It does look like there are sub threads or child threads based on the original thread and those go away after just a few thread refreshes. I say child/sub threads because they share a similar httpClient id but then have Worker-1, Worker-2, Worker-3 etc (see image below).

In any case, I went from having roughly over 200 httpClient threads down to just the few shared httpClients created from my script library. Many/Most of my tags were always calling the same script to begin with but this method clearly reduces that thread count and behavior.

client = system.net.httpClient()

def apiScriptA():
	res = client.post()
	
	return res
	
def apiScriptB():
	res = client.post()
	
	return res

1 Like