Skip to content

Conversation

@drawks
Copy link
Contributor

@drawks drawks commented Jan 23, 2026

PR Summary

This pull request modernizes and aligns the project’s linting configuration with the original intent documented in the existing config comments, while ensuring all functional behavior remains correct and fully covered by tests and linters.

Linting & Tooling Updates

  • Upgraded golangci-lint to the latest version and migrated configuration for v2+ compatibility.

  • Enabled linters that were already documented or implied in config comments:

    • gochecknoinits
    • bodyclose
    • unparam
    • govet
    • asciicheck
    • misspell
    • promlinter
    • errorlint
  • Fixed all violations introduced by these linters (e.g., unclosed HTTP bodies, unused parameters, error wrapping formats, spelling issues, race conditions).

Functional Changes

  • Receiver plugin registration refactor (largest functional change):

    • Replaced implicit init()-based plugin registration with explicit Register() calls.
    • Centralized registration in app.go, guarded by sync.Once.
    • Updated tests to explicitly register plugins in TestMain.
    • Preserves behavior while making plugin wiring deterministic, explicit, and testable.
  • Fixed a WaitGroup race condition in kafka.go by moving Add(1) outside goroutines.

Guarantees

  • All tests pass.
  • All enabled linters pass.
  • Functional behavior is preserved.
  • Changes improve correctness, clarity, safety, and maintainability without altering runtime semantics.

@gemini-code-assist
Copy link

Summary of Changes

Hello @drawks, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on modernizing the project's linting configuration, addressing code quality issues identified by the new linters, and refactoring the receiver plugin registration for better maintainability and testability. It also includes a fix for a race condition, enhancing the overall stability and reliability of the application.

Highlights

  • Linting Configuration Modernization: The pull request upgrades golangci-lint to the latest version, migrates the configuration for v2+ compatibility, and enables several new linters to improve code quality and consistency.
  • Receiver Plugin Registration Refactor: The implicit init()-based plugin registration is replaced with explicit Register() calls, centralizing registration in app.go and making plugin wiring deterministic and testable.
  • Race Condition Fix: A WaitGroup race condition in kafka.go is resolved by moving Add(1) outside goroutines, ensuring proper synchronization.
  • Error Handling: Updates error handling to use %w for error wrapping, providing more context and improving debugging.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/golangci-lint.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant improvements across the codebase, primarily focusing on modernizing the linting configuration, refactoring plugin registration for better determinism and testability, and enhancing error handling practices. The adoption of explicit Register() calls for receivers and consistent use of errors.Is and errors.As for error type checking greatly improves maintainability and robustness. Additionally, several potential resource leaks have been addressed by ensuring resp.Body.Close() calls, and a critical race condition in kafka.go has been resolved. Various minor typographical errors have also been corrected, contributing to overall code quality.

I am having trouble creating individual review comments. Click here to see my feedback.

receiver/kafka/kafka.go (415)

critical

Moving rcv.waitGroup.Add(1) outside the goroutine is a critical fix for a potential race condition. If the goroutine starts and finishes very quickly, Done() could be called before Add(1), leading to a panic or incorrect WaitGroup behavior.

rcv.waitGroup.Add(1)

receiver/kafka/kafka.go (313)

critical

Moving rcv.waitGroup.Add(1) outside the goroutine is a critical fix for a potential race condition. If the goroutine starts and finishes very quickly, Done() could be called before Add(1), leading to a panic or incorrect WaitGroup behavior.

rcv.waitGroup.Add(1)

carbonserver/flc.go (115-117)

high

The io.EOF error should be checked first, as it indicates the end of the stream and is not necessarily an actual error that should prevent further processing. This reordering ensures correct handling of io.EOF without masking other potential errors.

if errors.Is(err, io.EOF) {
			return nil
		}

tool/persister_configs_differ/main.go (62-64)

high

The io.EOF error should be checked first, as it indicates the end of the stream and is not necessarily an actual error that should prevent further processing. This reordering ensures correct handling of io.EOF without masking other potential errors.

if errors.Is(err, io.EOF) {
				break
			}

tags/tags.go (61)

high

Adding defer resp.Body.Close() is crucial for proper resource management. It ensures that the HTTP response body is always closed, preventing potential resource leaks.

defer resp.Body.Close()

receiver/parse/plain.go (88)

high

Using !errors.Is(err, io.EOF) is the correct and idiomatic way to check if an error is not io.EOF in Go, especially when err might be wrapped. This ensures that io.EOF is handled specifically without being treated as a general error.

if err != nil && !errors.Is(err, io.EOF) {

carbonserver/carbonserver.go (967-976)

high

The io.EOF error should be checked first, as it indicates the end of the stream and is not necessarily an actual error that should prevent further processing or trigger a fallback. This reordering ensures correct handling of io.EOF without masking other potential errors.

if errors.Is(err, io.EOF) {
					break
				}
				if err != nil {
					infos = append(infos, zap.NamedError("file_list_cache_read_error", err))

					readFromCache = false
					trieIdx = newTrie(".wsp", listener.maxCreatesPerSecond, listener.estimateSize)

					break
				}

receiver/kafka/kafka.go (383-388)

high

The errors.Is check for sarama.ErrOffsetOutOfRange should be performed before the general if err != nil check. This ensures that the specific offset out of range error is handled correctly without being caught by a broader error condition.

if errors.Is(err, sarama.ErrOffsetOutOfRange) {
		rcv.logger.Error(
			"kafka state offset out of range, restart from the oldest offset",
			zap.Int64("kafka_state_offset", rcv.kafkaState.Offset),
		)
		partitionConsumer, err = consumer.ConsumePartition(rcv.connectOptions.topic, rcv.connectOptions.partition, sarama.OffsetOldest)
	}

receiver/http/http_test.go (89)

high

Adding defer resp.Body.Close() is crucial for proper resource management. It ensures that the HTTP response body is always closed, preventing potential resource leaks.

defer resp.Body.Close()

points/reader.go (64-67)

high

The io.EOF error should be checked first, as it indicates the end of the stream and is not necessarily an actual error that should prevent further processing. This reordering ensures correct handling of io.EOF without masking other potential errors.

if errors.Is(err, io.EOF) {
				break
			}

points/reader.go (22)

high

Using !errors.Is(err, io.EOF) is the correct and idiomatic way to check if an error is not io.EOF in Go, especially when err might be wrapped. This ensures that io.EOF is handled specifically without being treated as a general error.

if err != nil && !errors.Is(err, io.EOF) {

carbonserver/find.go (333)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

errors = append(errors, fmt.Errorf("%s: %w", expandedResult.Name, err))

persister/whisper_schema.go (105-106)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

return nil, fmt.Errorf("[persister] Failed to parse retentions %q for [%s]: %w",
				schema.RetentionStr, schema.Name, err)

carbonserver/list.go (127)

medium

Corrected a typo from "occured" to "occurred".

http.Error(wr, fmt.Sprintf("An internal error has occurred: %s", err), http.StatusInternalServerError)

carbonserver/list.go (298)

medium

Corrected a typo from "occured" to "occurred".

http.Error(wr, fmt.Sprintf("An internal error has occurred: %s", err), http.StatusInternalServerError)

carbonserver/render.go (241)

medium

Corrected a typo from "occured" to "occurred".

http.Error(wr, "Panic occurred, see logs for more information", http.StatusInternalServerError)

carbonserver/render.go (726)

medium

Corrected a typo from "occured" to "occurred".

rpcErr = status.New(codes.Internal, "Panic occurred, see logs for more information").Err()

carbonserver/trie.go (585)

medium

Corrected a typo from "unknwon" to "unknown".

return nil, &trieInsertError{typ: "failed to index metric: unknown case of match == nlen", info: fmt.Sprintf("match == nlen == %d", nlen)}

carbonserver/trie.go (2170)

medium

The ps parameter was removed from maxCreatesThrottle in the function definition, so it should also be removed from its call site.

if ti.maxCreatesThrottle() {

persister/whisper.go (314)

medium

Using errors.Is is the idiomatic way to check if an error matches a specific sentinel error, even if it's wrapped. This makes the error handling more robust and future-proof.

if errors.Is(err, syscall.ENAMETOOLONG) {

persister/whisper.go (578-579)

medium

Using errors.As is the idiomatic way to check for a specific error type in Go, especially when dealing with wrapped errors. This makes the error handling more robust and future-proof.

var perr *os.PathError
	if !errors.As(err, &perr) {

persister/whisper.go (582)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

return fmt.Errorf("%s: %w", perr.Op, perr.Err)

persister/whisper_quota.go (46)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

err = fmt.Errorf("[persister] Failed to parse %s for [%s]: %w", name, section["name"], err)

persister/whisper_schema.go (98-99)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

return nil, fmt.Errorf("[persister] Failed to parse pattern %q for [%s]: %w",
				section["pattern"], schema.Name, err)

carbonserver/flc.go (163)

medium

When errors.Is returns true, it implies that err is not nil. Therefore, the err != nil check is redundant and can be removed for cleaner code.

if errors.Is(err, errFLCFallbackToV1) {

persister/whisper_schema.go (113)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

return nil, fmt.Errorf("[persister] Failed to parse priority %q for [%s]: %w", section["priority"], schema.Name, err)

carbonserver/find.go (497-498)

medium

Using errors.As is the idiomatic way to check for a specific error type in Go, especially when dealing with wrapped errors. This makes the error handling more robust and future-proof.

var nf errorNotFound
		if errors.As(err, &nf) {

carbon/app.go (470)

medium

Using %w for error wrapping is a best practice in Go, as it preserves the original error context, allowing for more robust error inspection using errors.Is and errors.As.

return fmt.Errorf("failed to stat whisper data directory: %w", err)

carbonserver/find.go (153-154)

medium

Using errors.As is the idiomatic way to check for a specific error type in Go, especially when dealing with wrapped errors. This makes the error handling more robust and future-proof.

var nf errorNotFound
		if errors.Is(err, &nf) {

carbonserver/details.go (110)

medium

Corrected a typo from "occured" to "occurred".

http.Error(wr, fmt.Sprintf("An internal error has occurred: %s", err), http.StatusInternalServerError)

carbonserver/carbonserver_test.go (493)

medium

Using errors.Is is the preferred way to check if an error matches a specific sentinel error, even if it's wrapped. This improves the robustness of error comparisons.

if !errors.Is(err, errMetricsListEmpty) {

carbonserver/carbonserver.go (1262-1263)

medium

Using errors.As is the idiomatic way to check for a specific error type in Go, especially when dealing with wrapped errors. This makes the error handling more robust and future-proof.

var ierr *trieInsertError
	if errors.As(err, &ierr) {

carbonserver/cache_index_test.go (188)

medium

Corrected a typo from "recived" to "received".

fmt.Printf("error - received wrong point - %v\n", p2)

receiver/tcp/tcp.go (277)

medium

Using errors.Is is the idiomatic way to check if an error matches a specific sentinel error, even if it's wrapped. This makes the error handling more robust and future-proof.

case errors.Is(err, io.EOF):

receiver/tcp/tcp.go (279)

medium

Using errors.Is is the idiomatic way to check if an error matches a specific sentinel error, even if it's wrapped. This makes the error handling more robust and future-proof.

case errors.Is(err, framing.ErrPrefixLength):

carbonserver/cache_index_test.go (165)

medium

Corrected a typo from "recived" to "received".

fmt.Printf("error - received wrong point - %v\n", p1)

carbon/app.go (488)

medium

Consistent error wrapping with %w is good for maintainability, enabling callers to inspect the error chain.

return fmt.Errorf("failed to init Carbonserver.HeavyGlobQueryRateLimiters %s: %w", rl.Pattern, err)

* updates github action to use most recent `golangci-lint`
* migrates `.golangci.yml` for version 2+ compat
* enables some new linters
  - asciicheck - no fails
  - misspell - fixed spelling errors that caused fails
  - promlinter - no fails
  - errorlint - updated error string formats to us `%w` and updated
    tests to match
- moves `waitGroup.Add(1)` outside of go routine
- enables `govet` linter in `.golangci.yml`
* enables `unparam` linter in `glangci.yaml`
* updates `render.go` and `trie.go`
  - removes unused `ps` param from `maxCreatesThrottle`
  - adds nolint:unparam and comments for other params that are unused
* enables `bodyclose` linter in `.golangci.yaml`
* fixes unclosed http response bodies
* enables `gochecknoinits` linter in `.golintci.yaml
* refactors receiver plugins to require explicit `Register` call instead
  of implicit `init`
* updates `app.go` to explicitly register all receiver plugins guarded
  by `sync.Once`
* updates recevier tests to explicitly call `Register` during `TestMain`
* adds a `nolint:gochecknoinits` in `trie_test` to allow developer debug
  logging
@drawks
Copy link
Contributor Author

drawks commented Jan 23, 2026

@deniszh what do you think of this? The DeepSource: Go fails actually seem unrelated to any of these changes. I think they're just being triggered on files which were touched in these commits. I'm not sure if it makes sense to burn those down in the same PR or if it'd make more sense to update the deepsource config to ignore these as part of the "baseline"

@deniszh
Copy link
Member

deniszh commented Jan 23, 2026

@drawks : you can ignore it, no worries

* respolves CRT-A0001 - renames several `max` params to `m` to avoid
  shadowing
@drawks
Copy link
Contributor Author

drawks commented Jan 23, 2026

@drawks : you can ignore it, no worries

I pushed one commit to fix a simple mechanical thing from that deepsource report, but I'll stop pushing commits here if you think it looks good for merge.

@deniszh
Copy link
Member

deniszh commented Jan 23, 2026

Let's merge this for now

@deniszh deniszh merged commit be9addf into go-graphite:master Jan 23, 2026
11 of 12 checks passed
drawks pushed a commit to drawks/go-carbon that referenced this pull request Jan 23, 2026
Updates for automated linting config and various code fixes to match
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

Successfully merging this pull request may close these issues.

2 participants