Skip to content

Commit 53a11c6

Browse files
perf: symmetrical pooling for zero-allocation request encoding and response decoding
1 parent 6c643b8 commit 53a11c6

1 file changed

Lines changed: 36 additions & 9 deletions

File tree

github/github.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,12 @@ func WithVersion(version string) RequestOption {
554554
}
555555
}
556556

557+
var requestBufferPool = sync.Pool{
558+
New: func() any {
559+
return new(bytes.Buffer)
560+
},
561+
}
562+
557563
// NewRequest creates an API request. A relative URL can be provided in urlStr,
558564
// in which case it is resolved relative to the BaseURL of the Client.
559565
// Relative URLs should always be specified without a preceding slash. If
@@ -573,18 +579,27 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body any
573579
return nil, err
574580
}
575581

576-
var buf io.ReadWriter
582+
var reqBody io.Reader
577583
if body != nil {
578-
buf = &bytes.Buffer{}
584+
buf := requestBufferPool.Get().(*bytes.Buffer)
579585
enc := json.NewEncoder(buf)
580586
enc.SetEscapeHTML(false)
581587
err := enc.Encode(body)
582588
if err != nil {
589+
buf.Reset()
590+
requestBufferPool.Put(buf)
583591
return nil, err
584592
}
593+
594+
b := make([]byte, buf.Len())
595+
copy(b, buf.Bytes())
596+
reqBody = bytes.NewReader(b)
597+
598+
buf.Reset()
599+
requestBufferPool.Put(buf)
585600
}
586601

587-
req, err := http.NewRequestWithContext(ctx, method, u.String(), buf)
602+
req, err := http.NewRequestWithContext(ctx, method, u.String(), reqBody)
588603
if err != nil {
589604
return nil, err
590605
}
@@ -1114,12 +1129,24 @@ func (c *Client) Do(req *http.Request, v any) (*Response, error) {
11141129
case io.Writer:
11151130
_, err = io.Copy(v, resp.Body)
11161131
default:
1117-
decErr := json.NewDecoder(resp.Body).Decode(v)
1118-
if decErr == io.EOF {
1119-
decErr = nil // ignore EOF errors caused by empty response body
1120-
}
1121-
if decErr != nil {
1122-
err = decErr
1132+
respBuf := requestBufferPool.Get().(*bytes.Buffer)
1133+
defer func() {
1134+
respBuf.Reset()
1135+
requestBufferPool.Put(respBuf)
1136+
}()
1137+
1138+
_, readErr := respBuf.ReadFrom(resp.Body)
1139+
if readErr != nil {
1140+
err = readErr
1141+
} else if respBuf.Len() > 0 {
1142+
b := respBuf.Bytes()
1143+
decErr := json.Unmarshal(b, v)
1144+
if decErr != nil && len(bytes.TrimSpace(b)) == 0 {
1145+
decErr = nil // ignore errors caused by empty response body
1146+
}
1147+
if decErr != nil {
1148+
err = decErr
1149+
}
11231150
}
11241151
}
11251152
return resp, err

0 commit comments

Comments
 (0)