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

  1. Handle per-connection errors inside the client-serving goroutine: log, close that conn, and return — never panic out of it
  2. Keep checkError only for fatal startup failures (resolve/listen); demote Accept errors to log-and-continue
  3. Add a deferred recover inside each connection goroutine so an unexpected panic cannot take down the Accept loop
  4. 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

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.