--- a/Beremiz_service.py Tue Apr 17 11:19:18 2018 +0200
+++ b/Beremiz_service.py Thu Apr 19 12:22:40 2018 +0200
@@ -30,9 +30,10 @@
-from threading import Thread, currentThread, Semaphore
+from threading import Thread, currentThread, Semaphore, Lock from runtime import PLCObject, ServicePublisher, MainWorker
@@ -399,6 +400,7 @@
def __init__(self, servicename, ip_addr, port,
@@ -436,8 +438,9 @@
+ def PyroLoop(self, when_ready): + Pyro.config.PYRO_MULTITHREADED = 0 self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
@@ -452,6 +455,7 @@
self.servicepublisher = ServicePublisher.ServicePublisher()
self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
self.daemon.requestLoop()
@@ -631,12 +635,20 @@
LogMessageAndException(_("WAMP client startup failed. "))
-pyro_thread = Thread(target=pyroserver.PyroLoop)
+pyro_thread_started = Lock() +pyro_thread_started.acquire() +pyro_thread = Thread(target=pyroserver.PyroLoop, + kwargs=dict(when_ready=pyro_thread_started.release)) +# Wait for pyro thread to be effective +pyro_thread_started.acquire() pyroserver.PrintServerInfo()
if havetwisted or havewx:
+ ui_thread_started = Lock() + ui_thread_started.acquire() # reactor._installSignalHandlersAgain()
@@ -649,6 +661,16 @@
ui_thread = Thread(target = ui_thread_target)
+ # This order ui loop to unblock main thread when ready. + reactor.callLater(0,ui_thread_started.release) + wx.CallAfter(ui_thread_started.release) + # Wait for ui thread to be effective + ui_thread_started.acquire() + print("UI thread started successfully.") MainWorker.runloop(pyroserver.AutoLoad)
except KeyboardInterrupt:
--- a/runtime/PLCObject.py Tue Apr 17 11:19:18 2018 +0200
+++ b/runtime/PLCObject.py Thu Apr 19 12:22:40 2018 +0200
@@ -95,6 +95,7 @@
self.todo = Condition(self.mutex)
self.done = Condition(self.mutex)
+ self.free = Condition(self.mutex) def runloop(self,*args,**kwargs):
@@ -110,7 +111,9 @@
def call(self, *args, **kwargs):
@@ -123,7 +126,7 @@
_job = job(*args,**kwargs)
- if self._threadID == thread.get_ident():
+ if self._threadID == thread.get_ident() or self._threadID is None: # if caller is worker thread execute immediately
@@ -131,7 +134,7 @@
while self.job is not None:
@@ -168,7 +171,6 @@
class PLCObject(pyro.ObjBase):
def __init__(self, server):
pyro.ObjBase.__init__(self)
self.evaluator = server.evaluator
self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec...
@@ -187,11 +189,11 @@
self.python_runtime_vars = None
- self.TraceWakeup = Event()
+ # First task of worker -> no @RunInMain - # Get the last transfered PLC if connector must be restart
+ # Get the last transfered PLC self.CurrentPLCFilename = open(
@@ -207,6 +209,7 @@
for callee in self.statuschange:
def LogMessage(self, *args):
@@ -218,16 +221,19 @@
return self._LogMessage(level, msg, len(msg))
if self._ResetLogCount is not None:
def GetLogCount(self, level):
if self._GetLogCount is not None:
return int(self._GetLogCount(level))
elif self._loading_error is not None and level == 0:
def GetLogMessage(self, level, msgid):
tv_sec = ctypes.c_uint32()
@@ -252,13 +258,13 @@
def _GetLibFileName(self):
return os.path.join(self.workingdir, self.CurrentPLCFilename)
Declare all functions, arguments and return values
md5 = open(self._GetMD5FileName(), "r").read()
+ self.PLClibraryLock.acquire() self._PLClibraryHandle = dlopen(self._GetLibFileName())
self.PLClibraryHandle = ctypes.CDLL(self.CurrentPLCFilename, handle=self._PLClibraryHandle)
@@ -335,14 +341,24 @@
self._loading_error = None
- self.PythonRuntimeInit()
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
+ self.PLClibraryLock.release() + self.PythonRuntimeInit()
@@ -375,15 +391,17 @@
This is also called by __init__ to create dummy C func proxies
self.PLClibraryLock.acquire()
- # Unload library explicitely
- if getattr(self, "_PLClibraryHandle", None) is not None:
- dlclose(self._PLClibraryHandle)
+ # Unload library explicitely + if getattr(self, "_PLClibraryHandle", None) is not None: + dlclose(self._PLClibraryHandle) - # Forget all refs to library
- self._InitPLCStubCalls()
+ # Forget all refs to library + self._InitPLCStubCalls() - self.PLClibraryLock.release()
+ self.PLClibraryLock.release() def PythonRuntimeCall(self, methodname):
@@ -396,6 +414,7 @@
self.LogMessage(0, '\n'.join(traceback.format_exception(*exp)))
def PythonRuntimeInit(self):
MethodNames = ["init", "start", "stop", "cleanup"]
self.python_runtime_vars = globals().copy()
@@ -447,6 +466,7 @@
self.PythonRuntimeCall("init")
def PythonRuntimeCleanup(self):
if self.python_runtime_vars is not None:
self.PythonRuntimeCall("cleanup")
@@ -509,12 +529,12 @@
self.PythonRuntimeCall("stop")
if self.TraceThread is not None:
return self.PLCStatus, map(self.GetLogCount, xrange(LogLevelsCount))
@@ -524,14 +544,25 @@
NewFileName = md5sum + lib_ext
extra_files_log = os.path.join(self.workingdir, "extra_files.txt")
+ old_PLC_filename = os.path.join(self.workingdir, \ + self.CurrentPLCFilename) \ + if self.CurrentPLCFilename is not None \ + new_PLC_filename = os.path.join(self.workingdir, NewFileName) + # Some platform (i.e. Xenomai) don't like reloading same .so file + replace_PLC_shared_object = new_PLC_filename != old_PLC_filename + if replace_PLC_shared_object: self.LogMessage("NewPLC (%s)" % md5sum)
- os.remove(os.path.join(self.workingdir,
- self.CurrentPLCFilename))
+ if replace_PLC_shared_object: + os.remove(old_PLC_filename) for filename in file(extra_files_log, "r").readlines() + [extra_files_log]:
os.remove(os.path.join(self.workingdir, filename.strip()))
@@ -542,8 +573,8 @@
- open(os.path.join(self.workingdir, NewFileName),
+ if replace_PLC_shared_object: + open(new_PLC_filename, 'wb').write(data) # Store new PLC filename based on md5 key
open(self._GetMD5FileName(), "w").write(md5sum)
@@ -563,7 +594,9 @@
PLCprint(traceback.format_exc())
+ if not replace_PLC_shared_object: + self.PLCStatus = "Stopped" self.PLCStatus = "Stopped"
self.PLCStatus = "Broken"
@@ -602,13 +635,6 @@
- def _TracesPush(self, trace):
- self.TraceLock.acquire()
- if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024:
- self.Traces.append(trace)
- self.TraceLock.release()
self.LastSwapTrace = time()
@@ -619,26 +645,9 @@
- def _TracesAutoSuspend(self):
- # TraceProc stops here if Traces not polled for 3 seconds
- traces_age = time() - self.LastSwapTrace
- self.TraceLock.acquire()
- self.TraceLock.release()
- self._suspendDebug(True) # Disable debugger
- self.TraceWakeup.clear()
- self.TraceWakeup.wait()
- self._resumeDebug() # Re-enable debugger
- def _TracesFlush(self):
- self.TraceLock.acquire()
- self.TraceLock.release()
def GetTraceVariables(self):
return self.PLCStatus, self._TracesSwap()
@@ -646,28 +655,47 @@
Return a list of traces, corresponding to the list of required idx
+ self._resumeDebug() # Re-enable debugger while self.PLCStatus == "Started":
- if self.PLClibraryLock.acquire(False):
- res = self._GetDebugData(ctypes.byref(tick),
- TraceBuffer = ctypes.string_at(buff.value, size.value)
- self.PLClibraryLock.release()
+ self.PLClibraryLock.acquire() + res = self._GetDebugData(ctypes.byref(tick), + TraceBuffer = ctypes.string_at(buff.value, size.value) + self.PLClibraryLock.release()
+ # leave thread if GetDebugData isn't happy. if TraceBuffer is not None:
- self._TracesPush((tick.value, TraceBuffer))
- self._TracesAutoSuspend()
+ self.TraceLock.acquire() + if lT != 0 and lT * len(self.Traces[0]) > 1024 * 1024: + self.Traces.append((tick.value, TraceBuffer)) + self.TraceLock.release() + # TraceProc stops here if Traces not polled for 3 seconds + traces_age = time() - self.LastSwapTrace + self.TraceLock.acquire() + self.TraceLock.release() + self._suspendDebug(True) # Disable debugger + self.TraceThread = None def RemoteExec(self, script, *kwargs):