unknwon/the-way-to-go_ZH_CN · error
Error:
Error message
Error:
What it means
Panic from checkError in server1.go, the chapter-15 TCP chat server. Any error from Accept, Read, or Write — even from a single client — reaches checkError and crashes the entire server with 'Error: <err>'. The source comment itself flags the trade-off: 'the server process has to stop at any error: a simple return continues in the function where we came from'.
Source
Thrown at eBook/exercises/chapter_15/server1.go:64
if strings.Contains(input, ": WHO") {
DisplayList()
}
// extract clientname:
ix := strings.Index(input, "says")
clName := input[0 : ix-1]
//fmt.Printf("The clientname is ---%s---\n", string(clName))
// set clientname active in mapUsers:
mapUsers[string(clName)] = 1
fmt.Printf("Received data: --%v--", string(buf))
}
}
// advantage: code is cleaner,
// disadvantage: the server process has to stop at any error:
// a simple return continues in the function where we came from!
func checkError(error error) {
if error != nil {
panic("Error: " + error.Error()) // terminate program
}
}
func DisplayList() {
fmt.Println("--------------------------------------------")
fmt.Println("This is the client list: 1=active, 0=inactive")
for key, value := range mapUsers {
fmt.Printf("User %s is %d\n", key, value)
}
fmt.Println("--------------------------------------------")
}
View on GitHub (pinned to 7a54d34d36)
Solutions
- Handle per-connection errors inside the client-serving goroutine: log, close that conn, and return — never panic out of it
- Keep checkError only for fatal startup failures (resolve/listen); demote Accept errors to log-and-continue
- Add a deferred recover inside each connection goroutine so an unexpected panic cannot take down the Accept loop
- If the message says 'address already in use', stop the stale server process or pick another port before restarting
Example fix
// before
// inside client loop:
n, error := conn.Read(buf)
checkError(error)
// after
n, err := conn.Read(buf)
if err != nil {
fmt.Fprintln(os.Stderr, "client", clName, "disconnected:", err)
conn.Close()
return
} Defensive patterns
Strategy: try-catch
Validate before calling
func portFree(addr string) bool {
l, err := net.Listen("tcp", addr)
if err != nil {
return false
}
l.Close()
return true
}
if !portFree(":5000") {
log.Fatal("port 5000 already in use — stop the stale server first")
} Try / catch
// per-goroutine recover so one bad client cannot kill the server
for {
conn, err := listener.Accept()
if err != nil { log.Println("accept:", err); continue }
go func(c net.Conn) {
defer func() {
if r := recover(); r != nil {
log.Println("connection panic:", r)
}
}()
defer c.Close()
serve(c)
}(conn)
} Prevention
- Never share one fatal checkError across startup and per-connection code paths
- Always defer conn.Close() in the owning goroutine
- Log the remote address with every per-connection error so resets are diagnosable
When it happens
Trigger: A client disconnects abruptly (terminal closed, Ctrl-C) and the server's next conn.Read returns 'connection reset by peer'; or the listener socket fails (e.g. 'bind: address already in use') and the error reaches checkError, killing all connected users.
Common situations: One user closing their chat window kills the chat for everyone; starting a second server instance on a port the first still holds; running on a privileged port without permissions; long-lived demo servers hit by transient peer resets.
Related errors
AI-assisted analysis of unknwon/the-way-to-go_ZH_CN@7a54d34d36 (2026-08-15).
Data as JSON: /api/errors/d741ae44e7ed24a8.
Report an issue: GitHub.