grule-rule-engine
grule-rule-engine copied to clipboard
Getting Max Cycle Reached Error
Describe the bug
I created a sample program that reads structs and changes some totals and subtotals. It works fine.
Then I copied your multi threaded text and changed it to reflect my structure and made the changes needed to reflect to the struct and rules.
The program executes but I keep getting ERRO[0000] Max cycle reached lib=grule-rule-engine `package=engine`
I have looked at the documentation, the closed issues but cannot figure out what is wrong.
To Reproduce Steps to reproduce the behavior:
- I created the sample working program to change totals and subtotals as part of the Init (line 126).
- I created the Lib, rb, nb, and created rule from nb.
- Ranging to get the claimant address (line 147)
- call the beginThread ( line 148)
- I have checked the rule conditions and the 2 sample rules cannot both be executed
Expected behavior The value of the fields in the struct should be changed.
Additional context
The code:
// create go file main.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"time"
"github.com/hyperjumptech/grule-rule-engine/ast"
"github.com/hyperjumptech/grule-rule-engine/builder"
"github.com/hyperjumptech/grule-rule-engine/engine"
"github.com/hyperjumptech/grule-rule-engine/logger"
"github.com/hyperjumptech/grule-rule-engine/pkg"
"github.com/sirupsen/logrus"
)
const (
EligibilityRule = `
rule EligibilityRule1 "Salary More" salience 10 {
when
Claimant.IncomeTotal > 13000 || Claimant.SubTotalIncome > 10000 &&
Claimant.EligibleIncomeTotal == 0
then
Claimant.EligibleIncomeTotal = 1;
Log("EligibilityRule1 : " + Claimant.Name);
}
rule EligibilityRule2 "Salary Less" salience 10 {
when
Claimant.IncomeTotal < 14000 || Claimant.SubTotalIncome < 14000 &&
Claimant.EligibleSubTotalIncome==0
then
Claimant.EligibleSubTotalIncome=1;
Log("EligibilityRule2 : " + Claimant.Name);
}
`
)
var (
// threadFinishMap used to monitor the status of all thread
threadFinishMap = make(map[string]bool)
// syncD is a mutex object to protect threadFinishMap from concurrent map read/write
syncD = sync.Mutex{}
concurrencyTestlog = logger.Log.WithFields(logrus.Fields{
"lib": "grule",
"file": "examples/Concurrency_test.go",
})
)
type Claimants struct {
Claimants []Claimant `json:"Claimant"`
}
// create claimant struct
type Claimant struct {
Name string `json:"Name"`
ID string `json:"ID"`
Incomes []Incomes `json:"Incomes"`
SubTotalIncome float64
SubTotalExpense float64
IncomeTotal float64
EligibleIncomeTotal uint
EligibleSubTotalIncome uint
}
type Incomes struct {
Type string `json:"Type"`
Amount float64 `json:"Amount"`
BeginDate string `json:"BeginDate"`
EndDate string `json:"EndDate"`
IncomeCategoryCode int `json:"IncomeCategoryCode"`
}
// create function Claimants init
func (c *Claimants) Init(data []byte) {
// fmt.Printf("starting init [%v] \n", c)
//loop through claimants
err := json.Unmarshal(data, c)
if err != nil {
panic(err)
}
for i := 0; i < len(c.Claimants); i++ {
claimant := c.Claimants[i]
// fmt.Printf("claimant [%v] \n", claimant)
// loop through incomes
for j := 0; j < len(claimant.Incomes); j++ {
income := claimant.Incomes[j]
// fmt.Printf("income [%v] \n", income)
// update claimant with total income
if income.IncomeCategoryCode == 13 {
claimant.SubTotalIncome += income.Amount
} else {
claimant.SubTotalExpense += income.Amount
}
}
claimant.IncomeTotal = claimant.SubTotalIncome - claimant.SubTotalExpense
c.Claimants[i] = claimant
}
}
func main() {
// read json file from data/judge.json
claimantsFile, err := os.Open("../../data/judge.json")
if err != nil {
panic(err)
}
defer claimantsFile.Close()
claimantsBytes, err := ioutil.ReadAll(claimantsFile)
if err != nil {
panic(err)
}
//fmt.Printf("%s\n", claimantsBytes)
// unmarshal json file to claimants struct
var claimants Claimants
claimants.Init(claimantsBytes)
// create map to store claimants
//claimantsMap := make(map[string]Claimant)
// store claimants in claimantsMap
// for _, claimant := range claimants.Claimants {
// claimantsMap[claimant.ID] = claimant
// }
// Prepare knowledgebase library and load it with our rule.
lib := ast.NewKnowledgeLibrary()
rb := builder.NewRuleBuilder(lib)
nb := pkg.NewBytesResource([]byte(EligibilityRule))
err = rb.BuildRuleFromResource("EligibilityRule", "0.0.1", nb)
// for i := 1; i <= 500; i++ {
// go beginThread(fmt.Sprintf("T%d", i), lib), claimant)
// }
for i := range claimants.Claimants {
claimant := claimants.Claimants[i]
go beginThread(claimant.ID, lib, &claimant)
}
// Wait until all thread finishes
time.Sleep(1 * time.Second)
for !isAllThreadFinished() {
time.Sleep(500 * time.Millisecond)
}
for _, claimant := range claimants.Claimants {
fmt.Printf("[%s]'s SubTotalIncome >>%v<< SubTotalExpense >>%v<< total income is >>%v<<, ER >>%d<< ER2 >>%d<< \n", claimant.Name, claimant.SubTotalIncome, claimant.SubTotalExpense,
claimant.IncomeTotal, claimant.EligibleIncomeTotal, claimant.EligibleSubTotalIncome)
}
}
func beginThread(threadName string, lib *ast.KnowledgeLibrary, claimant *Claimant) {
concurrencyTestlog.Tracef("Beginning thread %s", threadName)
// Register this thread into our simple finish map check
startThread(threadName)
defer finishThread(threadName)
// Prepare new DataContext to be used in this Thread
dataContext := ast.NewDataContext()
// Create our model and add into data context
err := dataContext.Add("Claimant", claimant)
if err != nil {
fmt.Errorf("DataContext error on thread %s, got %s", threadName, err)
}
// Create a new engine for this thread
engine := &engine.GruleEngine{MaxCycle: 100}
// Get an instance of our KnowledgeBase from KnowledgeLibrary
kb := lib.NewKnowledgeBaseInstance("EligibilityRule", "0.0.1")
// Execute the KnowledgeBase against DataContext
err = engine.Execute(dataContext, kb)
if err != nil {
fmt.Errorf("Engine execution error on thread %s, got %s", threadName, err)
}
}
func startThread(threadName string) {
syncD.Lock()
defer syncD.Unlock()
threadFinishMap[threadName] = false
}
func finishThread(threadName string) {
syncD.Lock()
defer syncD.Unlock()
threadFinishMap[threadName] = true
}
func isAllThreadFinished() bool {
syncD.Lock()
defer syncD.Unlock()
fin := true
for _, b := range threadFinishMap {
fin = fin && b
if !fin {
return false
}
}
return true
}
Sample Json
{ "Claimant": [ { "Name": "Sally", "ID": "ABCD1011amaladnSally", "Incomes": [ { "Type": "Salary", "Amount": 5000, "BeginDate": "1/1/2022", "EndDate": "5/7/2022", "IncomeCategoryCode": 13 }, { "Type": "Salary", "Amount": 10000, "BeginDate": "5/12/2022", "EndDate": null, "IncomeCategoryCode": 13 }, { "Type": "Rent", "Amount": 1000, "BeginDate": "1/12/2000", "EndDate": null, "IncomeCategoryCode": 2 } ] }, { "Name": "Joe", "ID": "ABCD1011amaladnJoe", "Incomes": [ { "Type": "Salary", "Amount": 4000, "BeginDate": "1/1/2022", "EndDate": "5/7/2022", "IncomeCategoryCode": 13 }, { "Type": "Salary", "Amount": 10000, "BeginDate": "5/12/2022", "EndDate": null, "IncomeCategoryCode": 13 }, { "Type": "Rent", "Amount": 1000, "BeginDate": "1/12/2000", "EndDate": null, "IncomeCategoryCode": 2 } ] }, { "Name": "Bob", "ID": "ABCD1011amaladnBob", "Incomes": [ { "Type": "Salary", "Amount": 3000, "BeginDate": "1/1/2022", "EndDate": "5/7/2022", "IncomeCategoryCode": 13 }, { "Type": "Salary", "Amount": 10000, "BeginDate": "5/12/2022", "EndDate": null, "IncomeCategoryCode": 13 }, { "Type": "Rent", "Amount": 1000, "BeginDate": "1/12/2000", "EndDate": null, "IncomeCategoryCode": 2 } ] } ] }