| Event | Goroutine | Access |
|---|---|---|
| A handshake completes | The new connection's | Add a client |
| A close frame or read error | The closing connection's | Remove a client |
| No pong in time | A heartbeat goroutine | Remove a client |
| A user's list changes | Whoever changed it | Read every client |
| A DM is sent | The sender's | Find the recipient's clients |
type Registry struct {
mu sync.Mutex
clients map[*Client]bool
}
func (r *Registry) Add(client *Client) {
r.mu.Lock()
defer r.mu.Unlock()
r.clients[client] = true
}Lock() until it is unlockedLock() and Unlock() is a critical sectiondefer unlocks even if the function returns early// Wrong: the map can change between the two critical sections
r.mu.Lock()
_, exists := r.users[username]
r.mu.Unlock()
if !exists {
r.mu.Lock()
r.users[username] = client
r.mu.Unlock()
}exists == false before either writesThis is an optional objective on HW4
conn.Write(header) // Goroutine A sends a frame in two writes...
// ...Goroutine B: conn.Write(pingFrame)
conn.Write(payload)net.Conn allows concurrent writes, and the bytes from one Write call are sent together
Write calls for one frame can be split apartRead is parked until its socket has data
ulimit -n is often 1024 by default (Can be raised to millions)go run -race . while the test runs