Skip to content
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

False positive with body close inside separate function #30

Open
renta opened this issue Jun 7, 2020 · 11 comments
Open

False positive with body close inside separate function #30

renta opened this issue Jun 7, 2020 · 11 comments

Comments

@renta
Copy link

renta commented Jun 7, 2020

I think that bodyclose produces false positive warning on this code: response body is closed in a separate function inside each logic case. I can not use defer response.Body.Close() due to infinite loop with ticker.

func isAppReady(
	logger *zap.Logger,
	shutdownChannel <-chan bool,
	appHost string,
	appHealthCheckURI string,
	appCheckFrequency int,
) bool {
	logger.Debug(
		"[Consumer] check that App is available in the infinite loop with frequency",
		zap.Int("appCheckFrequency", appCheckFrequency),
		zap.String("app url", appHost+appHealthCheckURI),
	)

	ticker := time.NewTicker(time.Duration(appCheckFrequency) * time.Second)
	for {
		select {
		case <-shutdownChannel:
			logger.Info("[Consumer] got Shutdown signal, terminating app health check")
			return false
		case <-ticker.C:
			response, err := http.Get(appHost + appHealthCheckURI)
			if err != nil {
				logger.Error("[Consumer] error after the app health check request", zap.Error(err))
				closeResponse(response.Body, logger)
				continue
			}
			if response.StatusCode == http.StatusOK {
				logger.Info("[Consumer] got 200 OK: application started",
					zap.String("app url", appHost+appHealthCheckURI),
					zap.Int("response.StatusCode", response.StatusCode),
				)
				closeResponse(response.Body, logger)
				return true
			}

			closeResponse(response.Body, logger)
			logger.Debug(
				"[Consumer] app is not ready yet to consume events, continue to wait",
				zap.String("app url", appHost+appHealthCheckURI),
				zap.Int("response.StatusCode", response.StatusCode),
			)
		}
	}
}

func closeResponse(responseBody io.Closer, logger *zap.Logger) {
	if err := responseBody.Close(); err != nil {
		logger.Error("[Consumer] can not close the response")
	}
}
@justinpage
Copy link

@timakin Any feedback on the above? This pattern is quite common in many projects. In fact, the following code that is demonstrated in this links closes the body inside another function:

https://blog.golang.org/context

The tool would raise a flag against go func() { c <- f(http.DefaultClient.Do(req)) }() because the response body doesn't appear to be closed by f function but in fact is.

I have creative ways to work around this but I think this tool ignores certain practices I see from the community.

@justinpage
Copy link

@renta Did you come up with a way around this? I have some creative ways, like in my example above, I would have the resp object return and I handle the close in the goroutine instead of the closure I use above.

@renta
Copy link
Author

renta commented Aug 20, 2020

@justinpage I've not found a proper way to fix this issue, so I've used //nolint:bodyclose before isAppReady function to suppress this check.

@kostyaBro
Copy link

Another example:

resp, err := http.DefaultClient.Do(req) //nolint:bodyclose // linters bug
if err != nil {
	return rc, err
}

defer func(Body io.ReadCloser) {
	err := Body.Close()
	if err != nil {
		logger.GetLogger().Errorf("can not to close body: %s", err.Error())
	}
}(resp.Body)

@frederikhors
Copy link

I think the same happened here. I opened ory/kratos#1727 for the same reason, I think.

Any news, @timakin?

@arvenil
Copy link

arvenil commented Sep 6, 2021

IntelliJ/Goland right now auto generates

defer func(Body io.ReadCloser) {
	err := Body.Close()
	if err != nil {
	   // here most obvious is to put log.Error(err) or similar
    }
}(resp.Body)

and this linter doesn't accept that
I think above is the most, yet verbose, solution as we don't ignore error.

@frederikhors
Copy link

this linter doesn't accept that

It works for me.

This issue is for another reason.

@mikeschinkel
Copy link

mikeschinkel commented Feb 1, 2022

And chance in a config file you could allow us to provide a regexp for matching our closers? Using //nolint:bodyclose means it may miss catching other non-closed Body's, especially ones added later.

In my case I have a package called must and a package function called Close() that I call like so:

defer must.Close(resp.Body)

It would be nice if I could specify that anything matching "must\.Close\(\s*[a-zA-Z0-9_]+\.Body\s*\)" would satisfy body closure.

@credativ-dar
Copy link

credativ-dar commented Mar 20, 2023

I have to wonder how the false positive / actual positive ratio is with this bug unresolved for over 2 years 🤔

For some reason I get new warnings after upgrading to golangci-lint 1.52.0 from 1.50.1, is it expected to find more issues now?

credativ-dar added a commit to credativ/plutono that referenced this issue Mar 20, 2023
Unfortunately bodyclose fails if the body is closed in a separate function:
timakin/bodyclose#30
@SVilgelm
Copy link

I think the generic solution is adding the ignore rules, like if a response or body is passed to a defined in the rules function, then ignore that case

@sdischer-sap
Copy link

@timakin would you be open for somebody to contribute here?
So making sure that a structure like shown by @arvenil is passing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants