grpc/grpc-go · critical

Cannot ref freed buffer

Error message

Cannot ref freed buffer

What it means

buffer.Ref() (mem/buffers.go:149) increments the atomic refcount and panics at line 150-152 if the new count is <= 1, meaning the prior count was <= 0 (already freed). You cannot take a new reference to a buffer whose lifetime has ended; doing so would resurrect pooled memory. Each goroutine that wants to use a buffer must Ref() it while it is still alive.

Source

Thrown at mem/buffers.go:151

		copy(buf, data)
		return buf
	}

	buf := pool.Get(len(data))
	copy(*buf, data)
	return NewBuffer(buf, pool)
}

func (b *buffer) ReadOnlyData() []byte {
	if b.rootBuf == nil {
		panic("Cannot read freed buffer")
	}
	return b.data
}

func (b *buffer) Ref() {
	if b.refs.Add(1) <= 1 {
		panic("Cannot ref freed buffer")
	}
}

func (b *buffer) Free() {
	refs := b.refs.Add(-1)
	if refs < 0 {
		panic("Cannot free freed buffer")
	}
	if refs > 0 {
		return
	}

	b.data = nil
	if b.rootBuf == b {
		// This buffer is the owner of the data slice and its ref count reached
		// 0, free the slice.
		if b.pool != nil {
			b.pool.Put(b.origData)

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Ref() the buffer at the point you receive it (while it is guaranteed alive), before storing or sending it.
  2. Pair every Ref() with a later Free() in the same scope that owns the reference.
  3. Never assume a buffer received from an API is still alive after that API returns; take your own reference up front.

Example fix

// before
ch <- buf      // send raw buffer
go func() {
    b := <-ch
    b.Ref()       // panic: already freed by sender
}()
buf.Free()

// after
buf.Ref()       // take ref while alive
ch <- buf
go func() {
    b := <-ch
    defer b.Free()
    _ = b.ReadOnlyData()
}()
buf.Free()
Defensive patterns

Strategy: validation

Validate before calling

// Ref() at the point of receipt, while the buffer is guaranteed alive.
func handOff(b mem.Buffer) mem.Buffer {
    b.Ref() // take a new reference before giving it away
    return b
}

Prevention

When it happens

Trigger: Calling buf.Ref() after buf.Free() has already dropped the count to 0; ref-ing a buffer obtained from a path that already freed it; concurrent code where one goroutine freed the buffer before another called Ref().

Common situations: Sharing a Buffer across goroutines without Ref()-before-send; stashing a buffer for later use and the original owner already freed it; off-by-one in manual refcount management in a custom codec or interceptor.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/6049d2c6bffeffd5. Report an issue: GitHub.