Hello,
after some time I found a bug in the matching engine. It appeared in a production system where user combined an (exists ) test on input facts with a (random ) function in one rule. The rule from time to time fired even when the fact based condition was not met.
I isolated the problem to purely deterministic repro.
The following example missfires in every single run regardless whether the CLIPS environment was newly created or re-used for next evaluations.
I know there is a workaround of not to put such volatile function in a single rule with other conditions but as it is not forbidden, users are sometimes quite creative. And, to be honest, it does not look like doing anything wrong from user's perspective.
This is a CLIPS code for reproduction:
(defglobal ?*fire-bug* = 0)
(defglobal ?*trials* = 0)
(defglobal ?*snt-counter* = 0)
(defglobal ?*last-value* = FALSE)
(defglobal ?*seq* = (create$ TRUE FALSE TRUE FALSE)) ; the first value must be TRUE - for FALSE does not appear
;(defglobal ?*seq* = (create$ FALSE TRUE FALSE TRUE)) ; the bug never happens
(defglobal ?*seq-idx* = 0)
(defglobal ?*history* = (create$))
;(deffunction get-next-value ()
; (return (< (random 1 1000) 500))
;)
(deffunction get-next-value ()
(bind ?*seq-idx* (+ ?*seq-idx* 1))
(if (> ?*seq-idx* (length$ ?*seq*)) then (bind ?*seq-idx* 1))
(return (nth$ ?*seq-idx* ?*seq*))
)
(deffunction alternating-test ()
(bind ?*last-value* (get-next-value))
(bind ?*history* (create$ ?*history* ?*last-value*))
(if (> (length$ ?*history*) 5)
then (bind ?*history* (rest$ ?*history*)))
(return ?*last-value*))
;; --- Varianta 1: presna replika bugu ---
(defrule variant-bug
?f <- (STEP-BUG ?doc)
(SD01 ?doc "ZAH")
(and (exists (G ?d2)
(G ?d2 GAP ?g)
(G ?d2 GAP ?g GAP01 ?v)
(or (test (eq ?v "F48")) (test (eq ?v "F53"))))
(test (alternating-test)))
=>
(retract ?f)
(bind ?*fire-bug* (+ ?*fire-bug* 1))
(printout t "BUG HIT doc=" ?doc " trial=" ?*trials* " snt-counter=" ?*snt-counter* " last-value=" ?*last-value* " history=" ?*history* crlf))
and this is my control loop in .NET app:
escLibService.Load(@"D:\Projects\ERIAN\local\Develop\TestApps\ConsoleApplication21\Templates\1.clp");
escLibService.Reset();
for (int i = 0; i < 1000; i++)
{
if (i % 10 == 0)
{
Console.WriteLine(string.Format("Loop: {0}", i));
escLibService.Dispose();
escLibService = container.Resolve<IESCLibService>();
escLibService.Initialize("default_domain", "default_domain", routerMap);
escLibService.ResetOutput();
escLibService.Clear();
escLibService.ClearEnvironmentSharedStructures();
escLibService.Load(@"D:\Projects\ERIAN\local\Develop\TestApps\ConsoleApplication21\Templates\1.clp");
}
escLibService.RouteCommand("(bind ?*trials* (+ ?*trials* 1))");
escLibService.AssertString(string.Format("(SD01 {0} \"ZAH\"))", i));
escLibService.AssertString(string.Format("(G {0}))", i));
escLibService.AssertString(string.Format("(G {0} GAP 1))", i));
escLibService.AssertString(string.Format("(G {0} GAP 1 GAP01 \"000\"))", i)); // nikdy F48/F53
escLibService.AssertString(string.Format("(STEP-BUG {0}))", i));
escLibService.Run(-1);
}
Anonymous
forgot to specify. Using CLIPS 6.42