-
-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide a way to shutdown http cache fetcher to avoid go-routine leaks. #928
Comments
Hmmm, I tried allowing setting a fetcher object like so (pseudocode) ctx, cancel := context.WithCancel(context.Background())
defer cancel()
jwk.SetFlobalFetcher( httprc.NewFetcher(ctx) )
goleak.VerifyTestMain(m) But I don't think this does the trick because presumably Of course, this problem can be fixed easily by requiring I may not be understanding how If I'm mistaken or if there are ways to force cleanup before |
I guess the issue you have is not related to I guess in your case above the problem is that you still use If the func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestFetcherShutdown(t *testing.T) {
// arrange
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fetcher := httprc.NewFetcher(ctx, httprc.WithFetcherWorkerCount(1))
jwk.SetGlobalFetcher(fetcher)
// act
cancel()
// assert
goleak.VerifyNone(t)
} |
How about something along the lines? diff --git a/jwk/fetch.go b/jwk/fetch.go
index daca177..300295d 100644
--- a/jwk/fetch.go
+++ b/jwk/fetch.go
@@ -20,6 +20,7 @@ func (f FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption)
return f(ctx, u, options...)
}
+var fetcherCanceller context.CancelFunc
var globalFetcher httprc.Fetcher
func init() {
@@ -32,7 +33,15 @@ func init() {
nworkers = 3
}
- globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers))
+ var ctx context.Context
+
+ ctx, fetcherCanceller = context.WithCancel(context.Background())
+
+ globalFetcher = httprc.NewFetcher(ctx, httprc.WithFetcherWorkerCount(nworkers))
+}
+
+func Shutdown() {
+ fetcherCanceller()
}
// Fetch fetches a JWK resource specified by a URL. The url must be func TestFetcherShutdown(t *testing.T) {
jwk.Shutdown()
// assert
goleak.VerifyNone(t)
} A bit ugly, but backward-compatible and does what's expected. |
No, when I made my changes I removed Furthermore, I think your code below will break: func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestFetcherShutdown(t *testing.T) {
// arrange
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fetcher := httprc.NewFetcher(ctx, httprc.WithFetcherWorkerCount(1))
jwk.SetGlobalFetcher(fetcher)
// act
cancel()
// assert
goleak.VerifyNone(t)
} I'm sure And as for func TestFetcherShutdown(t *testing.T) {
jwk.Shutdown()
// assert
goleak.VerifyNone(t)
} Again, this is okay for So I'd like to help you, but here's my dilemma:
Taking the above, and the fact that you can get away with marking particular packages as exempt in
func TestGH922(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
jwk.SetGlobalFetcher(httprc.NewFetcher(ctx))
defer jwk.SetGlobalFetcher(nil) // This must be included to restore default behavior
cancel() // stop fetcher goroutines
goleak.VerifyNone(t)
} The benefits
// your main.go, or whatever top-most level code. Make sure that there are
// no other code that calls jwk.Fetch after `Run()` is called
func Run() {
ctx, cancel := context.WithCancel(context.Background())
jwk.SetGlobalFetcher(httprc.NewFetcher(ctx))
defer cancel()
// your main code
}
// your test code:
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m) // call tests that use Run()
} So, to conclude: To your original request, the answer is "no, we can't fix it the way you want us to". However, we can add ways to avoid the same problem if you are careful to now call Does that sound acceptable? |
Thank you for your detailed answer. It all makes sense. Forgive me if I’m missing anything, but my understanding is that the option to provide a global fetcher will only work for us if the init function won’t run another fetcher before or if there will be a way to optionally ignore the init function (via an env variable for example?). |
@screwyprof yes, that's correct, and as I (briefly) wrote elsewhere my intention is to get rid of |
I have a PoC, let me just make a PR with it... |
@screwyprof Please see if this works for you? |
Hey @lestrrat. This does work for me, thank you very much! Thank you for a quick and nice solution. The docs also look slick! |
Abstract
At the moment
jwx
usesgithub.com/lestrrat-go/httprc
under the hood to run a fetcher. The problem is that theglobalFetcher
instance created in the init function starts a go-routine which will run perpetually with no way to shutdown it.We use go-leak to check for go-routines leaks in our tests:
It would be good if
jwx
had a way to shutdown the fetcher.Describe the proposed solution/change
jwx
could expose a method to do a shutdown for examplejwk.Shutdown()
which will stop the fetcher's go-routines.Analysis
Alternatively,
jwx
could provide an option to disable the fetch/http cache all together:The text was updated successfully, but these errors were encountered: