Python runtime: ensure that python thread finished before returning from StopPLC.
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Sat, 25 May 2024 15:28:48 +0200
changeset 3950 79bdee48ced8
parent 3949 f64dce4e2f62
child 3951 fe7c5e8b20bb
Python runtime: ensure that python thread finished before returning from StopPLC.
runtime/PLCObject.py
--- a/runtime/PLCObject.py	Fri May 24 18:55:46 2024 +0200
+++ b/runtime/PLCObject.py	Sat May 25 15:28:48 2024 +0200
@@ -98,6 +98,10 @@
         self.Traces = []
         self.DebugToken = 0
 
+        # Event to signal when PLC is stopped.
+        self.PlcStopped = Event()
+        self.PlcStopped.set()
+
         self._init_blobs()
 
     # First task of worker -> no @RunInMain
@@ -326,7 +330,7 @@
 
         return False
 
-    def PythonRuntimeCall(self, methodname, use_evaluator=True, reverse_order=False):
+    def PythonRuntimeCall(self, methodname, reverse_order=False):
         """
         Calls init, start, stop or cleanup method provided by
         runtime python files, loaded when new PLC uploaded
@@ -335,10 +339,7 @@
         if reverse_order:
             methods = reversed(methods)
         for method in methods:
-            if use_evaluator:
-                _res, exp = self.evaluator(method)
-            else:
-                _res, exp = default_evaluator(method)
+            _res, exp = default_evaluator(method)
             if exp is not None:
                 self.LogMessage(0, '\n'.join(traceback.format_exception(*exp)))
 
@@ -402,7 +403,7 @@
             self.LogMessage(0, traceback.format_exc())
             raise
 
-        self.PythonRuntimeCall("init", use_evaluator=False)
+        self.PythonRuntimeCall("init")
 
         self.PythonThreadCondLock = Lock()
         self.PythonThreadCmdCond = Condition(self.PythonThreadCondLock)
@@ -417,7 +418,7 @@
         if self.python_runtime_vars is not None:
             self.PythonThreadCommand("Finish")
             self.PythonThread.join()
-            self.PythonRuntimeCall("cleanup", use_evaluator=False, reverse_order=True)
+            self.PythonRuntimeCall("cleanup", reverse_order=True)
 
         self.python_runtime_vars = None
 
@@ -470,6 +471,10 @@
                 self._PostStartPLC()
                 self.PythonThreadLoop()
                 self.PythonRuntimeCall("stop", reverse_order=True)
+                
+                # Signal that python runtime has stopped
+                self.PlcStopped.set()
+
             elif cmd == "Finish":
                 self.PythonThreadAcknowledge(cmd)
                 break
@@ -525,6 +530,11 @@
     @RunInMain
     def StartPLC(self):
 
+        # Prevent accidental call to StartPLC when already Started
+        if self.PLCStatus != PlcStatus.Stopped:
+            self.LogMessage(0,_("Problem starting PLC : PLC is not Stopped"))
+            return
+
         if self.PLClibraryHandle is None:
             if not self.LoadPLC():
                 self._fail(_("Problem starting PLC : can't load PLC"))
@@ -538,6 +548,7 @@
                 self.PLCStatus = PlcStatus.Started
                 self.StatusChange()
                 self.PythonThreadCommand("Start")
+                self.PlcStopped.clear()
             else:
                 self._fail(_("Problem starting PLC : error %d" % res))
 
@@ -546,13 +557,18 @@
         if self.PLCStatus == PlcStatus.Started:
             self.LogMessage("PLC stopped")
             self._stopPLC()
-            self.PLCStatus = PlcStatus.Stopped
-            self.StatusChange()
             if self.TraceThread is not None:
                 self.TraceThread.join()
                 self.TraceThread = None
-            return True
-        return False
+
+            # Wait for python runtime stop to complete
+            if self.PlcStopped.wait(timeout=5):
+                self.PLCStatus = PlcStatus.Stopped
+                self.StatusChange()
+            else:
+                self._fail(_("PLC timed out while stopping"))
+                
+        return self.PLCStatus == PlcStatus.Stopped
 
     def GetPLCstatus(self):
         try: