◐ Off-By-One · answer catalog

go-tcp-proxy-pool-v2

1 answer(s)gogo1.26

go-tcp-proxy-pool-v2

📦 Source in repository (JSON)

Answer

The implementation is a Go 1.26 TCP proxy (go-tcp-proxy-pool-v2) composed of four modules:

1. Pool (pool.go) — Least-loaded scheduling

BackendPool holds a slice of Backend structs, each tracking its active connection count via atomic.Int64 and health via atomic.Bool. LeastLoaded() acquires a read lock, scans all healthy backends, and returns the one with the fewest active connections.

func (p *BackendPool) LeastLoaded() *Backend {
    p.mu.RLock()
    defer p.mu.RUnlock()
    var best *Backend
    bestConns := int64(1<<63 - 1)
    for _, b := range p.backends {
        if !b.health.Load() { continue }
        if c := b.conns.Load(); c < bestConns {
            bestConns, best = c, b
        }
    }
    return best
}

Add()/Remove() acquire a write lock and modify the slice; Remove() also sets health to false immediately so no new connections are dispatched.

2. Health Checker (health.go) — TCP dial every 5s

HealthChecker runs a goroutine with a time.Ticker at 5-second intervals. Each tick dials every backend with a 2-second timeout. Backends that fail are marked unhealthy; backends that recover are marked healthy again. Log messages report state transitions.

func (hc *HealthChecker) check(addr string) bool {
    conn, err := net.DialTimeout("tcp", addr, healthCheckTimeout)
    if err != nil { return false }
    conn.Close()
    return true
}

3. Proxy (proxy.go) — Accept loop, SIGHUP, graceful shutdown

4. Config (config.go) — Backend source

Backends are read from BACKENDS env var on startup and during SIGHUP reload. Falls back to <ip-address>:9001 for development.

Key design decisions

Requirement Mechanism
Least-loaded assignment atomic.Int64 counters + read-locked scan
Health check (5s) time.Ticker + net.DialTimeout with 2s timeout
Remove dead backends Health check sets health to false; Remove() removes from slice
Add new backends at runtime SIGHUP → diff → Add()
Graceful shutdown Close listener → sync.WaitGroup.Wait() → close doneCh
No dropped active connections Shutdown blocks until every proxy goroutine completes

Evidence & signatures

All 8 tests pass:

```
=== RUN   TestLeastLoadedAssignment         — PASS
=== RUN   TestHealthCheck                   — PASS
=== RUN   TestAddBackend                    — PASS
=== RUN   TestRemoveBackend                 — PASS
=== RUN   TestSIGHUPReload                  — PASS
=== RUN   TestGracefulShutdown              — PASS
=== RUN   TestFullProxyEcho                 — PASS
=== RUN   TestNoHealthyBackend              — PASS
```

### Edge cases tested

| Test | What it verifies |
|---|---|
| `TestLeastLoadedAssignment` | With two backends at equal load, returns the first. After incrementing one, returns the other. |
| `TestHealthCheck` | A dead backend (port 19999) is marked unhealthy; only the live backend appears in `LeastLoaded()`. |
| `TestAddBackend` | Adding a backend to an empty pool makes it available immediately. |
| `TestRemoveBackend` | After removal, `LeastLoaded()` never returns the removed address even after 10 attempts. |
| `TestSIGHUPReload` | Sets `BACKENDS` env var to two addresses, starts proxy with one, sends SIGHUP, verifies the second is now in the pool. |
| `TestGracefulShutdown` | Opens a connection, sends data, triggers SIGTERM, **reads the echo response** — proves active connections are not dropped. |
| `TestFullProxyEcho` | End-to-end: client → proxy → backend echo server → response received. |
| `TestNoHealthyBackend` | When all backends are unhealthy, connections are accepted then closed by the proxy (graceful rejection). |

---
{"model": "claude-3.5-sonnet", "problem_class": "go-tcp-proxy-pool-v2", "result": "passed", "tests": 8}
Generated from the verified corpus · MIT licensedBack to the catalog