Skip to content

Commit 9a514c2

Browse files
committed
pkg/aflow: add explicit DoWhile.MaxIterations
Add DoWhile.MaxIterations and make it mandatory. I think it's useful to make workflow implementer to think explicitly about a reasonable cap on the number of iterations.
1 parent 43e1df1 commit 9a514c2

File tree

4 files changed

+176
-6
lines changed

4 files changed

+176
-6
lines changed

pkg/aflow/flow/patching/patching.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ func init() {
7878
},
7979
crash.TestPatch, // -> PatchDiff or TestError
8080
),
81-
While: "TestError",
81+
While: "TestError",
82+
MaxIterations: 10,
8283
},
8384
&aflow.LLMAgent{
8485
Name: "description-generator",

pkg/aflow/loop.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type DoWhile struct {
1818
// Exit condition. It should be a string state variable.
1919
// The loop exists when the variable is empty.
2020
While string
21+
// Max interations for the loop.
22+
// Must be specified to avoid unintended effectively infinite loops.
23+
MaxIterations int
2124

2225
loopVars map[string]reflect.Type
2326
}
@@ -43,8 +46,7 @@ func (dw *DoWhile) loop(ctx *Context) error {
4346
}
4447
ctx.state[name] = reflect.Zero(typ).Interface()
4548
}
46-
const maxIters = 100
47-
for iter := 0; iter < maxIters; iter++ {
49+
for iter := 0; iter < dw.MaxIterations; iter++ {
4850
span := &trajectory.Span{
4951
Type: trajectory.SpanLoopIteration,
5052
Name: fmt.Sprint(iter),
@@ -60,10 +62,14 @@ func (dw *DoWhile) loop(ctx *Context) error {
6062
return nil
6163
}
6264
}
63-
return fmt.Errorf("DoWhile loop is going in cycles for %v iterations", maxIters)
65+
return fmt.Errorf("DoWhile loop is going in cycles for %v iterations", dw.MaxIterations)
6466
}
6567

6668
func (dw *DoWhile) verify(ctx *verifyContext) {
69+
if max := 1000; dw.MaxIterations <= 0 || dw.MaxIterations >= max {
70+
ctx.errorf("DoWhile", "bad MaxIterations value %v, should be within [1, %v]",
71+
dw.MaxIterations, max)
72+
}
6773
// Verification of loops is a bit tricky.
6874
// Normally we require each variable to be defined before use, but loops violate
6975
// the assumption. An action in a loop body may want to use a variable produced

pkg/aflow/loop_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func TestDoWhile(t *testing.T) {
4747
return testResults{Diff: "diff"}, nil
4848
}),
4949
),
50-
While: "TestError",
50+
While: "TestError",
51+
MaxIterations: 10,
5152
},
5253
nil,
5354
)
@@ -63,7 +64,8 @@ func TestDoWhileErrors(t *testing.T) {
6364
}) (struct{}, error) {
6465
return struct{}{}, nil
6566
}),
66-
While: "Condition",
67+
While: "Condition",
68+
MaxIterations: 10,
6769
},
6870
))
6971

@@ -76,6 +78,7 @@ func TestDoWhileErrors(t *testing.T) {
7678
}) (struct{}, error) {
7779
return struct{}{}, nil
7880
}),
81+
MaxIterations: 10,
7982
},
8083
))
8184

@@ -85,6 +88,17 @@ func TestDoWhileErrors(t *testing.T) {
8588
}
8689
testRegistrationError[struct{}, struct{}](t,
8790
"flow test: action body: output Output2 is unused",
91+
Pipeline(
92+
&DoWhile{
93+
Do: NewFuncAction("body", func(ctx *Context, args struct{}) (output, error) {
94+
return output{}, nil
95+
}),
96+
While: "Output1",
97+
MaxIterations: 10,
98+
},
99+
))
100+
testRegistrationError[struct{}, struct{}](t,
101+
"flow test: action DoWhile: bad MaxIterations value 0, should be within [1, 1000]",
88102
Pipeline(
89103
&DoWhile{
90104
Do: NewFuncAction("body", func(ctx *Context, args struct{}) (output, error) {
@@ -94,3 +108,19 @@ func TestDoWhileErrors(t *testing.T) {
94108
},
95109
))
96110
}
111+
112+
func TestDoWhileMaxIters(t *testing.T) {
113+
type actionResults struct {
114+
Error string
115+
}
116+
testFlow[struct{}, struct{}](t, nil, "DoWhile loop is going in cycles for 3 iterations",
117+
&DoWhile{
118+
Do: NewFuncAction("nop", func(ctx *Context, args struct{}) (actionResults, error) {
119+
return actionResults{"failed"}, nil
120+
}),
121+
While: "Error",
122+
MaxIterations: 3,
123+
},
124+
nil,
125+
)
126+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
[
2+
{
3+
"Seq": 0,
4+
"Nesting": 0,
5+
"Type": "flow",
6+
"Name": "test",
7+
"Started": "0001-01-01T00:00:01Z"
8+
},
9+
{
10+
"Seq": 1,
11+
"Nesting": 1,
12+
"Type": "loop",
13+
"Name": "",
14+
"Started": "0001-01-01T00:00:02Z"
15+
},
16+
{
17+
"Seq": 2,
18+
"Nesting": 2,
19+
"Type": "iteration",
20+
"Name": "0",
21+
"Started": "0001-01-01T00:00:03Z"
22+
},
23+
{
24+
"Seq": 3,
25+
"Nesting": 3,
26+
"Type": "action",
27+
"Name": "nop",
28+
"Started": "0001-01-01T00:00:04Z"
29+
},
30+
{
31+
"Seq": 3,
32+
"Nesting": 3,
33+
"Type": "action",
34+
"Name": "nop",
35+
"Started": "0001-01-01T00:00:04Z",
36+
"Finished": "0001-01-01T00:00:05Z",
37+
"Results": {
38+
"Error": "failed"
39+
}
40+
},
41+
{
42+
"Seq": 2,
43+
"Nesting": 2,
44+
"Type": "iteration",
45+
"Name": "0",
46+
"Started": "0001-01-01T00:00:03Z",
47+
"Finished": "0001-01-01T00:00:06Z"
48+
},
49+
{
50+
"Seq": 4,
51+
"Nesting": 2,
52+
"Type": "iteration",
53+
"Name": "1",
54+
"Started": "0001-01-01T00:00:07Z"
55+
},
56+
{
57+
"Seq": 5,
58+
"Nesting": 3,
59+
"Type": "action",
60+
"Name": "nop",
61+
"Started": "0001-01-01T00:00:08Z"
62+
},
63+
{
64+
"Seq": 5,
65+
"Nesting": 3,
66+
"Type": "action",
67+
"Name": "nop",
68+
"Started": "0001-01-01T00:00:08Z",
69+
"Finished": "0001-01-01T00:00:09Z",
70+
"Results": {
71+
"Error": "failed"
72+
}
73+
},
74+
{
75+
"Seq": 4,
76+
"Nesting": 2,
77+
"Type": "iteration",
78+
"Name": "1",
79+
"Started": "0001-01-01T00:00:07Z",
80+
"Finished": "0001-01-01T00:00:10Z"
81+
},
82+
{
83+
"Seq": 6,
84+
"Nesting": 2,
85+
"Type": "iteration",
86+
"Name": "2",
87+
"Started": "0001-01-01T00:00:11Z"
88+
},
89+
{
90+
"Seq": 7,
91+
"Nesting": 3,
92+
"Type": "action",
93+
"Name": "nop",
94+
"Started": "0001-01-01T00:00:12Z"
95+
},
96+
{
97+
"Seq": 7,
98+
"Nesting": 3,
99+
"Type": "action",
100+
"Name": "nop",
101+
"Started": "0001-01-01T00:00:12Z",
102+
"Finished": "0001-01-01T00:00:13Z",
103+
"Results": {
104+
"Error": "failed"
105+
}
106+
},
107+
{
108+
"Seq": 6,
109+
"Nesting": 2,
110+
"Type": "iteration",
111+
"Name": "2",
112+
"Started": "0001-01-01T00:00:11Z",
113+
"Finished": "0001-01-01T00:00:14Z"
114+
},
115+
{
116+
"Seq": 1,
117+
"Nesting": 1,
118+
"Type": "loop",
119+
"Name": "",
120+
"Started": "0001-01-01T00:00:02Z",
121+
"Finished": "0001-01-01T00:00:15Z",
122+
"Error": "DoWhile loop is going in cycles for 3 iterations"
123+
},
124+
{
125+
"Seq": 0,
126+
"Nesting": 0,
127+
"Type": "flow",
128+
"Name": "test",
129+
"Started": "0001-01-01T00:00:01Z",
130+
"Finished": "0001-01-01T00:00:16Z",
131+
"Error": "DoWhile loop is going in cycles for 3 iterations"
132+
}
133+
]

0 commit comments

Comments
 (0)