jsonrpc: Handle closing channels
This commit is contained in:
parent
64e3272fbf
commit
40fa1becb5
@ -45,7 +45,8 @@ type clientRequest struct {
|
|||||||
req request
|
req request
|
||||||
ready chan clientResponse
|
ready chan clientResponse
|
||||||
|
|
||||||
retCh func() func([]byte, bool)
|
// retCh provides a context and sink for handling incoming channel messages
|
||||||
|
retCh func() (context.Context, func([]byte, bool))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientCloser is used to close Client from further use
|
// ClientCloser is used to close Client from further use
|
||||||
@ -135,28 +136,40 @@ func NewClient(addr string, namespace string, handler interface{}) (ClientCloser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
if hasCtx == 1 {
|
||||||
|
ctx = args[0].Interface().(context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
var retVal reflect.Value
|
var retVal reflect.Value
|
||||||
var chCtor func() func([]byte, bool)
|
|
||||||
|
// if the function returns a channel, we need to provide a sink for the
|
||||||
|
// messages
|
||||||
|
var chCtor func() (context.Context, func([]byte, bool))
|
||||||
|
|
||||||
if retCh {
|
if retCh {
|
||||||
retVal = reflect.Zero(ftyp.Out(valOut))
|
retVal = reflect.Zero(ftyp.Out(valOut))
|
||||||
chCtor = func() func([]byte, bool) {
|
|
||||||
|
chCtor = func() (context.Context, func([]byte, bool)) {
|
||||||
// unpack chan type to make sure it's reflect.BothDir
|
// unpack chan type to make sure it's reflect.BothDir
|
||||||
ctyp := reflect.ChanOf(reflect.BothDir, ftyp.Out(valOut).Elem())
|
ctyp := reflect.ChanOf(reflect.BothDir, ftyp.Out(valOut).Elem())
|
||||||
ch := reflect.MakeChan(ctyp, 0) // todo: buffer?
|
ch := reflect.MakeChan(ctyp, 0) // todo: buffer?
|
||||||
retVal = ch.Convert(ftyp.Out(valOut))
|
retVal = ch.Convert(ftyp.Out(valOut))
|
||||||
|
|
||||||
return func(result []byte, ok bool) {
|
return ctx, func(result []byte, ok bool) {
|
||||||
if !ok {
|
if !ok {
|
||||||
|
// remote channel closed, close ours too
|
||||||
ch.Close()
|
ch.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val := reflect.New(ftyp.Out(valOut).Elem())
|
val := reflect.New(ftyp.Out(valOut).Elem())
|
||||||
if err := json.Unmarshal(result, val.Interface()); err != nil {
|
if err := json.Unmarshal(result, val.Interface()); err != nil {
|
||||||
log.Errorf("error unmarshaling chan response: %s", err)
|
log.Errorf("error unmarshaling chan response: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ch.Send(val.Elem()) // todo: select on ctx
|
ch.Send(val.Elem()) // todo: select on ctx is probably a good idea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,8 +191,8 @@ func NewClient(addr string, namespace string, handler interface{}) (ClientCloser
|
|||||||
var ctxDone <-chan struct{}
|
var ctxDone <-chan struct{}
|
||||||
var resp clientResponse
|
var resp clientResponse
|
||||||
|
|
||||||
if hasCtx == 1 {
|
if ctx != nil {
|
||||||
ctxDone = args[0].Interface().(context.Context).Done()
|
ctxDone = ctx.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for response, handle context cancellation
|
// wait for response, handle context cancellation
|
||||||
|
@ -3,6 +3,7 @@ package jsonrpc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -241,6 +242,7 @@ func TestCtx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UnUnmarshalable int
|
type UnUnmarshalable int
|
||||||
|
|
||||||
func (*UnUnmarshalable) UnmarshalJSON([]byte) error {
|
func (*UnUnmarshalable) UnmarshalJSON([]byte) error {
|
||||||
return errors.New("nope")
|
return errors.New("nope")
|
||||||
}
|
}
|
||||||
@ -274,7 +276,7 @@ type ChanHandler struct {
|
|||||||
wait chan struct{}
|
wait chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChanHandler) Sub(ctx context.Context, i int) (<-chan int, error) {
|
func (h *ChanHandler) Sub(ctx context.Context, i int, eq int) (<-chan int, error) {
|
||||||
out := make(chan int)
|
out := make(chan int)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -282,11 +284,23 @@ func (h *ChanHandler) Sub(ctx context.Context, i int) (<-chan int, error) {
|
|||||||
var n int
|
var n int
|
||||||
|
|
||||||
for {
|
for {
|
||||||
<-h.wait
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("ctxdone1")
|
||||||
|
return
|
||||||
|
case <-h.wait:
|
||||||
|
}
|
||||||
|
|
||||||
n += i
|
n += i
|
||||||
|
|
||||||
|
if n == eq {
|
||||||
|
fmt.Println("eq")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
fmt.Println("ctxdone2")
|
||||||
return
|
return
|
||||||
case out <- n:
|
case out <- n:
|
||||||
}
|
}
|
||||||
@ -298,7 +312,7 @@ func (h *ChanHandler) Sub(ctx context.Context, i int) (<-chan int, error) {
|
|||||||
|
|
||||||
func TestChan(t *testing.T) {
|
func TestChan(t *testing.T) {
|
||||||
var client struct {
|
var client struct {
|
||||||
Sub func(context.Context, int) (<-chan int, error)
|
Sub func(context.Context, int, int) (<-chan int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverHandler := &ChanHandler{
|
serverHandler := &ChanHandler{
|
||||||
@ -319,13 +333,47 @@ func TestChan(t *testing.T) {
|
|||||||
serverHandler.wait <- struct{}{}
|
serverHandler.wait <- struct{}{}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
|
// sub
|
||||||
|
|
||||||
|
sub, err := client.Sub(ctx, 2, -1)
|
||||||
sub, err := client.Sub(ctx, 2)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// recv one
|
||||||
|
|
||||||
require.Equal(t, 2, <-sub)
|
require.Equal(t, 2, <-sub)
|
||||||
|
|
||||||
|
// recv many (order)
|
||||||
|
|
||||||
|
serverHandler.wait <- struct{}{}
|
||||||
|
serverHandler.wait <- struct{}{}
|
||||||
|
serverHandler.wait <- struct{}{}
|
||||||
|
|
||||||
|
require.Equal(t, 4, <-sub)
|
||||||
|
require.Equal(t, 6, <-sub)
|
||||||
|
require.Equal(t, 8, <-sub)
|
||||||
|
|
||||||
|
// close (through ctx)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, ok := <-sub
|
||||||
|
require.Equal(t, false, ok)
|
||||||
|
|
||||||
|
// sub (again)
|
||||||
|
|
||||||
|
serverHandler.wait <- struct{}{}
|
||||||
|
|
||||||
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sub, err = client.Sub(ctx, 3, 6)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 3, <-sub)
|
||||||
|
|
||||||
|
// close (remote)
|
||||||
|
serverHandler.wait <- struct{}{}
|
||||||
|
_, ok = <-sub
|
||||||
|
require.Equal(t, false, ok)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,16 @@ func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, r
|
|||||||
caseToId[chosen-1] = caseToId[n-1]
|
caseToId[chosen-1] = caseToId[n-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id := caseToId[chosen-1]
|
||||||
cases = cases[:n]
|
cases = cases[:n]
|
||||||
caseToId = caseToId[:n-1]
|
caseToId = caseToId[:n-1]
|
||||||
|
|
||||||
|
sendReq(request{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
ID: nil, // notification
|
||||||
|
Method: chClose,
|
||||||
|
Params: []param{{v: reflect.ValueOf(id)}},
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +207,16 @@ func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, r
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
handleCtxAsync := func(actx context.Context, id int64) {
|
||||||
|
<-actx.Done()
|
||||||
|
|
||||||
|
sendReq(request{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
Method: wsCancel,
|
||||||
|
Params: []param{{v: reflect.ValueOf(id)}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// cancelCtx is a built-in rpc which handles context cancellation over rpc
|
// cancelCtx is a built-in rpc which handles context cancellation over rpc
|
||||||
cancelCtx := func(req frame) {
|
cancelCtx := func(req frame) {
|
||||||
if req.ID != nil {
|
if req.ID != nil {
|
||||||
@ -259,7 +277,9 @@ func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, r
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
chanHandlers[chid] = req.retCh()
|
var chanCtx context.Context
|
||||||
|
chanCtx, chanHandlers[chid] = req.retCh()
|
||||||
|
go handleCtxAsync(chanCtx, *frame.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ready <- clientResponse{
|
req.ready <- clientResponse{
|
||||||
@ -285,6 +305,22 @@ func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
hnd(frame.Params[1].data, true)
|
hnd(frame.Params[1].data, true)
|
||||||
|
case chClose:
|
||||||
|
var chid uint64
|
||||||
|
if err := json.Unmarshal(frame.Params[0].data, &chid); err != nil {
|
||||||
|
log.Error("failed to unmarshal channel id in xrpc.ch.val: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hnd, ok := chanHandlers[chid]
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("xrpc.ch.val: handler %d not found", chid)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(chanHandlers, chid)
|
||||||
|
|
||||||
|
hnd(nil, false)
|
||||||
default: // Remote call
|
default: // Remote call
|
||||||
req := request{
|
req := request{
|
||||||
Jsonrpc: frame.Jsonrpc,
|
Jsonrpc: frame.Jsonrpc,
|
||||||
@ -316,11 +352,10 @@ func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, r
|
|||||||
|
|
||||||
if !keepctx {
|
if !keepctx {
|
||||||
cf()
|
cf()
|
||||||
}
|
|
||||||
|
|
||||||
delete(handling, *frame.ID)
|
delete(handling, *frame.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go handler.handle(ctx, req, nw, rpcError, done, handleChanOut)
|
go handler.handle(ctx, req, nw, rpcError, done, handleChanOut)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user