Fix deadlock when indirectly calling PLCObject's evaluator() from twisted of wx event loops. Was freezing while transfer/start/stop through Wamp. Serialize all PLCObject's call to self.PythonRuntimeCall into the same PythonThread thread.
authorEdouard Tisserant
Tue, 09 Apr 2019 09:13:55 +0200
changeset 2582 8f0d6c5fd55f
parent 2581 20eb4e7a0647
child 2583 e172ab28d04e
Fix deadlock when indirectly calling PLCObject's evaluator() from twisted of wx event loops. Was freezing while transfer/start/stop through Wamp. Serialize all PLCObject's call to self.PythonRuntimeCall into the same PythonThread thread.
runtime/PLCObject.py
--- a/runtime/PLCObject.py	Mon Apr 08 12:53:18 2019 +0200
+++ b/runtime/PLCObject.py	Tue Apr 09 09:13:55 2019 +0200
@@ -23,7 +23,7 @@
 
 
 from __future__ import absolute_import
-from threading import Thread, Lock, Semaphore, Event
+from threading import Thread, Lock, Event, Condition
 import ctypes
 import os
 import sys
@@ -379,17 +379,22 @@
             self.LogMessage(0, traceback.format_exc())
             raise
 
-        self.PythonRuntimeCall("init")
+        self.PythonThreadCondLock = Lock()
+        self.PythonThreadCond = Condition(self.PythonThreadCondLock)
+        self.PythonThreadCmd = "Wait"
+        self.PythonThread = Thread(target=self.PythonThreadProc)
+        self.PythonThread.start()
+
 
     # used internaly
     def PythonRuntimeCleanup(self):
         if self.python_runtime_vars is not None:
-            self.PythonRuntimeCall("cleanup")
+            self.PythonThreadCommand("Finish")
+            self.PythonThread.join()
 
         self.python_runtime_vars = None
 
-    def PythonThreadProc(self):
-        self.StartSem.release()
+    def PythonThreadLoop(self):
         res, cmd, blkid = "None", "None", ctypes.c_void_p()
         compile_cache = {}
         while True:
@@ -415,6 +420,37 @@
                 res = "#EXCEPTION : "+str(e)
                 self.LogMessage(1, ('PyEval@0x%x(Code="%s") Exception "%s"') % (FBID, cmd, str(e)))
 
+    def PythonThreadProc(self):
+        print('self.PythonRuntimeCall("init")')
+        self.PythonRuntimeCall("init")
+
+        while True:
+            self.PythonThreadCondLock.acquire()
+            cmd = self.PythonThreadCmd
+            while cmd == "Wait":
+                self.PythonThreadCond.wait()
+                cmd = self.PythonThreadCmd
+                self.PythonThreadCmd = "Wait"
+            self.PythonThreadCondLock.release()
+            
+            if cmd == "Activate" :
+                print('self.PythonRuntimeCall("start")')
+                self.PythonRuntimeCall("start")
+
+                self.PythonThreadLoop()
+                
+                self.PythonRuntimeCall("stop")
+            else:  # "Finish"
+                break
+
+        self.PythonRuntimeCall("cleanup")
+
+    def PythonThreadCommand(self, cmd):
+        self.PythonThreadCondLock.acquire()
+        self.PythonThreadCmd = cmd 
+        self.PythonThreadCond.notify()
+        self.PythonThreadCondLock.release()
+
     @RunInMain
     def StartPLC(self):
         if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped:
@@ -423,11 +459,7 @@
             if res == 0:
                 self.PLCStatus = PlcStatus.Started
                 self.StatusChange()
-                self.PythonRuntimeCall("start")
-                self.StartSem = Semaphore(0)
-                self.PythonThread = Thread(target=self.PythonThreadProc)
-                self.PythonThread.start()
-                self.StartSem.acquire()
+                self.PythonThreadCommand("Activate")
                 self.LogMessage("PLC started")
             else:
                 self.LogMessage(0, _("Problem starting PLC : error %d" % res))
@@ -439,10 +471,8 @@
         if self.PLCStatus == PlcStatus.Started:
             self.LogMessage("PLC stopped")
             self._stopPLC()
-            self.PythonThread.join()
             self.PLCStatus = PlcStatus.Stopped
             self.StatusChange()
-            self.PythonRuntimeCall("stop")
             if self.TraceThread is not None:
                 self.TraceThread.join()
                 self.TraceThread = None