This is an automated email from the ASF dual-hosted git repository.

AlexStocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new 5f54b6a8c feat(triple): cut per-message marshal allocations with a 
pooled MarshalAppend codec path (#3718)
5f54b6a8c is described below

commit 5f54b6a8cc9702abb320aa324784176689878ed0
Author: Li Zining <[email protected]>
AuthorDate: Sun Sep 6 17:01:02 2026 +0800

    feat(triple): cut per-message marshal allocations with a pooled 
MarshalAppend codec path (#3718)
    
    * feat(triple): add marshalAppender optional codec interface
    
    Today the proto codec marshals via proto.Marshal, which always builds a 
fresh
    []byte from scratch, so every unary call allocates the whole payload even
    though the request buffer pool already holds reusable capacity. The 
marshaler
    should instead be able to serialize into a caller-provided buffer.
    
    This commit adds marshalAppender, an optional Codec extension whose
    MarshalAppend serializes the message into a caller-supplied dst slice.
    protoBinaryCodec implements it by delegating to
    proto.MarshalOptions{}.MarshalAppend, and tripleServerCodecSession 
implements
    it too, wrapping the generic response before forwarding when needed, so both
    IDL and non-IDL server calls expose the new capability. Codecs without the
    extension (hessian2, json, msgpack, third-party codecs) are untouched and
    keep their exact previous behavior, so the change is purely additive.
    
    Signed-off-by: lizining <[email protected]>
    
    * feat(triple): add pooled marshal pipeline via marshalToPool
    
    Adds marshalToPool, the shared borrow-marshal-adopt dance that the envelope
    writer and the unary marshaler both need. It borrows a buffer from the pool,
    lets the codec's MarshalAppend fill its spare capacity, and when 
MarshalAppend
    had to grow the backing array, swaps the larger array back into the pooled
    buffer so the capacity is recycled instead of dropped. On the happy path
    nothing is allocated and the buffer is handed to the caller ready to be
    wrapped or written.
    
    Signed-off-by: lizining <[email protected]>
    
    * feat(triple): use marshalAppend fast path in envelope writer
    
    The envelope writer used to marshal with codec.Marshal and then copy the
    freshly allocated slice into the outgoing envelope, so every message paid 
one
    allocation plus an extra copy. It now probes the codec for marshalAppender
    and, when present, runs marshalAndWrite: marshalToPool fills a pooled buffer
    in place and the bytes are wrapped straight into the envelope.
    
    The previous logic is kept as marshalWithFallback and serves codecs without
    the extension, so the produced bytes stay identical on both paths.
    
    Signed-off-by: lizining <[email protected]>
    
    * feat(triple): add marshalAppend fast path and shared compression tail
    
    tripleUnaryMarshaler.Marshal used to serialize every unary payload with
    codec.Marshal, which allocates the whole []byte from scratch, and then ran 
the
    compression and write tail inline at the end of the function.
    
    Marshal now probes the codec for marshalAppender: capable codecs take the 
new
    marshalAndWrite path, which marshals into a pooled buffer via marshalToPool 
so
    the common case allocates nothing, while codecs without the extension keep 
the
    old path. To keep the two paths from ever drifting, the compression tail was
    extracted from Marshal into one shared compressAndWrite that both paths 
call,
    so the threshold check, pooled gzip, sendMaxBytes enforcement, compression
    header and final write always behave the same.
    
    Signed-off-by: lizining <[email protected]>
    
    * test(triple): add marshal fast-path A/B benchmarks
    
    Adds A/B benchmarks that run the pooled MarshalAppend fast path against the
    plain codec.Marshal slow path, for both the unary marshaler and the envelope
    writer, with and without gzip. The fast path is expected to allocate nothing
    per op while the slow-path benches keep measuring the old baseline, so a
    regression in the fast path shows up immediately as an allocation or
    throughput change in the bench output.
    
    Signed-off-by: lizining <[email protected]>
    
    * test(triple): add regression tests for pooled marshal fast path
    
    Adds nine TestMarshalPerf* regression cases that lock the pooled marshal
    behavior in place so the fast and slow paths cannot silently diverge.
    WireParity asserts both paths emit byte-identical bodies and headers for 
every
    payload size, compressed or not; PoolInvariants and LargeBufferDropped check
    that pooled buffers come back clean and oversized ones are released;
    BackupCodecFallback, TypeGuard and ErrorGuard keep the previous fallback,
    type-guard and error semantics; CompressionAndMaxBytes and ConcurrentSend
    verify compression, sendMaxBytes and pool safety under concurrency; and
    ServerSessionFastPath drives tripleServerCodecSession through the fast path
    end to end.
    
    Signed-off-by: lizining <[email protected]>
    
    * style(triple): modernize regression test syntax for CI fmt check
    
    * style(triple): apply modernize and gofmt to marshal perf regression test
    
    Signed-off-by: lizining <[email protected]>
    
    * style(triple): align test comments with repo style and clarify semantics
    
    Signed-off-by: lizining <[email protected]>
    
    * test(triple): strengthen concurrent marshal regression tests
    
    Replace the concurrent send test's length-only assertion with per-frame
    content verification: each goroutine marshals globally unique messages and
    the output is decoded frame by frame, so cross-goroutine buffer reuse that
    corrupts payloads fails even without -race.
    
    Add a start gate so the shared buffer pool is actually contended, plus a
    fastPathProbeCodec-based test asserting envelopeWriter.Marshal takes the
    MarshalAppend fast path. Clarify that syncBuffer must not be shared by
    concurrent writers.
    
    Signed-off-by: lizining <[email protected]>
    
    ---------
    
    Signed-off-by: lizining <[email protected]>
---
 protocol/triple/triple_protocol/buffer_pool.go     |  26 +-
 protocol/triple/triple_protocol/codec.go           |  60 +-
 protocol/triple/triple_protocol/envelope.go        |  36 +-
 .../triple_protocol/marshal_perf_bench_test.go     | 169 ++++
 .../marshal_perf_regression_test.go                | 937 +++++++++++++++++++++
 protocol/triple/triple_protocol/protocol_triple.go |  36 +-
 6 files changed, 1249 insertions(+), 15 deletions(-)

diff --git a/protocol/triple/triple_protocol/buffer_pool.go 
b/protocol/triple/triple_protocol/buffer_pool.go
index 51dadb893..d34948b1b 100644
--- a/protocol/triple/triple_protocol/buffer_pool.go
+++ b/protocol/triple/triple_protocol/buffer_pool.go
@@ -24,7 +24,7 @@ import (
 
 const (
        initialBufferSize    = 512
-       maxRecycleBufferSize = 8 * 1024 * 1024 // if >8MiB, don't hold onto a 
buffer
+       maxRecycleBufferSize = 8 * 1024 * 1024 // Don't recycle buffers larger 
than this.
 )
 
 type bufferPool struct {
@@ -55,3 +55,27 @@ func (b *bufferPool) Put(buffer *bytes.Buffer) {
        buffer.Reset()
        b.Pool.Put(buffer)
 }
+
+// marshalToPool serializes message with appender into a *bytes.Buffer drawn
+// from pool and returns it. If the pooled array is too small and the appender
+// grows the slice, the larger array is swapped in so it can be recycled once
+// the caller returns the buffer. On failure the buffer is put back and a
+// CodeInternal error is returned.
+func marshalToPool(pool *bufferPool, appender marshalAppender, message any) 
(*bytes.Buffer, *Error) {
+       buffer := pool.Get()
+       raw, err := appender.MarshalAppend(buffer.Bytes(), message)
+       if err != nil {
+               pool.Put(buffer)
+               return nil, errorf(CodeInternal, "marshal message: %w", err)
+       }
+       if cap(raw) > buffer.Cap() {
+               // MarshalAppend grew the slice: swap the larger array in so it 
can be
+               // recycled next time.
+               *buffer = *bytes.NewBuffer(raw)
+       } else {
+               // No reallocation occurred; adopt the bytes the appender 
already wrote
+               // into the pooled array.
+               buffer.Write(raw)
+       }
+       return buffer, nil
+}
diff --git a/protocol/triple/triple_protocol/codec.go 
b/protocol/triple/triple_protocol/codec.go
index 8665b7c5c..b3ca089ff 100644
--- a/protocol/triple/triple_protocol/codec.go
+++ b/protocol/triple/triple_protocol/codec.go
@@ -98,6 +98,17 @@ type stableCodec interface {
        IsBinary() bool
 }
 
+// marshalAppender is an extension to Codec for serializing into a 
caller-provided
+// buffer. Codecs that implement it can serialize with zero allocation when the
+// buffer's capacity is sufficient (protobuf fast path); otherwise the appender
+// itself grows the buffer. MarshalAppend must produce output byte-identical to
+// Marshal for the same message — the extension only changes where the bytes 
are
+// written. Codecs that do not implement it are unaffected.
+type marshalAppender interface {
+       // MarshalAppend marshals the given message, appending the result to 
dst.
+       MarshalAppend(dst []byte, message any) ([]byte, error)
+}
+
 // protoBinaryCodec handles standard protobuf binary serialization for IDL
 // calls. Non-IDL (Java Dubbo Triple generic call) wrapper handling on the
 // server side is handled by tripleServerCodecSession, which delegates to
@@ -116,6 +127,14 @@ func (c *protoBinaryCodec) Marshal(message any) ([]byte, 
error) {
        return proto.Marshal(protoMessage)
 }
 
+func (c *protoBinaryCodec) MarshalAppend(dst []byte, message any) ([]byte, 
error) {
+       protoMessage, ok := message.(proto.Message)
+       if !ok {
+               return nil, errNotProto(message)
+       }
+       return proto.MarshalOptions{}.MarshalAppend(dst, protoMessage)
+}
+
 func (c *protoBinaryCodec) Unmarshal(data []byte, message any) error {
        protoMessage, ok := message.(proto.Message)
        if !ok {
@@ -636,6 +655,43 @@ func (s *tripleServerCodecSession) Marshal(message any) 
([]byte, error) {
        }
        // Non-IDL: wrap the response in a TripleResponseWrapper whose Data is
        // serialized with the inner codec resolved from the request's 
SerializeType.
+       wrapper, err := s.responseWrapper(message)
+       if err != nil {
+               return nil, err
+       }
+       return proto.Marshal(wrapper)
+}
+
+// MarshalAppend extends Marshal for the marshalAppender fast path. The IDL
+// proto leg forwards to the delegate's appender (zero allocation when the
+// pooled buffer's cap is sufficient); the Non-IDL leg still serializes the
+// inner payload with the inner codec, but appends the outer
+// TripleResponseWrapper into the caller-provided buffer instead of allocating
+// a fresh slice.
+func (s *tripleServerCodecSession) MarshalAppend(dst []byte, message any) 
([]byte, error) {
+       if pm, isProto := message.(proto.Message); isProto {
+               if appender, ok := s.delegate.(marshalAppender); ok {
+                       return appender.MarshalAppend(dst, pm)
+               }
+               // A custom proto codec without the appender extension must 
still
+               // round-trip; fall back to a plain marshal plus append. The 
output is
+               // byte-for-byte what Marshal would produce.
+               raw, err := s.delegate.Marshal(pm)
+               if err != nil {
+                       return nil, err
+               }
+               return append(dst, raw...), nil
+       }
+       wrapper, err := s.responseWrapper(message)
+       if err != nil {
+               return nil, err
+       }
+       return proto.MarshalOptions{}.MarshalAppend(dst, wrapper)
+}
+
+// responseWrapper builds the TripleResponseWrapper for a Non-IDL response,
+// resolving the inner codec from the SerializeType captured by Unmarshal.
+func (s *tripleServerCodecSession) responseWrapper(message any) 
(*interoperability.TripleResponseWrapper, error) {
        inner, err := resolveInnerCodec(s.serializeType)
        if err != nil {
                return nil, fmt.Errorf("marshal triple wrapper response: %w", 
err)
@@ -667,10 +723,10 @@ func (s *tripleServerCodecSession) Marshal(message any) 
([]byte, error) {
        }
        // Use inner.Name() instead of s.serializeType so that an absent 
SerializeType
        // (defaulted to hessian2 by resolveInnerCodec) is normalized on the 
wire.
-       return proto.Marshal(&interoperability.TripleResponseWrapper{
+       return &interoperability.TripleResponseWrapper{
                SerializeType: inner.Name(),
                Data:          data,
-       })
+       }, nil
 }
 
 // unmarshalWrapperRequestArgs decodes the inner args of a TripleRequestWrapper
diff --git a/protocol/triple/triple_protocol/envelope.go 
b/protocol/triple/triple_protocol/envelope.go
index a139092d4..228065202 100644
--- a/protocol/triple/triple_protocol/envelope.go
+++ b/protocol/triple/triple_protocol/envelope.go
@@ -76,15 +76,37 @@ func (w *envelopeWriter) Marshal(message any) *Error {
                }
                return nil
        }
-       raw, err := w.codec.Marshal(message)
-       if err != nil {
-               if w.backupCodec != nil && w.codec.Name() != 
w.backupCodec.Name() {
-                       logger.Debugf("[Triple][Codec] failed to marshal 
message with primary codec %s, trying fallback codec %s", w.codec.Name(), 
w.backupCodec.Name())
-                       raw, err = w.backupCodec.Marshal(message)
-               }
+       // Fast path: if the codec can append to a caller-provided buffer, 
marshal
+       // directly into a pooled buffer to avoid the per-request allocation.
+       if appender, ok := w.codec.(marshalAppender); ok {
+               buffer, err := marshalToPool(w.bufferPool, appender, message)
                if err != nil {
-                       return errorf(CodeInternal, "marshal message: %w", err)
+                       // Preserve the backup-codec fallback semantics of the 
slow path.
+                       // Only a marshal failure may fall back; write failures 
(I/O,
+                       // compression, or the sendMaxBytes limit) are terminal 
and must
+                       // never retry through the backup codec.
+                       if w.backupCodec != nil && w.codec.Name() != 
w.backupCodec.Name() {
+                               logger.Debugf("[Triple][Codec] failed to 
marshal message with primary codec %s, trying fallback codec %s", 
w.codec.Name(), w.backupCodec.Name())
+                               return w.marshalWithFallback(w.backupCodec, 
message)
+                       }
+                       return err
                }
+               defer w.bufferPool.Put(buffer)
+               return w.Write(&envelope{Data: buffer})
+       }
+       return w.marshalWithFallback(w.codec, message)
+}
+
+// marshalWithFallback is the slow path: Marshal, falling back to the backup
+// codec on error, then wrap the result in a fresh buffer.
+func (w *envelopeWriter) marshalWithFallback(codec Codec, message any) *Error {
+       raw, err := codec.Marshal(message)
+       if err != nil && w.backupCodec != nil && codec.Name() != 
w.backupCodec.Name() {
+               logger.Debugf("[Triple][Codec] failed to marshal message with 
primary codec %s, trying fallback codec %s", codec.Name(), w.backupCodec.Name())
+               raw, err = w.backupCodec.Marshal(message)
+       }
+       if err != nil {
+               return errorf(CodeInternal, "marshal message: %w", err)
        }
        // We can't avoid allocating the byte slice, so we may as well reuse it 
once
        // we're done with it.
diff --git a/protocol/triple/triple_protocol/marshal_perf_bench_test.go 
b/protocol/triple/triple_protocol/marshal_perf_bench_test.go
new file mode 100644
index 000000000..e5f3efac5
--- /dev/null
+++ b/protocol/triple/triple_protocol/marshal_perf_bench_test.go
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "compress/gzip"
+       "fmt"
+       "io"
+       "net/http"
+       "strings"
+       "testing"
+)
+
+import (
+       pingv1 
"dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol/internal/gen/proto/connect/ping/v1"
+)
+
+// noAppenderCodec embeds a Codec without the marshalAppender extension, so
+// the marshaler's appender type assertion fails and marshaling takes the
+// codec.Marshal slow path — the baseline that the fast path is measured
+// against.
+type noAppenderCodec struct{ Codec }
+
+// marshalPerfPayloadSizes covers fixed-overhead-dominated small messages
+// through bandwidth-dominated large messages.
+var marshalPerfPayloadSizes = []int{128, 1024, 16 * 1024, 1024 * 1024}
+
+func marshalPerfSizeLabel(size int) string {
+       if size == 1024*1024 {
+               return "1MiB"
+       }
+       return fmt.Sprintf("%dB", size)
+}
+
+func newMarshalPerfMessage(size int) *pingv1.PingRequest {
+       return &pingv1.PingRequest{Text: strings.Repeat("a", size)}
+}
+
+// benchMarshalConfig selects which marshal branches a benchmark drives: gzip
+// compression and the sendMaxBytes limit checks.
+type benchMarshalConfig struct {
+       compress     bool
+       sendMaxBytes int
+}
+
+// newBenchGzipPool returns a gzip compression pool for benchmarks. The
+// initial io.Discard sink is replaced by the compressionPool with Reset on
+// each use.
+func newBenchGzipPool() *compressionPool {
+       return newCompressionPool(
+               func() Decompressor { return &gzip.Reader{} },
+               func() Compressor { return gzip.NewWriter(io.Discard) },
+       )
+}
+
+// benchTripleUnaryMarshaler drives tripleUnaryMarshaler.Marshal with the given
+// codec. protoBinaryCodec exercises the MarshalAppend fast path;
+// noAppenderCodec exercises the codec.Marshal slow path.
+func benchTripleUnaryMarshaler(b *testing.B, codec Codec, message 
*pingv1.PingRequest, cfg benchMarshalConfig) {
+       b.Helper()
+       m := &tripleUnaryMarshaler{
+               writer:       io.Discard,
+               codec:        codec,
+               bufferPool:   newBufferPool(),
+               sendMaxBytes: cfg.sendMaxBytes,
+       }
+       if cfg.compress {
+               m.compressionPool = newBenchGzipPool()
+               m.compressionName = compressionGzip
+               // setHeaderCanonical writes into header on the compressed path.
+               m.header = make(http.Header)
+       }
+       b.ReportAllocs()
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               if err := m.Marshal(message); err != nil {
+                       b.Fatal(err)
+               }
+       }
+}
+
+func runUnaryMarshalerBench(b *testing.B, codec Codec, cfg benchMarshalConfig) 
{
+       for _, size := range marshalPerfPayloadSizes {
+               b.Run(marshalPerfSizeLabel(size), func(b *testing.B) {
+                       benchTripleUnaryMarshaler(b, codec, 
newMarshalPerfMessage(size), cfg)
+               })
+       }
+}
+
+func BenchmarkUnaryMarshalerFastPath(b *testing.B) {
+       runUnaryMarshalerBench(b, &protoBinaryCodec{}, benchMarshalConfig{})
+}
+
+func BenchmarkUnaryMarshalerSlowPath(b *testing.B) {
+       runUnaryMarshalerBench(b, &noAppenderCodec{&protoBinaryCodec{}}, 
benchMarshalConfig{})
+}
+
+// BenchmarkUnaryMarshaler*Compressed drive the gzip-compressed fast and slow
+// paths with a generous sendMaxBytes that is exercised but never tripped.
+func BenchmarkUnaryMarshalerFastPathCompressed(b *testing.B) {
+       runUnaryMarshalerBench(b, &protoBinaryCodec{}, 
benchMarshalConfig{compress: true, sendMaxBytes: 1 << 30})
+}
+
+func BenchmarkUnaryMarshalerSlowPathCompressed(b *testing.B) {
+       runUnaryMarshalerBench(b, &noAppenderCodec{&protoBinaryCodec{}}, 
benchMarshalConfig{compress: true, sendMaxBytes: 1 << 30})
+}
+
+// benchEnvelopeWriter drives envelopeWriter.Marshal with the given codec.
+func benchEnvelopeWriter(b *testing.B, codec Codec, message 
*pingv1.PingRequest, cfg benchMarshalConfig) {
+       b.Helper()
+       w := &envelopeWriter{
+               writer:       io.Discard,
+               codec:        codec,
+               bufferPool:   newBufferPool(),
+               sendMaxBytes: cfg.sendMaxBytes,
+       }
+       if cfg.compress {
+               w.compressionPool = newBenchGzipPool()
+       }
+       b.ReportAllocs()
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               if err := w.Marshal(message); err != nil {
+                       b.Fatal(err)
+               }
+       }
+}
+
+func runEnvelopeWriterBench(b *testing.B, codec Codec, cfg benchMarshalConfig) 
{
+       for _, size := range marshalPerfPayloadSizes {
+               b.Run(marshalPerfSizeLabel(size), func(b *testing.B) {
+                       benchEnvelopeWriter(b, codec, 
newMarshalPerfMessage(size), cfg)
+               })
+       }
+}
+
+func BenchmarkEnvelopeWriterFastPath(b *testing.B) {
+       runEnvelopeWriterBench(b, &protoBinaryCodec{}, benchMarshalConfig{})
+}
+
+func BenchmarkEnvelopeWriterSlowPath(b *testing.B) {
+       runEnvelopeWriterBench(b, &noAppenderCodec{&protoBinaryCodec{}}, 
benchMarshalConfig{})
+}
+
+// BenchmarkEnvelopeWriter*Compressed drive the gzip-compressed branch of
+// envelopeWriter.Write (compression into a second pooled buffer plus the
+// compressed-size limit check).
+func BenchmarkEnvelopeWriterFastPathCompressed(b *testing.B) {
+       runEnvelopeWriterBench(b, &protoBinaryCodec{}, 
benchMarshalConfig{compress: true, sendMaxBytes: 1 << 30})
+}
+
+func BenchmarkEnvelopeWriterSlowPathCompressed(b *testing.B) {
+       runEnvelopeWriterBench(b, &noAppenderCodec{&protoBinaryCodec{}}, 
benchMarshalConfig{compress: true, sendMaxBytes: 1 << 30})
+}
diff --git a/protocol/triple/triple_protocol/marshal_perf_regression_test.go 
b/protocol/triple/triple_protocol/marshal_perf_regression_test.go
new file mode 100644
index 000000000..24bb7695e
--- /dev/null
+++ b/protocol/triple/triple_protocol/marshal_perf_regression_test.go
@@ -0,0 +1,937 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "bytes"
+       "compress/gzip"
+       "encoding/binary"
+       "errors"
+       "fmt"
+       "io"
+       "net/http"
+       "strings"
+       "sync"
+       "testing"
+)
+
+import (
+       pingv1 
"dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol/internal/gen/proto/connect/ping/v1"
+)
+
+// syncBuffer is an io.Writer whose individual Write calls are safe for
+// concurrent use. Each goroutine in the concurrency test writes to its own
+// syncBuffer: emitting an envelope frame takes two Write calls (5-byte prefix
+// then payload), so giving each writer its own buffer prevents frames from
+// interleaving.
+type syncBuffer struct {
+       mu  sync.Mutex
+       buf bytes.Buffer
+}
+
+func (s *syncBuffer) Write(p []byte) (int, error) {
+       s.mu.Lock()
+       defer s.mu.Unlock()
+       return s.buf.Write(p)
+}
+
+func (s *syncBuffer) Bytes() []byte {
+       s.mu.Lock()
+       defer s.mu.Unlock()
+       return append([]byte(nil), s.buf.Bytes()...)
+}
+
+func (s *syncBuffer) Len() int {
+       s.mu.Lock()
+       defer s.mu.Unlock()
+       return s.buf.Len()
+}
+
+// regressionCodecList returns the two codecs under test: the fast path
+// (implements marshalAppender) and the slow path (does not).
+func regressionCodecList() []struct {
+       name  string
+       codec Codec
+} {
+       return []struct {
+               name  string
+               codec Codec
+       }{
+               {name: "fast", codec: &protoBinaryCodec{}},
+               {name: "slow", codec: &noAppenderCodec{&protoBinaryCodec{}}},
+       }
+}
+
+func newTestGzipCompressionPool() *compressionPool {
+       return newCompressionPool(
+               func() Decompressor { return &gzip.Reader{} },
+               func() Compressor { return gzip.NewWriter(io.Discard) },
+       )
+}
+
+// newEnvelopeWriterForTest builds an envelopeWriter (gRPC/Triple wire) with
+// optional gzip compression.
+func newEnvelopeWriterForTest(t *testing.T, codec Codec, compress bool, 
compressMinBytes, sendMaxBytes int) (*envelopeWriter, *syncBuffer) {
+       t.Helper()
+       out := &syncBuffer{}
+       w := &envelopeWriter{
+               writer:           out,
+               codec:            codec,
+               compressMinBytes: compressMinBytes,
+               bufferPool:       newBufferPool(),
+               sendMaxBytes:     sendMaxBytes,
+       }
+       if compress {
+               w.compressionPool = newTestGzipCompressionPool()
+       }
+       return w, out
+}
+
+// newTripleMarshalerForTest builds a tripleUnaryMarshaler (Triple HTTP body
+// wire) with optional gzip compression.
+func newTripleMarshalerForTest(
+       t *testing.T,
+       codec Codec,
+       compress bool,
+       compressMinBytes, sendMaxBytes int,
+) (*tripleUnaryMarshaler, *syncBuffer, http.Header) {
+       t.Helper()
+       out := &syncBuffer{}
+       header := make(http.Header)
+       m := &tripleUnaryMarshaler{
+               writer:           out,
+               codec:            codec,
+               compressMinBytes: compressMinBytes,
+               bufferPool:       newBufferPool(),
+               header:           header,
+               sendMaxBytes:     sendMaxBytes,
+       }
+       if compress {
+               m.compressionPool = newTestGzipCompressionPool()
+               m.compressionName = compressionGzip
+       }
+       return m, out, header
+}
+
+func regressionPayloadSizes() []int {
+       mi := 1024 * 1024
+       return []int{0, 1, 511, 512, 513, 1024, 8*mi - 1, 8 * mi, 8*mi + 1}
+}
+
+func regressionSizeLabel(size int) string {
+       switch size {
+       case 0:
+               return "empty"
+       case 1024:
+               return "1KiB"
+       case 8*1024*1024 - 1:
+               return "8MiB-1"
+       case 8 * 1024 * 1024:
+               return "8MiB"
+       case 8*1024*1024 + 1:
+               return "8MiB+1"
+       default:
+               return fmt.Sprintf("%dB", size)
+       }
+}
+
+// envelopePrefix parses the 5-byte gRPC/Triple envelope prefix written by
+// envelopeWriter: byte 0 is flags, bytes 1..4 (wire[1:5]) are the big-endian
+// payload length.
+func envelopePrefix(t *testing.T, wire []byte) (flags byte, length int) {
+       t.Helper()
+       if len(wire) < 5 {
+               t.Fatalf("wire output shorter than envelope prefix: got %d 
bytes", len(wire))
+       }
+       return wire[0], int(binary.BigEndian.Uint32(wire[1:5]))
+}
+
+func wantCode(t *testing.T, err *Error, want Code) {
+       t.Helper()
+       if err == nil {
+               t.Fatalf("expected *Error with code %v, got nil", want)
+       }
+       if got := err.Code(); got != want {
+               t.Fatalf("expected code %v, got %v (message: %v)", want, got, 
err)
+       }
+}
+
+// TestMarshalPerfWireParity verifies that the MarshalAppend fast path and the
+// codec.Marshal slow path emit byte-identical wire output on both wire types.
+func TestMarshalPerfWireParity(t *testing.T) {
+       for _, compress := range []bool{false, true} {
+               t.Run(fmt.Sprintf("compressed=%v", compress), func(t 
*testing.T) {
+                       for _, size := range regressionPayloadSizes() {
+                               t.Run(regressionSizeLabel(size), func(t 
*testing.T) {
+                                       t.Parallel()
+                                       msg := newMarshalPerfMessage(size)
+
+                                       // triple HTTP body wire
+                                       t.Run("triple", func(t *testing.T) {
+                                               var fastWire, slowWire []byte
+                                               var fastHeader, slowHeader 
http.Header
+                                               for _, c := range 
regressionCodecList() {
+                                                       m, out, header := 
newTripleMarshalerForTest(t, c.codec, compress, 0, 0)
+                                                       if err := 
m.Marshal(msg); err != nil {
+                                                               t.Fatalf("%s 
path triple marshal: %v", c.name, err)
+                                                       }
+                                                       switch c.name {
+                                                       case "fast":
+                                                               fastWire, 
fastHeader = out.Bytes(), header
+                                                       case "slow":
+                                                               slowWire, 
slowHeader = out.Bytes(), header
+                                                       }
+                                               }
+                                               if !bytes.Equal(fastWire, 
slowWire) {
+                                                       t.Fatalf("triple wire 
mismatch: fast %d bytes vs slow %d bytes", len(fastWire), len(slowWire))
+                                               }
+                                               if compress {
+                                                       if 
fastHeader.Get(tripleUnaryHeaderCompression) != compressionGzip ||
+                                                               
slowHeader.Get(tripleUnaryHeaderCompression) != compressionGzip {
+                                                               
t.Fatalf("compression header not set identically: fast=%q slow=%q",
+                                                                       
fastHeader.Get(tripleUnaryHeaderCompression), 
slowHeader.Get(tripleUnaryHeaderCompression))
+                                                       }
+                                               }
+                                       })
+
+                                       // gRPC/Triple envelope wire (5-byte 
prefix framing)
+                                       t.Run("envelope", func(t *testing.T) {
+                                               var fastWire, slowWire []byte
+                                               for _, c := range 
regressionCodecList() {
+                                                       w, out := 
newEnvelopeWriterForTest(t, c.codec, compress, 0, 0)
+                                                       if err := 
w.Marshal(msg); err != nil {
+                                                               t.Fatalf("%s 
path envelope marshal: %v", c.name, err)
+                                                       }
+                                                       switch c.name {
+                                                       case "fast":
+                                                               fastWire = 
out.Bytes()
+                                                       case "slow":
+                                                               slowWire = 
out.Bytes()
+                                                       }
+                                               }
+                                               if !bytes.Equal(fastWire, 
slowWire) {
+                                                       t.Fatalf("envelope wire 
mismatch: fast %d bytes vs slow %d bytes", len(fastWire), len(slowWire))
+                                               }
+                                       })
+                               })
+                       }
+               })
+       }
+}
+
+// TestMarshalPerfPoolInvariants verifies buffer pool reuse invariants and
+// envelope framing for empty and nil messages.
+func TestMarshalPerfPoolInvariants(t *testing.T) {
+       t.Parallel()
+
+       t.Run("get returns empty buffer", func(t *testing.T) {
+               pool := newBufferPool()
+               buf := pool.Get()
+               defer pool.Put(buf)
+               if buf.Len() != 0 {
+                       t.Fatalf("bufferPool.Get() returned buffer with 
Len()==%d, want 0", buf.Len())
+               }
+       })
+
+       t.Run("put resets length", func(t *testing.T) {
+               pool := newBufferPool()
+               buf := pool.Get()
+               if _, err := buf.WriteString("junk to reset"); err != nil {
+                       t.Fatal(err)
+               }
+               pool.Put(buf)
+               again := pool.Get()
+               defer pool.Put(again)
+               if again.Len() != 0 {
+                       t.Fatalf("reused buffer not reset: Len()==%d, want 0", 
again.Len())
+               }
+       })
+
+       t.Run("nil message writes nothing", func(t *testing.T) {
+               for _, c := range regressionCodecList() {
+                       w, out := newEnvelopeWriterForTest(t, c.codec, false, 
0, 0)
+                       if err := w.Marshal(nil); err != nil {
+                               t.Fatalf("%s path nil message: %v", c.name, err)
+                       }
+                       if out.Len() != 0 {
+                               t.Fatalf("%s path nil message wrote %d bytes, 
want 0", c.name, out.Len())
+                       }
+               }
+       })
+
+       t.Run("empty proto yields zero-length envelope", func(t *testing.T) {
+               // compressMinBytes=1: an empty payload (0 < 1) must not be 
compressed,
+               // so the envelope is exactly a 5-byte prefix announcing length 
0.
+               for _, c := range regressionCodecList() {
+                       w, out := newEnvelopeWriterForTest(t, c.codec, true, 1, 
0)
+                       msg := &pingv1.PingRequest{Text: ""}
+                       if err := w.Marshal(msg); err != nil {
+                               t.Fatalf("%s path: %v", c.name, err)
+                       }
+                       flags, length := envelopePrefix(t, out.Bytes())
+                       if flags != 0 {
+                               t.Fatalf("%s path: empty payload compressed 
(flags=%#x), want 0", c.name, flags)
+                       }
+                       if length != 0 {
+                               t.Fatalf("%s path: envelope length %d, want 0", 
c.name, length)
+                       }
+                       if out.Len() != 5 {
+                               t.Fatalf("%s path: wire length %d, want exactly 
5 (prefix only)", c.name, out.Len())
+                       }
+               }
+       })
+
+       t.Run("compressMinBytes boundary does not panic", func(t *testing.T) {
+               // The compression decision uses the codec's encoded output 
length: a
+               // threshold equal to the encoded length compresses, one byte 
more does not.
+               codec := &protoBinaryCodec{}
+               for _, size := range []int{511, 512, 513} {
+                       msg := newMarshalPerfMessage(size)
+                       raw, err := codec.Marshal(msg)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       encodedLen := len(raw)
+                       for _, minBytes := range []int{encodedLen, encodedLen + 
1} {
+                               wantCompressed := minBytes <= encodedLen
+                               for _, c := range regressionCodecList() {
+                                       w, out := newEnvelopeWriterForTest(t, 
c.codec, true, minBytes, 0)
+                                       if err := w.Marshal(msg); err != nil {
+                                               t.Fatalf("%s path size %d 
minBytes %d: %v", c.name, size, minBytes, err)
+                                       }
+                                       flags, _ := envelopePrefix(t, 
out.Bytes())
+                                       gotCompressed := 
flags&flagEnvelopeCompressed != 0
+                                       if gotCompressed != wantCompressed {
+                                               t.Fatalf("%s path size %d 
(encoded %d) minBytes %d: compressed=%v, want %v",
+                                                       c.name, size, 
encodedLen, minBytes, gotCompressed, wantCompressed)
+                                       }
+                               }
+                       }
+               }
+       })
+}
+
+// errorCodec implements Codec and marshalAppender, always failing. It forces
+// the envelopeWriter fast path (MarshalAppend) and slow path (Marshal) into
+// their backup-codec fallback branches.
+type errorCodec struct{}
+
+var _ Codec = errorCodec{}
+var _ marshalAppender = errorCodec{}
+
+func (errorCodec) Name() string { return "always-error" }
+
+func (errorCodec) Marshal(any) ([]byte, error) {
+       return nil, errors.New("primary codec marshal failed")
+}
+
+func (errorCodec) MarshalAppend(dst []byte, _ any) ([]byte, error) {
+       return nil, errors.New("primary codec marshal failed")
+}
+
+func (errorCodec) Unmarshal([]byte, any) error {
+       return errors.New("primary codec unmarshal failed")
+}
+
+// TestMarshalPerfBackupCodecFallback verifies the backup-codec fallback on the
+// fast (MarshalAppend) and slow (Marshal) paths.
+func TestMarshalPerfBackupCodecFallback(t *testing.T) {
+       msg := &pingv1.PingRequest{Text: "fallback"}
+       // Reference wire bytes produced by the healthy codec.
+       var refBytes []byte
+       {
+               ref, out := newEnvelopeWriterForTest(t, &protoBinaryCodec{}, 
false, 0, 0)
+               if err := ref.Marshal(msg); err != nil {
+                       t.Fatal(err)
+               }
+               refBytes = out.Bytes()
+       }
+
+       t.Run("primary fails fast path falls back", func(t *testing.T) {
+               // errorCodec implements marshalAppender, so the fast path is 
taken;
+               // on failure it must fall back to the backup codec and produce 
the
+               // same wire bytes as the healthy codec.
+               w, out := newEnvelopeWriterForTest(t, errorCodec{}, false, 0, 0)
+               w.backupCodec = &protoBinaryCodec{}
+               if err := w.Marshal(msg); err != nil {
+                       t.Fatalf("fast fallback: %v", err)
+               }
+               if got := out.Bytes(); !bytes.Equal(got, refBytes) {
+                       t.Fatalf("fast fallback wire mismatch: got %d bytes, 
want %d", len(got), len(refBytes))
+               }
+       })
+
+       t.Run("primary fails slow path falls back", func(t *testing.T) {
+               w, out := newEnvelopeWriterForTest(t, 
&noAppenderCodec{errorCodec{}}, false, 0, 0)
+               w.backupCodec = &protoBinaryCodec{}
+               if err := w.Marshal(msg); err != nil {
+                       t.Fatalf("slow fallback: %v", err)
+               }
+               if got := out.Bytes(); !bytes.Equal(got, refBytes) {
+                       t.Fatalf("slow fallback wire mismatch: got %d bytes, 
want %d", len(got), len(refBytes))
+               }
+       })
+
+       t.Run("write failure with healthy appender does not fall back", func(t 
*testing.T) {
+               // The fast path falls back only on a marshal failure: a write 
error
+               // must surface as-is, without consulting the backup codec or 
retrying.
+               backup := &countingNamedCodec{Codec: &protoBinaryCodec{}, name: 
"hessian2"}
+               out := &failingWriter{}
+               w := &envelopeWriter{
+                       writer:      out,
+                       codec:       &protoBinaryCodec{}, // implements 
marshalAppender
+                       backupCodec: backup,
+                       bufferPool:  newBufferPool(),
+               }
+               wantCode(t, w.Marshal(msg), CodeUnknown)
+               if out.writes != 1 {
+                       t.Fatalf("writer called %d times, want 1 (write failure 
must not retry)", out.writes)
+               }
+               if backup.marshalCalls != 0 {
+                       t.Fatalf("backup codec Marshal called %d times on a 
write failure, want 0", backup.marshalCalls)
+               }
+       })
+
+       t.Run("same name does not double fallback", func(t *testing.T) {
+               // A backup sharing the codec's name must not be consulted, so 
the error
+               // surfaces on both the fast and slow legs.
+               legs := []struct {
+                       name  string
+                       codec Codec
+               }{
+                       {name: "fast", codec: errorCodec{}},
+                       {name: "slow", codec: &noAppenderCodec{errorCodec{}}},
+               }
+               for _, leg := range legs {
+                       w, _ := newEnvelopeWriterForTest(t, leg.codec, false, 
0, 0)
+                       w.backupCodec = &failingNamedCodec{name: "always-error"}
+                       if err := w.Marshal(msg); err == nil {
+                               t.Fatalf("%s path: expected error when backup 
shares codec name, got nil", leg.name)
+                       } else if asErr, ok := asError(err); ok && asErr.Code() 
!= CodeInternal {
+                               t.Fatalf("%s path: expected CodeInternal, got 
%v", leg.name, asErr.Code())
+                       }
+               }
+       })
+
+       t.Run("nil backup returns internal error", func(t *testing.T) {
+               for _, c := range regressionCodecList() {
+                       w, _ := newEnvelopeWriterForTest(t, errorCodec{}, 
false, 0, 0)
+                       if c.name == "slow" {
+                               w.codec = &noAppenderCodec{errorCodec{}}
+                       }
+                       w.backupCodec = nil
+                       err := w.Marshal(msg)
+                       if err == nil {
+                               t.Fatalf("%s path: expected error with nil 
backup, got nil", c.name)
+                       }
+                       wantCode(t, err, CodeInternal)
+               }
+       })
+}
+
+// failingNamedCodec lets tests control the Name() seen by the fallback check.
+type failingNamedCodec struct {
+       errorCodec
+       name string
+}
+
+func (c *failingNamedCodec) Name() string { return c.name }
+
+// failingWriter is an io.Writer that records how many times it was called and
+// always fails, letting tests observe spurious write retries.
+type failingWriter struct {
+       writes int
+}
+
+func (w *failingWriter) Write(p []byte) (int, error) {
+       w.writes++
+       return 0, errors.New("write failed")
+}
+
+// countingNamedCodec wraps a codec under a distinct Name and counts Marshal
+// calls, letting tests observe whether the backup-codec fallback ran.
+type countingNamedCodec struct {
+       Codec
+       name         string
+       marshalCalls int
+}
+
+func (c *countingNamedCodec) Name() string { return c.name }
+
+func (c *countingNamedCodec) Marshal(message any) ([]byte, error) {
+       c.marshalCalls++
+       return c.Codec.Marshal(message)
+}
+
+// TestMarshalPerfCompressionAndMaxBytes verifies the sendMaxBytes limit and
+// compression headers on both paths.
+func TestMarshalPerfCompressionAndMaxBytes(t *testing.T) {
+       t.Parallel()
+
+       t.Run("sendMaxBytes exceeded returns ResourceExhausted on both paths", 
func(t *testing.T) {
+               msg := newMarshalPerfMessage(1024)
+               for _, wire := range []string{"envelope", "triple"} {
+                       for _, c := range regressionCodecList() {
+                               switch wire {
+                               case "envelope":
+                                       w, _ := newEnvelopeWriterForTest(t, 
c.codec, false, 0, 100)
+                                       wantCode(t, w.Marshal(msg), 
CodeResourceExhausted)
+                               case "triple":
+                                       m, _, _ := newTripleMarshalerForTest(t, 
c.codec, false, 0, 100)
+                                       wantCode(t, m.Marshal(msg), 
CodeResourceExhausted)
+                               }
+                       }
+               }
+       })
+
+       t.Run("compressed over-limit also returns ResourceExhausted", func(t 
*testing.T) {
+               // sendMaxBytes=1 with gzip enabled: even the compressed form 
of any
+               // non-empty message exceeds 1 byte, so both paths must reject 
it.
+               msg := newMarshalPerfMessage(64)
+               for _, wire := range []string{"envelope", "triple"} {
+                       for _, c := range regressionCodecList() {
+                               switch wire {
+                               case "envelope":
+                                       w, _ := newEnvelopeWriterForTest(t, 
c.codec, true, 0, 1)
+                                       wantCode(t, w.Marshal(msg), 
CodeResourceExhausted)
+                               case "triple":
+                                       m, _, _ := newTripleMarshalerForTest(t, 
c.codec, true, 0, 1)
+                                       wantCode(t, m.Marshal(msg), 
CodeResourceExhausted)
+                               }
+                       }
+               }
+       })
+
+       t.Run("compression header set identically on triple wire", func(t 
*testing.T) {
+               msg := newMarshalPerfMessage(1024)
+               for _, c := range regressionCodecList() {
+                       m, _, header := newTripleMarshalerForTest(t, c.codec, 
true, 512, 0)
+                       if err := m.Marshal(msg); err != nil {
+                               t.Fatalf("%s path: %v", c.name, err)
+                       }
+                       if got := header.Get(tripleUnaryHeaderCompression); got 
!= compressionGzip {
+                               t.Fatalf("%s path: compression header %q, want 
%q", c.name, got, compressionGzip)
+                       }
+               }
+       })
+}
+
+// TestMarshalPerfLargeBufferDropped verifies that buffers larger than
+// maxRecycleBufferSize are dropped rather than recycled.
+func TestMarshalPerfLargeBufferDropped(t *testing.T) {
+       t.Parallel()
+
+       t.Run("pool refuses to recycle oversized buffer", func(t *testing.T) {
+               pool := newBufferPool()
+               big := pool.Get()
+               data := make([]byte, 8*1024*1024+1)
+               if _, err := big.Write(data); err != nil {
+                       t.Fatal(err)
+               }
+               if big.Cap() <= maxRecycleBufferSize {
+                       t.Fatalf("test setup: buffer cap %d not larger than 
%d", big.Cap(), maxRecycleBufferSize)
+               }
+               pool.Put(big)
+               next := pool.Get()
+               defer pool.Put(next)
+               if next.Cap() > maxRecycleBufferSize {
+                       t.Fatalf("pool recycled an oversized buffer: cap %d > 
max %d", next.Cap(), maxRecycleBufferSize)
+               }
+               if next.Len() != 0 {
+                       t.Fatalf("pool returned non-empty buffer: Len()==%d", 
next.Len())
+               }
+       })
+
+       t.Run("large message does not poison subsequent small message", func(t 
*testing.T) {
+               large := newMarshalPerfMessage(8*1024*1024 + 1)
+               small := newMarshalPerfMessage(1)
+               for _, wire := range []string{"envelope", "triple"} {
+                       for _, c := range regressionCodecList() {
+                               // newMarshalTo returns a marshal function 
whose marshaler is
+                               // bound to the given buffer pool, so callers 
control sharing.
+                               newMarshalTo := func(pool *bufferPool) 
func(*pingv1.PingRequest) ([]byte, error) {
+                                       out := &bytes.Buffer{}
+                                       switch wire {
+                                       case "envelope":
+                                               w := &envelopeWriter{writer: 
out, codec: c.codec, bufferPool: pool}
+                                               return func(msg 
*pingv1.PingRequest) ([]byte, error) {
+                                                       out.Reset()
+                                                       if err := 
w.Marshal(msg); err != nil {
+                                                               return nil, err
+                                                       }
+                                                       return out.Bytes(), nil
+                                               }
+                                       default: // triple
+                                               m := 
&tripleUnaryMarshaler{writer: out, codec: c.codec, bufferPool: pool}
+                                               return func(msg 
*pingv1.PingRequest) ([]byte, error) {
+                                                       out.Reset()
+                                                       if err := 
m.Marshal(msg); err != nil {
+                                                               return nil, err
+                                                       }
+                                                       return out.Bytes(), nil
+                                               }
+                                       }
+                               }
+                               // Marshal the huge message first, then the 
tiny one through the
+                               // same pool; recycled residue would leak into 
the small output.
+                               shared := newMarshalTo(newBufferPool())
+                               if _, err := shared(large); err != nil {
+                                       t.Fatalf("%s/%s large marshal: %v", 
wire, c.name, err)
+                               }
+                               afterLarge, err := shared(small)
+                               if err != nil {
+                                       t.Fatalf("%s/%s small marshal after 
large: %v", wire, c.name, err)
+                               }
+                               // The tiny message's wire bytes must be 
exactly the same as if
+                               // it had never been preceded by an 8MiB+1 
message. The reference
+                               // is marshaled on an independent, 
uncontaminated pool.
+                               expected, err := 
newMarshalTo(newBufferPool())(small)
+                               if err != nil {
+                                       t.Fatalf("%s/%s reference small 
marshal: %v", wire, c.name, err)
+                               }
+                               if !bytes.Equal(afterLarge, expected) {
+                                       t.Fatalf("%s/%s small message output 
changed after 8MiB+1 message: got %d bytes, want %d",
+                                               wire, c.name, len(afterLarge), 
len(expected))
+                               }
+                               if len(afterLarge) == 0 {
+                                       t.Fatalf("%s/%s small message produced 
empty output", wire, c.name)
+                               }
+                       }
+               }
+       })
+}
+
+// TestMarshalPerfConcurrentSend verifies bufferPool safety under concurrent 
use.
+func TestMarshalPerfConcurrentSend(t *testing.T) {
+       for _, c := range regressionCodecList() {
+               t.Run(c.name, func(t *testing.T) {
+                       // Drive concurrent envelopeWriters sharing one 
bufferPool. Each
+                       // goroutine sends a globally unique payload per 
message, then
+                       // decodes every emitted frame and verifies its 
content. Distinct
+                       // content plus per-frame decode prevents 
cross-goroutine corruption
+                       // from going unnoticed without -race: overwritten 
frames keep the
+                       // same length, which a length-only check would not 
catch.
+                       sharedPool := newBufferPool()
+                       const goroutines = 32
+                       const iters = 100
+                       var wg sync.WaitGroup
+                       errCh := make(chan error, goroutines)
+                       // Start gate: every goroutine blocks until all 32 are 
ready. This
+                       // keeps the pool actually contended and prevents the 
outcome from
+                       // depending on the scheduler interleaving the 
goroutines.
+                       var ready sync.WaitGroup
+                       ready.Add(goroutines)
+                       start := make(chan struct{})
+                       for g := range goroutines {
+                               wg.Go(func() {
+                                       ready.Done()
+                                       <-start
+                                       out := &syncBuffer{}
+                                       w := &envelopeWriter{
+                                               writer:     out,
+                                               codec:      c.codec,
+                                               bufferPool: sharedPool,
+                                       }
+                                       // Number uniquely identifies 
(goroutine, iteration) across
+                                       // the whole test, so content swapped 
between goroutines is
+                                       // reported, not silently accepted.
+                                       want := make([]*pingv1.PingRequest, 0, 
iters)
+                                       for i := range iters {
+                                               msg := &pingv1.PingRequest{
+                                                       Number: int64(g*iters + 
i),
+                                                       Text:   
fmt.Sprintf("goroutine-%02d-message-%03d", g, i),
+                                               }
+                                               want = append(want, msg)
+                                               if err := w.Marshal(msg); err 
!= nil {
+                                                       errCh <- 
fmt.Errorf("goroutine %d marshal message %d: %w", g, i, err)
+                                                       return
+                                               }
+                                       }
+                                       if err := 
verifyEnvelopeFrames(out.Bytes(), c.codec, want); err != nil {
+                                               errCh <- fmt.Errorf("goroutine 
%d: %w", g, err)
+                                       }
+                               })
+                       }
+                       ready.Wait()
+                       close(start)
+                       wg.Wait()
+                       close(errCh)
+                       for err := range errCh {
+                               t.Fatal(err)
+                       }
+               })
+       }
+}
+
+// verifyEnvelopeFrames walks the gRPC/Triple envelope stream written by
+// envelopeWriter and checks that every frame unmarshals to the corresponding
+// expected message. The frames must be uncompressed (flags == 0) and exactly
+// account for the whole stream.
+func verifyEnvelopeFrames(wire []byte, codec Codec, want 
[]*pingv1.PingRequest) error {
+       pos := 0
+       for n, expected := range want {
+               if len(wire)-pos < 5 {
+                       return fmt.Errorf("frame %d: truncated envelope prefix 
(%d bytes left)", n, len(wire)-pos)
+               }
+               if flags := wire[pos]; flags != 0 {
+                       return fmt.Errorf("frame %d: unexpected flags 0x%x 
(only uncompressed frames expected)", n, flags)
+               }
+               length := int(binary.BigEndian.Uint32(wire[pos+1 : pos+5]))
+               pos += 5
+               if len(wire)-pos < length {
+                       return fmt.Errorf("frame %d: payload length %d exceeds 
remaining %d bytes", n, length, len(wire)-pos)
+               }
+               payload := wire[pos : pos+length]
+               pos += length
+               var got pingv1.PingRequest
+               if err := codec.Unmarshal(payload, &got); err != nil {
+                       return fmt.Errorf("frame %d: unmarshal: %w", n, err)
+               }
+               if got.Number != expected.Number || got.Text != expected.Text {
+                       return fmt.Errorf(
+                               "frame %d: content mismatch: got Number=%d 
Text=%q, want Number=%d Text=%q",
+                               n, got.Number, got.Text, expected.Number, 
expected.Text,
+                       )
+               }
+       }
+       if pos != len(wire) {
+               return fmt.Errorf("%d trailing bytes after %d frames", 
len(wire)-pos, len(want))
+       }
+       return nil
+}
+
+// Type guard: only protoBinaryCodec and the tripleServerCodecSession wrapper
+// may expose the marshalAppender fast path; all other codecs must stay on
+// codec.Marshal.
+func TestMarshalPerfTypeGuard(t *testing.T) {
+       t.Parallel()
+
+       implements := func(c Codec) bool {
+               _, ok := c.(marshalAppender)
+               return ok
+       }
+
+       if !implements(&protoBinaryCodec{}) {
+               t.Fatal("protoBinaryCodec must implement marshalAppender")
+       }
+       // tripleServerCodecSession wraps the proto codec on the server response
+       // path; it must reach the fast path too, or the optimization is skipped
+       // for every native triple server response.
+       if !implements(&tripleServerCodecSession{delegate: 
&protoBinaryCodec{}}) {
+               t.Fatal("tripleServerCodecSession must implement 
marshalAppender (server response fast path)")
+       }
+
+       never := []struct {
+               name  string
+               codec Codec
+       }{
+               {name: "noAppender wrapper", codec: 
&noAppenderCodec{&protoBinaryCodec{}}},
+               {name: "proto wrapper (hessian inner)", codec: 
newProtoWrapperCodec(&hessian2Codec{})},
+               {name: "hessian2", codec: &hessian2Codec{}},
+               {name: "msgpack", codec: &msgpackCodec{}},
+               {name: "json", codec: &protoJSONCodec{name: codecNameJSON}},
+       }
+       for _, tc := range never {
+               if implements(tc.codec) {
+                       t.Fatalf("%s must NOT implement marshalAppender (fast 
path scope leak)", tc.name)
+               }
+       }
+}
+
+// TestMarshalPerfEnvelopeWriterFastPath verifies that envelopeWriter.Marshal
+// really takes the MarshalAppend fast path when the codec implements
+// marshalAppender. fastPathProbeCodec.Marshal always fails, so only the
+// appender branch can produce a successful write; the probe counts the
+// appender calls to make the branch observable.
+func TestMarshalPerfEnvelopeWriterFastPath(t *testing.T) {
+       probe := &fastPathProbeCodec{}
+       w, out := newEnvelopeWriterForTest(t, probe, false, 0, 0)
+       if err := w.Marshal(&pingv1.PingRequest{Text: "fast-path"}); err != nil 
{
+               t.Fatalf("envelopeWriter.Marshal with appender codec failed: 
%v", err)
+       }
+       if probe.appendCalls != 1 {
+               t.Fatalf("MarshalAppend called %d times, want 1 (fast path not 
taken)", probe.appendCalls)
+       }
+       if out.Len() == 0 {
+               t.Fatal("fast path wrote no bytes")
+       }
+}
+
+// TestMarshalPerfErrorGuard verifies that non-proto messages never panic:
+// MarshalAppend returns errNotProto, and the envelope and triple marshalers
+// surface CodeInternal on both the fast and slow paths.
+func TestMarshalPerfErrorGuard(t *testing.T) {
+       t.Parallel()
+
+       nonProto := any("definitely not a proto message")
+
+       t.Run("MarshalAppend rejects non-proto", func(t *testing.T) {
+               c := &protoBinaryCodec{}
+               _, err := c.MarshalAppend(make([]byte, 0, 16), nonProto)
+               if err == nil {
+                       t.Fatal("expected errNotProto from MarshalAppend, got 
nil")
+               }
+       })
+
+       t.Run("marshalers return CodeInternal without panicking", func(t 
*testing.T) {
+               for _, wire := range []string{"envelope", "triple"} {
+                       for _, c := range regressionCodecList() {
+                               var err *Error
+                               switch wire {
+                               case "envelope":
+                                       w, _ := newEnvelopeWriterForTest(t, 
c.codec, false, 0, 0)
+                                       err = w.Marshal(nonProto)
+                               case "triple":
+                                       m, _, _ := newTripleMarshalerForTest(t, 
c.codec, false, 0, 0)
+                                       err = m.Marshal(nonProto)
+                               }
+                               wantCode(t, err, CodeInternal)
+                       }
+               }
+       })
+
+       t.Run("primary error with nil backup is CodeInternal", func(t 
*testing.T) {
+               // Already exercised in the fallback test; assert the plain 
error text
+               // does not swallow the underlying errNotProto cause.
+               c := &protoBinaryCodec{}
+               _, err := c.MarshalAppend(nil, nonProto)
+               if err == nil || !strings.Contains(err.Error(), "doesn't 
implement proto.Message") {
+                       t.Fatalf("expected errNotProto naming proto.Message, 
got: %v", err)
+               }
+       })
+}
+
+// fastPathProbeCodec distinguishes the fast and slow paths: Marshal always
+// fails while MarshalAppend counts calls, so a slow-path leak fails the test.
+type fastPathProbeCodec struct {
+       appendCalls int
+}
+
+var _ Codec = (*fastPathProbeCodec)(nil)
+var _ marshalAppender = (*fastPathProbeCodec)(nil)
+
+func (c *fastPathProbeCodec) Name() string { return codecNameProto }
+func (c *fastPathProbeCodec) Marshal(any) ([]byte, error) {
+       return nil, errors.New("slow path taken: codec.Marshal must not run on 
the marshalAppender fast path")
+}
+func (c *fastPathProbeCodec) Unmarshal([]byte, any) error { return nil }
+func (c *fastPathProbeCodec) MarshalAppend(dst []byte, _ any) ([]byte, error) {
+       c.appendCalls++
+       return append(dst, "probe-payload"...), nil
+}
+
+// TestMarshalPerfServerSessionFastPath verifies that tripleServerCodecSession
+// responses reach the MarshalAppend fast path: IDL proto output stays
+// byte-identical to the naked codec, non-IDL MarshalAppend equals Marshal,
+// and delegates without the appender extension fall back gracefully.
+func TestMarshalPerfServerSessionFastPath(t *testing.T) {
+       t.Parallel()
+
+       msg := &pingv1.PingRequest{Text: "server-session"}
+
+       t.Run("IDL proto response matches naked codec wire", func(t *testing.T) 
{
+               // codecSession implements marshalAppender, so 
tripleUnaryMarshaler
+               // takes the MarshalAppend branch on the server response path.
+               fast, outFast, _ := newTripleMarshalerForTest(t, 
&tripleServerCodecSession{delegate: &protoBinaryCodec{}}, false, 0, 0)
+               if err := fast.Marshal(msg); err != nil {
+                       t.Fatalf("session marshal: %v", err)
+               }
+               ref, outRef, _ := newTripleMarshalerForTest(t, 
&protoBinaryCodec{}, false, 0, 0)
+               if err := ref.Marshal(msg); err != nil {
+                       t.Fatalf("reference marshal: %v", err)
+               }
+               if got, want := outFast.Bytes(), outRef.Bytes(); 
!bytes.Equal(got, want) {
+                       t.Fatalf("session fast path wire mismatch: got %d 
bytes, want %d", len(got), len(want))
+               }
+       })
+
+       t.Run("Non-IDL wrapped MarshalAppend equals Marshal", func(t 
*testing.T) {
+               messages := []struct {
+                       name string
+                       msg  any
+               }{
+                       {name: "scalar payload", msg: "payload"},
+                       {name: "one-result container", msg: []any{"payload"}},
+                       {name: "void container", msg: []any{}},
+               }
+               for _, serializeType := range []string{codecNameHessian2, 
codecNameMsgPack} {
+                       for _, mc := range messages {
+                               session := &tripleServerCodecSession{delegate: 
&protoBinaryCodec{}, serializeType: serializeType}
+                               got, err := session.MarshalAppend(nil, mc.msg)
+                               if err != nil {
+                                       t.Fatalf("%s/%s MarshalAppend: %v", 
serializeType, mc.name, err)
+                               }
+                               want, err := session.Marshal(mc.msg)
+                               if err != nil {
+                                       t.Fatalf("%s/%s Marshal: %v", 
serializeType, mc.name, err)
+                               }
+                               if !bytes.Equal(got, want) {
+                                       t.Fatalf("%s/%s MarshalAppend != 
Marshal (%d vs %d bytes)", serializeType, mc.name, len(got), len(want))
+                               }
+                       }
+               }
+       })
+
+       t.Run("custom proto delegate without appender falls back", func(t 
*testing.T) {
+               // A session over a proto codec lacking the appender extension 
must not
+               // break: MarshalAppend degrades to marshal-plus-append, 
byte-equal to
+               // the naked codec's output.
+               session := &tripleServerCodecSession{delegate: 
&noAppenderCodec{&protoBinaryCodec{}}}
+               got, err := session.MarshalAppend(nil, msg)
+               if err != nil {
+                       t.Fatalf("fallback MarshalAppend: %v", err)
+               }
+               want, err := (&protoBinaryCodec{}).Marshal(msg)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               if !bytes.Equal(got, want) {
+                       t.Fatalf("fallback MarshalAppend mismatch: got %d 
bytes, want %d", len(got), len(want))
+               }
+       })
+
+       t.Run("server response must hit MarshalAppend, not the slow path", 
func(t *testing.T) {
+               // The probe's Marshal always fails, so a successful Marshal 
proves the
+               // MarshalAppend fast path was taken.
+               for _, compress := range []bool{false, true} {
+                       probe := &fastPathProbeCodec{}
+                       session := &tripleServerCodecSession{delegate: probe}
+                       m, _, _ := newTripleMarshalerForTest(t, session, 
compress, 0, 0)
+                       if err := m.Marshal(msg); err != nil {
+                               t.Fatalf("compressed=%v Marshal: %v (slow path 
leaked into server responses?)", compress, err)
+                       }
+                       if probe.appendCalls != 1 {
+                               t.Fatalf("compressed=%v MarshalAppend calls = 
%d, want 1 (fast path not taken)", compress, probe.appendCalls)
+                       }
+               }
+       })
+
+       t.Run("envelope writer session must hit MarshalAppend", func(t 
*testing.T) {
+               // The session may also sit behind an envelopeWriter 
(gRPC/Triple
+               // envelope wire); the same no-slow-path guarantee must hold 
there.
+               probe := &fastPathProbeCodec{}
+               session := &tripleServerCodecSession{delegate: probe}
+               w, _ := newEnvelopeWriterForTest(t, session, false, 0, 0)
+               if err := w.Marshal(msg); err != nil {
+                       t.Fatalf("envelope Marshal: %v (slow path leaked?)", 
err)
+               }
+               if probe.appendCalls != 1 {
+                       t.Fatalf("envelope MarshalAppend calls = %d, want 1 
(fast path not taken)", probe.appendCalls)
+               }
+       })
+}
diff --git a/protocol/triple/triple_protocol/protocol_triple.go 
b/protocol/triple/triple_protocol/protocol_triple.go
index d5c093863..e48a018b4 100644
--- a/protocol/triple/triple_protocol/protocol_triple.go
+++ b/protocol/triple/triple_protocol/protocol_triple.go
@@ -505,6 +505,11 @@ func (m *tripleUnaryMarshaler) Marshal(message any) *Error 
{
        if message == nil {
                return m.write(nil)
        }
+       // Fast path: if the codec can append to a caller-provided buffer, 
marshal
+       // directly into a pooled buffer to avoid the per-request allocation.
+       if appender, ok := m.codec.(marshalAppender); ok {
+               return m.marshalAndWrite(message, appender)
+       }
        data, err := m.codec.Marshal(message)
        if err != nil {
                return errorf(CodeInternal, "marshal message: %w", err)
@@ -512,15 +517,36 @@ func (m *tripleUnaryMarshaler) Marshal(message any) 
*Error {
        // Can't avoid allocating the slice, but we can reuse it.
        uncompressed := bytes.NewBuffer(data)
        defer m.bufferPool.Put(uncompressed)
-       if len(data) < m.compressMinBytes || m.compressionPool == nil {
-               if m.sendMaxBytes > 0 && len(data) > m.sendMaxBytes {
-                       return NewError(CodeResourceExhausted, 
fmt.Errorf("message size %d exceeds sendMaxBytes %d", len(data), 
m.sendMaxBytes))
+       return m.compressAndWrite(uncompressed)
+}
+
+// marshalAndWrite serializes message into a pooled *bytes.Buffer, compressing 
if
+// necessary, and writes it.
+func (m *tripleUnaryMarshaler) marshalAndWrite(message any, appender 
marshalAppender) *Error {
+       buffer, err := marshalToPool(m.bufferPool, appender, message)
+       if err != nil {
+               return err
+       }
+       defer m.bufferPool.Put(buffer)
+       return m.compressAndWrite(buffer)
+}
+
+// compressAndWrite enforces the compression threshold and the sendMaxBytes
+// limit on the marshaled bytes in buffer, sets the compression header when the
+// payload is compressed, and writes the result. It is the shared tail of both
+// Marshal (slow path, bytes produced by codec.Marshal) and marshalAndWrite
+// (fast path, bytes produced by marshalAppender.MarshalAppend), so the
+// compression policy can never drift between the two paths.
+func (m *tripleUnaryMarshaler) compressAndWrite(buffer *bytes.Buffer) *Error {
+       if buffer.Len() < m.compressMinBytes || m.compressionPool == nil {
+               if m.sendMaxBytes > 0 && buffer.Len() > m.sendMaxBytes {
+                       return NewError(CodeResourceExhausted, 
fmt.Errorf("message size %d exceeds sendMaxBytes %d", buffer.Len(), 
m.sendMaxBytes))
                }
-               return m.write(data)
+               return m.write(buffer.Bytes())
        }
        compressed := m.bufferPool.Get()
        defer m.bufferPool.Put(compressed)
-       if err := m.compressionPool.Compress(compressed, uncompressed); err != 
nil {
+       if err := m.compressionPool.Compress(compressed, buffer); err != nil {
                return err
        }
        if m.sendMaxBytes > 0 && compressed.Len() > m.sendMaxBytes {

Reply via email to