Frontend Labs
⋯

Day 57 — Error Propagation in Network Code

Learning Objectives

  • Classify network errors correctly for retry logic
  • Know the difference between io.EOF (clean close) and io.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.


Generated from week8.md · 8 Oct 2026 16:36