starpc 0.49.9 → 0.49.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/go.mod +2 -2
- package/go.sum +6 -4
- package/package.json +1 -1
- package/srpc/common-rpc.go +11 -1
- package/srpc/common-rpc_test.go +65 -0
- package/srpc/packet-rw.go +55 -5
- package/srpc/packet-rw_test.go +68 -0
package/go.mod
CHANGED
|
@@ -5,7 +5,7 @@ go 1.25.0
|
|
|
5
5
|
require (
|
|
6
6
|
github.com/aperturerobotics/common v0.33.0 // latest
|
|
7
7
|
github.com/aperturerobotics/protobuf-go-lite v0.13.0 // latest
|
|
8
|
-
github.com/aperturerobotics/util v1.34.
|
|
8
|
+
github.com/aperturerobotics/util v1.34.5-0.20260516103104-cbfc6d6a0589 // latest
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
require (
|
|
@@ -29,5 +29,5 @@ require (
|
|
|
29
29
|
github.com/tetratelabs/wazero v1.11.0 // indirect
|
|
30
30
|
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
|
|
31
31
|
golang.org/x/mod v0.35.0 // indirect
|
|
32
|
-
golang.org/x/sys v0.
|
|
32
|
+
golang.org/x/sys v0.44.0 // indirect
|
|
33
33
|
)
|
package/go.sum
CHANGED
|
@@ -16,8 +16,10 @@ github.com/aperturerobotics/protobuf v0.0.0-20260203024654-8201686529c4 h1:4Dy3B
|
|
|
16
16
|
github.com/aperturerobotics/protobuf v0.0.0-20260203024654-8201686529c4/go.mod h1:tMgO7y6SJo/d9ZcvrpNqIQtdYT9de+QmYaHOZ4KnhOg=
|
|
17
17
|
github.com/aperturerobotics/protobuf-go-lite v0.13.0 h1:jEvCJhHaJEikDY/va2AUnS0DOb/0n82aISLAqxSh4Sk=
|
|
18
18
|
github.com/aperturerobotics/protobuf-go-lite v0.13.0/go.mod h1:lGH3s5ArCTXKI4wJdlNpaybUtwSjfAG0vdWjxOfMcF8=
|
|
19
|
-
github.com/aperturerobotics/util v1.34.
|
|
20
|
-
github.com/aperturerobotics/util v1.34.
|
|
19
|
+
github.com/aperturerobotics/util v1.34.5-0.20260515183346-68f9eac1d69f h1:xISFLs00h441uZcMVxhZbLIZsMRcjOM5Yont18i7WjA=
|
|
20
|
+
github.com/aperturerobotics/util v1.34.5-0.20260515183346-68f9eac1d69f/go.mod h1:mDe7WnncVuV7yjeeVSsagyfrw4xfncu7d+f0+d70niY=
|
|
21
|
+
github.com/aperturerobotics/util v1.34.5-0.20260516103104-cbfc6d6a0589 h1:8B9O13He1sz8Spr2pc+RL3hBzAMveLgUCXT7BpAfvEY=
|
|
22
|
+
github.com/aperturerobotics/util v1.34.5-0.20260516103104-cbfc6d6a0589/go.mod h1:mDe7WnncVuV7yjeeVSsagyfrw4xfncu7d+f0+d70niY=
|
|
21
23
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
22
24
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
23
25
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
@@ -40,8 +42,8 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAz
|
|
|
40
42
|
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
|
41
43
|
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
|
42
44
|
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
|
43
|
-
golang.org/x/sys v0.
|
|
44
|
-
golang.org/x/sys v0.
|
|
45
|
+
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
46
|
+
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
45
47
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
|
46
48
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
47
49
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
package/package.json
CHANGED
package/srpc/common-rpc.go
CHANGED
|
@@ -146,15 +146,22 @@ func (c *commonRPC) WriteCallData(data []byte, dataIsZero, complete bool, err er
|
|
|
146
146
|
|
|
147
147
|
// HandleStreamClose handles the incoming stream closing w/ optional error.
|
|
148
148
|
func (c *commonRPC) HandleStreamClose(closeErr error) {
|
|
149
|
+
var writer PacketWriter
|
|
149
150
|
c.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
|
|
151
|
+
if c.dataClosed {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
150
154
|
if closeErr != nil && c.remoteErr == nil {
|
|
151
155
|
c.remoteErr = closeErr
|
|
152
156
|
}
|
|
153
157
|
c.dataClosed = true
|
|
154
158
|
c.ctxCancel()
|
|
155
|
-
|
|
159
|
+
writer = c.writer
|
|
156
160
|
broadcast()
|
|
157
161
|
})
|
|
162
|
+
if writer != nil {
|
|
163
|
+
_ = writer.Close()
|
|
164
|
+
}
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
// HandleCallCancel handles the call cancel packet.
|
|
@@ -210,6 +217,9 @@ func (c *commonRPC) WriteCallCancel() error {
|
|
|
210
217
|
|
|
211
218
|
// closeLocked releases resources held by the RPC.
|
|
212
219
|
func (c *commonRPC) closeLocked(broadcast func()) {
|
|
220
|
+
if c.dataClosed {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
213
223
|
c.dataClosed = true
|
|
214
224
|
c.localCompleted.Store(true)
|
|
215
225
|
if c.remoteErr == nil {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
"sync/atomic"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type closeCountingPacketWriter struct {
|
|
11
|
+
closed atomic.Int32
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type closeCallbackPacketWriter struct {
|
|
15
|
+
closeFn func()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func (w *closeCountingPacketWriter) WritePacket(*Packet) error {
|
|
19
|
+
return nil
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func (w *closeCountingPacketWriter) Close() error {
|
|
23
|
+
w.closed.Add(1)
|
|
24
|
+
return nil
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (w *closeCallbackPacketWriter) WritePacket(*Packet) error {
|
|
28
|
+
return nil
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func (w *closeCallbackPacketWriter) Close() error {
|
|
32
|
+
if w.closeFn != nil {
|
|
33
|
+
w.closeFn()
|
|
34
|
+
}
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func TestCommonRPCHandleStreamCloseIdempotent(t *testing.T) {
|
|
39
|
+
writer := &closeCountingPacketWriter{}
|
|
40
|
+
rpc := NewServerRPC(context.Background(), InvokerFunc(nil), writer)
|
|
41
|
+
|
|
42
|
+
rpc.HandleStreamClose(io.EOF)
|
|
43
|
+
rpc.HandleStreamClose(context.Canceled)
|
|
44
|
+
|
|
45
|
+
if got := writer.closed.Load(); got != 1 {
|
|
46
|
+
t.Fatalf("expected writer closed once, got %d", got)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func TestCommonRPCHandleStreamCloseClosesWriterOutsideBroadcastLock(t *testing.T) {
|
|
51
|
+
var rpc *ServerRPC
|
|
52
|
+
writerClosedOutsideLock := false
|
|
53
|
+
writer := &closeCallbackPacketWriter{
|
|
54
|
+
closeFn: func() {
|
|
55
|
+
writerClosedOutsideLock = rpc.bcast.TryHoldLock(func(func(), func() <-chan struct{}) {})
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
rpc = NewServerRPC(context.Background(), InvokerFunc(nil), writer)
|
|
59
|
+
|
|
60
|
+
rpc.HandleStreamClose(io.EOF)
|
|
61
|
+
|
|
62
|
+
if !writerClosedOutsideLock {
|
|
63
|
+
t.Fatal("expected writer close outside broadcast lock")
|
|
64
|
+
}
|
|
65
|
+
}
|
package/srpc/packet-rw.go
CHANGED
|
@@ -10,8 +10,31 @@ import (
|
|
|
10
10
|
"github.com/pkg/errors"
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const (
|
|
14
|
+
// maxMessageSize is the max message size in bytes.
|
|
15
|
+
maxMessageSize = 10_000_000
|
|
16
|
+
// readBufferSize is the packet read scratch buffer size.
|
|
17
|
+
readBufferSize = 2048
|
|
18
|
+
// pooledWriteBufferMaxSize is the largest outbound frame buffer to pool.
|
|
19
|
+
pooledWriteBufferMaxSize = 64 * 1024
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
var (
|
|
23
|
+
readBufferPool = sync.Pool{
|
|
24
|
+
New: func() any {
|
|
25
|
+
return new([readBufferSize]byte)
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
writeBufferPool = sync.Pool{
|
|
29
|
+
New: func() any {
|
|
30
|
+
return new(writeBuffer)
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
type writeBuffer struct {
|
|
36
|
+
data []byte
|
|
37
|
+
}
|
|
15
38
|
|
|
16
39
|
// PacketReadWriter reads and writes packets from a io.ReadWriter.
|
|
17
40
|
// Uses a LittleEndian uint32 length prefix.
|
|
@@ -46,7 +69,9 @@ func (r *PacketReadWriter) WritePacket(p *Packet) error {
|
|
|
46
69
|
return errors.Errorf("message size %v greater than maximum %v", msgSize, maxMessageSize)
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
|
|
72
|
+
writeBuf := getWriteBuffer(4 + msgSize)
|
|
73
|
+
defer putWriteBuffer(writeBuf)
|
|
74
|
+
data := writeBuf.data
|
|
50
75
|
binary.LittleEndian.PutUint32(data, uint32(msgSize)) //nolint:gosec
|
|
51
76
|
|
|
52
77
|
_, err := p.MarshalToSizedBufferVT(data[4:])
|
|
@@ -56,10 +81,13 @@ func (r *PacketReadWriter) WritePacket(p *Packet) error {
|
|
|
56
81
|
|
|
57
82
|
var written, n int
|
|
58
83
|
for written < len(data) {
|
|
59
|
-
n, err = r.rw.Write(data)
|
|
84
|
+
n, err = r.rw.Write(data[written:])
|
|
60
85
|
if err != nil {
|
|
61
86
|
return err
|
|
62
87
|
}
|
|
88
|
+
if n == 0 {
|
|
89
|
+
return io.ErrShortWrite
|
|
90
|
+
}
|
|
63
91
|
written += n
|
|
64
92
|
}
|
|
65
93
|
|
|
@@ -81,7 +109,9 @@ func (r *PacketReadWriter) ReadPump(cb PacketDataHandler, closed CloseHandler) {
|
|
|
81
109
|
// Does not handle closing the stream, use ReadPump instead.
|
|
82
110
|
func (r *PacketReadWriter) ReadToHandler(cb PacketDataHandler) error {
|
|
83
111
|
var currLen uint32
|
|
84
|
-
|
|
112
|
+
bufPtr := readBufferPool.Get().(*[readBufferSize]byte)
|
|
113
|
+
defer readBufferPool.Put(bufPtr)
|
|
114
|
+
buf := bufPtr[:]
|
|
85
115
|
isOpen := true
|
|
86
116
|
|
|
87
117
|
for isOpen {
|
|
@@ -150,5 +180,25 @@ func (r *PacketReadWriter) readLengthPrefix(b []byte) uint32 {
|
|
|
150
180
|
return binary.LittleEndian.Uint32(b)
|
|
151
181
|
}
|
|
152
182
|
|
|
183
|
+
func getWriteBuffer(size int) *writeBuffer {
|
|
184
|
+
if size > pooledWriteBufferMaxSize {
|
|
185
|
+
return &writeBuffer{data: make([]byte, size)}
|
|
186
|
+
}
|
|
187
|
+
buf := writeBufferPool.Get().(*writeBuffer)
|
|
188
|
+
if cap(buf.data) < size {
|
|
189
|
+
buf.data = make([]byte, size)
|
|
190
|
+
}
|
|
191
|
+
buf.data = buf.data[:size]
|
|
192
|
+
return buf
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func putWriteBuffer(buf *writeBuffer) {
|
|
196
|
+
if cap(buf.data) <= pooledWriteBufferMaxSize {
|
|
197
|
+
clear(buf.data)
|
|
198
|
+
buf.data = buf.data[:0]
|
|
199
|
+
writeBufferPool.Put(buf)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
153
203
|
// _ is a type assertion
|
|
154
204
|
var _ PacketWriter = (*PacketReadWriter)(nil)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/binary"
|
|
6
|
+
"io"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type chunkedReadWriteCloser struct {
|
|
11
|
+
bytes.Buffer
|
|
12
|
+
maxWrite int
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func (c *chunkedReadWriteCloser) Read([]byte) (int, error) {
|
|
16
|
+
return 0, io.EOF
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func (c *chunkedReadWriteCloser) Write(p []byte) (int, error) {
|
|
20
|
+
if len(p) > c.maxWrite {
|
|
21
|
+
p = p[:c.maxWrite]
|
|
22
|
+
}
|
|
23
|
+
return c.Buffer.Write(p)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (c *chunkedReadWriteCloser) Close() error {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func TestPacketReadWriterWritePacketHandlesShortWrites(t *testing.T) {
|
|
31
|
+
pkt := NewCallDataPacket([]byte("packet payload"), false, true, nil)
|
|
32
|
+
size := pkt.SizeVT()
|
|
33
|
+
want := make([]byte, 4+size)
|
|
34
|
+
binary.LittleEndian.PutUint32(want, uint32(size)) //nolint:gosec
|
|
35
|
+
if _, err := pkt.MarshalToSizedBufferVT(want[4:]); err != nil {
|
|
36
|
+
t.Fatal(err)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
rwc := &chunkedReadWriteCloser{maxWrite: 3}
|
|
40
|
+
if err := NewPacketReadWriter(rwc).WritePacket(pkt); err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
if got := rwc.Bytes(); !bytes.Equal(got, want) {
|
|
44
|
+
t.Fatalf("written packet mismatch:\ngot %x\nwant %x", got, want)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func TestPacketUnmarshalCopiesByteFields(t *testing.T) {
|
|
49
|
+
want := []byte("stable data")
|
|
50
|
+
srcPkt := NewCallDataPacket(want, false, true, nil)
|
|
51
|
+
data, err := srcPkt.MarshalVT()
|
|
52
|
+
if err != nil {
|
|
53
|
+
t.Fatal(err)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var pkt Packet
|
|
57
|
+
if err := pkt.UnmarshalVT(data); err != nil {
|
|
58
|
+
t.Fatal(err)
|
|
59
|
+
}
|
|
60
|
+
for i := range data {
|
|
61
|
+
data[i] = 0xff
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
got := pkt.GetCallData().GetData()
|
|
65
|
+
if !bytes.Equal(got, want) {
|
|
66
|
+
t.Fatalf("unmarshal retained source bytes: got %q want %q", got, want)
|
|
67
|
+
}
|
|
68
|
+
}
|