mirror of
https://github.com/sourcegraph/jsonrpc2.git
synced 2026-11-10 05:56:46 +01:00
Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f50bb34c70 | ||
|
|
ae9a040819 | ||
|
|
bf8fe04036 | ||
|
|
fb2b860c84 | ||
|
|
bacb7f31f4 | ||
|
|
f0eebfe60c |
8 changed files with 93 additions and 31 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
id: go
|
id: go
|
||||||
|
|
|
||||||
20
.github/workflows/scip.yml
vendored
20
.github/workflows/scip.yml
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
name: SCIP
|
|
||||||
'on':
|
|
||||||
- push
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
jobs:
|
|
||||||
scip-go:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: sourcegraph/scip-go
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Get src-cli
|
|
||||||
run: curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src;
|
|
||||||
chmod +x /usr/local/bin/src
|
|
||||||
- name: Set directory to safe for git
|
|
||||||
run: git config --global --add safe.directory $GITHUB_WORKSPACE
|
|
||||||
- name: Generate SCIP data
|
|
||||||
run: scip-go
|
|
||||||
- name: Upload SCIP data
|
|
||||||
run: src code-intel upload -github-token=${{ secrets.GITHUB_TOKEN }}
|
|
||||||
3
async.go
3
async.go
|
|
@ -3,7 +3,8 @@ package jsonrpc2
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
// AsyncHandler wraps a Handler such that each request is handled in its own
|
// AsyncHandler wraps a Handler such that each request is handled in its own
|
||||||
// goroutine. It is a convenience wrapper.
|
// goroutine. Handlers may therefore start or finish in a different order than
|
||||||
|
// requests are received. It is a convenience wrapper.
|
||||||
func AsyncHandler(h Handler) Handler {
|
func AsyncHandler(h Handler) Handler {
|
||||||
return asyncHandler{h}
|
return asyncHandler{h}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
conn.go
11
conn.go
|
|
@ -156,7 +156,9 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}, op
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply sends a successful response with a result.
|
// Reply sends a successful response with a result. It returns after writing
|
||||||
|
// the response to the ObjectStream; it does not wait for the peer to receive or
|
||||||
|
// process the response.
|
||||||
func (c *Conn) Reply(ctx context.Context, id ID, result interface{}) error {
|
func (c *Conn) Reply(ctx context.Context, id ID, result interface{}) error {
|
||||||
resp := &Response{ID: id}
|
resp := &Response{ID: id}
|
||||||
if err := resp.SetResult(result); err != nil {
|
if err := resp.SetResult(result); err != nil {
|
||||||
|
|
@ -186,8 +188,9 @@ func (c *Conn) close(cause error) error {
|
||||||
}
|
}
|
||||||
c.closed = true
|
c.closed = true
|
||||||
|
|
||||||
for _, call := range c.pending {
|
for id, call := range c.pending {
|
||||||
close(call.done)
|
close(call.done)
|
||||||
|
delete(c.pending, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(c.disconnect)
|
close(c.disconnect)
|
||||||
|
|
@ -318,6 +321,10 @@ func (c *Conn) send(_ context.Context, m *anyMessage, wait bool) (cc *call, err
|
||||||
// responses.
|
// responses.
|
||||||
if m.request != nil && wait {
|
if m.request != nil && wait {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
if c.closed {
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
id = m.request.ID
|
id = m.request.ID
|
||||||
c.pending[id] = cc
|
c.pending[id] = cc
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
|
||||||
72
conn_test.go
72
conn_test.go
|
|
@ -275,6 +275,78 @@ func TestConn_Close(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConn_CloseAfterResponseRead(t *testing.T) {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
responseRead := make(chan struct{})
|
||||||
|
processResponse := make(chan struct{})
|
||||||
|
responseIgnored := make(chan struct{})
|
||||||
|
var resumeResponse sync.Once
|
||||||
|
resume := func() { resumeResponse.Do(func() { close(processResponse) }) }
|
||||||
|
defer resume()
|
||||||
|
|
||||||
|
clientStream := &pauseAfterReadStream{
|
||||||
|
ObjectStream: jsonrpc2.NewPlainObjectStream(client),
|
||||||
|
read: responseRead,
|
||||||
|
resume: processResponse,
|
||||||
|
}
|
||||||
|
clientConn := jsonrpc2.NewConn(
|
||||||
|
context.Background(),
|
||||||
|
clientStream,
|
||||||
|
noopHandler{},
|
||||||
|
jsonrpc2.SetLogger(signalLogger{called: responseIgnored}),
|
||||||
|
)
|
||||||
|
serverConn := jsonrpc2.NewConn(
|
||||||
|
context.Background(),
|
||||||
|
jsonrpc2.NewPlainObjectStream(server),
|
||||||
|
handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
|
||||||
|
if err := conn.Reply(ctx, req.ID, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer serverConn.Close()
|
||||||
|
|
||||||
|
call, err := clientConn.DispatchCall(context.Background(), "m", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
<-responseRead
|
||||||
|
|
||||||
|
if err := clientConn.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
resume()
|
||||||
|
<-responseIgnored
|
||||||
|
|
||||||
|
if err := call.Wait(context.Background(), nil); err != jsonrpc2.ErrClosed {
|
||||||
|
t.Fatalf("got error %v, want %v", err, jsonrpc2.ErrClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pauseAfterReadStream struct {
|
||||||
|
jsonrpc2.ObjectStream
|
||||||
|
read chan<- struct{}
|
||||||
|
resume <-chan struct{}
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *pauseAfterReadStream) ReadObject(v interface{}) error {
|
||||||
|
err := s.ObjectStream.ReadObject(v)
|
||||||
|
s.once.Do(func() {
|
||||||
|
close(s.read)
|
||||||
|
<-s.resume
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type signalLogger struct {
|
||||||
|
called chan<- struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l signalLogger) Printf(string, ...interface{}) {
|
||||||
|
close(l.called)
|
||||||
|
}
|
||||||
|
|
||||||
func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) {
|
func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) {
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) {
|
handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) {
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -2,4 +2,4 @@ module github.com/sourcegraph/jsonrpc2
|
||||||
|
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.4.1
|
require github.com/gorilla/websocket v1.5.3
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -1,2 +1,2 @@
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
|
|
||||||
10
jsonrpc2.go
10
jsonrpc2.go
|
|
@ -59,10 +59,12 @@ const (
|
||||||
|
|
||||||
// Handler handles JSON-RPC requests and notifications.
|
// Handler handles JSON-RPC requests and notifications.
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
// Handle is called to handle a request. No other requests are handled until
|
// Handle is called to handle a request. While it is running, the connection
|
||||||
// it returns. If you do not require strict ordering behavior of received
|
// does not process any other requests or responses. A handler that waits for
|
||||||
// RPCs, it is suggested to wrap your handler in AsyncHandler. The context
|
// a call's response must therefore run asynchronously. If you do not require
|
||||||
// is automatically canceled when the connection closes.
|
// strict ordering behavior of received RPCs, it is suggested to wrap your
|
||||||
|
// handler in AsyncHandler. The context is automatically canceled when the
|
||||||
|
// connection closes.
|
||||||
Handle(context.Context, *Conn, *Request)
|
Handle(context.Context, *Conn, *Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue