golang/go · warning
lzw: reader/writer is closed
Error message
lzw: reader/writer is closed
What it means
Defined as the package-level errClosed sentinel and assigned to r.err by Reader.Close(). Any subsequent Read returns it. Close is idempotent for the reader state, but the error is sticky — it does not auto-reset.
Source
Thrown at src/compress/lzw/reader.go:226
// Undo the d.hi++ a few lines above, so that (1) we maintain
// the invariant that d.hi < d.overflow, and (2) d.hi does not
// eventually overflow a uint16.
r.hi--
} else {
r.width++
r.overflow = 1 << r.width
}
}
if r.o >= flushBuffer {
break
}
}
// Flush pending output.
r.toRead = r.output[:r.o]
r.o = 0
}
var errClosed = errors.New("lzw: reader/writer is closed")
// Close closes the [Reader] and returns an error for any future read operation.
// It does not close the underlying [io.Reader].
func (r *Reader) Close() error {
r.err = errClosed // in case any Reads come along
return nil
}
// Reset clears the [Reader]'s state and allows it to be reused again
// as a new [Reader].
func (r *Reader) Reset(src io.Reader, order Order, litWidth int) {
*r = Reader{}
r.init(src, order, litWidth)
}
// NewReader creates a new [io.ReadCloser].
// Reads from the returned [io.ReadCloser] read and decompress data from r.
// If r does not also implement [io.ByteReader],View on GitHub (pinned to b6b368adc5)
Solutions
- Move the drain (io.Copy / io.ReadAll) ahead of Close, or drop the defer and close explicitly after draining.
- If reusing a pooled Reader, call Reset(src, order, litWidth) before each use — Reset clears the error.
- Check the returned error: errors.Is(err, lzw's closed sentinel) where applicable, or simply treat any read-after-close as a use-after-free bug in your code.
- Use sync.Pool with care: never return a closed Reader to the pool.
Example fix
// before
defer r.Close()
go func() { io.Copy(dst, r) }() // races with defer
// after: drain before close
if _, err := io.Copy(dst, r); err != nil { return err }
if err := r.Close(); err != nil { return err } Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure Read happens-before Close in your goroutine layout.
// A pre-flight check is just: track an explicit closed flag.
type safeLZW struct {
r *lzw.Reader
closed atomic.Bool
}
func (s *safeLZW) Read(p []byte) (int, error) {
if s.closed.Load() { return 0, errors.New("reader closed") }
return s.r.Read(p)
} Try / catch
n, err := r.Read(buf)
if err != nil && err.Error() == "lzw: reader/writer is closed" {
// Use-after-close bug: drain before Close, or Reset before reuse.
return nil
} Prevention
- Never defer Close before the drain loop in a goroutine.
- For pooled readers, always Reset before use and never return a closed reader to the pool.
- Treat read-after-close as a bug in your code, not a runtime condition to handle.
When it happens
Trigger: Calling Read on an lzw.Reader (or Write on an lzw.Writer) after Close has been invoked. Common with deferred Close followed by a drain loop, or when a Reader is pooled and a goroutine races ahead of the close.
Common situations: Using `defer r.Close()` and then reading in a separate goroutine; reading from a sync.Pool'd Reader that the previous owner forgot to Reset; misuse of io.Copy after Close in a cleanup path.
Related errors
- lzw: unknown order
- lzw: invalid code
- lzw: input byte too large for the litWidth
- lzw: unknown order
- Go program has already exited
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/e305cf4c3c001bc2.
Report an issue: GitHub.