--- /dev/null
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "golang.org/x/tools/internal/event"
+)
+
+// Handler is invoked to handle incoming requests.
+// The Replier sends a reply to the request and must be called exactly once.
+type Handler func(ctx context.Context, reply Replier, req Request) error
+
+// Replier is passed to handlers to allow them to reply to the request.
+// If err is set then result will be ignored.
+type Replier func(ctx context.Context, result interface{}, err error) error
+
+// MethodNotFound is a Handler that replies to all call requests with the
+// standard method not found response.
+// This should normally be the final handler in a chain.
+func MethodNotFound(ctx context.Context, reply Replier, req Request) error {
+ return reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method()))
+}
+
+// MustReplyHandler creates a Handler that panics if the wrapped handler does
+// not call Reply for every request that it is passed.
+func MustReplyHandler(handler Handler) Handler {
+ return func(ctx context.Context, reply Replier, req Request) error {
+ called := false
+ err := handler(ctx, func(ctx context.Context, result interface{}, err error) error {
+ if called {
+ panic(fmt.Errorf("request %q replied to more than once", req.Method()))
+ }
+ called = true
+ return reply(ctx, result, err)
+ }, req)
+ if !called {
+ panic(fmt.Errorf("request %q was never replied to", req.Method()))
+ }
+ return err
+ }
+}
+
+// CancelHandler returns a handler that supports cancellation, and a function
+// that can be used to trigger canceling in progress requests.
+func CancelHandler(handler Handler) (Handler, func(id ID)) {
+ var mu sync.Mutex
+ handling := make(map[ID]context.CancelFunc)
+ wrapped := func(ctx context.Context, reply Replier, req Request) error {
+ if call, ok := req.(*Call); ok {
+ cancelCtx, cancel := context.WithCancel(ctx)
+ ctx = cancelCtx
+ mu.Lock()
+ handling[call.ID()] = cancel
+ mu.Unlock()
+ innerReply := reply
+ reply = func(ctx context.Context, result interface{}, err error) error {
+ mu.Lock()
+ delete(handling, call.ID())
+ mu.Unlock()
+ return innerReply(ctx, result, err)
+ }
+ }
+ return handler(ctx, reply, req)
+ }
+ return wrapped, func(id ID) {
+ mu.Lock()
+ cancel, found := handling[id]
+ mu.Unlock()
+ if found {
+ cancel()
+ }
+ }
+}
+
+// AsyncHandler returns a handler that processes each request goes in its own
+// goroutine.
+// The handler returns immediately, without the request being processed.
+// Each request then waits for the previous request to finish before it starts.
+// This allows the stream to unblock at the cost of unbounded goroutines
+// all stalled on the previous one.
+func AsyncHandler(handler Handler) Handler {
+ nextRequest := make(chan struct{})
+ close(nextRequest)
+ return func(ctx context.Context, reply Replier, req Request) error {
+ waitForPrevious := nextRequest
+ nextRequest = make(chan struct{})
+ unlockNext := nextRequest
+ innerReply := reply
+ reply = func(ctx context.Context, result interface{}, err error) error {
+ close(unlockNext)
+ return innerReply(ctx, result, err)
+ }
+ _, queueDone := event.Start(ctx, "queued")
+ go func() {
+ <-waitForPrevious
+ queueDone()
+ if err := handler(ctx, reply, req); err != nil {
+ event.Error(ctx, "jsonrpc2 async message delivery failed", err)
+ }
+ }()
+ return nil
+ }
+}