controllogic: fix data race in setPath nested-array assignment
setPath mutated nested sub-arrays in place; since graph-local slices (and their nested sub-slices) may be read concurrently by other flow goroutines, an action.array.set with a multi-level path raced readers of the same local. The single-threaded TS source mutates in place safely, but the Go port runs flows on concurrent goroutines, so setPath now copies on descent (every level returns a freshly allocated slice). Adds a -race regression test that concurrently drives nested set + inner-element reads, plus a nested-set correctness test asserting the prior stored value is not mutated. Found by whole-branch review (Opus); missed by the per-task reviews. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -277,7 +277,11 @@ func (e *Engine) liveGet(ds, name string) float64 {
|
||||
}
|
||||
|
||||
// setPath assigns v at the nested index path within arr, growing (zero-filling)
|
||||
// as needed and resolving negative indices against length. Port of logic.ts.
|
||||
// as needed and resolving negative indices against length. Adapted from logic.ts;
|
||||
// unlike the single-threaded TS source it copies on descent rather than mutating in
|
||||
// place, because a graph-local slice (and its nested sub-slices) may be read
|
||||
// concurrently by other flow goroutines — in-place mutation of shared backing would
|
||||
// race. Every level returns a freshly allocated slice.
|
||||
func setPath(arr []Value, path []int, v Value) []Value {
|
||||
if len(path) == 0 {
|
||||
return arr
|
||||
@@ -289,16 +293,17 @@ func setPath(arr []Value, path []int, v Value) []Value {
|
||||
if k < 0 {
|
||||
return arr
|
||||
}
|
||||
for len(arr) <= k {
|
||||
arr = append(arr, 0.0)
|
||||
out := append([]Value{}, arr...) // copy: backing may be shared with the live local
|
||||
for len(out) <= k {
|
||||
out = append(out, 0.0)
|
||||
}
|
||||
if len(path) == 1 {
|
||||
arr[k] = v
|
||||
return arr
|
||||
out[k] = v
|
||||
return out
|
||||
}
|
||||
sub, _ := arr[k].([]Value)
|
||||
arr[k] = setPath(sub, path[1:], v)
|
||||
return arr
|
||||
sub, _ := out[k].([]Value)
|
||||
out[k] = setPath(sub, path[1:], v)
|
||||
return out
|
||||
}
|
||||
|
||||
// write applies an action.write/lua-set to a target: a bare name updates a
|
||||
|
||||
Reference in New Issue
Block a user