Add windowUpdateThreshold field to stream struct to cache the
MaxStreamBuffer/2 calculation. This avoids repeated pointer
dereferences (s.sess.config.MaxStreamBuffer) and integer division
on every tryReadV2() and writeToV2() call in the hot path.
- Add windowUpdateThreshold uint32 field to stream struct
- Initialize threshold in newStream()
- Use cached value in tryReadV2() and writeToV2()
Move protocol version check from inside the read loop to outside,
eliminating per-iteration switch statement overhead. The version
is constant for the stream's lifetime, so checking once is sufficient.
Before: switch inside for loop (branch on every iteration)
After: if-else with separate loops (branch once per Read call)
- bufferRing: use bitmask instead of modulo for index calculation
* Ensure capacity is always power of 2
* Replace % with & for faster index wrapping in push/pop/grow/consumeFront
- shaperLoop: batch drain pending write requests
* Collect multiple requests before notifying sendLoop
* Reduces goroutine wake-up overhead under high load
- 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.
commit c55575ea34
Author: xtaci <imap@live.com>
Date: Mon Jan 26 20:47:46 2026 +0800
feat: autocleanup when both sides called CloseWrite()
commit baf236b97a
Author: xtaci <imap@live.com>
Date: Mon Jan 26 18:20:43 2026 +0800
feat(stream): implement TCP half-close support via CloseWrite()
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 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%.
commit c8a401fdb4
Author: xtaci <imap@live.com>
Date: Sat Jan 24 22:29:03 2026 +0800
fix(session): reject cmdUPD in protocol version 1
cmdUPD is a v2-only command used for flow control window updates.
Previously, recvLoop would accept and process cmdUPD even when
the session was configured for protocol version 1, which violates
the protocol specification.
This change adds a version check before processing cmdUPD. If the
session is not running v2, it now correctly returns ErrInvalidProtocol
and terminates the receive loop.
cmdUPD is a v2-only command used for flow control window updates.
Previously, recvLoop would accept and process cmdUPD even when
the session was configured for protocol version 1, which violates
the protocol specification.
This change adds a version check before processing cmdUPD. If the
session is not running v2, it now correctly returns ErrInvalidProtocol
and terminates the receive loop.