Terminate Gateway Async Thread

Is there a way to terminate/force close an async thread created from a tag event?

No, you just need to be careful not to hang yourself. If you spawned off an async infinite loop you need to restart your gateway now.

No, I have a java file watching service in the thread. I have a getStatus() function that returns the thread Id, if it is alive, and if it is interrupted. Sometimes it stops working despite it still being alive and not interrupted.

while trying to get it to work, I spawned a second thread by accident.

Looks like I will have to interrupt() the thread then join() it with the other thread to get rid of it without a gateway start.

That kind of async thread should be checking a memory tag as part of its otherwise infinite loop. .interrupt() response is somewhat voluntary–only checked by java for you on certain operations.

In my async while loop I check it every iteration using the isInterrupted() method.

Do the other operations in the loop have timeouts to ensure .isInterrupted() is checked reliably?

while self.thread.isAlive() and not self.thread.isInterrupted():				
	k = self.watcher.poll(5,TimeUnit.SECONDS)
	if k:
		for e in k.pollEvents():
			kind = e.kind()
			
			if kind == swek.ENTRY_DELETE:
				filePath = p.resolve(e.context()).toString()
				system.tag.write('Cutting/watchService/deletedFile',filePath)
					
			elif kind == swek.ENTRY_CREATE or kind == swek.ENTRY_MODIFY:
				filePath = p.resolve(e.context()).toString()
				system.tag.write('Cutting/watchService/changedFile',filePath)
		k.reset()

Not sure what the problem is. But two recommendations:

  • Don’t bother checking for alive within the thread–it will never be false.
  • Consider using the static method Thread.interrupted() instead of the instance method.
1 Like

I think my problem is coming from the fact that the directory I’m watching is a mapped drive. I think I read that the WatchService API is not suited for this and I should use FileAlterationMonitor instead.

I’ve started to port a Java example over to Jython, but I’m getting tripped up at the @overide parts as I don’t understand how to register the file events.

What I have so far

from java.io                          import File
#from org.apache.commons.io            import FileDeleteStrategy,FileUtils
from org.apache.commons.io.filefilter import FileFilterUtils
from org.apache.commons.io.monitor    import FileAlterationListenerAdaptor
from org.apache.commons.io.monitor    import FileAlterationMonitor
from org.apache.commons.io.monitor    import FileAlterationObserver

class FileMonitor():
	isRunning = False

	def __init__(self):
		self.monitor = None
		self.thread = None
		self.run()
		
	def _doAsync(self):
		self.isRunning = True
		
		# Directory to monitor for changes
		directory = File("L:\Files\CFC MINIPLOTS").toPath()
		# Only monitor directory for changes on pdf file extensions
		fileFilter = FileFilterUtils.suffixFileFilter("pdf")
		# Create a new file alteration observer
		observer = FileAlterationObserver(directory, fileFilter)	
		# Add listner to the observer	
		observer.addListner(FileAlterationListenerAdaptor())
		# Create file alteration monitor at 1 second polling rate
		self.monitor = fileAlterationMonitor(1000,observer)
		# Start monitoring for file alterations
		self.monitor.start()
		
		logger = system.util.getLogger("File Alteration Monitoring")
		
		while self.isRunning and not self.thread.interrupted():
			logger.debug('In While Loop')
			
	def run(self): # Create and run the File Monitor in an Aysnc Thread
		self.thread = system.util.invokeAsynchronous(self._doAsync)
			
	def stop(self):
		self.isRunning = False
		self.monitor.stop()

		
'''	
Tutorials Point Java Example Code

   	usingFileAlterationMonitor() throws IOException {

      FileAlterationObserver observer = new
      FileAlterationObserver(parentDirectory);
      observer.addListener(new FileAlterationListenerAdaptor(){
         @Override
         public void onDirectoryCreate(File file) {
            System.out.println("Folder created: " + file.getName());
         }
         @Override
         public void onDirectoryDelete(File file) {
            System.out.println("Folder deleted: " + file.getName());
         }
         @Override
         public void onFileCreate(File file) {
            System.out.println("File created: " + file.getName());
         }
         @Override
         public void onFileDelete(File file) {
            System.out.println("File deleted: " + file.getName());
         }
      });
      //create a monitor to check changes after every 500 ms
      FileAlterationMonitor monitor = new FileAlterationMonitor(500, observer);
      try {
         monitor.start();
         //create a new directory
         File newFolder = new File("test");
         File newFile = new File("test1");
         newFolder.mkdirs();
         Thread.sleep(1000);
         newFile.createNewFile();
         Thread.sleep(1000);
         FileDeleteStrategy.NORMAL.delete(newFolder);
         Thread.sleep(1000);
         FileDeleteStrategy.NORMAL.delete(newFile);
         Thread.sleep(1000);
         monitor.stop(10000);
      } catch(IOException e) {
         System.out.println(e.getMessage());
      } catch(InterruptedException e) {
         System.out.println(e.getMessage());
      } catch (Exception e) {
         System.out.println(e.getMessage());
      }
   }
}

To subclass a Java class in Jython, you make a new class that’s a subclass of the Java one, call the Java constructor (if necessary) by defining an __init__ method that takes the same parameters and calls super.__init__, then override the Java methods by defining a method with the same arity plus the self argument:

class NetworkListenerAdapter(FileAlterationListenerAdaptor):
    def __init__(self):
        # do any initialization; superclass has no parameters
        pass

    def onDirectoryCreate(self, file):
        pass
        
    def onDirectoryDelete(self, file):
        pass
1 Like

I made some changes and was on this track, but I didn’t quite have it correct. Thanks for the example, makes more sense now.

from java.io                          import File
#from org.apache.commons.io            import FileDeleteStrategy,FileUtils
from org.apache.commons.io.filefilter import FileFilterUtils
from org.apache.commons.io.monitor    import FileAlterationListenerAdaptor
from org.apache.commons.io.monitor    import FileAlterationMonitor
from org.apache.commons.io.monitor    import FileAlterationObserver

class FileMonitor():
	isRunning = False

	def __init__(self):
		self.monitor = None
		self.thread = None
		self.run()
		
	def _doAsync(self):
		self.isRunning = True
		
		# Directory to monitor for changes
		directory = File("L:\Files\CFC MINIPLOTS").toPath()
		# Only monitor directory for changes on pdf file extensions
		fileFilter = FileFilterUtils.suffixFileFilter(".pdf")
		# Create a new file alteration observer
		observer = FileAlterationObserver(directory, fileFilter)	
		# Add listner to the observer with overridden class methods	
		observer.addListner(NetworkListenerAdapter())
		# Create file alteration monitor at 1 second polling rate
		self.monitor = fileAlterationMonitor(1000,observer)
		# Start monitoring for file alterations
		self.monitor.start()
		
		logger = system.util.getLogger("File Alteration Monitoring")
		logger.debug('Thread Started')
			
	def run(self): # Create and run the File Monitor in an Aysnc Thread
		self.thread = system.util.invokeAsynchronous(self._doAsync)
			
	def stop(self):
		self.isRunning = False
		self.monitor.stop()


class NetworkListenerAdapter(FileAlterationListenerAdaptor):
	def __init__(self):
		self.logger = system.util.getLogger("File Alteration Monitoring")
		
	def onFileChange(self, file):
		self.logger.debug('File Change Detected')
		system.tag.write('Cutting/watchService/changedFile',filePath)
		
	def onFileCreate(self, file):
		self.logger.debug('File Creation Detected')
		system.tag.write('Cutting/watchService/changedFile',filePath)
		
	def onfileDelete(self, file):
		self.logger.debug('File Deletion Detected')
		system.tag.write('Cutting/watchService/deletedFile',filePath)
1 Like

Okay, I’ve debugged some typos in my script. Now, when I start the file monitor, the thread state is Runnable but I don’t see any file events being logged. Any idea what I’m missing?

from java.io                          import File
#from org.apache.commons.io            import FileDeleteStrategy,FileUtils
from org.apache.commons.io.filefilter import FileFilterUtils
from org.apache.commons.io.monitor    import FileAlterationListenerAdaptor
from org.apache.commons.io.monitor    import FileAlterationMonitor
from org.apache.commons.io.monitor    import FileAlterationObserver
from java.lang                        import Exception

class FileMonitor():
	isRunning = False

	def __init__(self):
		self.monitor = None
		self.thread = None
		self.run()
		
	def _doAsync(self):
		self.isRunning = True
		logger = system.util.getLogger("File Alteration Monitoring")
		# Directory to monitor for changes
		directory = str(File("C:\Users\dhayes\Desktop\TestFolder").toPath())
		# Only monitor directory for changes on pdf file extensions
		fileFilter = FileFilterUtils.suffixFileFilter(".pdf")
		# Create a new file alteration observer
		observer = FileAlterationObserver(directory, fileFilter)	
		# Add listner to the observer with overridden class methods	
		observer = observer.addListener(NetworkListenerAdapter())
		# Create file alteration monitor at 1 second polling rate
		self.monitor = FileAlterationMonitor(1000,observer)
		# Start monitoring for file alterations
		self.monitor.start()
		logger.debug('Thread Started')
		print 'Thread Started'
						
	def run(self): # Create and run the File Monitor in an Aysnc Thread
		self.thread = system.util.invokeAsynchronous(self._doAsync)
			
	def stop(self):
		self.isRunning = False
		self.monitor.stop()


class NetworkListenerAdapter(FileAlterationListenerAdaptor):
	def __init__(self):
		self.logger = system.util.getLogger("File Alteration Monitoring")
		
	def onFileChange(self, file):
		self.logger.debug('File Change Detected')
		system.tag.write('Cutting/watchService/changedFile',filePath)
		
	def onFileCreate(self, file):
		self.logger.debug('File Creation Detected')
		system.tag.write('Cutting/watchService/changedFile',filePath)
		
	def onfileDelete(self, file):
		self.logger.debug('File Deletion Detected')
		system.tag.write('Cutting/watchService/deletedFile',filePath)

Here’s my problem, not sure what this means…

14:30:41.868 [AWT-EventQueue-0] INFO designer.update-and-save - Save finished in 40ms.
<type 'org.apache.commons.io.monitor.FileAlterationObserver'>
14:30:55.773 [AWT-EventQueue-0] ERROR com.inductiveautomation.ignition.client.util.gui.ErrorUtil - <HTML>Error running function from <code>fpmi.system.invokeAsynchronous</code>
com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last):
  File "<module:shared.fileMonitor>", line 32, in _doAsync
TypeError: org.apache.commons.io.monitor.FileAlterationMonitor(): 2nd arg can't be coerced to org.apache.commons.io.monitor.FileAlterationObserver[]

Looks like you need to supply a list/array of observers.

I just figured it out. I had to do this before passing the observer to the FAM

observer.initialize()

So I have the file monitor working, but in order to keep the thread Runnable, I had to add this while loop

while self.isRunning and not self.thread.interrupted():
    observer.checkAndNotify()

This is not good, as you would put it, “This is a horrible CPU burning script.” It runs at 24% CPU by itself lol. I can’t get the file events to register unless I manually call the checkAndNotify() method. Is there a way to make the check poll() instead of loop out of control?

Why do you want it “Runnable”? That explicitly means it will burn CPU. Waiting is the correct state when literally waiting on a notification.

It is Runnable by default? That is its state when I call it, not sure how to make it Timed_Waiting. You think it would be that since FileAlterationMonitor is being feed a 1000 ms poll rate. However, if I don’t have a while loop, the thread goes immediately into a Terminated state.

Weird thing is the Monitor has two methods run() and start(). Maybe I should call run() then start() instead of just start()?

Interesting…

run
public void run()
Runs this monitor.
Specified by:
run in interface Runnable