MM-24397: Reusing the read buffer while reading messages from websockets (#14368)

* MM-24397: Reusing the read buffer while reading messages from websockets

The core problem was that conn.ReadMessage allocated a buffer every time it was read.
This created heavy amount of allocations every single time we read a message from the websocket.

To avoid this, we bypass the ReadMessage which was more of a helper method,
and actually call the NextReader which returns a reader object.
We can then reuse a single byte.Buffer instance to read the object unmarshal
into a WebSocketEvent object. This gets rid of the allocation in the read path completly
and allows GC more time to do other tasks.

* Incorporate review comments

* Move reset buffer to top of loop

* Cleanup further

* Fix test

* Final fix
Этот коммит содержится в:
Agniva De Sarker
2020-04-28 19:38:11 +05:30
коммит произвёл GitHub
родитель 8781c36eb3
Коммит f0eb0a9a01

Просмотреть файл

@@ -30,6 +30,8 @@ type writeMessage struct {
data interface{} data interface{}
} }
const avgReadMsgSizeBytes = 1024
// WebSocketClient stores the necessary information required to // WebSocketClient stores the necessary information required to
// communicate with a WebSocket endpoint. // communicate with a WebSocket endpoint.
type WebSocketClient struct { type WebSocketClient struct {
@@ -167,19 +169,30 @@ func (wsc *WebSocketClient) Listen() {
close(wsc.ResponseChannel) close(wsc.ResponseChannel)
close(wsc.quitPingWatchdog) close(wsc.quitPingWatchdog)
}() }()
var buf bytes.Buffer
buf.Grow(avgReadMsgSizeBytes)
for { for {
var rawMsg json.RawMessage // Reset buffer.
var err error buf.Reset()
if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { _, r, err := wsc.Conn.NextReader()
if err != nil {
if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError) wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
return
}
// Use pre-allocated buffer.
_, err = buf.ReadFrom(r)
if err != nil {
// This should use a different error ID, but en.json is not imported anyways.
// It's a different bug altogether but we let it be for now.
// See MM-24520.
wsc.ListenError = NewAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error(), http.StatusInternalServerError)
return return
} }
event := WebSocketEventFromJson(bytes.NewReader(rawMsg)) event := WebSocketEventFromJson(bytes.NewReader(buf.Bytes()))
if event == nil { if event == nil {
continue continue
} }
@@ -189,7 +202,7 @@ func (wsc *WebSocketClient) Listen() {
} }
var response WebSocketResponse var response WebSocketResponse
if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { if err := json.Unmarshal(buf.Bytes(), &response); err == nil && response.IsValid() {
wsc.ResponseChannel <- &response wsc.ResponseChannel <- &response
continue continue
} }