go-sdk/internal/jsonrpc2/wire.go
Bryan C. Mills 9821e82f8e internal/jsonrpc2_v2: rework concurrency in idleListener
This eliminates a race between a successful Accept call and a
concurrent Close call, which previously could have shut down the
'run' goroutine before Accept sent to the newConns channel, causing
Accept to deadlock.

In fact, it eliminates the long-running 'run' goroutine entirely
(replacing it with a time.Timer), and in the process avoids leaking
O(N) closed connections when only the very first one is long-lived.

It also eliminates a potential double-Close bug: if the run method had
called l.wrapped.Close due to an idle timeout, a subsequent call to
Close would invoke l.wrapped.Close again. The io.Closer method
explicitly documents doubled Close calls as undefined behavior, and
many Closer implementations (especially test fakes) panic or deadlock
in that case.

It also eliminates a timer leak if the Listener rapidly oscillates
between active and idle: previously the implementation used
time.After, but it now uses an explicit time.Timer which can be
stopped (and garbage-collected) when the listener becomes active.

Idleness is now tracked based on the connection's Close method rather
than Read: we have no guarantee in general that a caller will ever
actually invoke Read (if, for example, they Close the connection as
soon as it is dialed), but we can reasonably expect a caller to at
least try to ensure that Close is always called.

We now also verify, using a finalizer on a best-effort basis, that the
Close method on each connection is called. We use the finalizer to
verify the Close call — rather than to close the connection implicitly
— because closing the connection in a finalizer would delay the start
of the idle timer by an arbitrary and unbounded duration after the
last connection is actually no longer in use.

Fixes golang/go#46047.
Fixes golang/go#51435.
For golang/go#46520.
For golang/go#49387.

Change-Id: If173a3ed7a44aff14734b72c8340122e8d020f26
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388597
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
2022-10-17 20:15:04 +00:00

86 lines
3.2 KiB
Go

// Copyright 2018 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package jsonrpc2
import (
"encoding/json"
)
// This file contains the go forms of the wire specification.
// see http://www.jsonrpc.org/specification for details
var (
// ErrParse is used when invalid JSON was received by the server.
ErrParse = NewError(-32700, "JSON RPC parse error")
// ErrInvalidRequest is used when the JSON sent is not a valid Request object.
ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request")
// ErrMethodNotFound should be returned by the handler when the method does
// not exist / is not available.
ErrMethodNotFound = NewError(-32601, "JSON RPC method not found")
// ErrInvalidParams should be returned by the handler when method
// parameter(s) were invalid.
ErrInvalidParams = NewError(-32602, "JSON RPC invalid params")
// ErrInternal indicates a failure to process a call correctly
ErrInternal = NewError(-32603, "JSON RPC internal error")
// The following errors are not part of the json specification, but
// compliant extensions specific to this implementation.
// ErrServerOverloaded is returned when a message was refused due to a
// server being temporarily unable to accept any new messages.
ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
// ErrUnknown should be used for all non coded errors.
ErrUnknown = NewError(-32001, "JSON RPC unknown error")
// ErrServerClosing is returned for calls that arrive while the server is closing.
ErrServerClosing = NewError(-32002, "JSON RPC server is closing")
// ErrClientClosing is a dummy error returned for calls initiated while the client is closing.
ErrClientClosing = NewError(-32003, "JSON RPC client is closing")
)
const wireVersion = "2.0"
// wireCombined has all the fields of both Request and Response.
// We can decode this and then work out which it is.
type wireCombined struct {
VersionTag string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *wireError `json:"error,omitempty"`
}
// wireError represents a structured error in a Response.
type wireError struct {
// Code is an error code indicating the type of failure.
Code int64 `json:"code"`
// Message is a short description of the error.
Message string `json:"message"`
// Data is optional structured data containing additional information about the error.
Data json.RawMessage `json:"data,omitempty"`
}
// NewError returns an error that will encode on the wire correctly.
// The standard codes are made available from this package, this function should
// only be used to build errors for application specific codes as allowed by the
// specification.
func NewError(code int64, message string) error {
return &wireError{
Code: code,
Message: message,
}
}
func (err *wireError) Error() string {
return err.Message
}
func (err *wireError) Is(other error) bool {
w, ok := other.(*wireError)
if !ok {
return false
}
return err.Code == w.Code
}