Fixed bad IPC choice for debugger/PLC/control thread collaboration
authoretisserant
Fri, 05 Sep 2008 16:25:57 +0200
changeset 239 112b4bc523b3
parent 238 02d0daed3e46
child 240 992ae3f46fa1
Fixed bad IPC choice for debugger/PLC/control thread collaboration
plugger.py
runtime/PLCObject.py
targets/Linux/plc_Linux_main.c
targets/plc_common_main.c
targets/plc_debug.c
--- 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)
--- 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)
--- 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);
+}
+
--- 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;
-}
--- 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();
     }
 }