# HG changeset patch # User etisserant # Date 1220624757 -7200 # Node ID 112b4bc523b37cb8936e90a1c97ca1f9e5685736 # Parent 02d0daed3e461904e2f179005a25f468676dbf5f Fixed bad IPC choice for debugger/PLC/control thread collaboration diff -r 02d0daed3e46 -r 112b4bc523b3 plugger.py --- a/plugger.py Thu Sep 04 16:07:14 2008 +0200 +++ b/plugger.py Fri Sep 05 16:25:57 2008 +0200 @@ -600,7 +600,7 @@ ieclib_path = os.path.join(base_folder, "matiec", "lib") # import for project creation timestamping -from threading import Timer, Lock, Thread +from threading import Timer, Lock, Thread, Semaphore from time import localtime from datetime import datetime # import necessary stuff from PLCOpenEditor @@ -1054,62 +1054,6 @@ for v in self._VariablesList if v["type"] in DebugTypes ])} return debug_code - - def RegisterDebugVarToConnector(self): - self.DebugTimer=None - Idxs = [] - self.TracedIECPath = [] - if self._connector is not None: - self.IECdebug_lock.acquire() - for IECPath,data_tuple in self.IECdebug_datas.iteritems(): - WeakCallableDict, data_log, status = data_tuple - if len(WeakCallableDict) == 0: - # Callable Dict is empty. - # This variable is not needed anymore! - # self.IECdebug_callables.pop(IECPath) - # TODO - print "Unused : " + IECPath - else: - # Convert - Idx = self._IECPathToIdx.get(IECPath,None) - if Idx is not None: - Idxs.append(Idx) - self.TracedIECPath.append(IECPath) - else: - self.logger.write_warning("Debug : Unknown variable %s\n"%IECPath) - self._connector.SetTraceVariablesList(Idxs) - self.IECdebug_lock.release() - - def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs): - """ - Dispatching use a dictionnary linking IEC variable paths - to a WeakKeyDictionary linking - weakly referenced callables to optionnal args - """ - self.IECdebug_lock.acquire() - # If no entry exist, create a new one with a fresh WeakKeyDictionary - IECdebug_data = self.IECdebug_datas.get(IECPath, None) - if IECdebug_data is None: - IECdebug_data = [ - WeakKeyDictionary(), # Callables - [], # Data storage [(tick, data),...] - "Registered"] # Variable status - self.IECdebug_datas[IECPath] = IECdebug_data - - IECdebug_data[0][callableobj]=(args, kwargs) - - self.IECdebug_lock.release() - - if self.DebugTimer is not None: - self.DebugTimer.cancel() - - # Timer to prevent rapid-fire when registering many variables - # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead - self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector]) - # Rearm anti-rapid-fire timer - self.DebugTimer.start() - - return IECdebug_data[1] def Generate_plc_common_main(self): """ @@ -1337,10 +1281,85 @@ self.logger.write_error("Couldn't start PLC !\n") self.UpdateMethodsFromPLCStatus() + def RegisterDebugVarToConnector(self): + self.DebugTimer=None + Idxs = [] + self.TracedIECPath = [] + if self._connector is not None: + self.IECdebug_lock.acquire() + IECPathsToPop = [] + for IECPath,data_tuple in self.IECdebug_datas.iteritems(): + WeakCallableDict, data_log, status = data_tuple + if len(WeakCallableDict) == 0: + # Callable Dict is empty. + # This variable is not needed anymore! + #print "Unused : " + IECPath + IECPathsToPop.append(IECPath) + else: + # Convert + Idx = self._IECPathToIdx.get(IECPath,None) + if Idx is not None: + Idxs.append(Idx) + self.TracedIECPath.append(IECPath) + else: + self.logger.write_warning("Debug : Unknown variable %s\n"%IECPath) + for IECPathToPop in IECPathsToPop: + self.IECdebug_datas.pop(IECPathToPop) + + self._connector.SetTraceVariablesList(Idxs) + self.IECdebug_lock.release() + + def SubscribeDebugIECVariable(self, IECPath, callableobj, *args, **kwargs): + """ + Dispatching use a dictionnary linking IEC variable paths + to a WeakKeyDictionary linking + weakly referenced callables to optionnal args + """ + self.IECdebug_lock.acquire() + # If no entry exist, create a new one with a fresh WeakKeyDictionary + IECdebug_data = self.IECdebug_datas.get(IECPath, None) + if IECdebug_data is None: + IECdebug_data = [ + WeakKeyDictionary(), # Callables + [], # Data storage [(tick, data),...] + "Registered"] # Variable status + self.IECdebug_datas[IECPath] = IECdebug_data + + IECdebug_data[0][callableobj]=(args, kwargs) + + self.IECdebug_lock.release() + + if self.DebugTimer is not None: + self.DebugTimer.cancel() + + # Timer to prevent rapid-fire when registering many variables + # use wx.CallAfter use keep using same thread. TODO : use wx.Timer instead + self.DebugTimer=Timer(0.5,wx.CallAfter,args = [self.RegisterDebugVarToConnector]) + # Rearm anti-rapid-fire timer + self.DebugTimer.start() + + return IECdebug_data[1] + + def UnsubscribeDebugIECVariable(self, IECPath, callableobj): + IECdebug_data = self.IECdebug_datas.get(IECPath, None) + if IECdebug_data is None: + IECdebug_data[0].pop(callableobj,None) + + def DebugCallerFunc(self, weakcallable, value, *args, **kwargs): + # do the call + weakcallable.SetValue(value, *args, **kwargs) + # will unlock debug thread + self.DebugThreadSlowDownLock.release() + def DebugThreadProc(self): + """ + This thread waid PLC debug data, and dispatch them to subscribers + """ + # This lock is used to avoid flooding wx event stack calling callafter + self.DebugThreadSlowDownLock = Semaphore(0) while self._connector is not None: debug_tick, debug_vars = self._connector.GetTraceVariables() - print debug_tick, debug_vars + #print debug_tick, debug_vars if debug_vars is not None and \ len(debug_vars) == len(self.TracedIECPath): for IECPath,value in zip(self.TracedIECPath, debug_vars): @@ -1349,13 +1368,16 @@ WeakCallableDict, data_log, status = data_tuple data_log.append((debug_tick, value)) for weakcallable,(args,kwargs) in WeakCallableDict.iteritems(): - wx.CallAfter(weakcallable.SetValue, value, *args, **kwargs) + # delegate call to wx event loop + wx.CallAfter(self.DebugCallerFunc, weakcallable, value, *args, **kwargs) + # This will block thread if more than one call is waiting + self.DebugThreadSlowDownLock.acquire() elif debug_vars is not None: wx.CallAfter(self.logger.write_warning, "debug data not coherent %d != %d"%(len(debug_vars), len(self.TracedIECPath))) - elif debug_tick == -1: + #elif debug_tick == -1: #wx.CallAfter(self.logger.write, "Debugger unavailable\n") - pass + # pass else: wx.CallAfter(self.logger.write, "Debugger disabled\n") break @@ -1367,6 +1389,7 @@ if self.GetIECProgramsAndVariables() and \ self._connector.StartPLC(debug=True): self.logger.write("Starting PLC (debug mode)\n") + self.TracedIECPath = [] # TODO : laucnch PLCOpenEditor in Debug Mode self.DebugThread = Thread(target=self.DebugThreadProc) self.DebugThread.start() @@ -1379,12 +1402,13 @@ # self.temporary_non_weak_callable_refs = [] # for IEC_Path, idx in self._IECPathToIdx.iteritems(): # class tmpcls: -# def __init__(self): +# def __init__(_self): # self.buf = None -# def setbuf(self,buf): +# def setbuf(_self,buf): # self.buf = buf -# def SetValue(self, value, idx, name): -# print "debug call:", value, idx, name, self.buf +# def SetValue(_self, value, idx, name): +# self.logger.write("debug call: %s %d %s\n"%(repr(value), idx, name)) +# #self.logger.write("debug call: %s %d %s %s\n"%(repr(value), idx, name, repr(self.buf))) # a = tmpcls() # res = self.SubscribeDebugIECVariable(IEC_Path, a, idx, IEC_Path) # a.setbuf(res) diff -r 02d0daed3e46 -r 112b4bc523b3 runtime/PLCObject.py --- a/runtime/PLCObject.py Thu Sep 04 16:07:14 2008 +0200 +++ b/runtime/PLCObject.py Fri Sep 05 16:25:57 2008 +0200 @@ -302,9 +302,9 @@ for given_idx in self._Idxs: buffer=self._IterDebugData(ctypes.byref(idx), ctypes.byref(typename)) - c_type,unpack_func = self.TypeTranslator.get(typename.value, None) + c_type,unpack_func = self.TypeTranslator.get(typename.value, (None,None)) if c_type is not None and given_idx == idx.value: - res.append(unpack_func(ctypes.cast(buffer, + res.append(unpack_func(ctypes.cast(buffer, ctypes.POINTER(c_type)).contents)) else: print "Debug error idx : %d, expected_idx %d, type : %s"%(idx.value, given_idx,typename.value) diff -r 02d0daed3e46 -r 112b4bc523b3 targets/Linux/plc_Linux_main.c --- a/targets/Linux/plc_Linux_main.c Thu Sep 04 16:07:14 2008 +0200 +++ b/targets/Linux/plc_Linux_main.c Fri Sep 05 16:25:57 2008 +0200 @@ -61,6 +61,12 @@ exit(0); } + +static int __debug_tick; + +static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER; + int startPLC(int argc,char **argv) { struct sigevent sigev; @@ -73,6 +79,8 @@ sigev.sigev_notify_attributes = NULL; sigev.sigev_notify_function = PLC_timer_notify; + pthread_mutex_lock(&wait_mutex); + timer_create (CLOCK_REALTIME, &sigev, &PLC_timer); if( __init(argc,argv) == 0 ){ PLC_SetTimer(Ttick,Ttick); @@ -86,18 +94,14 @@ return 0; } -static int __debug_tick; +int TryEnterDebugSection(void) +{ + return pthread_mutex_trylock(&debug_mutex) == 0; +} -static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; - -void AbortDebug() +void LeaveDebugSection(void) { - /* Eventually unlock debugger thread*/ - __debug_tick = -1; - //pthread_mutex_lock(&wait_mutex); - pthread_cond_broadcast(&wait_cond); - //pthread_mutex_unlock(&wait_mutex); + pthread_mutex_unlock(&debug_mutex); } int stopPLC() @@ -106,7 +110,8 @@ PLC_SetTimer(0,0); timer_delete (PLC_timer); __cleanup(); - AbortDebug(); + __debug_tick = -1; + pthread_mutex_unlock(&wait_mutex); } extern int __tick; @@ -115,8 +120,6 @@ { /* Wait signal from PLC thread */ pthread_mutex_lock(&wait_mutex); - pthread_cond_wait(&wait_cond, &wait_mutex); - pthread_mutex_unlock(&wait_mutex); return __debug_tick; } @@ -124,9 +127,23 @@ * This is supposed to unlock debugger thread in WaitDebugData*/ void InitiateDebugTransfer() { - /* signal debugger thread to continue*/ + /* Leave debugger section */ + pthread_mutex_unlock(&debug_mutex); + /* remember tick */ __debug_tick = __tick; - //pthread_mutex_lock(&wait_mutex); - pthread_cond_broadcast(&wait_cond); - //pthread_mutex_unlock(&wait_mutex); + /* signal debugger thread it can read data */ + pthread_mutex_unlock(&wait_mutex); } + +void suspendDebug() +{ + /* Prevent PLC to enter debug code */ + pthread_mutex_lock(&debug_mutex); +} + +void resumeDebug() +{ + /* Let PLC enter debug code */ + pthread_mutex_unlock(&debug_mutex); +} + diff -r 02d0daed3e46 -r 112b4bc523b3 targets/plc_common_main.c --- a/targets/plc_common_main.c Thu Sep 04 16:07:14 2008 +0200 +++ b/targets/plc_common_main.c Fri Sep 05 16:25:57 2008 +0200 @@ -54,13 +54,11 @@ %(retrieve_calls)s - if(Debugging) __retrieve_debug(); + /*__retrieve_debug();*/ config_run__(__tick); - if(Debugging) __publish_debug(); - else if(WasDebugging) AbortDebug(); - WasDebugging = Debugging; + __publish_debug(); %(publish_calls)s @@ -173,18 +171,3 @@ } } } - -extern int WaitDebugData(); -void suspendDebug() -{ - /* Prevent PLC to enter debug code */ - Debugging = 0; - /* wait next tick end to be sure*/ - WaitDebugData(); -} - -void resumeDebug() -{ - /* Let PLC enter debug code */ - Debugging = 1; -} diff -r 02d0daed3e46 -r 112b4bc523b3 targets/plc_debug.c --- a/targets/plc_debug.c Thu Sep 04 16:07:14 2008 +0200 +++ b/targets/plc_debug.c Fri Sep 05 16:25:57 2008 +0200 @@ -63,52 +63,62 @@ { } +extern int TryEnterDebugSection(void); +extern void LeaveDebugSection(void); + +extern int __tick; void __publish_debug() { - /* Lock buffer */ - long latest_state = AtomicCompareExchange( - &buffer_state, - BUFFER_FREE, - BUFFER_BUSY); - - /* If buffer was free */ - if(latest_state == BUFFER_FREE) - { - int* subscription; - - /* Reset buffer cursor */ - buffer_cursor = debug_buffer; - - /* iterate over subscriptions */ - for(subscription=subscription_table; - subscription < latest_subscription; - subscription++) + /* Check there is no running debugger re-configuration */ + if(TryEnterDebugSection()){ + /* Lock buffer */ + long latest_state = AtomicCompareExchange( + &buffer_state, + BUFFER_FREE, + BUFFER_BUSY); + + /* If buffer was free */ + if(latest_state == BUFFER_FREE) { - /* get variable descriptor */ - struct_plcvar* my_var = &variable_table[*subscription]; - char* next_cursor; - /* get variable size*/ - USINT size = __get_type_enum_size(my_var->type); - /* compute next cursor positon*/ - next_cursor = buffer_cursor + size; - /* if buffer not full */ - if(next_cursor < debug_buffer + BUFFER_SIZE) + int* subscription; + + /* Reset buffer cursor */ + buffer_cursor = debug_buffer; + + /* iterate over subscriptions */ + for(subscription=subscription_table; + subscription < latest_subscription; + subscription++) { - /* copy data to the buffer */ - memcpy(buffer_cursor, my_var->ptrvalue, size); - /* increment cursor according size*/ - buffer_cursor = next_cursor; - }else{ - /*TODO : signal overflow*/ + /* get variable descriptor */ + struct_plcvar* my_var = &variable_table[*subscription]; + char* next_cursor; + /* get variable size*/ + USINT size = __get_type_enum_size(my_var->type); + /* compute next cursor positon*/ + next_cursor = buffer_cursor + size; + /* if buffer not full */ + if(next_cursor < debug_buffer + BUFFER_SIZE) + { + /* copy data to the buffer */ + memcpy(buffer_cursor, my_var->ptrvalue, size); + /* increment cursor according size*/ + buffer_cursor = next_cursor; + }else{ + /*TODO : signal overflow*/ + } } + + /* Reset buffer cursor again (for IterDebugData)*/ + buffer_cursor = debug_buffer; + subscription_cursor = subscription_table; + + /* Leave debug section, + * Trigger asynchronous transmission + * (returns immediately) */ + InitiateDebugTransfer(); /* size */ } - - /* Reset buffer cursor again (for IterDebugData)*/ - buffer_cursor = debug_buffer; - subscription_cursor = subscription_table; - - /* Trigger asynchronous transmission (returns immediately) */ - InitiateDebugTransfer(); /* size */ + LeaveDebugSection(); } }