close
Skip to main content
BERJAYA

r/golang


Supervised fire-and-forget in Go
Supervised fire-and-forget in Go
discussion

While I see fewer unsynchronized go func() these days, they still appear often enough in regard to fire-and-forget work.

Unmanaged go func() results in unbounded concurrency, memory leaks, and other surprising behavior in a system.

So I traced a few repos at work to see how many of them appear irl. Turns out quite a bit in our huge monorepo.

I explored a tiny task manager that collects tasks as func() closures in a buffered channel and runs them in a bounded pool. It clamps down on unbounded concurrency, is aware of the parent context, allows task-specific timeouts, etc. All that requires less than 25 loc.

Might be interesting to you.

https://rednafi.com/go/supervised-fire-and-forget/

Update: I am aware of errgroup. It is not the right abstraction here as it cancels all inflight tasks if any of them fails. Also, the internal semaphore of errgroup works in a similar way as shown here


Advertisement: What if AI ran the busywork?
What if AI ran the busywork?
BERJAYA What if AI ran the busywork?


"go fix" on Go 1.26+ is awesome
"go fix" on Go 1.26+ is awesome
discussion

If you haven't tried the new "go fix" yet, give it a spin.

It's a proper revival of the old "gofix" idea. In Go 1.26, it was rebuilt on top of the Go analysis framework. So now it can modernize your code with newer language and stdlib features.

It respects the Go version in your module. If your "go.mod" says "go 1.26", it suggests Go 1.26-compatible changes. It doesn't rewrite your code into something your module hasn't opted into.

For teams using LLMs, this is great. You don't need to keep leaving PR comments like “use the newer API here” or “we don't write that pattern anymore.” Run it locally or on the CI instead.

Start from a clean git state:

go fix -diff ./...

Then apply it:

go fix ./...

Then run it again.

Some fixes expose more fixes. The Go team recommends rerunning it, and in practice two passes often catches more than one.

Some useful analyzers:

You can also run one analyzer at a time if you want smaller PRs:

go fix -newexpr ./...
go fix -forvar ./...

That's useful when one pass touches a lot of files and you don't want one giant mixed cleanup patch.

The other neat bit is //go:fix inline. Library authors can use it to help users migrate from old APIs to new APIs. That's much better than deprecating something and hoping everyone reads the changelog.

You can also write your custom analyzer to enforce your own rules. I am writing a few for fun.

The Go blog has two good posts on it:

I ran it on a large RPC service, and "newexpr" cleaned up a pile of pointer helper calls. Extremely satisfying.