traefik / yaegi

Yaegi is Another Elegant Go Interpreter
https://pkg.go.dev/github.com/traefik/yaegi
Apache License 2.0
6.94k stars 343 forks source link

go routines not working as expected #1498

Closed brucengdev closed 1 year ago

brucengdev commented 1 year ago

The following program sample.go triggers an unexpected result

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
    "sync"
)

func main() {
    hits := make([]string, 3)
    for i, _ := range hits {
        hits[i] = string("hit" + strconv.Itoa(i))
    }

    bytes, _ := json.Marshal(hits)
    fmt.Println("hits: " + string(bytes))

    var wg sync.WaitGroup
    for _, hit := range hits {
        wg.Add(1)
        go func(hit string) {
            fmt.Println("hit = " + hit)
            defer wg.Done()
        }(hit)
    }
    wg.Wait()
}

Expected result

hits: ["hit0","hit1","hit2"]
hit = hit2
hit = hit0
hit = hit1

Got

hits: ["hit0","hit1","hit2"]
hit = hit2
hit = hit2
hit = hit2

Yaegi Version

0.14.3

Additional Notes

No response

mpl commented 1 year ago

Tbh, I'm really puzzled that we could have this bug. I could have sworn we have a regression test for this pattern, but here we are.

Interestingly, if you use the "other version" of this pattern (I prefer your version, but I believe most people use the version below), then yaegi behaves correctly:

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
    "sync"
)

func main() {
    hits := make([]string, 3)
    for i, _ := range hits {
        hits[i] = string("hit" + strconv.Itoa(i))
    }

    bytes, _ := json.Marshal(hits)
    fmt.Println("hits: " + string(bytes))

    var wg sync.WaitGroup
    for _, hit := range hits {
        hit := hit
        wg.Add(1)
        go func() {
            fmt.Println("hit = " + hit)
            defer wg.Done()
        }()
    }
    wg.Wait()
}
brucengdev commented 1 year ago

Thanks, I'm using a workaround, using a counter to reference array elements. It seems that when a variable is passed as a parameter to the go routine, if the variable is changed outside by the range operation, the local variable in the go routines get updated, too. Which makes all go routines end up using the same value. Just a wild guess.

        var wg sync.WaitGroup
        i := 0
    for i < len(hits) {
        wg.Add(1)
        go func(hit string) {
            fmt.Println("hit = " + hit)
            defer wg.Done()
        }(hits[i])
                i++
    }
    wg.Wait()