While loops inside Gateway Events are very unreliable

I made a simple Update Gateway Event which had a while loop inside of it and rather than killing the current while loop and starting a new one when you update the project. It puts that while loop in the background and creates another one so you essentially have two.

There is also no good way to stop these loops (that I know of) once they start so you're forced to change the script to not include a loop and then restart the gateway.

I was wondering if this is something that would ever be fixed in the future so that upon the Gateway event project update it kills the current running code entirely.

Can you be more specific about what kind of script this was and where you defined it?

Here's some code I wrote to demonstrate this. The inside the if statement one prints 10 times and then the outside one keeps going forever. Even upon saving or updating the "broken while loop" is never triggered.

I also tried completely replacing the code with just print("test") with no while loop then saving and the outside if statement keeps going. Also if you do a thread dump on the gateway you can see this process still running. if you save a bunch of times it eventually bogs down the gateway and you need to manually kill it and restart it.

Ah right, okay.

You can cancel this script in the Diagnostics > Running Scripts area of the Ignition Gateway:

Generally speaking though, there is no place where you can safely write infinite loops. Thread.sleep / time.sleep is also anywhere from not encouraged to detrimental, depending where you're doing it.

2 Likes

I feel like for components and stuff that makes sense to discourage while loops but would this ever be fixed for stuff like gateway events and or project library scripts?

Also is there and way I could write this while loop to break upon an update or detect an update state before it happens?

What are you attempting to accomplish that you can't use a gateway timer script for?

4 Likes

There's nothing to "fix" because I'm not sure it's not broken. We don't cancel scripts that happen to be running when the scripting lifecycle restarts, and we wouldn't be able to tell the difference between a "legitimate" script that is about to finish and one that is never going to finish.

To be clear: use while loops all you want. But you can't write ones that never end, and you can't expect your thread to get killed for you.

Maybe you should be using a timer script to accomplish whatever it is you're trying to do?

3 Likes

Did you change to while True to while False before taking that screenshot? while False would never execute to begin with...

while True just isn't a good idea. Use a Timer script instead.

2 Likes

What I'm using it for is already not an ideal approach but it is working very well so far.

We have a machine with a linear encoder and we want to very quickly be able to read a value from that linear encoder and write it to a tag.

We've tried Modbus TCP/IP (slow)
Serial (slow because our HMI's run in virtual machines we VNC to)
and this UDP approach I wrote just for testing

def startFunction():
   import socket
   import system
   UDP_PORT = 5175
   sock = None
   try:
       if sock:
           print("Closing existing socket")
           sock.close()
       sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
       sock.bind(("0.0.0.0", UDP_PORT))
       sock.settimeout(1)
       print("Listening for UDP data on port", UDP_PORT)
       while True:
           try:
               data, addr = sock.recvfrom(1024)
               value = float(data.decode('utf-8').strip())
               print("Received from", addr, ":", value)
               system.tag.writeBlocking(
                   ['[TagProvider]EncoderBoards/MachineName/encoderBoardValue.value'],
                   [value]
               )
           except socket.timeout:
               pass
           except Exception as e:
               print("Error reading from socket:", str(e))
   except Exception as e:
       print("Failed to bind UDP socket:", str(e))
   finally:
       if sock:
           sock.close()
           print("Socket closed")
           
startFunction()

I realized another issue I had with the code and solve the while loop issue so that doesn't matter anymore

But do you have any other suggestions for how I can quickly update a tag? my options are pretty open as I'm also programming our custom PCB to read the value from the linear encoder

Your approach would be fine in an asynchronous thread. Events in Ignition are not supposed to run long--no more than a few milliseconds normally. Running long ties up a thread that usually has multiple duties.

You should be using system.util.invokeAsynchronous() in a gateway startup event to kick off this function. (Which should be defined in a project library script, not def'd in the event itself, for complicated reasons.) If you go this route, you will also need to program your loop to properly suicide on project edits:

https://forum.inductiveautomation.com/search?q=long-lived%20threads%20order%3Alatest

1 Like

this is actually what I had initially which made me believe that while loops was the issue. It was actually that I was using invokeAsynchronous and when it ran it would create a new thread that I was unable to kill and updates didn't kill.

If you go this route, you will also need to program your loop to properly suicide on project edits:

This must be what I was missing because the threads would just build up and kill my gateway. Is there an easy way to detect a project update and kill the thread?

Consider configuring this to store the last x reads with UTC timestamps in a ring buffer and then offload it in chunks at a lower rate (1s, 5s). If you don't care about the real-time data and just need to be able to plot the reads, this should be enough.

You can put the most recent value in a batch a display tag. Or put the entire batch in a dataset tag.

the only value we need is the most recent. So If I send two packets back to back I don't really care about the first one that was sent only the most recent.

It just needs to display the value to the HMI user quickly and we don't care about previous data. Just as long as they can see the current value with as minimal latency as possible

I usually use my system.util.globalVarMap() to hold objects that need to survive project edits. (It can be the native system.util.getGlobals() also, but there are quirks.) It is vital that only native java or jython object types go in there, unless your scripts are smart enough to clean old stuff out. I put the running thread's instance object in there, and the new one retrieves it, interrupts it, and replaces it in that function started from the gateway startup event.

Thread.interrupt()

1 Like

Perhaps you need my EtherNet/IP driver....

this seems like a good idea I'll write that down and look into that

I was also looking at going the OPC UA route and having my board communicate with ignition that way

my only concern is we used Modbus TCP/IP which was fairly slow and OPC UA also uses TCP so that's why I switched to UDP

we also don't really care about the three way handshake with TCP and making sure that packets arrive safely because it would be pretty obvious if there was an issue

The TCP listener class I made here for listening to barcode scanners might be of some use. I was offloading to a queue for another script to chew through, you'll just be writing to a tag. I think there is also a listener server class in the thread somewhere, I was anticipating barcode scanners that acted as clients as well.

Further up in the post it talks about the storing of the old instance and killing it on script environment update/reset.

I'll have a look at this too! Would you happen to know how much of a latency difference I would get between UDP and TCP? I know UDP is generally faster and has a smaller feature set (which is fine) but I don't know how much slower TCP would be

What are you up to that you need the data to update this quickly but you also only care about the latest value?

It just needs to display the value to the HMI user quickly

Is your HMI in Vision or Perspective?

It's a chop saw where you move the end stop and it outputs the value to the screen. And that value on the screen is the calculated length of the piece you are cutting.

So the value updates don't need to be instant but I went down and tried out the machine and it's just very odd with latency. And if you're trying to get a very precise value and move it slightly the latency doesn't help because you tend to overshoot where you move the saw stop

It's Vision