go-cache icon indicating copy to clipboard operation
go-cache copied to clipboard

goroutine leak at cache.go:1079 - `runJanitor`

Open tigerinus opened this issue 3 years ago • 5 comments

package main

import (
	"testing"
	"time"

	"github.com/patrickmn/go-cache"
	"go.uber.org/goleak"
)

func leaktest(t *testing.T) {
	defer goleak.VerifyNone(t)
	cache.New(5*time.Minute, 10*time.Minute)
}

func main() {
	testSuite := []testing.InternalTest{{Name: "leaktest", F: leaktest}}
	testing.Main(func(a, b string) (bool, error) { return a == b, nil }, testSuite, nil, nil)
}

Run above code (https://go.dev/play/p/PxN4Q4-F2rG)

Result:

        [Goroutine 6 in state select, with github.com/patrickmn/go-cache.(*janitor).Run on top of the stack:
        goroutine 6 [select]:
        github.com/patrickmn/go-cache.(*janitor).Run(0xc0002f16d0, 0x0?)
        	/home/wxh/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1079 +0x85
        created by github.com/patrickmn/go-cache.runJanitor
        	/home/wxh/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1099 +0xed
        ]

It looks like https://github.com/patrickmn/go-cache/blob/v2.1.0/cache.go#L1076-L1087 does not handle exit condition properly.

tigerinus avatar Mar 06 '23 22:03 tigerinus

it's work

import (
	"runtime"
	"testing"
	"time"

	"github.com/patrickmn/go-cache"
	"go.uber.org/goleak"
)

func leaktest(t *testing.T) {
	defer goleak.VerifyNone(t)
	cache.New(5*time.Minute, 10*time.Minute)
	runtime.GC()
}

func main() {
	testSuite := []testing.InternalTest{{Name: "leaktest", F: leaktest}}
	testing.Main(func(a, b string) (bool, error) { return a == b, nil }, testSuite, nil, nil)
}

vithnilica avatar Apr 03 '23 12:04 vithnilica

I am also getting the similar kind of error as follows

range_service  | goroutine 876 [select, 2 minutes]:
range_service  | github.com/patrickmn/go-cache.(*janitor).Run(0xc001627fb0, 0xc0011b9140?)
range_service  | 	/Users/jay/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1079 +0x85
range_service  | created by github.com/patrickmn/go-cache.runJanitor
range_service  | 	/Users/jay/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1099 +0xed
range_service  |
range_service  | goroutine 877 [select, 5 minutes]:
range_service  | github.com/patrickmn/go-cache.(*janitor).Run(0xc001634050, 0x0?)
range_service  | 	/Users/jay/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1079 +0x85
range_service  | created by github.com/patrickmn/go-cache.runJanitor
range_service  | 	/Users/jay/go/pkg/mod/github.com/patrickmn/[email protected]+incompatible/cache.go:1099 +0xed

@vithnilica Can you explain how runtime.GC() is helping and when to call this function. I am really new to go and don't understand much about memory leaks and all so can you please give an example to explain it better.

Thanks

ponder2000 avatar May 17 '23 13:05 ponder2000

It is not memory leaks (or goroutine leak). "go.uber.org/goleak" test does not wait for "garbage collector".

vithnilica avatar May 17 '23 14:05 vithnilica

It is not memory leaks (or goroutine leak). "go.uber.org/goleak" test does not wait for "garbage collector".

Can you please explain why I am getting the error(cause of the error) and how will adding a routine.GC() after every cache creation helps to resolve the error.

ponder2000 avatar May 17 '23 15:05 ponder2000

The test counts the number of goroutines at the beginning and at the end. You need to clean up the memory (garbage collection) before terminating the test. Golang typically does this with a delay. Before releasing an object from memory, the Golang calls a finalizer that terminates the goroutine. Finalizer registration in code: https://github.com/patrickmn/go-cache/blob/master/cache.go#L1123 About finalizer: https://medium.com/a-journey-with-go/go-finalizers-786df8e17687 About garbage collection: https://tip.golang.org/doc/gc-guide

vithnilica avatar May 17 '23 20:05 vithnilica