Master Go Panic/Recover, Thread Limits, and Error Handling – A Practical Guide
This article explores Go's panic/recover mechanism, identifies which runtime errors can be recovered, explains unrecoverable scenarios like thread exhaustion and map race, demonstrates best‑practice error handling with Go 1.13 features and popular error libraries, and offers concrete logging strategies for robust server development.
Introduction
The article is the second part of a series on key Go development techniques, focusing on common server‑side problems and how Go's defer, panic, and recover can be used to mitigate them.
1. Which Errors Can Be Recovered
Go provides three constructs: defer (usually for resource cleanup), panic (aborts normal flow and runs deferred calls), and recover (captures a panic inside a deferred function). recover only works when called directly from a deferred function; calling it from a nested function has no effect.
package main
import "fmt"
func main() {
f := func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}
defer f()
panic("ok")
}Running this program still panics because recover is not in the immediate defer.
Recoverable runtime panics include slice out‑of‑bounds, nil‑pointer dereference, divide‑by‑zero, and sending on a closed channel. Example for slice out‑of‑bounds:
package main
import (
"fmt"
"bytes"
)
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
b := []int{0, 1}
fmt.Println("Hello", b[2])
}Similar examples are provided for nil pointers, division by zero, and closed channels.
2. Recover Best Practices
After catching a panic, code should inspect the recovered value, optionally filter it through a custom Handler, and log it via a Logger interface.
type Handler interface { Filter(err error, r interface{}) error }
type Logger interface { Ef(format string, a ...interface{}) }
func HandlePanic(h Handler, l Logger) error {
return handlePanic(recover(), h, l)
}
func handlePanic(r interface{}, h Handler, l Logger) error {
if r != nil {
err, ok := r.(error)
if !ok { err = fmt.Errorf("r is %v", r) }
if h != nil { err = h.Filter(err, r) }
if err != nil && l != nil { l.Ef("panic err %+v", err) }
return err
}
return nil
}Usage examples show how to wrap a goroutine with defer HandlePanicFunc(...) and provide concrete logger functions.
3. Errors That Cannot Be Recovered
Some situations cause the runtime to abort without invoking deferred functions:
Thread limit exhaustion (runtime throws when the number of OS threads exceeds the limit set by debug.SetMaxThreads or the default of 10 000).
Concurrent map writes, which trigger a runtime panic; the recommended solution is to use sync.Map.
Runtime throw calls (over 690 places in Go 1.11).
Example of map race panic:
package main
import (
"fmt"
"time"
)
func main() {
m := map[string]int{}
p := func() {
defer func() { if r := recover(); r != nil { fmt.Println(r) } }()
for { m["t"] = 0 }
}
go p(); go p(); time.Sleep(time.Second)
}Running this may produce fatal error: thread exhaustion when the thread limit is reached. The article explains how to adjust the limit with debug.SetMaxThreads and how GOMAXPROCS controls the number of OS threads that execute Go code simultaneously, not the total number of threads created for blocked system calls.
4. Errors vs. Exceptions
Go chooses error values over exceptions because they make failure paths explicit. However, Go's error handling can become verbose. The article reviews Go 2 proposals that aim to improve error ergonomics, and shows three styles of implementing a CopyFile function:
“No Error Handling” – ignores errors entirely.
“Not Nice and still Wrong” – checks each error but repeats context.
“Good” – wraps errors with context using fmt.Errorf.
Go 1.13 introduced error wrapping with the %w verb and the errors.Is / errors.Unwrap helpers:
func foo() error { return fmt.Errorf("read err: %w", io.EOF) }
func bar() error {
if err := foo(); err != nil { return fmt.Errorf("foo err: %w", err) }
return nil
}The output demonstrates how errors.Is(err, io.EOF) correctly identifies the underlying cause.
5. Best Practices for Error Handling
Always handle returned error values; if intentionally ignored, do so explicitly with a comment.
Provide rich error information (stack trace, parameters, call chain).
Separate error reporting from logging – return errors up the call stack and let the top level decide how to log or monitor them.
Use error‑handling libraries (e.g., github.com/pkg/errors) to add stack traces and simplify wrapping.
Example using pkg/errors shows concise error propagation while preserving context.
6. Logging Best Practices
Effective logs should contain enough context to be searchable and to correlate events across services. The article demonstrates a naive logger that prints only timestamps, then improves it by adding request URLs, durations, and connection identifiers.
type Connection struct { url string; logger *log.Logger }
func (c *Connection) Process(ctx context.Context) {
go checkRequest(ctx, c.url)
d := time.Duration(rand.Int()%1500) * time.Millisecond
time.Sleep(d)
c.logger.Println(fmt.Sprintf("Process connection ok, url=%s, duration=%v", c.url, d))
}
func checkRequest(ctx context.Context, url string) {
d := time.Duration(rand.Int()%1500) * time.Millisecond
time.Sleep(d)
logger.Println(fmt.Sprintf("Check request ok, url=%s, duration=%v", url, d))
}By creating a logger per connection (e.g.,
log.New(os.Stdout, fmt.Sprintf("[CONN %s] ", url), log.LstdFlags)), each log line is automatically prefixed with a connection identifier, making it easy to grep logs for a specific request.
Additional recommendations include using context.WithValue to carry request IDs, emitting structured JSON logs for machine consumption, and employing the lumberjack library for log rotation.
Conclusion
The guide emphasizes that robust Go services should combine proper panic recovery, thoughtful error handling with modern Go 1.13 features or third‑party libraries, and context‑rich logging to simplify debugging and monitoring in production environments.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
