Day 57 — Error Propagation in Network Code
Learning Objectives
- Classify network errors correctly for retry logic
- Know the difference between
io.EOF(clean close) andio.ErrUnexpectedEOF(crash) - Build an error classifier used by your retry and circuit breaker logic
Exercise: Network Error Classifier
package main
import (
"errors"
"fmt"
"io"
"net"
"os"
"syscall"
)
type NetworkError struct {
Kind string
Retry bool // should the caller retry?
Severity string // "info", "warn", "error"
Err error
}
func ClassifyNetworkError(err error) NetworkError {
if err == nil {
return NetworkError{}
}
// Clean close — peer closed connection gracefully
if err == io.EOF {
return NetworkError{Kind: "EOF", Retry: false, Severity: "info", Err: err}
}
// Partial read — peer crashed mid-message
if err == io.ErrUnexpectedEOF {
return NetworkError{Kind: "UNEXPECTED_EOF", Retry: true, Severity: "warn", Err: err}
}
// Deadline exceeded (our timeout)
if os.IsTimeout(err) {
return NetworkError{Kind: "TIMEOUT", Retry: true, Severity: "warn", Err: err}
}
// Check net.Error interface
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Timeout() {
return NetworkError{Kind: "NET_TIMEOUT", Retry: true, Severity: "warn", Err: err}
}
}
// System call errors
var sysErr syscall.Errno
if errors.As(err, &sysErr) {
switch sysErr {
case syscall.ECONNREFUSED:
return NetworkError{Kind: "CONN_REFUSED", Retry: true, Severity: "error", Err: err}
case syscall.ECONNRESET:
return NetworkError{Kind: "CONN_RESET", Retry: false, Severity: "warn", Err: err}
case syscall.EPIPE:
return NetworkError{Kind: "BROKEN_PIPE", Retry: false, Severity: "warn", Err: err}
case syscall.ETIMEDOUT:
return NetworkError{Kind: "SYS_TIMEOUT", Retry: true, Severity: "warn", Err: err}
}
}
return NetworkError{Kind: "UNKNOWN", Retry: false, Severity: "error", Err: err}
}
func main() {
// Simulate different error types
testErrors := []error{
io.EOF,
io.ErrUnexpectedEOF,
&net.OpError{Op: "read", Net: "tcp", Err: syscall.ECONNRESET},
&net.OpError{Op: "dial", Net: "tcp", Err: syscall.ECONNREFUSED},
}
for _, err := range testErrors {
classified := ClassifyNetworkError(err)
fmt.Printf("Error: %-20v Kind: %-20s Retry: %-5v Severity: %s\n",
err, classified.Kind, classified.Retry, classified.Severity)
}
}
Real-World Context
In your circuit breaker (Day 51), ECONNRESET should count as a failure. io.EOF (client disconnected) should NOT count as a backend failure — it's the client's issue. Misclassifying errors causes false circuit breaker trips and unnecessary retries.