As reported in
https://github.com/modelcontextprotocol/go-sdk/issues/805, case
insensitive unmarshaling can pose security issues. This change adjusts
all non-test and non-example unmarshaling to use a case sensitive mode.
This introduces a new external dependency
`github.com/segmentio/encoding`.
When POST requests in the streamableClientConn return a transient error,
return this error to the caller rather than permanently breaking the
connection.
This is achieved by using the special sentinel ErrRejected error to the
jsonrpc2 layer. In doing so, the change revealed a pre-existing bug:
ErrRejected had the same code as ErrConnectionClosing, and
jsonrpc2.WireError implements errors.Is, so the two sentinel values
could be conflated. This is fixed by using a new internal code.
There's more to do for #683: we should also retry transient errors in
handleSSE.
For #683
After a deep dive with the team, document areas of the codebase
identified as unclear. For now, this is a documentation-only commit (apart
from one switch of jsonrpc2.Request.ID.IsValid->Request.IsCall). Special
TODO(cleanup) comments are added for additional code changes in
follow-up commits.
Also, include high level doc comments for server and client transports,
in new streamable_server.go and streamable_client.go. Eventually, the
server and client transport logic should be migrated to these files, as
streamable.go has gotten too large. However, we can do that at a future
date, to minimize diff.
After walking through our handling of streamable client context
cancellation (due to encountering a shutdown deadlock), I think I've
settled on a more coherent strategy for handling call cancellation:
- In our call handler, retire the request if the call exits due to
cancellation: the caller will never see the actual result anyway.
- In connectSSE, use the actual request context (the same context used
in Write) for the client request, so that it terminates when the context
is cancelled. Thread through the initialization context for the
standalone SSE request.
Also, a couple minor improvements:
- Use a detached context for the background context of the client
connection. We want to preserve context values (see #513), but it is not
right to cancel the connection after Connect has already returned, if
the context times out.
- Don't use Last-Event-ID != "" as the signal for whether the connectSSE
call is initial: if the standalone SSE stream disconnects without an
event ID, we'll still reconnect it, and don't want to do so without a
delay.
+ tests, updating the streamable client connection test harness to
accomodate the new aspects being exercised.
Fixes#662
Previously, the error message received from ClientSession.Wait would
only report the closeErr, which would often be nil even if the client
transport was broken. Wait should return the reason the session
terminated, if abnormal.
I'm not sure of the exact semantics of this, but surely returning nil is
less useful than returning a meaningful non-nil error. We can refine our
handling of errors once we have more feedback.
Also add a test for client termination on HTTP server shutdown,
described in #265. This should work as long as (1) the session is
stateful (with a hanging GET), or (2) the session is stateless but the
client has a keepalive ping.
Also: don't send DELETE if the session was terminated with 404; +test.
Fixes#265
A few problems with reconnection cropped up in the review of PR #307.
We should allow for the hanging GET to fail with StatusMethodNotAllowed.
This simply means that the server does not support sending notifications
or requests over the GET, which is allowed in the spec.
Also, we should fix the initial delay of the hanging GET request: it
should start with 0 delay. Fix the math for this and subsequent
attempts. Incidentally, this makes the tests take 3s on my machine, down
from 9s.
Also address some comments from #307.
Several improvements for the stateless streamable mode, plus support for
a 'distributed' (or rather, distributable) version of the stateless
server.
- Add a 'Stateless' option to StreamableHTTPOptions and
StreamableServerTransport, which controls stateless behavior.
GetSessionID may still return a non-empty session ID.
- Audit validation of stateless mode to allow requests with a session
id. Propagate this session ID to the temporary connection.
- Peek at requests to allow 'initialize' requests to go through to the
session, so that version negotiation can occur (FIXME: add tests).
Fixes#284
For #148
Implement the concurrency model described in #26: notifications are
synchronous, but calls are asynchronous (except for 'initialize'). To
achieve this, implement jsonrpc2.Async(ctx) to signal asynchronous
handling. This is simpler to use than returning ErrAsyncResponse and
calling Respond, and since this is an internal detail we don't need to
worry too much about whether it's idiomatic.
Add tests that verify both features, for both client and server.
Also:
- replace req.ID.IsValid with req.IsCall
- remove the methodHandler type as we can just use MethodHandler
Fixes#26
This CL introduces a fundamental change to the jsonrpc2 library:
connection writes, which were previously serialized by the jsonrpc2
library itself, are now allowed to be concurrent.
The change in semantics of the jsonrpc2 library should hopefully be easy
to review, since moving the synchronization to the Writer implementation
is equivalent to the previous logic. However, this change is critical
for the streamable client transport, because it allows for concurrent
http requests to the server.
Consider that a write is a POST to the server, and we don't know that
write succeeded until we get the response header. Previously, we had the
following problem: if the client POSTs a request, and the server blocks
its response on a request made through the hanging GET, the client was
unable to respond because the initial POST is still blocked. We could
update our streamable server transport to force a flush of the response
headers, but we can't guarantee that other servers behave the same way.
Fundamentally, writes in the spec are asynchronous, and we need to
support that.
In preparation for extracting the jsonrpc2_v2 package, update the Reader
and Writer interfaces to not report byte counts, and remove the
internal/event dependency.
Change-Id: Ie6b373736fe297c8427cd4ece7ddad33e275864b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/678956
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Add support for SSE transport, as defined by version 2024-11-05 and
2025-03-26 of the MCP spec.
In order to achieve this, the jsonrpc2 stream must be abstracted so that
we can implement it across multiple HTTP requests. This required some
refactoring of the jsonrpc2_v2 package, so that we can avoid the
Dialer/Binder interfaces and construct the Connection directly from its
constituent primitives.
Change-Id: Id627b5b5a474d92f2c613cfad904d14611e7c768
Reviewed-on: https://go-review.googlesource.com/c/tools/+/667475
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Produced with a patched version of modernize containing only
the efaceany pass:
$ go run ./gopls/internal/analysis/modernize/cmd/modernize/main.go -test -fix ./... ./gopls/...
This is very safe and forms the bulk of the modernize diff,
isolating the other changes for ease of review.
Change-Id: Iec9352bac5a639a5c03368427531c7842c6e9ad0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/650759
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
As part of the log audit in golang/go#66746, I noticed several
irregularities addressed by this CL:
- Move "tags" (domain-specific event keys, which are used construct
labels) closer to the packages that use them. Specifically, the
internal/event/tag package contained gopls-specific tags, but x/tools
should not care about gopls.
- Use consistent values for log attributes. For example, "method" was
being used to mean jsonrpc2 method and Go method. Also, "directory"
was being used as both file path and URI.
- Use log attributes for the view attributes logged when views are
created.
- Eliminate (yet another) redundant log during Load.
- Include the ViewID with snapshot.Labels, since snapshot IDs are only
meaningful relative to a View.
With these changes, my audit of logging is complete.
Fixesgolang/go#66746
Change-Id: Iaa60797a7412fb8e222e78e2e58eff2da9563bbb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/579335
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Prior to this CL we already shut down a jsonrpc2_v2.Conn when its
Reader breaks, which we expect to be the common shutdown path.
However, with certain kinds of connections (notably those over
stdin+stdout), it is possible for the Writer side to fail while
the Reader remains working.
If the Writer has failed, we have no way to return the required
Response messages for incoming calls, nor to write new Request
messages of our own. Since we have no way to return a response,
we will now mark those incoming calls as canceled.
However, even if the Writer has failed we may still be able to read
the responses for any outgoing calls that are already in flight. When
our in-flight calls complete, we could in theory even continue to
process Notification messages from the Reader; however, those are
unlikely to be useful with half the connection broken. It seems more
helpful — and less surprising — to go ahead and shut down the
connection completely when it becomes idle.
This is a redo of CL 446315, with additional fixes for bugs exposed on
the -race builders and some extra code cleanup from the process of
diagnosing those bugs.
Updates golang/go#46520.
Updates golang/go#49387.
Change-Id: I746409a7aa2c22d5651448ed0135b5ac21a9808e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/447035
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Prior to this CL we already shut down a jsonrpc2_v2.Conn when its
Reader breaks, which we expect to be the common shutdown path.
However, with certain kinds of connections (notably those over
stdin+stdout), it is possible for the Writer side to fail while
the Reader remains working.
If the Writer has failed, we have no way to return the required
Response messages for incoming calls, nor to write new Request
messages of our own. Since we have no way to return a response,
we will now mark those incoming calls as canceled.
However, even if the Writer has failed we may still be able to read
the responses for any outgoing calls that are already in flight. When
our in-flight calls complete, we could in theory even continue to
process Notification messages from the Reader; however, those are
unlikely to be useful with half the connection broken. It seems more
helpful — and less surprising — to go ahead and shut down the
connection completely when it becomes idle.
Updates golang/go#46520.
Updates golang/go#49387.
Change-Id: I713f172ca7031f4211da321560fe7eae57960a48
Reviewed-on: https://go-review.googlesource.com/c/tools/+/446315
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Also make (*Connection).Close safe to call from Bind.
A jsonrpc2_v2.Server has no good way to report an error from Bind.
If the Server saves the error to return from its own Wait method, that
might not ever actually happen: Wait waits for in-flight connections
to complete, but if some existing connection stays up then Wait will
not return.
If the Server goes ahead with establishing the connection and installs
its own Handler, that Handler needs to decide whether to serve the
error from Bind or something more opaque, and at that point Bind may
as well return a handler that makes that choice more precisely.
If the Server merely logs the error and closes the Connection, then
the Bind method itself may as well do that directly too.
It seems to me that the only winning move is not to play. Only Bind is
in a position to decide how to handle its errors appropriately, so it
should not return them to the Server.
Updates golang/go#56281.
Change-Id: I07dc43ddf31253ce23da21a92d2b6c0f8d4b3afe
Reviewed-on: https://go-review.googlesource.com/c/tools/+/443677
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Prior to this CL, (*Server).run only filters out inactive connections
when it accepts a new connection. If existing connections complete,
their associated resources can't be garbage-collected until either the
next connection is accepted or the Listener is closed.
This change moves the open-connection accounting to an explicit hook
passed to newConnection, eliminating the need to call Wait entirely.
For golang/go#46047
Change-Id: I3732cb463fcea0c142f17f2b1510fdfd2dbc81da
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388774
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This change fixes the semantics of Close to actually wait for
in-flight requests before closing the ReadWriteCloser. (Previously,
the Close method closed the ReadWriteCloser immediately, which I
suspect is what led to many of the failures observed in
golang/go#49387 and golang/go#46520.)
It achieves this by explicitly tracking the number of in-flight
requests, including requests with pending async responses, and
explicitly rejecting new Call requests (while keeping the read loop
open!) once Close has begun.
To make it easier for me to reason about the request lifetimes, I
reduced the number of long-lived goroutines from three to just one
(the Read loop), with an additional Handler goroutine that runs only
while the Handler queue is non-empty. Now, it is clearer (I hope!)
that the number of in-flight async requests strictly decreases
after Close has begun, even though the Read goroutine continues
to read requests (and, importantly, responses) and to forward
Notifications to the preempter.
For golang/go#49387
For golang/go#46520
Change-Id: Idf5960f848108a7ced78c5382099c8692e9b181e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388134
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
With the suffixes I end up a little lost in the “box” noise while I'm
reading — the channel ops alone suffice to make the storage mechanism
clear.
(To me, the mechanism of storing a value in a 1-buffered channel is
conceptually similar to storing it in a pointer, atomic.Pointer, or
similar — and we don't generally name those with a suffix either.)
For golang/go#46047.
For golang/go#46520.
For golang/go#49387.
Change-Id: I7f58a9ac532f597fe49ed70606d89bd8cbe33b55
Reviewed-on: https://go-review.googlesource.com/c/tools/+/443355
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Otherwise, the Await method on the corresponding AsyncCall will never
unblock, leading to a deadlock (detected by the test changes in
CL 388597).
For golang/go#46047
For golang/go#46520
For golang/go#49387
Change-Id: I7954f80786059772ddd7f8c98d8752d56d074919
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388775
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
(*Server).run calls Wait on all of its connections before returning,
but does not call Close explicitly. If Close is necessary to
release resources (as is often the case for a net.Conn),
the server ends up leaking resources.
For golang/go#46047
Change-Id: I4ad048c4f5e5d3f14f992eee6388acd42398c26b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388599
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This CL was created using the following commands:
./gopls/internal/migrate.sh
git add .
git codereview gofmt
For golang/go#54509
Change-Id: Iceeec602748a5e6f609c3ceda8d19157e5c94009
Reviewed-on: https://go-review.googlesource.com/c/tools/+/426796
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
As of golang/go#50827, gopls no longer needs to build at Go 1.12. This
was the only reason to continue using xerrors in the jsonrpc2 libraries.
Remove this usage as a step toward eliminating the xerrors dependency
from x/tools.
For golang/go#52442
Change-Id: I1a0a1bbf57e6606c66b99b27439adf6f65c26a60
Reviewed-on: https://go-review.googlesource.com/c/tools/+/401155
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Change-Id: Iaad847631b9d5ee40558fcf9d51f4dfa99600e6b
GitHub-Last-Rev: 6fcb64f8d9a093e7bd949e5d28bd1176c27005c0
GitHub-Pull-Request: golang/tools#372
Reviewed-on: https://go-review.googlesource.com/c/tools/+/389595
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
If a call request comes it at the same time that a response becomes
ready for another call (either via an explicit Respond call or the
return from a Handle call), the map read and deletion for the response
could race with the map assignment for the new (incoming) call.
Change-Id: Ifd81dbc490486251e81c7eeae438806356f514af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/388595
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Update the new binder tests to run both with a standalone server, and
with a forwarding chain.
Make a few superficial improvements along the way as well.
Change-Id: Icd197698093a3f6149ab58171806b2388ed75b7f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/321134
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
The lsprpc package needs to wait on the newly bound connection to
perform some tear-down, so it must be safe to call conn.Wait from Bind.
Achieve this by switching async from an init pattern to a constructor.
This means moving async fields to pointers, but since they weren't safe
to use prior to initialization anyway this feels correct.
Change-Id: I4f93980915e0b568fbf2db3cd7d062adf06e4b99
Reviewed-on: https://go-review.googlesource.com/c/tools/+/330929
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>