starpc 0.49.10 → 0.49.11

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 CHANGED
@@ -3,9 +3,9 @@ module github.com/aperturerobotics/starpc
3
3
  go 1.25.0
4
4
 
5
5
  require (
6
- github.com/aperturerobotics/common v0.33.0 // latest
6
+ github.com/aperturerobotics/common v0.33.1-0.20260516193515-675cfc5a0c12 // latest
7
7
  github.com/aperturerobotics/protobuf-go-lite v0.13.0 // latest
8
- github.com/aperturerobotics/util v1.34.5-0.20260516103104-cbfc6d6a0589 // latest
8
+ github.com/aperturerobotics/util v1.34.5 // latest
9
9
  )
10
10
 
11
11
  require (
package/go.sum CHANGED
@@ -2,8 +2,8 @@ github.com/aperturerobotics/abseil-cpp v0.0.0-20260131110040-4bb56e2f9017 h1:3U7
2
2
  github.com/aperturerobotics/abseil-cpp v0.0.0-20260131110040-4bb56e2f9017/go.mod h1:lNSJTKECIUFAnfeSqy01kXYTYe1BHubW7198jNX3nEw=
3
3
  github.com/aperturerobotics/cli v1.1.0 h1:7a+YRC+EY3npAnTzhHV5gLCiw91KS0Ts3XwLILGOsT8=
4
4
  github.com/aperturerobotics/cli v1.1.0/go.mod h1:M7BFP9wow5ytTzMyJQOOO991fGfsUqdTI7gGEsHfTQ8=
5
- github.com/aperturerobotics/common v0.33.0 h1:IheETbaQPmvUpkm6Z+/1jbuAQOXZF5REnRRMXTaIeVk=
6
- github.com/aperturerobotics/common v0.33.0/go.mod h1:xabIJydWovkzjs5YZD8ru/BgFTAXekgHwV8DrTl3R2w=
5
+ github.com/aperturerobotics/common v0.33.1-0.20260516193515-675cfc5a0c12 h1:KQiFEDyu5//G8JZ3yTN92kF2P3F+LHjSKsQikgROncU=
6
+ github.com/aperturerobotics/common v0.33.1-0.20260516193515-675cfc5a0c12/go.mod h1:pW7BhBpKzVrTxN8XIz6jb3MJERZ15GDxaGQ7jcIl0Xg=
7
7
  github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260329113538-218ccd8f20e0 h1:6/3RSSlPEQ6LeidslB1ZCJkxW+MnfYDkvdWMDklDXw4=
8
8
  github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260329113538-218ccd8f20e0/go.mod h1:OBb/beWmr/pDIZAUfi86j/4tBh2v5ctTxKMqSnh9c/4=
9
9
  github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260329113540-600516012db3 h1:lp+V8RYcBwTX1p81swkpZn5fhw1wn2xLorzETIxRyZQ=
@@ -16,10 +16,8 @@ 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.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=
19
+ github.com/aperturerobotics/util v1.34.5 h1:007MaOJrrsiGm5o1c8Tt7p8nVwUAxkM6pmGflrBww/U=
20
+ github.com/aperturerobotics/util v1.34.5/go.mod h1:mDe7WnncVuV7yjeeVSsagyfrw4xfncu7d+f0+d70niY=
23
21
  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24
22
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
25
23
  github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starpc",
3
- "version": "0.49.10",
3
+ "version": "0.49.11",
4
4
  "description": "Streaming protobuf RPC service protocol over any two-way channel.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -38,22 +38,22 @@ func (r *ClientRPC) Start(writer PacketWriter, writeFirstMsg bool, firstMsg []by
38
38
 
39
39
  var firstMsgEmpty bool
40
40
  var err error
41
- r.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
42
- r.writer = writer
41
+ locked := r.bcast.Lock()
42
+ r.writer = writer
43
43
 
44
- if writeFirstMsg {
45
- firstMsgEmpty = len(firstMsg) == 0
46
- }
44
+ if writeFirstMsg {
45
+ firstMsgEmpty = len(firstMsg) == 0
46
+ }
47
47
 
48
- pkt := NewCallStartPacket(r.service, r.method, firstMsg, firstMsgEmpty)
49
- err = writer.WritePacket(pkt)
50
- if err != nil {
51
- r.ctxCancel()
52
- _ = writer.Close()
53
- }
48
+ pkt := NewCallStartPacket(r.service, r.method, firstMsg, firstMsgEmpty)
49
+ err = writer.WritePacket(pkt)
50
+ if err != nil {
51
+ r.ctxCancel()
52
+ _ = writer.Close()
53
+ }
54
54
 
55
- broadcast()
56
- })
55
+ locked.Broadcast()
56
+ locked.Unlock()
57
57
 
58
58
  return err
59
59
  }
@@ -69,14 +69,14 @@ func (r *ClientRPC) HandlePacketData(data []byte) error {
69
69
 
70
70
  // HandleStreamClose handles the stream closing optionally w/ an error.
71
71
  func (r *ClientRPC) HandleStreamClose(closeErr error) {
72
- r.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
73
- if closeErr != nil && r.remoteErr == nil {
74
- r.remoteErr = closeErr
75
- }
76
- r.dataClosed = true
77
- r.ctxCancel()
78
- broadcast()
79
- })
72
+ locked := r.bcast.Lock()
73
+ if closeErr != nil && r.remoteErr == nil {
74
+ r.remoteErr = closeErr
75
+ }
76
+ r.dataClosed = true
77
+ r.ctxCancel()
78
+ locked.Broadcast()
79
+ locked.Unlock()
80
80
  }
81
81
 
82
82
  // HandlePacket handles an incoming parsed message packet.
@@ -108,11 +108,11 @@ func (r *ClientRPC) HandleCallStart(pkt *CallStart) error {
108
108
 
109
109
  // Close releases any resources held by the ClientRPC.
110
110
  func (r *ClientRPC) Close() {
111
- r.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
112
- // call did not start yet if writer is nil.
113
- if r.writer != nil {
114
- _ = r.WriteCallCancel()
115
- r.closeLocked(broadcast)
116
- }
117
- })
111
+ locked := r.bcast.Lock()
112
+ // call did not start yet if writer is nil.
113
+ if r.writer != nil {
114
+ _ = r.WriteCallCancel()
115
+ r.closeLocked(&locked)
116
+ }
117
+ locked.Unlock()
118
118
  }
@@ -52,10 +52,12 @@ func (c *commonRPC) Wait(ctx context.Context) error {
52
52
  var err error
53
53
  var waitCh <-chan struct{}
54
54
  var rpcCtx context.Context
55
- c.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
56
- rpcCtx, err = c.ctx, c.remoteErr
57
- waitCh = getWaitCh()
58
- })
55
+ locked := c.bcast.Lock()
56
+ rpcCtx, err = c.ctx, c.remoteErr
57
+ if err == nil && rpcCtx.Err() == nil {
58
+ waitCh = locked.WaitCh()
59
+ }
60
+ locked.Unlock()
59
61
 
60
62
  if err != nil {
61
63
  return err
@@ -78,43 +80,37 @@ func (c *commonRPC) Wait(ctx context.Context) error {
78
80
  //
79
81
  // returns io.EOF if the stream ended without a packet.
80
82
  func (c *commonRPC) ReadOne() ([]byte, error) {
81
- var hasMsg bool
82
- var msg []byte
83
- var err error
84
83
  var ctxDone bool
85
84
  for {
86
85
  var waitCh <-chan struct{}
87
- c.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
88
- if ctxDone && !c.dataClosed {
89
- // context must have been canceled locally
90
- c.closeLocked(broadcast)
91
- err = context.Canceled
92
- return
93
- }
94
-
95
- if len(c.dataQueue) != 0 {
96
- msg = c.dataQueue[0]
97
- hasMsg = true
98
- c.dataQueue[0] = nil
99
- c.dataQueue = c.dataQueue[1:]
100
- } else if c.dataClosed || c.remoteErr != nil {
101
- err = c.remoteErr
102
- if err == nil {
103
- err = io.EOF
104
- }
105
- }
106
-
107
- waitCh = getWaitCh()
108
- })
86
+ locked := c.bcast.Lock()
87
+ if ctxDone && !c.dataClosed {
88
+ // context must have been canceled locally
89
+ c.closeLocked(&locked)
90
+ locked.Unlock()
91
+ return nil, context.Canceled
92
+ }
109
93
 
110
- if hasMsg {
94
+ if len(c.dataQueue) != 0 {
95
+ msg := c.dataQueue[0]
96
+ c.dataQueue[0] = nil
97
+ c.dataQueue = c.dataQueue[1:]
98
+ locked.Unlock()
111
99
  return msg, nil
112
100
  }
113
101
 
114
- if err != nil {
102
+ if c.dataClosed || c.remoteErr != nil {
103
+ err := c.remoteErr
104
+ if err == nil {
105
+ err = io.EOF
106
+ }
107
+ locked.Unlock()
115
108
  return nil, err
116
109
  }
117
110
 
111
+ waitCh = locked.WaitCh()
112
+ locked.Unlock()
113
+
118
114
  select {
119
115
  case <-c.ctx.Done():
120
116
  ctxDone = true
@@ -147,18 +143,19 @@ func (c *commonRPC) WriteCallData(data []byte, dataIsZero, complete bool, err er
147
143
  // HandleStreamClose handles the incoming stream closing w/ optional error.
148
144
  func (c *commonRPC) HandleStreamClose(closeErr error) {
149
145
  var writer PacketWriter
150
- c.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
151
- if c.dataClosed {
152
- return
153
- }
154
- if closeErr != nil && c.remoteErr == nil {
155
- c.remoteErr = closeErr
156
- }
157
- c.dataClosed = true
158
- c.ctxCancel()
159
- writer = c.writer
160
- broadcast()
161
- })
146
+ locked := c.bcast.Lock()
147
+ if c.dataClosed {
148
+ locked.Unlock()
149
+ return
150
+ }
151
+ if closeErr != nil && c.remoteErr == nil {
152
+ c.remoteErr = closeErr
153
+ }
154
+ c.dataClosed = true
155
+ c.ctxCancel()
156
+ writer = c.writer
157
+ locked.Broadcast()
158
+ locked.Unlock()
162
159
  if writer != nil {
163
160
  _ = writer.Close()
164
161
  }
@@ -173,34 +170,33 @@ func (c *commonRPC) HandleCallCancel() error {
173
170
  // HandleCallData handles the call data packet.
174
171
  func (c *commonRPC) HandleCallData(pkt *CallData) error {
175
172
  var err error
176
- c.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
177
- if c.dataClosed {
178
- // If the packet is just indicating the call is complete, ignore it.
179
- if pkt.GetComplete() {
180
- return
181
- }
182
-
173
+ locked := c.bcast.Lock()
174
+ if c.dataClosed {
175
+ // If the packet is just indicating the call is complete, ignore it.
176
+ if !pkt.GetComplete() {
183
177
  // Otherwise, return ErrCompleted (unexpected packet).
184
178
  err = ErrCompleted
185
- return
186
179
  }
180
+ locked.Unlock()
181
+ return err
182
+ }
187
183
 
188
- if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
189
- c.dataQueue = append(c.dataQueue, data)
190
- }
184
+ if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
185
+ c.dataQueue = append(c.dataQueue, data)
186
+ }
191
187
 
192
- complete := pkt.GetComplete()
193
- if err := pkt.GetError(); len(err) != 0 {
194
- complete = true
195
- c.remoteErr = errors.New(err)
196
- }
188
+ complete := pkt.GetComplete()
189
+ if pktErr := pkt.GetError(); len(pktErr) != 0 {
190
+ complete = true
191
+ c.remoteErr = errors.New(pktErr)
192
+ }
197
193
 
198
- if complete {
199
- c.dataClosed = true
200
- }
194
+ if complete {
195
+ c.dataClosed = true
196
+ }
201
197
 
202
- broadcast()
203
- })
198
+ locked.Broadcast()
199
+ locked.Unlock()
204
200
 
205
201
  return err
206
202
  }
@@ -216,7 +212,7 @@ func (c *commonRPC) WriteCallCancel() error {
216
212
  }
217
213
 
218
214
  // closeLocked releases resources held by the RPC.
219
- func (c *commonRPC) closeLocked(broadcast func()) {
215
+ func (c *commonRPC) closeLocked(locked *broadcast.Locked) {
220
216
  if c.dataClosed {
221
217
  return
222
218
  }
@@ -226,6 +222,6 @@ func (c *commonRPC) closeLocked(broadcast func()) {
226
222
  c.remoteErr = context.Canceled
227
223
  }
228
224
  _ = c.writer.Close()
229
- broadcast()
225
+ locked.Broadcast()
230
226
  c.ctxCancel()
231
227
  }
@@ -1,6 +1,7 @@
1
1
  package srpc
2
2
 
3
3
  import (
4
+ "bytes"
4
5
  "context"
5
6
  "io"
6
7
  "sync/atomic"
@@ -52,7 +53,11 @@ func TestCommonRPCHandleStreamCloseClosesWriterOutsideBroadcastLock(t *testing.T
52
53
  writerClosedOutsideLock := false
53
54
  writer := &closeCallbackPacketWriter{
54
55
  closeFn: func() {
55
- writerClosedOutsideLock = rpc.bcast.TryHoldLock(func(func(), func() <-chan struct{}) {})
56
+ locked, ok := rpc.bcast.TryLock()
57
+ if ok {
58
+ locked.Unlock()
59
+ }
60
+ writerClosedOutsideLock = ok
56
61
  },
57
62
  }
58
63
  rpc = NewServerRPC(context.Background(), InvokerFunc(nil), writer)
@@ -63,3 +68,26 @@ func TestCommonRPCHandleStreamCloseClosesWriterOutsideBroadcastLock(t *testing.T
63
68
  t.Fatal("expected writer close outside broadcast lock")
64
69
  }
65
70
  }
71
+
72
+ func TestCommonRPCReadOneQueuedDoesNotAllocate(t *testing.T) {
73
+ msg := []byte("message")
74
+ queue := make([][]byte, 1)
75
+ rpc := NewServerRPC(context.Background(), InvokerFunc(nil), &closeCountingPacketWriter{})
76
+
77
+ allocs := testing.AllocsPerRun(1000, func() {
78
+ queue[0] = msg
79
+ rpc.dataQueue = queue
80
+
81
+ got, err := rpc.ReadOne()
82
+ if err != nil {
83
+ t.Fatalf("read one: %v", err)
84
+ }
85
+ if !bytes.Equal(got, msg) {
86
+ t.Fatalf("expected %q, got %q", msg, got)
87
+ }
88
+ })
89
+
90
+ if allocs != 0 {
91
+ t.Fatalf("expected queued ReadOne to avoid allocations, got %f", allocs)
92
+ }
93
+ }
@@ -59,29 +59,31 @@ func (r *ServerRPC) HandlePacket(msg *Packet) error {
59
59
  func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
60
60
  var err error
61
61
 
62
- r.bcast.HoldLock(func(broadcast func(), getWaitCh func() <-chan struct{}) {
63
- // process start: method and service
64
- if r.method != "" || r.service != "" {
65
- err = errors.New("call start must be sent only once")
66
- return
67
- }
68
- if r.dataClosed {
69
- err = ErrCompleted
70
- return
71
- }
62
+ locked := r.bcast.Lock()
63
+ // process start: method and service
64
+ if r.method != "" || r.service != "" {
65
+ err = errors.New("call start must be sent only once")
66
+ locked.Unlock()
67
+ return err
68
+ }
69
+ if r.dataClosed {
70
+ err = ErrCompleted
71
+ locked.Unlock()
72
+ return err
73
+ }
72
74
 
73
- service, method := pkt.GetRpcService(), pkt.GetRpcMethod()
74
- r.service, r.method = service, method
75
+ service, method := pkt.GetRpcService(), pkt.GetRpcMethod()
76
+ r.service, r.method = service, method
75
77
 
76
- // process first data packet, if included
77
- if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
78
- r.dataQueue = append(r.dataQueue, data)
79
- }
78
+ // process first data packet, if included
79
+ if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
80
+ r.dataQueue = append(r.dataQueue, data)
81
+ }
80
82
 
81
- // invoke the rpc
82
- broadcast()
83
- go r.invokeRPC(service, method)
84
- })
83
+ // invoke the rpc
84
+ locked.Broadcast()
85
+ go r.invokeRPC(service, method)
86
+ locked.Unlock()
85
87
 
86
88
  return err
87
89
  }