Section 14
Defer — Advanced
Now that mutexes, WaitGroups, and channels are introduced: the defer patterns that make all of them safe in concurrent code.
defer + Mutex — the most important engine pattern
always defer Unlock immediately after Lock
func (e *Engine) RegisterWorker(id string, w *Worker) {
e.mu.Lock() // acquire — no other goroutine can enter
defer e.mu.Unlock() // NEXT LINE — guarantee release
// If we forgot defer and this function panicked or returned early,
// the mutex would stay locked. Every other goroutine calling Lock()
// would block forever. The program deadlocks silently.
e.workers[id] = w
}
// Pattern is always:
// lock(); defer unlock() — two consecutive lines, every timedefer + wg.Done() — every goroutine in a WaitGroup
defer wg.Done() — first line of every tracked goroutine
var wg sync.WaitGroup
for _, msg := range batch {
wg.Add(1)
go func(m Message) {
defer wg.Done() // FIRST LINE of goroutine body
// If process(m) panics:
// WITHOUT defer: wg counter stays at +1, Wait() blocks forever
// WITH defer: Done() still runs, counter reaches 0, Wait() returns
process(m)
}(msg)
}
wg.Wait()defer + close(ch) — notify downstream when done
defer close — signal that no more values are coming
func produce(msgs []Message) <-chan Message {
out := make(chan Message, len(msgs))
go func() {
defer close(out) // close when this goroutine exits — always
// Downstream range loops exit cleanly when out is closed
// Without this, consumers wait forever after last message
for _, m := range msgs {
out <- m
}
}()
return out
}
for msg := range produce(messages) { // exits when out is closed
process(msg)
}defer + recover — prevent one bad message from crashing the engine
wrap goroutine in recover to contain panics
// A panic in a goroutine kills the ENTIRE program unless recovered.
// One corrupted message must not take down the whole engine.
func (w *Worker) processSafe(msg Message) (err error) {
// Deferred function runs even during panic unwind
defer func() {
if r := recover(); r != nil {
// Panic was caught — convert to an error
err = fmt.Errorf("panic in worker %s: %v", w.ID, r)
// Log the stack trace for debugging
buf := make([]byte, 4096)
n := runtime.Stack(buf, false) // false = this goroutine only
log.Printf("stack:\n%s", buf[:n])
}
}()
return w.process(msg) // if this panics, recover catches it above
}
// recover() returns nil when there was no panic
// recover() ONLY works inside a deferred function
// recover() called outside defer does nothing