Thebas opened a new issue, #2406:
URL: https://github.com/apache/incubator-kie-issues/issues/2406

   ### Component
   
   Drools
   
   ### Versions
   
    - Drools: 10.2.0
     - Java: 21 runtime (locally 21.0.2); Maven compilation target 17
     - Spring Boot: 3.5.16
     - Maven: 3.9.16 (locally installed)
   
   ### Problem
   
   `PhreakFromNode.doLeftDeletes` removes the staged left tuple from the from 
node's
   left tuple memory unconditionally:
   
   ```java
   for (TupleImpl leftTuple = srcLeftTuples.getDeleteFirst(); leftTuple != 
null; ) {
       TupleImpl next = leftTuple.getStagedNext();
   
       ltm.remove(leftTuple);
   ```
   
   When the tuple is not in that memory — it was staged and never actually 
added, or
   it was already removed — org.drools.base.util.LinkedList.remove() misses 
both the
   firstNode and lastNode branches, falls into the middle-node branch and
   dereferences a null previous:
   
   java.lang.NullPointerException: Cannot invoke
   
"org.drools.core.util.DoubleLinkedEntry.setNext(org.drools.core.util.SingleLinkedEntry)"
   because the return value of 
"org.drools.core.util.DoubleLinkedEntry.getPrevious()" is null
         at org.drools.core.util.LinkedList.remove(LinkedList.java:178)
         at org.drools.core.util.index.TupleList.remove(TupleList.java:51)
         at 
org.drools.core.phreak.PhreakFromNode.doLeftDeletes(PhreakFromNode.java:207)
         at org.drools.core.phreak.PhreakFromNode.doNode(PhreakFromNode.java:64)
         at 
org.drools.core.phreak.RuleNetworkEvaluatorImpl.evaluateNonBetaNonTerminalNode(RuleNetworkEvaluatorImpl.java:405)
         at 
org.drools.core.phreak.RuleNetworkEvaluatorImpl.evaluateNonTerminalNode(RuleNetworkEvaluatorImpl.java:385)
         at 
org.drools.core.phreak.RuleNetworkEvaluatorImpl.innerEval(RuleNetworkEvaluatorImpl.java:318)
         at 
org.drools.core.phreak.RuleNetworkEvaluatorImpl.outerEval(RuleNetworkEvaluatorImpl.java:250)
         at 
org.drools.core.phreak.RuleNetworkEvaluatorImpl.evaluateNetwork(RuleNetworkEvaluatorImpl.java:142)
         at 
org.drools.core.phreak.RuleExecutor.evaluateNetwork(RuleExecutor.java:225)
         at 
org.drools.core.phreak.RuleExecutor.evaluateNetworkIfDirty(RuleExecutor.java:236)
         at 
org.drools.core.phreak.RuleExecutor.evaluateNetworkAndFire(RuleExecutor.java:85)
         at 
org.drools.core.concurrent.AbstractGroupEvaluator.evaluateAndFire(AbstractGroupEvaluator.java:53)
         at 
org.drools.kiesession.agenda.DefaultAgenda.fireLoop(DefaultAgenda.java:616)
         at 
org.drools.kiesession.agenda.DefaultAgenda.internalFireAllRules(DefaultAgenda.java:569)
         at 
org.drools.kiesession.agenda.DefaultAgenda.fireAllRules(DefaultAgenda.java:561)
         at 
org.drools.kiesession.session.StatefulKnowledgeSessionImpl.internalFireAllRules(StatefulKnowledgeSessionImpl.java:1110)
         at 
org.drools.kiesession.session.StatefulKnowledgeSessionImpl.fireAllRules(StatefulKnowledgeSessionImpl.java:1101)
   
   Seen in production on a stateful KieSession driving fireAllRules() from a 
Kafka
   listener, with rules using from. The session is serialised behind a write 
lock, so
   the insert/fire path is single threaded.
   
   PhreakFromNode is the only Phreak node that does not guard this call. Every 
other
   one does, with the same comment:
   
   ┌───────────────────────────────────┐
   │               Node                                             │           
    Guard                                   │
   ├───────────────────────────────────┤
   │ PhreakJoinNode:409                                 │ if 
(leftTuple.getMemory() != null) │
   ├───────────────────────────────────┤
   │ PhreakNotNode:446                                 │ if 
(leftTuple.getMemory() != null) │
   ├───────────────────────────────────┤
   │ PhreakExistsNode:432                             │ if 
(leftTuple.getMemory() != null) │
   ├───────────────────────────────────┤
   │ PhreakAccumulateNode:556                   │ if (leftTuple.getMemory() != 
null) │
   ├───────────────────────────────────┤
   │ PhreakSubnetworkNotExistsNode:193 │ if (leftTuple.getMemory() != null) │
   ├───────────────────────────────────┤
   │ PhreakNodeOperations:219, 240            │ if (leftTuple.getMemory() != 
null) │
   ├───────────────────────────────────┤
   │ PhreakFromNode:207                                │ missing                
                           │
   └───────────────────────────────────┘
   
   PhreakReactiveFromNode extends PhreakFromNode and does not override
   doLeftDeletes, so it has the same gap.
   
   Expected behaviour
   
   Deleting a left tuple that is not in the from node's left tuple memory is a 
no-op, as
   it is on every other Phreak node — not an NPE that aborts fireAllRules() and 
leaves
   the session in an undefined state.
   
   Related
   
   RuleExecutorDormantTupleTest / issue #6422 is the same failure mode
   (LinkedList.remove on a tuple that is already unlinked) in RuleExecutor.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to