# Preventing "Event loop shut down?"

**URL:** <https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013>\
**Category:** Ignition\
**Tags:** scripting\
**Created:** [February 17, 2026, 7:41pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013 "2026-02-17T19:41:28Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![mark.landman89](https://avatars.discourse-cdn.com/v4/letter/m/ed8c4c/32.png) [@mark.landman89](https://forum.inductiveautomation.com/u/mark.landman89)\
**Post date:** [February 17, 2026, 7:41pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/1 "2026-02-17T19:41:28Z")

</div>

I have a script that performance a lengthy FTP transfer. Because of the time it takes the file transfer portion of the script is executed using ‘system.util.invokeAsynchronous’. Everything operates as intended but the output console and/or gateway log returns with the “rejectedExecution: Failed to submit a listener notification task. Event loop shut down?” statement each time.

Is there a way to avoid this from occurring each time?

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [February 17, 2026, 7:47pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/2 "2026-02-17T19:47:35Z")

</div>

Show your code and how you are calling it.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [February 17, 2026, 7:48pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/3 "2026-02-17T19:48:41Z")

</div>

I suspect this has something to do with Jython's janky socket implementation, but sharing your code won't hurt.

---

<div class="post-metadata">

**Author:** ![mark.landman89](https://avatars.discourse-cdn.com/v4/letter/m/ed8c4c/32.png) [@mark.landman89](https://forum.inductiveautomation.com/u/mark.landman89)\
**Post date:** [February 17, 2026, 8:08pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/4 "2026-02-17T20:08:36Z")

</div>

Omitting a couple of variables here, but general pattern:

Value Changed script on a UDT-

```python
if currentValue.value == True:
	node_path = tagPath.replace("[default]","")
	nodeID = node_path.split("/")[0]
	
	
	Units.CCU_Update.CCUSoftwareUpdate(nodeID)
return

```

CCUSoftwareUpdate() ultimately calls the invokeAsynchronous

def CCUSoftwareUpdate(nodeID):

```python

if model == 'V25':
	return
else:
	credentials = {named query for credentials}
	
	arguments = (nodeID, model, credentials, progress_tag)
	message = "{} software update in progress".format(nodeID)
	system.util.invokeAsynchronous(Units.CCU_Update.file_transfer, args = arguments, description= message)
	return
return

```

---

<div class="post-metadata">

**Author:** ![Greyship](https://avatars.discourse-cdn.com/v4/letter/g/898d66/32.png) [@Greyship](https://forum.inductiveautomation.com/u/Greyship)\
**Post date:** [February 17, 2026, 8:30pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/5 "2026-02-17T20:30:20Z")

</div>

> [@mark.landman89](#):
>
> `Units.CCU_Update.file_transfer`

Please show the code for this function as well.

---

<div class="post-metadata">

**Author:** ![mark.landman89](https://avatars.discourse-cdn.com/v4/letter/m/ed8c4c/32.png) [@mark.landman89](https://forum.inductiveautomation.com/u/mark.landman89)\
**Post date:** [February 17, 2026, 8:44pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/6 "2026-02-17T20:44:50Z")

</div>

def file\_transfer(nodeID, model, credentials, progress\_tag):

```python
import ftplib
import os
import StringIO
import socket
import uuid

system.util.getLogger("FTPTransfer").info("Updating CCU software on {}".format(nodeID))
#Set a 10-minute timeout for FTP operations
socket.setdefaulttimeout(600) 

batchID = str(uuid.uuid4())

try:
    # Image File Source Directory
    source_dir = r"C:/Ignition/"+ model +"/"
    
    # Find the only .ci3 file
    ci3_files = [f for f in os.listdir(source_dir) if f.endswith(".ci3")]
    if len(ci3_files) != 1:
        raise Exception("Expected only one .ci3 file, found {}".format(len(ci3_files)))

    # Store the current database filename for logging to the unit as the tag CCU.Statuses.CCU_Software_Rev
    original_filename = ci3_files[0]
    source_filepath = os.path.join(source_dir, original_filename)
    source_filesize = os.path.getsize(source_filepath)
    
     # Extract credentials for the specified device
    host = credentials.getValueAt(0,0)
    password = credentials.getValueAt(0,1)
    user = 'admin'
    activeTag = '[default]' + nodeID + '/Version_Info/Software/CCU_Update_Active'
    
    # Location to copy the image file to on the unit (SD card)
    if model == "MAX":
    	directory = "/DRIVE-E/"
    	port = 21
    else:
    	directory = "/"
    	port = 2111

    if not all([host, user, password]):
        raise Exception("Missing FTP credentials for device '{}'.".format(nodeID)) 
    
    #Establish FTP connection
    ftp = ftplib.FTP()
    ftp.connect(host, port, timeout = 60) #Time out after 60 seconds
    ftp.login(user, password)
    #Set 'Passive' to false, ensuring it is an active connection
    ftp.set_pasv(False)
    #Set the working directory
    ftp.cwd(directory) 
    ftp.voidcmd("TYPE I")

    # Try to read CCU_Software_Rev.txt from the edge node
    # This file contains the filename of the last database image file that was transferred to the unit via this script
    # A mismatch between what is in this file and the image file saved at C:/Ignition/ will trigger a transfer
    # The contents of this file are linked to the tag CCU.Statuses.CCU_Software_Rev
    rev_lines = []
    try:
        ftp.retrlines("RETR CCU_Software_Rev.txt", rev_lines.append)
        edge_rev = rev_lines[0].strip() if rev_lines else ""
    except:
        edge_rev = "" # File doesn't exist or can't be read

    # Compare device CCU software rev against current released CCU software rev
    if edge_rev != original_filename:
        # Update status to 1 in the file 'CCU_Software_Update_Status.txt'
        # This initiates notification popups on the unit HMI for the user to acknowledge
        status = "1"
        status_file = StringIO.StringIO(status + "\n")
        ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
        message = "Revision mismatch or missing. Updating CCU software on {} from {} to {}.".format(nodeID,edge_rev, original_filename)
        system.util.getLogger("FTPTransfer").info(message)
        
        #Set the Progress Active Flag to true
        	#This ensures the progress bar on the Unit Dashboard becomes visible
        
        system.tag.writeAsync(activeTag, True)
        
        #Inititate Manifest Log for the update instance
       
        newVersion = original_filename
        
        Units.CCU_Update.beginCCUmanifest(nodeID, batchID, newVersion, status, message)
		    		
        # Attempt the image file transfer
        try:
        	#set up the Callback function to be used for status updates on the file transfer
        	progress = Units.CCU_Update.progressCallback(source_filesize, progress_tag)
        	
        	with open(source_filepath, "rb") as file:
        		ftp.storbinary("STOR image.ci3", file, blocksize = 32768, callback = progress)

            # Transfer succeeded — update status to 2 and write the current revision to CCU_Software_Rev.txt
			status = "2"
			status_file = StringIO.StringIO(status + "\n")
			ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
			
			txt_content = StringIO.StringIO(original_filename + "\n")
			ftp.storlines("STOR CCU_Software_Rev.txt", txt_content)
			message = "Transfer complete. {} CCU software updated to: {}.".format(nodeID, original_filename)
			system.util.getLogger("FTPTransfer").info(message)
			
			#Completed CCU Manifest
			Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
			
        except socket.timeout as e:
            message = "CCU software update on {} timed out: {}".format(nodeID,e)
            system.util.getLogger("FTPTransfer").error(message)

            # Transfer failed due to timeout — set status to 3
            status = "3"
            status_file = StringIO.StringIO(status + "\n")
            try:
                ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
                message = "Timeout occurred while updating CCU software on {}. Status set to '3'.".format(nodeID)
                system.util.getLogger("FTPTransfer").info(message)
                Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
            except Exception as inner_e:
            	message = "Failed to reset status after timeout on {}: {} ".format(nodeID, inner_e)
            	system.util.getLogger("FTPTransfer").error(message)
            	Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
    else:
        # Transfer not needed, software already up to date — reset status to 0
        status = "0"
        status_file = StringIO.StringIO(status + "\n")
        ftp.storlines("STOR CCU_Software_Update_Status.txt", status_file)
        message = "No update needed. {} already running {}.".format(nodeID,edge_rev)
        system.util.getLogger("FTPTransfer").info(message)
        Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)
    
    ftp.quit()
    
    #Set Progress Tag to False
    	#Ensure progress bar visibility is disabled on the Unit Dashboard
    system.tag.writeAsync(activeTag, False)
    
except Exception as e:
	status = '3'
	message = "CCU software update on {} failed: ".format(nodeID) + str(e)
    system.util.getLogger("FTPTransfer").error(message)
    Units.CCU_Update.updateCCUmanifest(status, message, nodeID, batchID)

#Close the socket
socket.close()
return

```

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [February 17, 2026, 8:50pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/7 "2026-02-17T20:50:51Z")

</div>

> [@mark.landman89](#):
>
> ```python
> #Close the socket
> socket.close()
> 
> ```

This can't possibly do anything but error out... at no point in your script do you have a socket instance or assign one to a `socket` variable...

The only reference to `socket` is your import, and `setdefaulttimeout` works because it's defined in `_socket.py` and not associated with an instance.

---

<div class="post-metadata">

**Author:** ![mark.landman89](https://avatars.discourse-cdn.com/v4/letter/m/ed8c4c/32.png) [@mark.landman89](https://forum.inductiveautomation.com/u/mark.landman89)\
**Post date:** [February 17, 2026, 9:07pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/8 "2026-02-17T21:07:23Z")

</div>

That is correct, and has since been removed. The main code for this was inherited and included the socket.setdefaulttimeout(). The socket.close() was my novice attempt at closing the socket before the defaulttimeout.

Please correct me if I am wrong, but I don't see any reason for socket module to even be imported, as none of the transfers are initiated with it and the FTP transfer has its own timeout for the transfer instance.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [February 17, 2026, 9:14pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/9 "2026-02-17T21:14:17Z")

</div>

Without the `setdefaulttimeout` call I don’t see any reason either.

Unfortunately I don’t think any of this is related to your log message. It’s probably just Jython’s crappy stdlib.

---

<div class="post-metadata">

**Author:** ![Greyship](https://avatars.discourse-cdn.com/v4/letter/g/898d66/32.png) [@Greyship](https://forum.inductiveautomation.com/u/Greyship)\
**Post date:** [February 17, 2026, 10:14pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/10 "2026-02-17T22:14:54Z")

</div>

> [@mark.landman89](#):
>
> `system.tag.writeAsync(activeTag, True)`

Not sure this is it either, but that function call should be `system.tag.writeAsync([activeTag], [True])`

---

<div class="post-metadata">

**Author:** ![Greyship](https://avatars.discourse-cdn.com/v4/letter/g/898d66/32.png) [@Greyship](https://forum.inductiveautomation.com/u/Greyship)\
**Post date:** [February 17, 2026, 10:29pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/11 "2026-02-17T22:29:19Z")

</div>

> [@mark.landman89](#):
>
> ```python
> progress = Units.CCU_Update.progressCallback(source_filesize, progress_tag)
>         	
> with open(source_filepath, "rb") as file:
> ftp.storbinary("STOR image.ci3", file, blocksize = 32768, callback = progress)
> 
> ```

Also take a look at this, according to the ftplib documentation, the callback is a callable that should take a single parameter, which is the data as bytes. I’m not 100% on how this line works:

`progress = Units.CCU_Update.progressCallback(source_filesize, progress_tag)`

But I believe Python tries calling it like `progress(block)`, and that may be erroring out somehow since you’ve already provided parameters.

---

<div class="post-metadata">

**Author:** ![Matrix\_Engineering](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/matrix_engineering/32/19781_2.png) [@Matrix\_Engineering](https://forum.inductiveautomation.com/u/Matrix_Engineering)\
**Post date:** [February 17, 2026, 10:40pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/12 "2026-02-17T22:40:27Z")

</div>

OK so my files are tiny but try this for starters, your architecture seems a bit more complex than mine….

> [@Specify FTP port?](https://forum.inductiveautomation.com/t/specify-ftp-port/80587):
>
> I have a script to retrieve files via FTP after an event. This was working fine, however we then changed our architecture to move the gateway into the cloud, as a result we had to setup port forwarding on our remote routers. import os import system from ftplib import FTP import os.path localpath = "\\\\C:\\Temp\\redacted\\FTP\\" ftp = FTP('10.redacted') ftp.login('redacted','redacted') ftp.cwd('/data/redacted/') files = [] command = 'LIST' ftp.retrlines(command, files.append) f…

Also I need to reply to my own thread because I got that issue of my last reply sorted.

I’ll be VPN’d into that Gateway tomorrow.

---

<div class="post-metadata">

**Author:** ![mark.landman89](https://avatars.discourse-cdn.com/v4/letter/m/ed8c4c/32.png) [@mark.landman89](https://forum.inductiveautomation.com/u/mark.landman89)\
**Post date:** [February 17, 2026, 10:52pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/13 "2026-02-17T22:52:03Z")

</div>

> [@Greyship](#):
>
> Also take a look at this, according to the ftplib documentation, the callback is a callable that should take a single parameter, which is the data as bytes. I’m not 100% on how this line works:
> 
> `progress = Units.CCU_Update.progressCallback(source_filesize, progress_tag)`
> 
> But I believe Python tries calling it like `progress(block)`, and that may be erroring out somehow since you’ve already provided parameters.

This is a custom function I wrote to pass the value of bytes sent as a percentage of the total file to a progress bar on my perspective dashboard.

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/c/ccff1ddb5be9104d73e8f3cabf7f4797bd269f9e.png)

The progress bar is made visible through the tag.writeAsync() you mentioned here:

> [@mark.landman89](#):
>
> system.tag.writeAsync(activeTag, True)

If this is an improper use of its proper syntax than I got lucky because it is working appropriately

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [February 18, 2026, 8:08am UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/14 "2026-02-18T08:08:29Z")

</div>

Callbacks need to be callable, or in other words, functions.  
So unless `Units.CCU_Update.progressCallback` returns a function, this is wrong.

Note that returning a function that captures a context is a common pattern, we call it a closure.  
You just need to make sure you return something that can be called.

---

<div class="post-metadata">

**Author:** ![tgarrison](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/tgarrison/32/80965_2.png) [@tgarrison](https://forum.inductiveautomation.com/u/tgarrison)\
**Post date:** [February 18, 2026, 12:59pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/15 "2026-02-18T12:59:57Z")

</div>

writeAsync and writeBlocking will accept non-lists for single value calls. So what he has is fine.

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [February 18, 2026, 2:18pm UTC](https://forum.inductiveautomation.com/t/preventing-event-loop-shut-down/114013/16 "2026-02-18T14:18:23Z")

</div>

> Because of the time it takes the file transfer portion of the script is executed using

Apparently a known issue that the jython ftplib is slow though still the right move to do it async.

> [@FTP Client Speed via Scripting](https://forum.inductiveautomation.com/t/ftp-client-speed-via-scripting/26620/8):
>
> Dang! We really should try and figure out why Jython's FTPLib is so miserably slow - but kudos for figuring it out, and posting it back here!

You may want to do it directly with the java libraries as the OP in this post tried. You can also download WinSCP and shell out to that instead.
