21 # License along with this library; if not, write to the Free Software |
21 # License along with this library; if not, write to the Free Software |
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
23 |
24 |
24 |
25 from __future__ import absolute_import |
25 from __future__ import absolute_import |
26 import thread |
|
27 from threading import Thread, Lock, Semaphore, Event, Condition |
26 from threading import Thread, Lock, Semaphore, Event, Condition |
28 import ctypes |
27 import ctypes |
29 import os |
28 import os |
30 import sys |
29 import sys |
31 import traceback |
30 import traceback |
32 from time import time |
31 from time import time |
33 import _ctypes # pylint: disable=wrong-import-order |
32 import _ctypes # pylint: disable=wrong-import-order |
34 import Pyro.core as pyro |
|
35 |
33 |
36 from runtime.typemapping import TypeTranslator |
34 from runtime.typemapping import TypeTranslator |
37 from runtime.loglevels import LogLevelsDefault, LogLevelsCount |
35 from runtime.loglevels import LogLevelsDefault, LogLevelsCount |
|
36 from runtime import MainWorker |
38 |
37 |
39 if os.name in ("nt", "ce"): |
38 if os.name in ("nt", "ce"): |
40 dlopen = _ctypes.LoadLibrary |
39 dlopen = _ctypes.LoadLibrary |
41 dlclose = _ctypes.FreeLibrary |
40 dlclose = _ctypes.FreeLibrary |
42 elif os.name == "posix": |
41 elif os.name == "posix": |
59 def PLCprint(message): |
58 def PLCprint(message): |
60 sys.stdout.write("PLCobject : "+message+"\n") |
59 sys.stdout.write("PLCobject : "+message+"\n") |
61 sys.stdout.flush() |
60 sys.stdout.flush() |
62 |
61 |
63 |
62 |
64 class job(object): |
|
65 """ |
|
66 job to be executed by a worker |
|
67 """ |
|
68 def __init__(self, call, *args, **kwargs): |
|
69 self.job = (call, args, kwargs) |
|
70 self.result = None |
|
71 self.success = False |
|
72 self.exc_info = None |
|
73 |
|
74 def do(self): |
|
75 """ |
|
76 do the job by executing the call, and deal with exceptions |
|
77 """ |
|
78 try: |
|
79 call, args, kwargs = self.job |
|
80 self.result = call(*args, **kwargs) |
|
81 self.success = True |
|
82 except Exception: |
|
83 self.success = False |
|
84 self.exc_info = sys.exc_info() |
|
85 |
|
86 |
|
87 class worker(object): |
|
88 """ |
|
89 serialize main thread load/unload of PLC shared objects |
|
90 """ |
|
91 def __init__(self): |
|
92 # Only one job at a time |
|
93 self._finish = False |
|
94 self._threadID = None |
|
95 self.mutex = Lock() |
|
96 self.todo = Condition(self.mutex) |
|
97 self.done = Condition(self.mutex) |
|
98 self.free = Condition(self.mutex) |
|
99 self.job = None |
|
100 |
|
101 def runloop(self, *args, **kwargs): |
|
102 """ |
|
103 meant to be called by worker thread (blocking) |
|
104 """ |
|
105 self._threadID = thread.get_ident() |
|
106 if args or kwargs: |
|
107 job(*args, **kwargs).do() |
|
108 # result is ignored |
|
109 self.mutex.acquire() |
|
110 while not self._finish: |
|
111 self.todo.wait() |
|
112 if self.job is not None: |
|
113 self.job.do() |
|
114 self.done.notify() |
|
115 else: |
|
116 self.free.notify() |
|
117 self.mutex.release() |
|
118 |
|
119 def call(self, *args, **kwargs): |
|
120 """ |
|
121 creates a job, execute it in worker thread, and deliver result. |
|
122 if job execution raise exception, re-raise same exception |
|
123 meant to be called by non-worker threads, but this is accepted. |
|
124 blocking until job done |
|
125 """ |
|
126 |
|
127 _job = job(*args, **kwargs) |
|
128 |
|
129 if self._threadID == thread.get_ident() or self._threadID is None: |
|
130 # if caller is worker thread execute immediately |
|
131 _job.do() |
|
132 else: |
|
133 # otherwise notify and wait for completion |
|
134 self.mutex.acquire() |
|
135 |
|
136 while self.job is not None: |
|
137 self.free.wait() |
|
138 |
|
139 self.job = _job |
|
140 self.todo.notify() |
|
141 self.done.wait() |
|
142 _job = self.job |
|
143 self.job = None |
|
144 self.mutex.release() |
|
145 |
|
146 if _job.success: |
|
147 return _job.result |
|
148 else: |
|
149 raise _job.exc_info[0], _job.exc_info[1], _job.exc_info[2] |
|
150 |
|
151 def quit(self): |
|
152 """ |
|
153 unblocks main thread, and terminate execution of runloop() |
|
154 """ |
|
155 # mark queue |
|
156 self._finish = True |
|
157 self.mutex.acquire() |
|
158 self.job = None |
|
159 self.todo.notify() |
|
160 self.mutex.release() |
|
161 |
|
162 |
|
163 MainWorker = worker() |
|
164 |
|
165 |
63 |
166 def RunInMain(func): |
64 def RunInMain(func): |
167 def func_wrapper(*args, **kwargs): |
65 def func_wrapper(*args, **kwargs): |
168 return MainWorker.call(func, *args, **kwargs) |
66 return MainWorker.call(func, *args, **kwargs) |
169 return func_wrapper |
67 return func_wrapper |
170 |
68 |
171 |
69 |
172 class PLCObject(pyro.ObjBase): |
70 class PLCObject(object): |
173 def __init__(self, server): |
71 def __init__(self, WorkingDir, argv, statuschange, evaluator, pyruntimevars): |
174 pyro.ObjBase.__init__(self) |
72 self.workingdir = WorkingDir |
175 self.evaluator = server.evaluator |
73 # FIXME : is argv of any use nowadays ? |
176 self.argv = [server.workdir] + server.argv # force argv[0] to be "path" to exec... |
74 self.argv = [WorkingDir] + argv # force argv[0] to be "path" to exec... |
177 self.workingdir = server.workdir |
75 self.statuschange = statuschange |
|
76 self.evaluator = evaluator |
|
77 self.pyruntimevars = pyruntimevars |
178 self.PLCStatus = "Empty" |
78 self.PLCStatus = "Empty" |
179 self.PLClibraryHandle = None |
79 self.PLClibraryHandle = None |
180 self.PLClibraryLock = Lock() |
80 self.PLClibraryLock = Lock() |
181 self.DummyIteratorLock = None |
|
182 # Creates fake C funcs proxies |
81 # Creates fake C funcs proxies |
183 self._InitPLCStubCalls() |
82 self._InitPLCStubCalls() |
184 self.daemon = server.daemon |
|
185 self.statuschange = server.statuschange |
|
186 self.hmi_frame = None |
|
187 self.pyruntimevars = server.pyruntimevars |
|
188 self._loading_error = None |
83 self._loading_error = None |
189 self.python_runtime_vars = None |
84 self.python_runtime_vars = None |
190 self.TraceThread = None |
85 self.TraceThread = None |
191 self.TraceLock = Lock() |
86 self.TraceLock = Lock() |
192 self.Traces = [] |
87 self.Traces = [] |
193 |
88 |
194 # First task of worker -> no @RunInMain |
89 # First task of worker -> no @RunInMain |
195 def AutoLoad(self): |
90 def AutoLoad(self, autostart): |
196 # Get the last transfered PLC |
91 # Get the last transfered PLC |
197 try: |
92 try: |
198 self.CurrentPLCFilename = open( |
93 self.CurrentPLCFilename = open( |
199 self._GetMD5FileName(), |
94 self._GetMD5FileName(), |
200 "r").read().strip() + lib_ext |
95 "r").read().strip() + lib_ext |
201 if self.LoadPLC(): |
96 if self.LoadPLC(): |
202 self.PLCStatus = "Stopped" |
97 self.PLCStatus = "Stopped" |
|
98 if autostart: |
|
99 self.StartPLC() |
|
100 return |
203 except Exception: |
101 except Exception: |
204 self.PLCStatus = "Empty" |
102 self.PLCStatus = "Empty" |
205 self.CurrentPLCFilename = None |
103 self.CurrentPLCFilename = None |
|
104 |
|
105 self.StatusChange() |
206 |
106 |
207 def StatusChange(self): |
107 def StatusChange(self): |
208 if self.statuschange is not None: |
108 if self.statuschange is not None: |
209 for callee in self.statuschange: |
109 for callee in self.statuschange: |
210 callee(self.PLCStatus) |
110 callee(self.PLCStatus) |