Improve object reuse for grid messages (#18940)

Allow internal types to support a `Recycler` interface, which will allow for sharing of common types across handlers.

This means that all `grid.MSS` (and similar) objects are shared across in a common pool instead of a per-handler pool.

Add internal request reuse of internal types. Add for safe (pointerless) types explicitly.

Only log params for internal types. Doing Sprint(obj) is just a bit too messy.
This commit is contained in:
Klaus Post 2024-02-01 12:41:20 -08:00 committed by GitHub
parent 6440d0fbf3
commit b192bc348c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 256 additions and 76 deletions

View File

@ -152,10 +152,12 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg *ServerSys
return nil return nil
} }
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{})) recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSS())
if err != nil { if err != nil {
return err return err
} }
// We do not need the response after returning.
defer serverVerifyHandler.PutResponse(recvCfg)
return srcCfg.Diff(recvCfg) return srcCfg.Diff(recvCfg)
} }

View File

@ -981,7 +981,7 @@ func (s *peerRESTServer) ListenHandler(ctx context.Context, v *grid.URLValues, o
logger.LogOnceIf(ctx, err, "event: Encode failed") logger.LogOnceIf(ctx, err, "event: Encode failed")
continue continue
} }
out <- grid.NewBytesWith(append(grid.GetByteBuffer()[:0], buf.Bytes()...)) out <- grid.NewBytesWithCopyOf(buf.Bytes())
} }
} }
} }

View File

@ -346,6 +346,7 @@ func (client *storageRESTClient) StatVol(ctx context.Context, volume string) (vo
return vol, toStorageErr(err) return vol, toStorageErr(err)
} }
vol = *v vol = *v
// Performs shallow copy, so we can reuse.
storageStatVolHandler.PutResponse(v) storageStatVolHandler.PutResponse(v)
return vol, nil return vol, nil
} }
@ -455,6 +456,7 @@ func (client *storageRESTClient) RenameData(ctx context.Context, srcVolume, srcP
if err != nil { if err != nil {
return 0, toStorageErr(err) return 0, toStorageErr(err)
} }
defer storageRenameDataHandler.PutResponse(resp)
return resp.Signature, nil return resp.Signature, nil
} }

View File

@ -201,7 +201,7 @@ func (s *storageRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request
// DiskInfo types. // DiskInfo types.
// DiskInfo.Metrics elements are shared, so we cannot reuse. // DiskInfo.Metrics elements are shared, so we cannot reuse.
var storageDiskInfoHandler = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, var storageDiskInfoHandler = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} },
func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse() func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
// DiskInfoHandler - returns disk info. // DiskInfoHandler - returns disk info.
func (s *storageRESTServer) DiskInfoHandler(opts *DiskInfoOptions) (*DiskInfo, *grid.RemoteErr) { func (s *storageRESTServer) DiskInfoHandler(opts *DiskInfoOptions) (*DiskInfo, *grid.RemoteErr) {
@ -495,7 +495,7 @@ func (s *storageRESTServer) CheckPartsHandler(p *CheckPartsHandlerParams) (grid.
var storageReadAllHandler = grid.NewSingleHandler[*ReadAllHandlerParams, *grid.Bytes](grid.HandlerReadAll, func() *ReadAllHandlerParams { var storageReadAllHandler = grid.NewSingleHandler[*ReadAllHandlerParams, *grid.Bytes](grid.HandlerReadAll, func() *ReadAllHandlerParams {
return &ReadAllHandlerParams{} return &ReadAllHandlerParams{}
}, grid.NewBytes) }, grid.NewBytes).AllowCallRequestPool(true)
// ReadAllHandler - read all the contents of a file. // ReadAllHandler - read all the contents of a file.
func (s *storageRESTServer) ReadAllHandler(p *ReadAllHandlerParams) (*grid.Bytes, *grid.RemoteErr) { func (s *storageRESTServer) ReadAllHandler(p *ReadAllHandlerParams) (*grid.Bytes, *grid.RemoteErr) {
@ -673,7 +673,7 @@ func (s *storageRESTServer) ListDirHandler(w http.ResponseWriter, r *http.Reques
var storageDeleteFileHandler = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { var storageDeleteFileHandler = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams {
return &DeleteFileHandlerParams{} return &DeleteFileHandlerParams{}
}, grid.NewNoPayload) }, grid.NewNoPayload).AllowCallRequestPool(true)
// DeleteFileHandler - delete a file. // DeleteFileHandler - delete a file.
func (s *storageRESTServer) DeleteFileHandler(p *DeleteFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) { func (s *storageRESTServer) DeleteFileHandler(p *DeleteFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
@ -751,7 +751,7 @@ func (s *storageRESTServer) RenameDataHandler(p *RenameDataHandlerParams) (*Rena
var storageRenameFileHandler = grid.NewSingleHandler[*RenameFileHandlerParams, grid.NoPayload](grid.HandlerRenameFile, func() *RenameFileHandlerParams { var storageRenameFileHandler = grid.NewSingleHandler[*RenameFileHandlerParams, grid.NoPayload](grid.HandlerRenameFile, func() *RenameFileHandlerParams {
return &RenameFileHandlerParams{} return &RenameFileHandlerParams{}
}, grid.NewNoPayload) }, grid.NewNoPayload).AllowCallRequestPool(true)
// RenameFileHandler - rename a file from source to destination // RenameFileHandler - rename a file from source to destination
func (s *storageRESTServer) RenameFileHandler(p *RenameFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) { func (s *storageRESTServer) RenameFileHandler(p *RenameFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) {

View File

@ -231,6 +231,7 @@ func TestSingleRoundtripGenerics(t *testing.T) {
t.Errorf("want %q, got %q", testPayload, resp.OrgString) t.Errorf("want %q, got %q", testPayload, resp.OrgString)
} }
t.Log("Roundtrip:", time.Since(start)) t.Log("Roundtrip:", time.Since(start))
h1.PutResponse(resp)
start = time.Now() start = time.Now()
resp, err = h2.Call(context.Background(), remoteConn, &testRequest{Num: 1, String: testPayload}) resp, err = h2.Call(context.Background(), remoteConn, &testRequest{Num: 1, String: testPayload})
@ -241,9 +242,74 @@ func TestSingleRoundtripGenerics(t *testing.T) {
if resp != nil { if resp != nil {
t.Errorf("want nil, got %q", resp) t.Errorf("want nil, got %q", resp)
} }
h2.PutResponse(resp)
t.Log("Roundtrip:", time.Since(start)) t.Log("Roundtrip:", time.Since(start))
} }
func TestSingleRoundtripGenericsRecycle(t *testing.T) {
defer testlogger.T.SetLogTB(t)()
errFatal := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
grid, err := SetupTestGrid(2)
errFatal(err)
remoteHost := grid.Hosts[1]
local := grid.Managers[0]
remote := grid.Managers[1]
// 1: Echo
h1 := NewSingleHandler[*MSS, *MSS](handlerTest, NewMSS, NewMSS)
// Handles incoming requests, returns a response
handler1 := func(req *MSS) (resp *MSS, err *RemoteErr) {
resp = h1.NewResponse()
for k, v := range *req {
(*resp)[k] = v
}
return resp, nil
}
// Return error
h2 := NewSingleHandler[*MSS, *MSS](handlerTest2, NewMSS, NewMSS)
handler2 := func(req *MSS) (resp *MSS, err *RemoteErr) {
defer req.Recycle()
r := RemoteErr(req.Get("err"))
return nil, &r
}
errFatal(h1.Register(local, handler1))
errFatal(h2.Register(local, handler2))
errFatal(h1.Register(remote, handler1))
errFatal(h2.Register(remote, handler2))
// local to remote connection
remoteConn := local.Connection(remoteHost)
const testPayload = "Hello Grid World!"
start := time.Now()
req := NewMSSWith(map[string]string{"test": testPayload})
resp, err := h1.Call(context.Background(), remoteConn, req)
errFatal(err)
if resp.Get("test") != testPayload {
t.Errorf("want %q, got %q", testPayload, resp.Get("test"))
}
t.Log("Roundtrip:", time.Since(start))
h1.PutResponse(resp)
start = time.Now()
resp, err = h2.Call(context.Background(), remoteConn, NewMSSWith(map[string]string{"err": testPayload}))
t.Log("Roundtrip:", time.Since(start))
if err != RemoteErr(testPayload) {
t.Errorf("want error %v(%T), got %v(%T)", RemoteErr(testPayload), RemoteErr(testPayload), err, err)
}
if resp != nil {
t.Errorf("want nil, got %q", resp)
}
t.Log("Roundtrip:", time.Since(start))
h2.PutResponse(resp)
}
func TestStreamSuite(t *testing.T) { func TestStreamSuite(t *testing.T) {
defer testlogger.T.SetErrorTB(t)() defer testlogger.T.SetErrorTB(t)()
errFatal := func(err error) { errFatal := func(err error) {

View File

@ -335,14 +335,36 @@ type RoundTripper interface {
// SingleHandler is a type safe handler for single roundtrip requests. // SingleHandler is a type safe handler for single roundtrip requests.
type SingleHandler[Req, Resp RoundTripper] struct { type SingleHandler[Req, Resp RoundTripper] struct {
id HandlerID id HandlerID
sharedResponse bool sharedResp bool
callReuseReq bool
reqPool sync.Pool newReq func() Req
respPool sync.Pool newResp func() Resp
nilReq Req recycleReq func(Req)
nilResp Resp recycleResp func(Resp)
}
func recycleFunc[RT RoundTripper](newRT func() RT) (newFn func() RT, recycle func(r RT)) {
rAny := any(newRT())
if rc, ok := rAny.(Recycler); ok {
return newRT, func(r RT) {
rc.Recycle()
}
}
pool := sync.Pool{
New: func() interface{} {
return newRT()
},
}
var rZero RT
return func() RT { return pool.Get().(RT) },
func(r RT) {
if r != rZero {
pool.Put(r)
}
}
} }
// NewSingleHandler creates a typed handler that can provide Marshal/Unmarshal. // NewSingleHandler creates a typed handler that can provide Marshal/Unmarshal.
@ -350,27 +372,34 @@ type SingleHandler[Req, Resp RoundTripper] struct {
// Use Call to initiate a clientside call. // Use Call to initiate a clientside call.
func NewSingleHandler[Req, Resp RoundTripper](h HandlerID, newReq func() Req, newResp func() Resp) *SingleHandler[Req, Resp] { func NewSingleHandler[Req, Resp RoundTripper](h HandlerID, newReq func() Req, newResp func() Resp) *SingleHandler[Req, Resp] {
s := SingleHandler[Req, Resp]{id: h} s := SingleHandler[Req, Resp]{id: h}
s.reqPool.New = func() interface{} { s.newReq, s.recycleReq = recycleFunc[Req](newReq)
return newReq() s.newResp, s.recycleResp = recycleFunc[Resp](newResp)
} if _, ok := any(newReq()).(Recycler); ok {
s.respPool.New = func() interface{} { s.callReuseReq = true
return newResp()
} }
return &s return &s
} }
// PutResponse will accept a response for reuse. // PutResponse will accept a response for reuse.
// These should be returned by the caller. // This can be used by a caller to recycle a response after receiving it from a Call.
func (h *SingleHandler[Req, Resp]) PutResponse(r Resp) { func (h *SingleHandler[Req, Resp]) PutResponse(r Resp) {
if r != h.nilResp { h.recycleResp(r)
h.respPool.Put(r)
}
} }
// WithSharedResponse indicates it is unsafe to reuse the response. // AllowCallRequestPool indicates it is safe to reuse the request
// on the client side, meaning the request is recycled/pooled when a request is sent.
// CAREFUL: This should only be used when there are no pointers, slices that aren't freshly constructed.
func (h *SingleHandler[Req, Resp]) AllowCallRequestPool(b bool) *SingleHandler[Req, Resp] {
h.callReuseReq = b
return h
}
// WithSharedResponse indicates it is unsafe to reuse the response
// when it has been returned on a handler.
// This will disable automatic response recycling/pooling.
// Typically this is used when the response sharing part of its data structure. // Typically this is used when the response sharing part of its data structure.
func (h *SingleHandler[Req, Resp]) WithSharedResponse() *SingleHandler[Req, Resp] { func (h *SingleHandler[Req, Resp]) WithSharedResponse() *SingleHandler[Req, Resp] {
h.sharedResponse = true h.sharedResp = true
return h return h
} }
@ -378,26 +407,25 @@ func (h *SingleHandler[Req, Resp]) WithSharedResponse() *SingleHandler[Req, Resp
// Handlers can use this to create a reusable response. // Handlers can use this to create a reusable response.
// The response may be reused, so caller should clear any fields. // The response may be reused, so caller should clear any fields.
func (h *SingleHandler[Req, Resp]) NewResponse() Resp { func (h *SingleHandler[Req, Resp]) NewResponse() Resp {
return h.respPool.Get().(Resp) return h.newResp()
}
// putRequest will accept a request for reuse.
// This is not exported, since it shouldn't be needed.
func (h *SingleHandler[Req, Resp]) putRequest(r Req) {
if r != h.nilReq {
h.reqPool.Put(r)
}
} }
// NewRequest creates a new request. // NewRequest creates a new request.
// Handlers can use this to create a reusable request. // Handlers can use this to create a reusable request.
// The request may be reused, so caller should clear any fields. // The request may be reused, so caller should clear any fields.
func (h *SingleHandler[Req, Resp]) NewRequest() Req { func (h *SingleHandler[Req, Resp]) NewRequest() Req {
return h.reqPool.Get().(Req) return h.newReq()
} }
// Register a handler for a Req -> Resp roundtrip. // Register a handler for a Req -> Resp roundtrip.
// Requests are automatically recycled.
func (h *SingleHandler[Req, Resp]) Register(m *Manager, handle func(req Req) (resp Resp, err *RemoteErr), subroute ...string) error { func (h *SingleHandler[Req, Resp]) Register(m *Manager, handle func(req Req) (resp Resp, err *RemoteErr), subroute ...string) error {
if h.newReq == nil {
return errors.New("newReq nil in NewSingleHandler")
}
if h.newResp == nil {
return errors.New("newResp nil in NewSingleHandler")
}
return m.RegisterSingleHandler(h.id, func(payload []byte) ([]byte, *RemoteErr) { return m.RegisterSingleHandler(h.id, func(payload []byte) ([]byte, *RemoteErr) {
req := h.NewRequest() req := h.NewRequest()
_, err := req.UnmarshalMsg(payload) _, err := req.UnmarshalMsg(payload)
@ -407,13 +435,13 @@ func (h *SingleHandler[Req, Resp]) Register(m *Manager, handle func(req Req) (re
return nil, &r return nil, &r
} }
resp, rerr := handle(req) resp, rerr := handle(req)
h.putRequest(req) h.recycleReq(req)
if rerr != nil { if rerr != nil {
PutByteBuffer(payload) PutByteBuffer(payload)
return nil, rerr return nil, rerr
} }
payload, err = resp.MarshalMsg(payload[:0]) payload, err = resp.MarshalMsg(payload[:0])
if !h.sharedResponse { if !h.sharedResp {
h.PutResponse(resp) h.PutResponse(resp)
} }
if err != nil { if err != nil {
@ -438,7 +466,18 @@ func (h *SingleHandler[Req, Resp]) Call(ctx context.Context, c Requester, req Re
if err != nil { if err != nil {
return resp, err return resp, err
} }
ctx = context.WithValue(ctx, TraceParamsKey{}, req) switch any(req).(type) {
case *MSS, *Bytes, *URLValues:
ctx = context.WithValue(ctx, TraceParamsKey{}, req)
case *NoPayload:
default:
ctx = context.WithValue(ctx, TraceParamsKey{}, fmt.Sprintf("type=%T", req))
}
if h.callReuseReq {
defer func() {
h.recycleReq(req)
}()
}
res, err := c.Request(ctx, h.id, payload) res, err := c.Request(ctx, h.id, payload)
PutByteBuffer(payload) PutByteBuffer(payload)
if err != nil { if err != nil {
@ -764,26 +803,3 @@ func (h *StreamTypeHandler[Payload, Req, Resp]) Call(ctx context.Context, c Stre
return &TypedStream[Req, Resp]{responses: stream, newResp: h.NewResponse, Requests: reqT}, nil return &TypedStream[Req, Resp]{responses: stream, newResp: h.NewResponse, Requests: reqT}, nil
} }
// NoPayload is a type that can be used for handlers that do not use a payload.
type NoPayload struct{}
// Msgsize returns 0.
func (p NoPayload) Msgsize() int {
return 0
}
// UnmarshalMsg satisfies the interface, but is a no-op.
func (NoPayload) UnmarshalMsg(bytes []byte) ([]byte, error) {
return bytes, nil
}
// MarshalMsg satisfies the interface, but is a no-op.
func (NoPayload) MarshalMsg(bytes []byte) ([]byte, error) {
return bytes, nil
}
// NewNoPayload returns an empty NoPayload struct.
func NewNoPayload() NoPayload {
return NoPayload{}
}

View File

@ -21,7 +21,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time" "time"
@ -131,22 +130,26 @@ func (c *muxClient) traceRoundtrip(ctx context.Context, t *tracer, h HandlerID,
} }
// If the context contains a TraceParamsKey, add it to the trace path. // If the context contains a TraceParamsKey, add it to the trace path.
v := ctx.Value(TraceParamsKey{}) v := ctx.Value(TraceParamsKey{})
if p, ok := v.(*MSS); ok && p != nil { // Should match SingleHandler.Call checks.
trace.Path += p.ToQuery() switch typed := v.(type) {
trace.HTTP.ReqInfo.Path = trace.Path case *MSS:
} else if p, ok := v.(map[string]string); ok { trace.Path += typed.ToQuery()
m := MSS(p) case map[string]string:
m := MSS(typed)
trace.Path += m.ToQuery() trace.Path += m.ToQuery()
trace.HTTP.ReqInfo.Path = trace.Path case *URLValues:
} else if v != nil { trace.Path += typed.Values().Encode()
// Print exported fields as single request to path. case *NoPayload:
obj := fmt.Sprintf("%+v", v) case *Bytes:
if len(obj) > 1024 { if typed != nil {
obj = obj[:1024] + "..." trace.Path = fmt.Sprintf("%s?bytes=%d", trace.Path, len(*typed))
} }
trace.Path = fmt.Sprintf("%s?req=%s", trace.Path, url.QueryEscape(obj)) case string:
trace.HTTP.ReqInfo.Path = trace.Path trace.Path = fmt.Sprintf("%s?%s", trace.Path, typed)
default:
} }
trace.HTTP.ReqInfo.Path = trace.Path
t.Publisher.Publish(trace) t.Publisher.Publish(trace)
return resp, err return resp, err
} }

View File

@ -27,6 +27,14 @@ import (
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
) )
// Recycler will override the internal reuse in typed handlers.
// When this is supported, the handler will not do internal pooling of objects,
// call Recycle() when the object is no longer needed.
// The recycler should handle nil pointers.
type Recycler interface {
Recycle()
}
// MSS is a map[string]string that can be serialized. // MSS is a map[string]string that can be serialized.
// It is not very efficient, but it is only used for easy parameter passing. // It is not very efficient, but it is only used for easy parameter passing.
type MSS map[string]string type MSS map[string]string
@ -39,6 +47,14 @@ func (m *MSS) Get(key string) string {
return (*m)[key] return (*m)[key]
} }
// Set a key, value pair.
func (m *MSS) Set(key, value string) {
if m == nil {
*m = mssPool.Get().(map[string]string)
}
(*m)[key] = value
}
// UnmarshalMsg deserializes m from the provided byte slice and returns the // UnmarshalMsg deserializes m from the provided byte slice and returns the
// remainder of bytes. // remainder of bytes.
func (m *MSS) UnmarshalMsg(bts []byte) (o []byte, err error) { func (m *MSS) UnmarshalMsg(bts []byte) (o []byte, err error) {
@ -111,7 +127,10 @@ func (m *MSS) Msgsize() int {
// NewMSS returns a new MSS. // NewMSS returns a new MSS.
func NewMSS() *MSS { func NewMSS() *MSS {
m := MSS(make(map[string]string)) m := MSS(mssPool.Get().(map[string]string))
for k := range m {
delete(m, k)
}
return &m return &m
} }
@ -121,6 +140,20 @@ func NewMSSWith(m map[string]string) *MSS {
return &m2 return &m2
} }
var mssPool = sync.Pool{
New: func() interface{} {
return make(map[string]string, 5)
},
}
// Recycle the underlying map.
func (m *MSS) Recycle() {
if m != nil && *m != nil {
mssPool.Put(map[string]string(*m))
*m = nil
}
}
// ToQuery constructs a URL query string from the MSS, including "?" if there are any keys. // ToQuery constructs a URL query string from the MSS, including "?" if there are any keys.
func (m MSS) ToQuery() string { func (m MSS) ToQuery() string {
if len(m) == 0 { if len(m) == 0 {
@ -147,17 +180,36 @@ func (m MSS) ToQuery() string {
} }
// NewBytes returns a new Bytes. // NewBytes returns a new Bytes.
// A slice is preallocated.
func NewBytes() *Bytes { func NewBytes() *Bytes {
b := Bytes(GetByteBuffer()[:0]) b := Bytes(GetByteBuffer()[:0])
return &b return &b
} }
// NewBytesWith returns a new Bytes with the provided content. // NewBytesWith returns a new Bytes with the provided content.
// When sent as a parameter, the caller gives up ownership of the byte slice.
// When returned as response, the handler also gives up ownership of the byte slice.
func NewBytesWith(b []byte) *Bytes { func NewBytesWith(b []byte) *Bytes {
bb := Bytes(b) bb := Bytes(b)
return &bb return &bb
} }
// NewBytesWithCopyOf returns a new byte slice with a copy of the provided content.
func NewBytesWithCopyOf(b []byte) *Bytes {
if b == nil {
bb := Bytes(nil)
return &bb
}
if len(b) < maxBufferSize {
bb := NewBytes()
*bb = append(*bb, b...)
return bb
}
bb := Bytes(make([]byte, len(b)))
copy(bb, b)
return &bb
}
// Bytes provides a byte slice that can be serialized. // Bytes provides a byte slice that can be serialized.
type Bytes []byte type Bytes []byte
@ -168,6 +220,9 @@ func (b *Bytes) UnmarshalMsg(bytes []byte) ([]byte, error) {
return bytes, errors.New("Bytes: UnmarshalMsg on nil pointer") return bytes, errors.New("Bytes: UnmarshalMsg on nil pointer")
} }
if bytes, err := msgp.ReadNilBytes(bytes); err == nil { if bytes, err := msgp.ReadNilBytes(bytes); err == nil {
if *b != nil {
PutByteBuffer(*b)
}
*b = nil *b = nil
return bytes, nil return bytes, nil
} }
@ -179,7 +234,16 @@ func (b *Bytes) UnmarshalMsg(bytes []byte) ([]byte, error) {
*b = (*b)[:len(val)] *b = (*b)[:len(val)]
copy(*b, val) copy(*b, val)
} else { } else {
*b = append(make([]byte, 0, len(val)), val...) if cap(*b) == 0 && len(val) <= maxBufferSize {
PutByteBuffer(*b)
*b = GetByteBuffer()[:0]
} else {
PutByteBuffer(*b)
*b = make([]byte, 0, len(val))
}
in := *b
in = append(in[:0], val...)
*b = in
} }
return bytes, nil return bytes, nil
} }
@ -202,7 +266,8 @@ func (b *Bytes) Msgsize() int {
// Recycle puts the Bytes back into the pool. // Recycle puts the Bytes back into the pool.
func (b *Bytes) Recycle() { func (b *Bytes) Recycle() {
if *b != nil { if b != nil && *b != nil {
*b = (*b)[:0]
PutByteBuffer(*b) PutByteBuffer(*b)
*b = nil *b = nil
} }
@ -329,3 +394,29 @@ func (u URLValues) Msgsize() (s int) {
} }
return return
} }
// NoPayload is a type that can be used for handlers that do not use a payload.
type NoPayload struct{}
// Msgsize returns 0.
func (p NoPayload) Msgsize() int {
return 0
}
// UnmarshalMsg satisfies the interface, but is a no-op.
func (NoPayload) UnmarshalMsg(bytes []byte) ([]byte, error) {
return bytes, nil
}
// MarshalMsg satisfies the interface, but is a no-op.
func (NoPayload) MarshalMsg(bytes []byte) ([]byte, error) {
return bytes, nil
}
// NewNoPayload returns an empty NoPayload struct.
func NewNoPayload() NoPayload {
return NoPayload{}
}
// Recycle is a no-op.
func (NoPayload) Recycle() {}