--- a/Beremiz_service.py Thu Apr 12 16:38:09 2018 +0200
+++ b/Beremiz_service.py Thu Apr 12 22:20:38 2018 +0200
@@ -35,7 +35,7 @@
-from runtime import PLCObject, ServicePublisher
+from runtime import PLCObject, ServicePublisher, MainWorker import util.paths as paths
@@ -401,7 +401,7 @@
def __init__(self, servicename, ip_addr, port,
- workdir, argv, autostart=False,
statuschange=None, evaluator=default_evaluator,
@@ -413,12 +413,11 @@
self.servicepublisher = None
- self.autostart = autostart
self.statuschange = statuschange
self.evaluator = evaluator
self.pyruntimevars = pyruntimevars
self.daemon = pyro.Daemon(host=self.ip_addr, port=self.port)
@@ -426,7 +425,27 @@
# taking too small timeout value may cause
# unwanted diconnection when IDE is kept busy for long periods
self.daemon.setTimeout(60)
+ uri = self.daemon.connect(self.plcobj, "PLCObject") + print(_("Pyro port :"), self.port) + print(_("Pyro object's uri :"), uri) + # Beremiz IDE detects daemon start by looking + # for self.workdir in the daemon's stdout. + # Therefore don't delete the following line + print(_("Current working directory :"), self.workdir) + # Configure and publish service + # Not publish service if localhost in address params + if self.servicename is not None and \ + self.ip_addr is not None and \ + self.ip_addr != "localhost" and \ + self.ip_addr != "127.0.0.1": + print(_("Publishing service on local network")) + self.servicepublisher = ServicePublisher.ServicePublisher() + self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port) self.daemon.requestLoop()
@@ -440,38 +459,8 @@
- self.plcobj = PLCObject(self.workdir, self.daemon, self.argv,
- self.statuschange, self.evaluator,
- uri = self.daemon.connect(self.plcobj, "PLCObject")
- print(_("Pyro port :"), self.port)
- print(_("Pyro object's uri :"), uri)
- # Beremiz IDE detects daemon start by looking
- # for self.workdir in the daemon's stdout.
- # Therefore don't delete the following line
- print(_("Current working directory :"), self.workdir)
- # Configure and publish service
- # Not publish service if localhost in address params
- if self.servicename is not None and \
- self.ip_addr is not None and \
- self.ip_addr != "localhost" and \
- self.ip_addr != "127.0.0.1":
- print(_("Publishing service on local network"))
- self.servicepublisher = ServicePublisher.ServicePublisher()
- self.servicepublisher.RegisterService(self.servicename, self.ip_addr, self.port)
- if self.plcobj.GetPLCstatus()[0] != "Empty":
- self.plcobj.StatusChange()
+ def RegisterPLCObject(self, plcobj): if self.plcobj is not None:
@@ -529,13 +518,13 @@
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
statuschange, evaluator, pyruntimevars)
taskbar_instance = BeremizTaskBarIcon(pyroserver, enablewx)
pyroserver = Server(servicename, given_ip, port,
- WorkingDir, argv, autostart,
statuschange, pyruntimevars=pyruntimevars)
@@ -631,19 +620,36 @@
LogMessageAndException(_("WAMP client startup failed. "))
+plcobj = PLCObject(pyroserver) +if plcobj.GetPLCstatus()[0] == "Stopped": +pyro_thread = Thread(target=pyroserver.PyroLoop) if havetwisted or havewx:
- pyro_thread = Thread(target=pyroserver.Loop)
+ # reactor._installSignalHandlersAgain() + def ui_thread_target(): + # FIXME: had to disable SignaHandlers install because + # signal not working in non-main thread + reactor.run(installSignalHandlers=False) + ui_thread_target = app.MainLoop
- except KeyboardInterrupt:
+ ui_thread = Thread(target = ui_thread_target) +except KeyboardInterrupt: --- a/runtime/PLCObject.py Thu Apr 12 16:38:09 2018 +0200
+++ b/runtime/PLCObject.py Thu Apr 12 22:20:38 2018 +0200
@@ -23,7 +23,8 @@
from __future__ import absolute_import
-from threading import Timer, Thread, Lock, Semaphore, Event
+from threading import Timer, Thread, Lock, Semaphore, Event, Condition @@ -60,28 +61,133 @@
+ job to be executed by a worker + def __init__(self,call,*args,**kwargs): + self.job = (call,args,kwargs) + do the job by executing the call, and deal with exceptions + call, args, kwargs = self.job + self.result = call(*args,**kwargs) + self.exc_info = sys.exc_info() + serialize main thread load/unload of PLC shared objects + # Only one job at a time + self.todo = Condition(self.mutex) + self.done = Condition(self.mutex) + meant to be called by worker thread (blocking) + self._threadID = thread.get_ident() + while not self._finish: + if self.job is not None: + def call(self, *args, **kwargs): + print("call", args, kwargs) + creates a job, execute it in worker thread, and deliver result. + if job execution raise exception, re-raise same exception + meant to be called by non-worker threads, but this is accepted. + blocking until job done + _job = job(*args,**kwargs) + if self._threadID == thread.get_ident(): + # if caller is worker thread execute immediately + # otherwise notify and wait for completion + while self.job is not None: + raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2] + unblocks main thread, and terminate execution of runloop() + def func_wrapper(*args,**kwargs): + return MainWorker.call(func, *args, **kwargs) class PLCObject(pyro.ObjBase):
- def __init__(self, workingdir, daemon, argv, statuschange, evaluator, pyruntimevars):
+ def __init__(self, server): pyro.ObjBase.__init__(self)
- self.evaluator = evaluator
- self.argv = [workingdir] + argv # force argv[0] to be "path" to exec...
- self.workingdir = workingdir
+ self.evaluator = server.evaluator + self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec... + self.workingdir = server.workdir self.PLClibraryHandle = None
self.PLClibraryLock = Lock()
self.DummyIteratorLock = None
# Creates fake C funcs proxies
- self.statuschange = statuschange
+ self._InitPLCStubCalls() + self.daemon = server.daemon + self.statuschange = server.statuschange - self.pyruntimevars = pyruntimevars
+ self.pyruntimevars = server.pyruntimevars self._loading_error = None
self.python_runtime_vars = None
self.TraceWakeup = Event()
+ server.RegisterPLCObject(self) # Get the last transfered PLC if connector must be restart
@@ -145,6 +251,7 @@
def _GetLibFileName(self):
return os.path.join(self.workingdir, self.CurrentPLCFilename)
@@ -233,19 +340,18 @@
self._loading_error = traceback.format_exc()
PLCprint(self._loading_error)
self.PythonRuntimeCleanup()
+ def _InitPLCStubCalls(self):
- This is also called by __init__ to create dummy C func proxies
+ create dummy C func proxies - self.PLClibraryLock.acquire()
- # Forget all refs to library
self._startPLC = lambda x, y: None
self._stopPLC = lambda: None
self._ResetDebugVariables = lambda: None
@@ -259,11 +365,22 @@
self._GetLogMessage = None
+ self._PLClibraryHandle = None self.PLClibraryHandle = None
+ 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)
- self._PLClibraryHandle = None
+ # Forget all refs to library + self._InitPLCStubCalls() self.PLClibraryLock.release()
@@ -448,7 +565,6 @@
self.PLCStatus = "Stopped"
self.PLCStatus = "Broken"
return self.PLCStatus == "Stopped"
--- a/runtime/__init__.py Thu Apr 12 16:38:09 2018 +0200
+++ b/runtime/__init__.py Thu Apr 12 22:20:38 2018 +0200
@@ -24,5 +24,5 @@
from __future__ import absolute_import
-from runtime.PLCObject import PLCObject, PLCprint
+from runtime.PLCObject import PLCObject, PLCprint, MainWorker import runtime.ServicePublisher