Skip to content

Commit 44a9f01

Browse files
committed
Support sending notification calls
1 parent e4a5f2d commit 44a9f01

File tree

4 files changed

+106
-27
lines changed

4 files changed

+106
-27
lines changed

client.go

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,18 @@ func httpClient(ctx context.Context, addr string, namespace string, outs []inter
167167
defer httpResp.Body.Close()
168168

169169
var resp clientResponse
170-
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
171-
return clientResponse{}, xerrors.Errorf("http status %s unmarshaling response: %w", httpResp.Status, err)
172-
}
170+
if cr.req.ID != nil { // non-notification
171+
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
172+
return clientResponse{}, xerrors.Errorf("http status %s unmarshaling response: %w", httpResp.Status, err)
173+
}
173174

174-
if resp.ID, err = normalizeID(resp.ID); err != nil {
175-
return clientResponse{}, xerrors.Errorf("failed to response ID: %w", err)
176-
}
175+
if resp.ID, err = normalizeID(resp.ID); err != nil {
176+
return clientResponse{}, xerrors.Errorf("failed to response ID: %w", err)
177+
}
177178

178-
if resp.ID != cr.req.ID {
179-
return clientResponse{}, xerrors.New("request and response id didn't match")
179+
if resp.ID != cr.req.ID {
180+
return clientResponse{}, xerrors.New("request and response id didn't match")
181+
}
180182
}
181183

182184
return resp, nil
@@ -220,7 +222,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
220222
errors: config.errors,
221223
}
222224

223-
requests := c.setup()
225+
requests := c.setupRequestChan()
224226

225227
stop := make(chan struct{})
226228
exiting := make(chan struct{})
@@ -258,7 +260,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
258260
}, nil
259261
}
260262

261-
func (c *client) setup() chan clientRequest {
263+
func (c *client) setupRequestChan() chan clientRequest {
262264
requests := make(chan clientRequest)
263265

264266
c.doRequest = func(ctx context.Context, cr clientRequest) (clientResponse, error) {
@@ -290,6 +292,7 @@ func (c *client) setup() chan clientRequest {
290292
Method: wsCancel,
291293
Params: []param{{v: reflect.ValueOf(cr.req.ID)}},
292294
},
295+
ready: make(chan clientResponse, 1),
293296
}
294297
select {
295298
case requests <- cancelReq:
@@ -452,7 +455,8 @@ type rpcFunc struct {
452455
hasCtx int
453456
returnValueIsChannel bool
454457

455-
retry bool
458+
retry bool
459+
notify bool
456460
}
457461

458462
func (fn *rpcFunc) processResponse(resp clientResponse, rval reflect.Value) []reflect.Value {
@@ -487,7 +491,22 @@ func (fn *rpcFunc) processError(err error) []reflect.Value {
487491
}
488492

489493
func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value) {
490-
var id interface{} = atomic.AddInt64(&fn.client.idCtr, 1)
494+
var id interface{}
495+
if !fn.notify {
496+
id = atomic.AddInt64(&fn.client.idCtr, 1)
497+
498+
// Prepare the ID to send on the wire.
499+
// We track int64 ids as float64 in the inflight map (because that's what
500+
// they'll be decoded to). encoding/json outputs numbers with their minimal
501+
// encoding, avoding the decimal point when possible, i.e. 3 will never get
502+
// converted to 3.0.
503+
var err error
504+
id, err = normalizeID(id)
505+
if err != nil {
506+
return fn.processError(fmt.Errorf("failed to normalize id")) // should probably panic
507+
}
508+
}
509+
491510
params := make([]param, len(args)-fn.hasCtx)
492511
for i, arg := range args[fn.hasCtx:] {
493512
enc, found := fn.client.paramEncoders[arg.Type()]
@@ -522,16 +541,6 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
522541
retVal, chCtor = fn.client.makeOutChan(ctx, fn.ftyp, fn.valOut)
523542
}
524543

525-
// Prepare the ID to send on the wire.
526-
// We track int64 ids as float64 in the inflight map (because that's what
527-
// they'll be decoded to). encoding/json outputs numbers with their minimal
528-
// encoding, avoding the decimal point when possible, i.e. 3 will never get
529-
// converted to 3.0.
530-
id, err := normalizeID(id)
531-
if err != nil {
532-
return fn.processError(fmt.Errorf("failed to normalize id")) // should probably panic
533-
}
534-
535544
req := request{
536545
Jsonrpc: "2.0",
537546
ID: id,
@@ -554,6 +563,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
554563
minDelay: methodMinRetryDelay,
555564
}
556565

566+
var err error
557567
var resp clientResponse
558568
// keep retrying if got a forced closed websocket conn and calling method
559569
// has retry annotation
@@ -563,7 +573,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
563573
return fn.processError(fmt.Errorf("sendRequest failed: %w", err))
564574
}
565575

566-
if resp.ID != req.ID {
576+
if !fn.notify && resp.ID != req.ID {
567577
return fn.processError(xerrors.New("request and response id didn't match"))
568578
}
569579

@@ -593,6 +603,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
593603

594604
const (
595605
ProxyTagRetry = "retry"
606+
ProxyTagNotify = "notify"
596607
ProxyTagRPCMethod = "rpc_method"
597608
)
598609

@@ -612,9 +623,14 @@ func (c *client) makeRpcFunc(f reflect.StructField) (reflect.Value, error) {
612623
ftyp: ftyp,
613624
name: name,
614625
retry: f.Tag.Get(ProxyTagRetry) == "true",
626+
notify: f.Tag.Get(ProxyTagNotify) == "true",
615627
}
616628
fun.valOut, fun.errOut, fun.nout = processFuncOut(ftyp)
617629

630+
if fun.valOut != -1 && fun.notify {
631+
return reflect.Value{}, xerrors.New("notify methods cannot return values")
632+
}
633+
618634
if ftyp.NumIn() > 0 && ftyp.In(0) == contextType {
619635
fun.hasCtx = 1
620636
}

options_server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func WithReverseClient[RP any](namespace string) ServerOption {
7272
stop := make(chan struct{}) // todo better stop?
7373
cl.exiting = stop
7474

75-
requests := cl.setup()
75+
requests := cl.setupRequestChan()
7676
conn.requests = requests
7777

7878
calls := new(RP)

rpc_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,55 @@ func TestAliasedCall(t *testing.T) {
11711171
closer()
11721172
}
11731173

1174+
type NotifHandler struct {
1175+
notified chan struct{}
1176+
}
1177+
1178+
func (h *NotifHandler) Notif() {
1179+
close(h.notified)
1180+
}
1181+
1182+
func TestNotif(t *testing.T) {
1183+
tc := func(proto string) func(t *testing.T) {
1184+
return func(t *testing.T) {
1185+
// setup server
1186+
1187+
nh := &NotifHandler{
1188+
notified: make(chan struct{}),
1189+
}
1190+
1191+
rpcServer := NewServer()
1192+
rpcServer.Register("Notif", nh)
1193+
1194+
// httptest stuff
1195+
testServ := httptest.NewServer(rpcServer)
1196+
defer testServ.Close()
1197+
1198+
// setup client
1199+
var client struct {
1200+
Notif func() error `notify:"true"`
1201+
}
1202+
closer, err := NewMergeClient(context.Background(), proto+"://"+testServ.Listener.Addr().String(), "Notif", []interface{}{
1203+
&client,
1204+
}, nil)
1205+
require.NoError(t, err)
1206+
1207+
// do the call!
1208+
1209+
// this will block if it's not sent as a notification
1210+
err = client.Notif()
1211+
require.NoError(t, err)
1212+
1213+
<-nh.notified
1214+
1215+
closer()
1216+
}
1217+
}
1218+
1219+
t.Run("ws", tc("ws"))
1220+
t.Run("http", tc("http"))
1221+
}
1222+
11741223
// 1. make server call on client **
11751224
// 2. make client handle **
11761225
// 3. alias on client **

websocket.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ func (c *wsConn) handleWsConn(ctx context.Context) {
655655
action = fmt.Sprintf("send-request(%s,%v)", req.req.Method, req.req.ID)
656656

657657
c.writeLk.Lock()
658-
if req.req.ID != nil {
658+
if req.req.ID != nil { // non-notification
659659
if c.incomingErr != nil { // No conn?, immediate fail
660660
req.ready <- clientResponse{
661661
Jsonrpc: "2.0",
@@ -671,9 +671,23 @@ func (c *wsConn) handleWsConn(ctx context.Context) {
671671
c.inflight[req.req.ID] = req
672672
}
673673
c.writeLk.Unlock()
674-
if err := c.sendRequest(req.req); err != nil {
675-
log.Errorf("sendReqest failed (Handle me): %s", err)
674+
serr := c.sendRequest(req.req)
675+
if serr != nil {
676+
log.Errorf("sendReqest failed (Handle me): %s", serr)
676677
}
678+
if req.req.ID == nil { // notification, return immediately
679+
resp := clientResponse{
680+
Jsonrpc: "2.0",
681+
}
682+
if serr != nil {
683+
resp.Error = &respError{
684+
Code: eTempWSError,
685+
Message: fmt.Sprintf("sendRequest: %s", serr),
686+
}
687+
}
688+
req.ready <- resp
689+
}
690+
677691
case <-c.pongs:
678692
action = "pong"
679693

0 commit comments

Comments
 (0)