diff --git a/go/hanging-goroutine.go b/go/hanging-goroutine.go index c9d9e37..5da028e 100644 --- a/go/hanging-goroutine.go +++ b/go/hanging-goroutine.go @@ -11,10 +11,9 @@ var ( ) func main() { - req1(1) - time.Sleep(time.Second * 5) - fmt.Println(result) - fmt.Println(runtime.NumGoroutine()) + req6(1, false) + fmt.Println("Result: ", result) + fmt.Println("Goroutines (must be 1 for FPs):", runtime.NumGoroutine()) } func req1(timeout time.Duration) string { @@ -85,6 +84,101 @@ func req2_FP(timeout time.Duration) string { } } +func req3(timeout time.Duration) { + ch := make(chan string) + // ruleid: hanging-goroutine + for i := 0; i < 3; i++ { + go func() { + newData := test() + ch <- newData // block + }() + } + result = <- ch + fmt.Println("finished req3") +} + +func req3_FP(timeout time.Duration) { + ch := make(chan string, 3) + // ok: hanging-goroutine + for i := 0; i < 3; i++ { + go func() { + newData := test() + ch <- newData // block + }() + } + result = <- ch + fmt.Println("finished req3") +} + +func req4_FP(timeout time.Duration) string { + ch := make(chan string) + // ok: hanging-goroutine + go func() { + newData := test() + ch <- newData // block + }() + select { + case result = <- ch: + fmt.Println("case result") + return result + case <- time.After(timeout): + result = <- ch + fmt.Println("case time.Afer") + return "" + } +} + +func req5_FP(timeout time.Duration) { + ch := make(chan string) + tick := time.Tick(100 * time.Millisecond) + quit := time.After(2 * time.Second) + // ok: hanging-goroutine + go func() { + newData := test() + ch <- newData // block + }() + for { + select { + case <-tick: + fmt.Print("|") + case <-quit: + result = <- ch + fmt.Println("\nquit") + return + default: + fmt.Print(".") + time.Sleep(50 * time.Millisecond) + } + } +} + +func req6(timeout time.Duration, doclose bool) { + ch := make(chan string) + tick := time.Tick(100 * time.Millisecond) + quit := time.After(2 * time.Second) + // todoruleid: hanging-goroutine + go func() { + newData := test() + ch <- newData // block + }() + for { + select { + case <-tick: + fmt.Print("|") + case <-quit: + if doclose { + result = <- ch + } + fmt.Println("\nquit") + return + default: + fmt.Print(".") + time.Sleep(50 * time.Millisecond) + } + } +} + + func test() string { time.Sleep(time.Second * 2) return "very important data" diff --git a/go/hanging-goroutine.yaml b/go/hanging-goroutine.yaml index a0a5bd5..5238eb8 100644 --- a/go/hanging-goroutine.yaml +++ b/go/hanging-goroutine.yaml @@ -30,18 +30,6 @@ rules: ... $Y = <- $CHANNEL ... - - pattern: | - for ... { - ... - go func(...) { - ... - $CHANNEL <- $VAL - ... - }(...) - } - ... - $Y := <- $CHANNEL - ... - pattern: | for ... { ... @@ -81,18 +69,6 @@ rules: case $Y = <- $CHANNEL: ... } - - pattern: | - go func(...){ - ... - $CHANNEL <- $X - ... - }(...) - ... - select { - case ... - case $Y := <- $CHANNEL: - ... - } - pattern: | go func(...){ ... @@ -117,31 +93,34 @@ rules: case $Y <- $CHANNEL: ... } - - pattern-not: | - for ... { - ... - go func(...) { - ... - $CHANNEL <- $VAL - ... - }(...) - } - ... - $Y = <- $CHANNEL - ... - pattern-inside: | $CHANNEL := make(...) ... - pattern-not-inside: | + $CHANNEL := make(..., $T) + ... + + # heuristics to limit FPs, we may miss some leaks because of these + - pattern-not: | + go func(...){ + ... + $CHANNEL <- $X + ... + }(...) ... select { case ... case ...: ... - ... =<- $CHANNEL + ... = <- $CHANNEL ... } - - pattern-not-inside: | + - pattern-not: | + go func(...){ + ... + $CHANNEL <- $X + ... + }(...) ... select { case ... @@ -150,6 +129,3 @@ rules: <-$CHANNEL ... } - - pattern-not-inside: | - $CHANNEL := make(..., $T) - ...