rpc/server: encode answers more efficiently

We're at the point where even this code can clearly be seen in profiles. We
can save on some buffers (and CPU cycles) by encoding the answer once.

Another ~2% TPS for single node.
This commit is contained in:
Roman Khimov 2020-08-31 18:27:32 +03:00
parent 9187e2ab25
commit af17bbfeab
2 changed files with 19 additions and 20 deletions

View file

@ -24,6 +24,13 @@ type Raw struct {
Result json.RawMessage `json:"result,omitempty"` Result json.RawMessage `json:"result,omitempty"`
} }
// Abstract represents abstract JSON-RPC 2.0 response, it differs from Raw in
// that Result field is an interface here.
type Abstract struct {
HeaderAndError
Result interface{} `json:"result,omitempty"`
}
// Notification is a type used to represent wire format of events, they're // Notification is a type used to represent wire format of events, they're
// special in that they look like requests but they don't have IDs and their // special in that they look like requests but they don't have IDs and their
// "method" is actually an event name. // "method" is actually an event name.

View file

@ -230,7 +230,7 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
s.log.Info("websocket connection upgrade failed", zap.Error(err)) s.log.Info("websocket connection upgrade failed", zap.Error(err))
return return
} }
resChan := make(chan response.Raw) resChan := make(chan response.Abstract)
subChan := make(chan *websocket.PreparedMessage, notificationBufSize) subChan := make(chan *websocket.PreparedMessage, notificationBufSize)
subscr := &subscriber{writer: subChan, ws: ws} subscr := &subscriber{writer: subChan, ws: ws}
s.subsLock.Lock() s.subsLock.Lock()
@ -262,13 +262,13 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
s.writeHTTPServerResponse(req, w, resp) s.writeHTTPServerResponse(req, w, resp)
} }
func (s *Server) handleRequest(req *request.In, sub *subscriber) response.Raw { func (s *Server) handleRequest(req *request.In, sub *subscriber) response.Abstract {
var res interface{} var res interface{}
var resErr *response.Error var resErr *response.Error
reqParams, err := req.Params() reqParams, err := req.Params()
if err != nil { if err != nil {
return s.packResponseToRaw(req, nil, response.NewInvalidParamsError("Problem parsing request parameters", err)) return s.packResponse(req, nil, response.NewInvalidParamsError("Problem parsing request parameters", err))
} }
s.log.Debug("processing rpc request", s.log.Debug("processing rpc request",
@ -287,10 +287,10 @@ func (s *Server) handleRequest(req *request.In, sub *subscriber) response.Raw {
res, resErr = handler(s, *reqParams, sub) res, resErr = handler(s, *reqParams, sub)
} }
} }
return s.packResponseToRaw(req, res, resErr) return s.packResponse(req, res, resErr)
} }
func (s *Server) handleWsWrites(ws *websocket.Conn, resChan <-chan response.Raw, subChan <-chan *websocket.PreparedMessage) { func (s *Server) handleWsWrites(ws *websocket.Conn, resChan <-chan response.Abstract, subChan <-chan *websocket.PreparedMessage) {
pingTicker := time.NewTicker(wsPingPeriod) pingTicker := time.NewTicker(wsPingPeriod)
eventloop: eventloop:
for { for {
@ -337,7 +337,7 @@ drainloop:
} }
} }
func (s *Server) handleWsReads(ws *websocket.Conn, resChan chan<- response.Raw, subscr *subscriber) { func (s *Server) handleWsReads(ws *websocket.Conn, resChan chan<- response.Abstract, subscr *subscriber) {
ws.SetReadLimit(wsReadLimit) ws.SetReadLimit(wsReadLimit)
ws.SetReadDeadline(time.Now().Add(wsPongLimit)) ws.SetReadDeadline(time.Now().Add(wsPongLimit))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(wsPongLimit)); return nil }) ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(wsPongLimit)); return nil })
@ -712,7 +712,7 @@ func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
item := s.chain.GetStorageItem(id, key) item := s.chain.GetStorageItem(id, key)
if item == nil { if item == nil {
return nil, nil return "", nil
} }
return hex.EncodeToString(item.Value), nil return hex.EncodeToString(item.Value), nil
@ -1250,8 +1250,8 @@ func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.Erro
return num, nil return num, nil
} }
func (s *Server) packResponseToRaw(r *request.In, result interface{}, respErr *response.Error) response.Raw { func (s *Server) packResponse(r *request.In, result interface{}, respErr *response.Error) response.Abstract {
resp := response.Raw{ resp := response.Abstract{
HeaderAndError: response.HeaderAndError{ HeaderAndError: response.HeaderAndError{
Header: response.Header{ Header: response.Header{
JSONRPC: r.JSONRPC, JSONRPC: r.JSONRPC,
@ -1262,15 +1262,7 @@ func (s *Server) packResponseToRaw(r *request.In, result interface{}, respErr *r
if respErr != nil { if respErr != nil {
resp.Error = respErr resp.Error = respErr
} else { } else {
resJSON, err := json.Marshal(result) resp.Result = result
if err != nil {
s.log.Error("failed to marshal result",
zap.Error(err),
zap.String("method", r.Method))
resp.Error = response.NewInternalServerError("failed to encode result", err)
} else {
resp.Result = resJSON
}
} }
return resp return resp
} }
@ -1292,11 +1284,11 @@ func (s *Server) logRequestError(r *request.In, jsonErr *response.Error) {
// writeHTTPErrorResponse writes an error response to the ResponseWriter. // writeHTTPErrorResponse writes an error response to the ResponseWriter.
func (s *Server) writeHTTPErrorResponse(r *request.In, w http.ResponseWriter, jsonErr *response.Error) { func (s *Server) writeHTTPErrorResponse(r *request.In, w http.ResponseWriter, jsonErr *response.Error) {
resp := s.packResponseToRaw(r, nil, jsonErr) resp := s.packResponse(r, nil, jsonErr)
s.writeHTTPServerResponse(r, w, resp) s.writeHTTPServerResponse(r, w, resp)
} }
func (s *Server) writeHTTPServerResponse(r *request.In, w http.ResponseWriter, resp response.Raw) { func (s *Server) writeHTTPServerResponse(r *request.In, w http.ResponseWriter, resp response.Abstract) {
// Errors can happen in many places and we can only catch ALL of them here. // Errors can happen in many places and we can only catch ALL of them here.
if resp.Error != nil { if resp.Error != nil {
s.logRequestError(r, resp.Error) s.logRequestError(r, resp.Error)