Изучаю сетевое программирование в golang, нашел в каком-то блоге вот такой пример websocket чат-сервера. Как он работает - вполне понятно - тупо бродкастит сообщения от клиентов. Но почему после нескольких коннектов ядро прибивает процесс из-за OOM - не могу разобраться. Вроде все ресурсы освобождаются, каналы закрываются. Пробовал проанализировать кучу с помощью pprof - не могу поймать момент, когда происходит утечка. Как вообще отлаживаются подобные ошибки в go?
package main
import (
  "encoding/json"
  "fmt"
  "net"
  "net/http"
  "github.com/gorilla/websocket"
  uuid "github.com/satori/go.uuid"
)
//Client management
type ClientManager struct {
  //The client map stores and manages all long connection clients, online is TRUE, and those who are not there are FALSE
  clients map[*Client]bool
  //Web side MESSAGE we use Broadcast to receive, and finally distribute it to all clients
  broadcast chan []byte
  //Newly created long connection client
  register chan *Client
  //Newly canceled long connection client
  unregister chan *Client
}
//Client
type Client struct {
	//User ID
	id string
	//Connected socket
	socket *websocket.Conn
	//Message
	send chan []byte
}
//Will formatting Message into JSON
type Message struct {
	//Message Struct
	Sender    string `json:"sender,omitempty"`
	Recipient string `json:"recipient,omitempty"`
	Content   string `json:"content,omitempty"`
	ServerIP  string `json:"serverIp,omitempty"`
	SenderIP  string `json:"senderIp,omitempty"` 
}
//Create a client manager
var manager = ClientManager{
	broadcast:  make(chan []byte),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}
func (manager *ClientManager) start() {
  for {
    select {
    //If there is a new connection access, pass the connection to conn through the channel
    case conn := <-manager.register:
      //Set the client connection to true
      manager.clients[conn] = true
      //Format the message of returning to the successful connection JSON
      jsonMessage, _ := json.Marshal(&Message{Content: "/A new socket has connected. ", ServerIP: LocalIp(), SenderIP: conn.socket.RemoteAddr().String()})
      //Call the client's send method and send messages
      manager.send(jsonMessage, conn)
      //If the connection is disconnected
    case conn := <-manager.unregister:
      //Determine the state of the connection, if it is true, turn off Send and delete the value of connecting client
      if _, ok := manager.clients[conn]; ok {
      	close(conn.send)
      	delete(manager.clients, conn)
      	jsonMessage, _ := json.Marshal(&Message{Content: "/A socket has disconnected. ", ServerIP: LocalIp(), SenderIP: conn.socket.RemoteAddr().String()})
      	manager.send(jsonMessage, conn)
      }
    	//broadcast
    case message := <-manager.broadcast:
    	//Traversing the client that has been connected, send the message to them
    	for conn := range manager.clients {
          select {
          case conn.send <- message:
      	  default:
      	    close(conn.send)
            delete(manager.clients, conn)
      	  }
    	}
    }
  }
}
//Define the send method of client management
func (manager *ClientManager) send(message []byte, ignore *Client) {
  for conn := range manager.clients {
    //Send messages not to the shielded connection
    if conn != ignore {
      conn.send <- message
    }
  }
}
//Define the read method of the client structure
func (c *Client) read() {
  defer func() {
    manager.unregister <- c
    _ = c.socket.Close()
  }()
  for {
    //Read message
    _, message, err := c.socket.ReadMessage()
    //If there is an error message, cancel this connection and then close it
    if err != nil {
    	manager.unregister <- c
    	_ = c.socket.Close()
    	break
    }
    //If there is no error message, put the information in Broadcast
    jsonMessage, _ := json.Marshal(&Message{Sender: c.id, Content: string(message), ServerIP: LocalIp(), SenderIP: c.socket.RemoteAddr().String()})
    manager.broadcast <- jsonMessage
  }
}
func (c *Client) write() {
  defer func() {
    _ = c.socket.Close()
  }()
  for {
    select {
    //Read the message from send
    case message, ok := <-c.send:
      //If there is no message
      if !ok {
        _ = c.socket.WriteMessage(websocket.CloseMessage, []byte{})
        return
      }
      //Write it if there is news and send it to the web side
      _ = c.socket.WriteMessage(websocket.TextMessage, message)
    }
  }
}
func main() {
	fmt.Println("Starting application...")
	//Open a goroutine execution start program
	go manager.start()
	//Register the default route to /ws, and use the wsHandler method
	http.HandleFunc("/ws", wsHandler)
	http.HandleFunc("/health", healthHandler)
	//Surveying the local 8011 port
	fmt.Println("chat server start.....")
	//Note that this must be 0.0.0.0 to deploy in the server to use
	_ = http.ListenAndServe("0.0.0.0:8448", nil)
}
var upgrader = websocket.Upgrader{
  ReadBufferSize:  1024 * 1024 * 1024,
  WriteBufferSize: 1024 * 1024 * 1024,
  //Solving cross-domain problems
  CheckOrigin: func(r *http.Request) bool {
    return true
  },
}
func wsHandler(res http.ResponseWriter, req *http.Request) {
  //Upgrade the HTTP protocol to the websocket protocol
  conn, err := upgrader.Upgrade(res, req, nil)
  if err != nil {
    http.NotFound(res, req)
    return
  }
  //Every connection will open a new client, client.id generates through UUID to ensure that each time it is different
  client := &Client{id: uuid.Must(uuid.NewV4(), nil).String(), socket: conn, send: make(chan []byte)}
  //Register a new link
  manager.register <- client
  //Start the message to collect the news from the web side
  go client.read()
  //Start the corporation to return the message to the web side
  go client.write()
}
func healthHandler(res http.ResponseWriter, _ *http.Request) {
  _, _ = res.Write([]byte("ok"))
}
func LocalIp() string {
  address, _ := net.InterfaceAddrs()
  var ip = "localhost"
  for _, address := range address {
    if ipAddress, ok := address.(*net.IPNet); ok && !ipAddress.IP.IsLoopback() {
      if ipAddress.IP.To4() != nil {
        ip = ipAddress.IP.String()
      }
    }
  }
  return ip
}






