When THP always Undoes the Go Scavenger's Work

A fleet of Go services kept getting OOM-killed inside containers whose memory limits were generous — two to three times the live heap the runtime reported. We had GODEBUG=madvdontneed=1 set from years back, so I expected RSS to track the heap closely. Instead RSS climbed in a sawtooth that never came back down, while runtime.MemStats.HeapReleased insisted the memory had been handed back to the kernel. Both were telling the truth. The host had /sys/kernel/mm/transparent_hugepage/enabled set to always. ...

September 3, 2026 · 5 min · Moises Vega

When Request.Body.Close Started Returning io.EOF

While testing an internal HTTP service against Go 1.27, a handler that had worked for years started reporting errors from an unlikely place: r.Body.Close(). The handler does what any length-aware decoder does — it reads exactly the payload it expects and stops, without reading far enough to observe io.EOF. Then it closes the body. On Go 1.26 that Close returned nil. On 1.27 it returns io.EOF. Pinning it down Timer-driven HTTP repros are flaky, so I drove net/http over a net.Pipe with a one-shot listener: fully deterministic, no real sockets, no timeouts. The whole thing fits in a playground link. A handler reads 2 of 4 body bytes, closes, and reports the error: ...

August 20, 2026 · 2 min · Moises Vega

Testing Concurrent Go with synctest

Notes from Alexander Baygeldin’s GopherCon 2026 talk, “(Sync)testing Concurrent Code with Confidence.” Testing concurrent code used to mean picking one of three bad options: hacky (poke at internals), flaky (time.Sleep(100 * time.Millisecond) and hope), or slow (sleep long enough that it’s reliable). testing/synctest removes the tradeoff by giving the test a fake clock. The fake clock Inside synctest.Test, time doesn’t pass on its own. It jumps forward only when every goroutine in the bubble is blocked. A one-hour timeout resolves instantly and deterministically. ...

August 16, 2026 · 2 min · Moises Vega

Why Go's Generics Rules Keep Loosening

Notes from Naoki Kuroda’s GopherCon 2026 talk, “Loosening the Reins: Go Generics Get More Flexible.” The talk starts from one line in the Go 1.26 release notes: type Ordered[T Ordered[T]] interface { ... } A constraint that refers to itself. Go 1.25 rejected it as an invalid recursive type; Go 1.26 accepts it. Kuroda went through the spec history, issues, and the go/types checker to find out what changed — and the answer wasn’t “we found a clever fix.” The original rule had simply been drawn wider than the problem it was guarding against. ...

August 16, 2026 · 2 min · Moises Vega

Building Agentic Systems in Go with Ollama

I worked through Joel Boursiquot’s GopherCon 2026 workshop, Agentic Systems the Hard Way. Zero third-party dependencies. The framing that stuck: Go is the control plane; the LLM is the decision engine. Here’s the core of it. What an agent actually is Model + tools + loop. The model never executes anything — it emits a wish (“call search_docs with these args”), your Go code validates and runs it, feeds the result back, and repeats until the model answers without asking for tools. Deterministic Go around a nondeterministic model. That’s the whole trick. ...

August 15, 2026 · 3 min · Moises Vega

Local LLM Inference in Go with Kronk

I’ve been running local models through Ollama’s HTTP API for a while. It works, but it means a separate daemon, JSON over localhost, and no control over when models load or unload. Kronk, from Ardan Labs, takes the other route: it’s a Go SDK that binds llama.cpp (and whisper.cpp, via Bucky) directly into your process. No Python, no sidecar server. The model lives in your binary’s memory and you drive it with Go calls. ...

August 15, 2026 · 3 min · Moises Vega

Go 1.27: The Highlights

Go 1.27 lands this month. The release notes are long; these are the changes I actually care about. Generic methods The headline: methods can finally declare their own type parameters. Helpers that used to be package-scoped functions can now live on the type they belong to. type Set[E comparable] struct{ m map[E]struct{} } func (s *Set[E]) Map[T comparable](f func(E) T) *Set[T] { out := &Set[T]{m: make(map[T]struct{}, len(s.m))} for e := range s.m { out.m[f(e)] = struct{}{} } return out } One catch: interface methods cannot declare type parameters, so you cannot abstract over generic methods with an interface. ...

August 14, 2026 · 2 min · Moises Vega

Table-Driven Tests in Go

Every Go codebase converges on the same test shape: a slice of cases, a loop, one t.Run per case. It is the closest thing Go has to a testing idiom, and it is worth writing deliberately rather than by habit. The shape func TestParseDuration(t *testing.T) { tests := []struct { name string give string want time.Duration wantErr string }{ { name: "seconds", give: "30s", want: 30 * time.Second, }, { name: "compound", give: "1h30m", want: 90 * time.Minute, }, { name: "missing unit", give: "30", wantErr: "missing unit", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseDuration(tt.give) if tt.wantErr != "" { require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, got) }) } } Three things earn their place here. ...

August 12, 2026 · 3 min · Moises Vega