fix NPE from rule timers 53/66753/2
authorJim Hahn <jrh3@att.com>
Fri, 14 Sep 2018 20:30:51 +0000 (16:30 -0400)
committerJim Hahn <jrh3@att.com>
Fri, 14 Sep 2018 21:17:53 +0000 (17:17 -0400)
This problem occurs in rules that have "timer(expr)" expressions before
the "when" clause.  If a working memory element, other than the timer
object, appears in the "when" clause and is updated BY ANY RULE, then
an NPE is thrown.  The solution is to segregate the objects so that
rules having "timer(expr)" expressions only refer to one object, the
XxxTimer object.  In particular, the rule simply sets an "expired" flag
in the XxxTimer object and then all other rules check that flag, rather
than having their own "timer(expr)" expressions.

Corrected comment associated with rule TIMER.FIRED.

Change-Id: I9e6c20ee46af99234daee8ece4862edbd41ba714
Issue-ID: POLICY-1106
Signed-off-by: Jim Hahn <jrh3@att.com>
controlloop/templates/archetype-cl-amsterdam/src/main/resources/archetype-resources/src/main/resources/__closedLoopControlName__.drl
controlloop/templates/archetype-cl-casablanca/src/main/resources/archetype-resources/src/main/resources/__closedLoopControlName__.drl

index 2484972..87a4774 100644 (file)
@@ -105,23 +105,17 @@ declare ParamsCleaner
   controlLoopYaml : String
 end
 
-
 /*
- * Operation Timer
- */
-declare OperationTimer
-  closedLoopControlName : String
-  requestID : String
-  delay : String
-end
-
-/*
- * Control Loop Timer
+ * This object is to provide support for timeouts
+ * due to a bug in drools' built-in timers
  */
 declare ControlLoopTimer
-  closedLoopControlName : String
-  requestID : String
-  delay : String
+    closedLoopControlName : String
+    requestID : String
+    delay : String
+    expired : boolean
+    //timerType is the type of timer: either "ClosedLoop" or "Operation"
+    timerType : String
 end
 
 /*
@@ -221,6 +215,7 @@ rule "${policyName}.EVENT"
                 // Setup the Overall Control Loop timer
                 //
                 ControlLoopTimer clTimer = new ControlLoopTimer();
+                clTimer.setTimerType("ClosedLoop");
                 clTimer.setClosedLoopControlName($event.getClosedLoopControlName());
                 clTimer.setRequestID($event.getRequestId().toString());
                 clTimer.setDelay(manager.getControlLoopTimeout(1500) + "s");
@@ -276,7 +271,7 @@ rule "${policyName}.EVENT.MANAGER"
         $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" )
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $clTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "ClosedLoop", !expired )
     then
 
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -439,7 +434,8 @@ rule "${policyName}.EVENT.MANAGER"
                //
                // insert operation timeout object
                //
-               OperationTimer opTimer = new OperationTimer();
+               ControlLoopTimer opTimer = new ControlLoopTimer();
+               opTimer.setTimerType("Operation");
                opTimer.setClosedLoopControlName($event.getClosedLoopControlName());
                opTimer.setRequestID($event.getRequestId().toString());
                opTimer.setDelay(operation.getOperationTimeout().toString() + "s");
@@ -516,7 +512,7 @@ rule "${policyName}.EVENT.MANAGER.OPERATION.LOCKED.GUARD_PERMITTED"
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId(), "Permit".equalsIgnoreCase(getGuardApprovalStatus()) )
         $lock : TargetLock (requestID == $event.getRequestId())
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
     then
 
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -707,7 +703,7 @@ rule "${policyName}.GUARD.RESPONSE"
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() ) 
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
         $lock : TargetLock (requestID == $event.getRequestId())
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $guardResponse : PolicyGuardResponse(requestID == $event.getRequestId(), $operation.policy.recipe == operation)
     then
 
@@ -770,7 +766,7 @@ rule "${policyName}.APPC.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) 
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : Response( getCommonHeader().RequestId == $event.getRequestId() )
     then
@@ -884,7 +880,7 @@ rule "${policyName}.APPC.LCM.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET ) 
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : LcmResponseWrapper( getBody().getCommonHeader().getRequestId() == $event.getRequestId() )
     then
@@ -989,7 +985,7 @@ rule "${policyName}.SO.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : SOResponseWrapper(requestID.toString() == $event.getRequestId().toString() )
     then
@@ -1073,7 +1069,7 @@ rule "${policyName}.VFC.RESPONSE"
                $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName(), closedLoopEventStatus == ControlLoopEventStatus.ONSET )
                $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
                $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-               $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+               $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
                $response : VFCResponse( requestId.toString() == $event.getRequestId().toString() )     
        then
@@ -1134,19 +1130,34 @@ rule "${policyName}.VFC.RESPONSE"
 
 end
 
+/*
+*
+* This manages a single timer.
+* Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause
+*
+*/
+rule "${policyName}.TIMER.FIRED"
+    timer (expr: $timeout)
+    when
+        $timer : ControlLoopTimer($timeout : delay, !expired)
+    then
+        Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
+        logger.info("This is ${policyName}.TIMER.FIRED");
+        modify($timer){setExpired(true)};
+    end
+
 /*
 *
 * This is the timer that manages the timeout for an individual operation.
 *
 */
 rule "${policyName}.EVENT.MANAGER.OPERATION.TIMEOUT"
-    timer (expr: $to )
     when
         $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" )
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), expired, timerType == "Operation" )
         $lock : TargetLock (requestID == $event.getRequestId())
     then
     
@@ -1205,12 +1216,11 @@ end
 *
 */
 rule "${policyName}.EVENT.MANAGER.TIMEOUT"
-    timer (expr: $to )
     when
         $params : Params( getClosedLoopControlName() == "${closedLoopControlName}" )
         $event : VirtualControlLoopEvent( closedLoopControlName == $params.getClosedLoopControlName() )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() )
+        $clTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), expired, timerType == "ClosedLoop" )
     then
     
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -1248,11 +1258,10 @@ end
 rule "${policyName}.EVENT.MANAGER.CLEANUP"
     when
         $manager : ControlLoopEventManager( $clName : getClosedLoopControlName(), $requestId : getRequestID() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $clName, requestID == $requestId.toString() )
         $operations : LinkedList()
                         from collect( ControlLoopOperationManager( onset.closedLoopControlName == $clName, onset.getRequestId() == $requestId ) )
-        $opTimers : LinkedList()
-                        from collect( OperationTimer( closedLoopControlName == $clName, requestID == $requestId.toString() ) )
+        $timers : LinkedList()
+                        from collect( ControlLoopTimer( closedLoopControlName == $clName, requestID == $requestId.toString() ) )
         $locks : LinkedList()
                         from collect( TargetLock (requestID == $requestId) )
         not( VirtualControlLoopEvent( closedLoopControlName == $clName, requestId == $requestId ) )
@@ -1261,21 +1270,20 @@ rule "${policyName}.EVENT.MANAGER.CLEANUP"
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
     logger.info("{}: {}", $clName, drools.getRule().getName());
 
-    logger.debug("{}: {}: manager={} clTimer={} operations={}", 
+    logger.debug("{}: {}: manager={} timers={} operations={}", 
               $clName, drools.getRule().getName(),
-              $manager, $clTimer, $operations.size());
+              $manager, $timers.size(), $operations.size());
     
     //
     // Retract EVERYTHING
     //
     retract($manager);
-    retract($clTimer);
     
     for(Object manager: $operations) {
         retract((ControlLoopOperationManager) manager);
     }
-    for(Object opTimer: $opTimers) {
-        retract((OperationTimer) opTimer);
+    for(Object timer: $timers) {
+        retract((ControlLoopTimer) timer);
     }
     for(Object lock: $locks) {
         TargetLock tgt = (TargetLock) lock;
index 24f70cd..ea8411a 100644 (file)
@@ -83,21 +83,16 @@ import java.util.Iterator;
 import org.onap.policy.drools.system.PolicyEngine;
 
 /*
- * Operation Timer
- */
-declare OperationTimer
-  closedLoopControlName : String
-  requestID : String
-  delay : String
-end
-
-/*
- * Control Loop Timer
+ * This object is to provide support for timeouts
+ * due to a bug in drools' built-in timers
  */
 declare ControlLoopTimer
-  closedLoopControlName : String
-  requestID : String
-  delay : String
+    closedLoopControlName : String
+    requestID : String
+    delay : String
+    expired : boolean
+    //timerType is the type of timer: either "ClosedLoop" or "Operation"
+    timerType : String
 end
 
 /*
@@ -189,6 +184,7 @@ rule "EVENT"
                 // Setup the Overall Control Loop timer
                 //
                 ControlLoopTimer clTimer = new ControlLoopTimer();
+                clTimer.setTimerType("ClosedLoop");
                 clTimer.setClosedLoopControlName($event.getClosedLoopControlName());
                 clTimer.setRequestID($event.getRequestId().toString());
                 clTimer.setDelay(manager.getControlLoopTimeout(1500) + "s");
@@ -244,7 +240,7 @@ rule "EVENT.MANAGER"
         $params : ControlLoopParams( $clName : getClosedLoopControlName() )
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $clTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "ClosedLoop", !expired )
     then
 
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -403,7 +399,8 @@ rule "EVENT.MANAGER"
                   //
                   // insert operation timeout object
                   //
-                  OperationTimer opTimer = new OperationTimer();
+                  ControlLoopTimer opTimer = new ControlLoopTimer();
+                  opTimer.setTimerType("Operation");
                   opTimer.setClosedLoopControlName($event.getClosedLoopControlName());
                   opTimer.setRequestID($event.getRequestId().toString());
                   opTimer.setDelay(operation.getOperationTimeout().toString() + "s");
@@ -480,7 +477,7 @@ rule "EVENT.MANAGER.OPERATION.LOCKED.GUARD_PERMITTED"
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId(), "Permit".equalsIgnoreCase(getGuardApprovalStatus()) )
         $lock : TargetLock (requestID == $event.getRequestId())
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
     then
 
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -678,7 +675,7 @@ rule "GUARD.RESPONSE"
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
         $lock : TargetLock (requestID == $event.getRequestId())
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $guardResponse : PolicyGuardResponse(requestID == $event.getRequestId(), $operation.policy.recipe == operation)
     then
 
@@ -741,7 +738,7 @@ rule "APPC.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName, closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : Response( getCommonHeader().RequestId == $event.getRequestId() )
     then
@@ -855,7 +852,7 @@ rule "APPC.LCM.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName, closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : LcmResponseWrapper( getBody().getCommonHeader().getRequestId() == $event.getRequestId() )
     then
@@ -960,7 +957,7 @@ rule "SO.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName, closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : SOResponseWrapper(requestID.toString() == $event.getRequestId().toString() )
     then
@@ -1044,7 +1041,7 @@ rule "VFC.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName, closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : VFCResponse( requestId.toString() == $event.getRequestId().toString() )
     then
@@ -1105,19 +1102,34 @@ rule "VFC.RESPONSE"
 
 end
 
+/*
+*
+* This manages a single timer.
+* Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause
+*
+*/
+rule "TIMER.FIRED"
+    timer (expr: $timeout)
+    when
+        $timer : ControlLoopTimer($timeout : delay, !expired)
+    then
+        Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
+        logger.info("This is TIMER.FIRED");
+        modify($timer){setExpired(true)};
+    end
+
 /*
 *
 * This is the timer that manages the timeout for an individual operation.
 *
 */
 rule "EVENT.MANAGER.OPERATION.TIMEOUT"
-    timer (expr: $to )
     when
         $params : ControlLoopParams( $clName : getClosedLoopControlName() )
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", expired )
         $lock : TargetLock (requestID == $event.getRequestId())
     then
 
@@ -1176,12 +1188,11 @@ end
 *
 */
 rule "EVENT.MANAGER.TIMEOUT"
-    timer (expr: $to )
     when
         $params : ControlLoopParams( $clName : getClosedLoopControlName() )
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), $to : getDelay() )
+        $clTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "ClosedLoop", expired )
     then
 
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
@@ -1219,11 +1230,10 @@ end
 rule "EVENT.MANAGER.CLEANUP"
     when
         $manager : ControlLoopEventManager( $clName : getClosedLoopControlName(), $requestId : getRequestID() )
-        $clTimer : ControlLoopTimer ( closedLoopControlName == $clName, requestID == $requestId.toString() )
         $operations : LinkedList()
                         from collect( ControlLoopOperationManager( onset.closedLoopControlName == $clName, onset.getRequestId() == $requestId ) )
-        $opTimers : LinkedList()
-                        from collect( OperationTimer( closedLoopControlName == $clName, requestID == $requestId.toString() ) )
+        $timers : LinkedList()
+                        from collect( ControlLoopTimer( closedLoopControlName == $clName, requestID == $requestId.toString() ) )
         $locks : LinkedList()
                         from collect( TargetLock (requestID == $requestId) )
         not( VirtualControlLoopEvent( closedLoopControlName == $clName, requestId == $requestId ) )
@@ -1232,21 +1242,20 @@ rule "EVENT.MANAGER.CLEANUP"
     Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage());
     logger.info("{}: {}", $clName, drools.getRule().getName());
 
-    logger.debug("{}: {}: manager={} clTimer={} operations={}",
+    logger.debug("{}: {}: manager={} timers={} operations={}",
               $clName, drools.getRule().getName(),
-              $manager, $clTimer, $operations.size());
+              $manager, $timers.size(), $operations.size());
 
     //
     // Retract EVERYTHING
     //
     retract($manager);
-    retract($clTimer);
 
     for(Object manager: $operations) {
         retract((ControlLoopOperationManager) manager);
     }
-    for(Object opTimer: $opTimers) {
-        retract((OperationTimer) opTimer);
+    for(Object timer: $timers) {
+        retract((ControlLoopTimer) timer);
     }
     for(Object lock: $locks) {
         TargetLock tgt = (TargetLock) lock;
@@ -1289,7 +1298,7 @@ rule "SDNR.RESPONSE"
         $event : VirtualControlLoopEvent( closedLoopControlName == $clName, closedLoopEventStatus == ControlLoopEventStatus.ONSET )
         $manager : ControlLoopEventManager( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId() )
         $operation : ControlLoopOperationManager( onset.closedLoopControlName == $event.getClosedLoopControlName(), onset.getRequestId() == $event.getRequestId() )
-        $opTimer : OperationTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString() )
+        $opTimer : ControlLoopTimer( closedLoopControlName == $event.getClosedLoopControlName(), requestID == $event.getRequestId().toString(), timerType == "Operation", !expired )
         $lock : TargetLock (requestID == $event.getRequestId())
         $response : PciResponseWrapper( getBody().getCommonHeader().getRequestId() == $event.getRequestId() )
     then