- bufferRing: add consumeFront() method to eliminate duplicate buffer
consumption code in tryReadV1/tryReadV2, move allocator.Put() outside
critical section to reduce lock hold time
- Session.IsClosed(): replace select-based check with atomic flag for
faster hot path execution, add 'closed' field with atomic operations
- shaperQueue: convert count to atomic int64, Len() and IsEmpty() now
lock-free using atomic.LoadInt64()
- recvLoop: move stream.fin() and stream.update() calls outside lock
scope in cmdFIN/cmdUPD handlers to reduce lock contention
- stream: extract checkWriteClosed() helper to deduplicate close checks
in writeV1/writeV2
All tests pass with race detector enabled.
Add CloseWrite() method to enable TCP-like half-close semantics without
protocol changes. This allows one side to signal EOF while still receiving
data from the peer.
Changes:
- Add chWriteClosed channel to track local write-side closure
- Add CloseWrite() method that sends FIN but keeps read side open
- Modify writeV1/writeV2 to check local write state instead of remote FIN
- Update Close() to also close write side for backward compatibility
The existing cmdFIN protocol command is reused - CloseWrite sends FIN to
signal write completion while the stream remains readable.
Usage:
stream.Write(data)
stream.CloseWrite() // send FIN, can still read
io.ReadAll(stream) // read peer's response
- Add writeRequestPool to reuse writeRequest objects and their result
channels in session.go, reducing allocations per write operation
- Add shaperHeapPool to reuse shaperHeap objects in shaper.go, avoiding
heap allocations when streams are created/destroyed
- Pre-allocate buffers and heads slices with capacity 8 in newStream()
to reduce slice growth allocations during data transfer
Benchmark results (128KB data transfer):
Before: 36 allocs/op, ~910 MB/s
After: 20 allocs/op, ~950 MB/s
This reduces memory allocations by 44% and improves throughput by ~5%.