
Goroutines with an owner: concurrency patterns for Go agents
Start goroutines with an owner and a way to stop, merge their results, and prove none is left behind.
The whole loopGo makes starting concurrent work cheap: go f() is all it takes.
An agent uses that everywhere, for tool calls in parallel, one run per batch of work, a server answering several requests at once, a timer that watches a stream.
The hard part is never starting a goroutine; it is knowing who stops it, and making sure it can stop.
The rule this page follows comes from the companion code’s style guide: every goroutine has an owner and a way to stop. The owner is the code that waits for it to finish, and the way to stop is almost always a context. The examples are from the companion repository, where they are tested with the race detector.
Three words used below
A goroutine is a function running at the same time as the rest of the program; you start one by putting go in front of a call.
A channel passes values between goroutines: a send waits until someone receives, and close tells readers that no more values are coming.
A select statement waits on several channel operations at once and takes whichever is ready first.
If these are new, the Tour of Go has a concurrency section that covers each in a few minutes.
Start and wait: sync.WaitGroup.Go
sync.WaitGroup is how an owner waits for the goroutines it started.
Since Go 1.25 it has a Go method, which starts a function in a goroutine and counts it in one call, wg.Go(func() { ... }).
wg.Wait() blocks until every function started this way has returned.
wg.Go replaces the older three-step form, wg.Add(1), go func() { defer wg.Done(); ... }(), and the mistakes that came with it: an Add placed inside the goroutine, where Wait can run before it, or a Done forgotten on one return path.
A closure started in a loop can also use the loop’s variables directly.
Since Go 1.22, each iteration of a for loop has its own copy of its variables, so the old habit of copying them first, name := name, is no longer needed.
Older code you meet will still do it.
Merging several runs into one stream
Running several agents at once, one per group of crash reports, is a fan-out; reading all their events as one stream is a fan-in.
Merge starts one goroutine per run; each sends its run’s events into one shared channel, and the caller reads that channel as a single stream of events:
// Merge runs several event streams at once, such as one agent's run per
// fingerprint group, and yields their events as they happen, each tagged with
// the name of the run it came from. Each run's StoppedEvent comes
// through like any other event, even when ctx is cancelled; Merge ends
// when every run has stopped. Breaking out of the range loop cancels
// every run and waits for their goroutines to finish, so none is left
// blocked on a send.
func Merge(ctx context.Context,
runs map[string]func(context.Context) iter.Seq[Event]) iter.Seq[Event] {
return func(yield func(Event) bool) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
events := make(chan Event)
quit := make(chan struct{}) // closed when the caller stops reading
var wg sync.WaitGroup
for name, run := range runs {
wg.Go(func() {
for ev := range run(ctx) {
ev.Agent = name
select {
case events <- ev:
case <-quit:
return
}
}
})
}
go func() { wg.Wait(); close(events) }()
Each goroutine forwards its run’s events, tagged with the run’s name. A separate goroutine waits for all of them and then closes the channel, so the reader knows when every run has finished; closing a channel is the sender’s job, and here the senders’ owner does it once, after the last of them is done.
The reading side is where the leaks are avoided:
for ev := range events {
if !yield(ev) {
close(quit)
cancel()
for range events {
}
return
}
}
}
}
When the caller stops reading, by breaking out of its range loop, three things happen in order.
quit is closed, so a goroutine waiting to send gives up and returns, which also ends its run.
The context is cancelled, which stops the runs that were busy rather than sending.
And the reader drains the channel until it closes, which happens only once every goroutine has returned, so when Merge returns, nothing it started is still running.
Look at what the send doesn’t wait on: the context.
After Ctrl-C, each run still has a last event to report, the one that says how it stopped, and the caller is still reading.
A send that also gave up on ctx.Done() would sometimes drop it, because when several cases of a select are ready, Go picks one at random; TestMergeCancel checks that every run’s last event arrives.
Without both protections, the quit case and the drain, a goroutine blocked on a send that nobody will ever receive stays blocked for the life of the process: a goroutine leak.
In a service that runs agents all day, each abandoned run leaves one behind.
Proving there’s no leak
A leak doesn’t fail anything; it just accumulates.
The companion code tests for one with the goroutine leak profile in runtime/pprof, which reports goroutines blocked forever on a channel or lock that no running code can reach anymore.
The test runs a deliberately broken version, leakyMerge, beside the real one, so it checks both that the profile finds a leak when there is one and that Merge leaves none.
The test itself:
// TestMergeLeak reads one event from each fan-in and stops. The leak
// profile finds leakyMerge's blocked senders, and none from Merge.
func TestMergeLeak(t *testing.T) {
ctx := t.Context()
for range leakyMerge([]iter.Seq[Event]{counter(9)(ctx), counter(9)(ctx)}) {
break
}
for range Merge(ctx, map[string]func(context.Context) iter.Seq[Event]{
"a": counter(9), "b": counter(9)}) {
break
}
var b bytes.Buffer
if err := pprof.Lookup("goroutineleak").WriteTo(&b, 1); err != nil {
t.Fatal(err)
}
t.Log(b.String())
if !strings.Contains(b.String(), "leakyMerge") {
t.Error("the leak profile didn't find leakyMerge's senders")
}
if strings.Contains(b.String(), "agent.Merge") {
t.Error("Merge left a goroutine blocked")
}
}
The other tool is the race detector, go test -race, which reports two goroutines touching the same memory without synchronization while the tests run.
The companion code’s checks run every test with it.
Requests in flight: a map behind a mutex
A server that answers several requests at once needs to know which are running, to cancel one when the client asks.
The MCP server keeps each request’s cancel function in a map, and starts each request in its own goroutine.
The map is shared by every request goroutine and the reading loop, so it sits behind a sync.Mutex, and every access locks it.
How the server starts a request and records it:
// inflight holds the requests that are running, by ID, with the cancel
// function of each, so a notifications/cancelled can stop it.
type inflight struct {
c *conn
wg sync.WaitGroup
mu sync.Mutex
cancel map[string]context.CancelCauseFunc
}
// start runs a request in a goroutine of its own. A request that was
// cancelled gets no response: the client has stopped waiting for it.
func (s *Server) start(ctx context.Context, m message,
calls *inflight) {
id := canonicalID(m.ID)
ctx, cancel := context.WithCancelCause(ctx)
if !calls.add(id, cancel) {
s.reply(calls.c, m.ID, nil, &Error{Code: CodeInvalidRequest,
Message: "request ID " + id + " is already in use"})
return
}
calls.wg.Go(func() {
defer calls.remove(id)
result, rpcErr := s.handle(ctx, m)
if ctx.Err() == nil {
s.reply(calls.c, m.ID, result, rpcErr)
}
})
}
The map and its lock:
func (f *inflight) add(id string, cancel context.CancelCauseFunc) bool {
f.mu.Lock()
defer f.mu.Unlock()
if _, ok := f.cancel[id]; ok {
cancel(nil)
return false
}
f.cancel[id] = cancel
return true
}
func (f *inflight) remove(id string) {
f.mu.Lock()
defer f.mu.Unlock()
f.cancel[id](nil)
delete(f.cancel, id)
}
add refuses a request ID already in use and cancels the context it was given, so nothing leaks from a request that never starts.
remove runs when the request’s goroutine ends, and cancels its context too, which releases what context.WithCancelCause holds.
Shutting down in order
The server’s main loop owns every request goroutine, and its deferred calls put the shutdown in order:
// Serve answers requests from r on w until r ends, each request in a
// goroutine of its own, and waits for them all before it returns.
func (s *Server) Serve(ctx context.Context, r io.Reader,
w io.Writer) error {
ctx, stop := context.WithCancelCause(ctx)
calls := &inflight{c: newConn(r, w),
cancel: map[string]context.CancelCauseFunc{}}
defer calls.wg.Wait()
defer stop(errServerStopped) // runs first: cancels what is running
for {
m, err := calls.c.receive()
if errors.Is(err, io.EOF) { // the client closed our input
calls.finish(drainGrace) // then the defers cancel the rest
return nil
}
if rpcErr, ok := errors.AsType[*Error](err); ok {
s.reply(calls.c, jsontext.Value("null"), nil, rpcErr)
continue
}
if err != nil {
return fmt.Errorf("read request: %w", err)
}
s.dispatch(ctx, m, calls)
}
}
Deferred calls run in reverse order, so stop runs before calls.wg.Wait(): first every running request is cancelled, then the loop waits for each goroutine to return.
The other order would wait for requests that nobody has told to stop.
When the client closes its end, finish first gives the requests a short grace period to answer:
// finish waits up to d for the running requests to end.
func (f *inflight) finish(d time.Duration) {
done := make(chan struct{})
go func() { // ends once every request does, cancelled or not
f.wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(d):
}
}
The goroutine inside finish can outlive the call, when the grace period runs out first; it ends when the last request does.
The deferred calls cancel the requests’ contexts right after, so that happens soon.
Limiting how much runs at once
Starting a goroutine per task is fine for a handful; for hundreds of tool calls or crash reports, something has to cap how many run at once, both to stay within a provider’s rate limit and to bound memory.
The companion code uses a buffered channel as a counting semaphore, with an Acquire that gives up when the context ends; the context page shows it, beside a rate limiter that spaces requests out in time.
The parallel tool calls guide shows the runner that decides which of a reply’s tool calls may run together, and what running them in parallel saved.
Mistakes to avoid
- A goroutine with no way to stop. Every blocking operation inside it needs a
ctx.Done()case, or a channel its owner closes. - A send nobody will receive. If the reader can leave early, the sender needs another way out, like
Merge’squit, or the reader has to drain what is left. - Closing a channel from the receiving side, or more than once. Close it from the sender’s side, once, after the last send; with several senders, their owner closes it after they finish.
wg.Addinside the goroutine. Usewg.Go, which counts before it starts.- Waiting before cancelling. At shutdown, cancel first, then wait; deferred calls run last-in, first-out, so defer the wait before the cancel.
- Shared maps without a lock. A map read and written from several goroutines needs a mutex; the race detector finds the ones the tests reach.
Try it
From the code directory of a clone of the companion repository:
$ go test -race ./ch15/agent -run 'TestMerge' -v
- In
TestMergeLeak, read the leak profile the test logs, and findleakyMerge’s blocked senders in it. - In
Merge, remove both thecase <-quitbranch and the drain loop, runTestMergeLeakagain, and see the profile nameMerge’s blocked sends. - Put the
quitcase back and add acase <-ctx.Done(): returnbeside it, then runTestMergeCancel: some runs’ last events go missing.
func Merge(ctx context.Context,
runs map[string]func(context.Context) iter.Seq[Event]) iter.Seq[Event]
Merge runs several event streams at once, such as one agent's run per fingerprint group, and yields their events as they happen, each tagged with the name of the run it came from.
Merge runs several event streams at once, such as one agent's run per fingerprint group, and yields their events as they happen, each tagged with the name of the run it came from. func Merge(ctx context.Context, runs map[string]func(context.Context) iter.Seq[Event]) iter.Seq[Event]. In ch15/agent/fanin.go, line 18.
var ctx context.Context
var ctx context.Context. In ch15/agent/fanin.go, line 18.
package context ("context")
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. package context ("context"). Documented at pkg.go.dev, context.
type Context interface {
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// The close of the Done channel may happen asynchronously,
// ...
}
A Context carries a deadline, a cancellation signal, and other values across API boundaries.
A Context carries a deadline, a cancellation signal, and other values across API boundaries. type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. // The close of the Done channel may happen asynchronously, // ... }. Documented at pkg.go.dev, context.Context.
var runs map[string]func(context.Context) iter.Seq[Event]
var runs map[string]func(context.Context) iter.Seq[Event]. In ch15/agent/fanin.go, line 19.
type string string
string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text.
string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. type string string. Documented at pkg.go.dev, builtin.string.
package iter ("iter")
Package iter provides basic definitions and operations related to iterators over sequences.
Package iter provides basic definitions and operations related to iterators over sequences. package iter ("iter"). Documented at pkg.go.dev, iter.
type Seq[V any] func(yield func(V) bool)
Seq is an iterator over sequences of individual values.
Seq is an iterator over sequences of individual values. type Seq[V any] func(yield func(V) bool). Documented at pkg.go.dev, iter.Seq.
type Event struct {
Kind EventKind
Text string // TextEvent: the new text
Call ToolCall // CallEvent: the call
Tool Message // ToolResultEvent: the tool message
Turn int // TurnEvent: how many turns have ended
Result Result // StoppedEvent: the run, as far as it went
Err error // StoppedEvent: why it stopped; nil for an answer
// Agent names the run an event came from when several run at once;
// ...
}
An Event is something that happened during a run.
An Event is something that happened during a run. type Event struct { Kind EventKind Text string // TextEvent: the new text Call ToolCall // CallEvent: the call Tool Message // ToolResultEvent: the tool message Turn int // TurnEvent: how many turns have ended Result Result // StoppedEvent: the run, as far as it went Err error // StoppedEvent: why it stopped; nil for an answer // Agent names the run an event came from when several run at once; // ... }. In ch15/agent/events.go, line 34.
var yield func(Event) bool
var yield func(Event) bool. In ch15/agent/fanin.go, line 20.
type bool bool
bool is the set of boolean values, true and false.
bool is the set of boolean values, true and false. type bool bool. Documented at pkg.go.dev, builtin.bool.
var ctx context.Context
var ctx context.Context. In ch15/agent/fanin.go, line 21.
var cancel context.CancelFunc
var cancel context.CancelFunc. In ch15/agent/fanin.go, line 21.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel returns a derived context that points to the parent context but has a new Done channel.
WithCancel returns a derived context that points to the parent context but has a new Done channel. func WithCancel(parent Context) (ctx Context, cancel CancelFunc). Documented at pkg.go.dev, context.WithCancel.
func make(t Type, size ...IntegerType) Type
The make built-in function allocates and initializes an object of type slice, map, or chan (only).
The make built-in function allocates and initializes an object of type slice, map, or chan (only). func make(t Type, size ...IntegerType) Type. Documented at pkg.go.dev, builtin.make.
var quit chan struct{}
var quit chan struct{}. In ch15/agent/fanin.go, line 24.
package sync ("sync")
Package sync provides basic synchronization primitives such as mutual exclusion locks.
Package sync provides basic synchronization primitives such as mutual exclusion locks. package sync ("sync"). Documented at pkg.go.dev, sync.
type WaitGroup struct {
noCopy noCopy
// Bits (high to low):
// bits[0:32] counter
// bits[32] flag: synctest bubble membership
// bits[33:64] wait count
state atomic.Uint64
sema uint32
}
A WaitGroup is a counting semaphore typically used to wait for a group of goroutines or tasks to finish.
A WaitGroup is a counting semaphore typically used to wait for a group of goroutines or tasks to finish. type WaitGroup struct { noCopy noCopy // Bits (high to low): // bits[0:32] counter // bits[32] flag: synctest bubble membership // bits[33:64] wait count state atomic.Uint64 sema uint32 }. Documented at pkg.go.dev, sync.WaitGroup.
var run func(context.Context) iter.Seq[Event]
var run func(context.Context) iter.Seq[Event]. In ch15/agent/fanin.go, line 26.
func (wg *WaitGroup) Go(f func())
Go calls f in a new goroutine and adds that task to the [WaitGroup].
Go calls f in a new goroutine and adds that task to the [WaitGroup]. func (wg *WaitGroup) Go(f func()). Documented at pkg.go.dev, sync.WaitGroup.Go.
field Agent string
Agent names the run an event came from when several run at once; Merge sets it (chapter 9).
Agent names the run an event came from when several run at once; Merge sets it (chapter 9). field Agent string. In ch15/agent/events.go, line 45.
func (wg *WaitGroup) Wait()
Wait blocks until the [WaitGroup] task counter is zero.
Wait blocks until the [WaitGroup] task counter is zero. func (wg *WaitGroup) Wait(). Documented at pkg.go.dev, sync.WaitGroup.Wait.
func close(c chan<- Type)
The close built-in function closes a channel, which must be either bidirectional or send-only.
The close built-in function closes a channel, which must be either bidirectional or send-only. func close(c chan<- Type). Documented at pkg.go.dev, builtin.close.
func TestMergeLeak(t *testing.T)
TestMergeLeak reads one event from each fan-in and stops.
TestMergeLeak reads one event from each fan-in and stops. func TestMergeLeak(t *testing.T). In ch15/agent/shared_test.go, line 155.
var t *testing.T
var t *testing.T. In ch15/agent/shared_test.go, line 155.
package testing ("testing")
Package testing provides support for automated testing of Go packages.
Package testing provides support for automated testing of Go packages. package testing ("testing"). Documented at pkg.go.dev, testing.
type T struct {
common
denyParallel bool
tstate *testState // For running tests and subtests.
}
T is a type passed to Test functions to manage test state and support formatted test logs.
T is a type passed to Test functions to manage test state and support formatted test logs. type T struct { common denyParallel bool tstate *testState // For running tests and subtests. }. Documented at pkg.go.dev, testing.T.
var ctx context.Context
var ctx context.Context. In ch15/agent/shared_test.go, line 156.
func (c *common) Context() context.Context
Context returns a context that is canceled just before Cleanup-registered functions are called.
Context returns a context that is canceled just before Cleanup-registered functions are called. func (c *common) Context() context.Context. Documented at pkg.go.dev, testing.common.Context.
func leakyMerge(runs []iter.Seq[Event]) iter.Seq[Event]
leakyMerge is Merge without the cancel and the drain: after the caller stops reading, the runs' goroutines block forever on a send no one will receive.
leakyMerge is Merge without the cancel and the drain: after the caller stops reading, the runs' goroutines block forever on a send no one will receive. func leakyMerge(runs []iter.Seq[Event]) iter.Seq[Event]. In ch15/agent/shared_test.go, line 131.
package iter ("iter")
Package iter provides basic definitions and operations related to iterators over sequences.
Package iter provides basic definitions and operations related to iterators over sequences. package iter ("iter"). Documented at pkg.go.dev, iter.
func counter(n int) func(context.Context) iter.Seq[Event]
counter is a run that reports n text events and then stops.
counter is a run that reports n text events and then stops. func counter(n int) func(context.Context) iter.Seq[Event]. In ch15/agent/shared_test.go, line 94.
package context ("context")
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. package context ("context"). Documented at pkg.go.dev, context.
var b bytes.Buffer
var b bytes.Buffer. In ch15/agent/shared_test.go, line 164.
package bytes ("bytes")
Package bytes implements functions for the manipulation of byte slices.
Package bytes implements functions for the manipulation of byte slices. package bytes ("bytes"). Documented at pkg.go.dev, bytes.
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
lastRead readOp // last read operation, so that Unread* can work correctly.
// Copying and modifying a non-zero Buffer is prone to error,
// but we cannot employ the noCopy trick used by WaitGroup and Mutex,
// which causes vet's copylocks checker to report misuse, as vet
// cannot reliably distinguish the zero and non-zero cases.
// See #26462, #25907, #47276, #48398 for history.
}
A Buffer is a variable-sized buffer of bytes with [Buffer.Read] and [Buffer.Write] methods.
A Buffer is a variable-sized buffer of bytes with [Buffer.Read] and [Buffer.Write] methods. type Buffer struct { buf []byte // contents are the bytes buf[off : len(buf)] off int // read at &buf[off], write at &buf[len(buf)] lastRead readOp // last read operation, so that Unread* can work correctly. // Copying and modifying a non-zero Buffer is prone to error, // but we cannot employ the noCopy trick used by WaitGroup and Mutex, // which causes vet's copylocks checker to report misuse, as vet // cannot reliably distinguish the zero and non-zero cases. // See #26462, #25907, #47276, #48398 for history. }. Documented at pkg.go.dev, bytes.Buffer.
package pprof ("runtime/pprof")
Package pprof writes runtime profiling data in the format expected by the pprof visualization tool.
Package pprof writes runtime profiling data in the format expected by the pprof visualization tool. package pprof ("runtime/pprof"). Documented at pkg.go.dev, runtime/pprof.
func Lookup(name string) *Profile
Lookup returns the profile with the given name, or nil if no such profile exists.
Lookup returns the profile with the given name, or nil if no such profile exists. func Lookup(name string) *Profile. Documented at pkg.go.dev, runtime/pprof.Lookup.
func (p *Profile) WriteTo(w io.Writer, debug int) error
WriteTo writes a pprof-formatted snapshot of the profile to w.
WriteTo writes a pprof-formatted snapshot of the profile to w. func (p *Profile) WriteTo(w io.Writer, debug int) error. Documented at pkg.go.dev, runtime/pprof.Profile.WriteTo.
var nil Type
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type.
nil is a predeclared identifier representing the zero value for a pointer, channel, func, interface, map, or slice type. var nil Type. Documented at pkg.go.dev, builtin.nil.
func (c *common) Fatal(args ...any)
Fatal is equivalent to Log followed by FailNow.
Fatal is equivalent to Log followed by FailNow. func (c *common) Fatal(args ...any). Documented at pkg.go.dev, testing.common.Fatal.
func (c *common) Log(args ...any)
Log formats its arguments using default formatting, analogous to fmt.Println, and records the text in the error log.
Log formats its arguments using default formatting, analogous to fmt.Println, and records the text in the error log. func (c *common) Log(args ...any). Documented at pkg.go.dev, testing.common.Log.
func (b *Buffer) String() string
String returns the contents of the unread portion of the buffer as a string.
String returns the contents of the unread portion of the buffer as a string. func (b *Buffer) String() string. Documented at pkg.go.dev, bytes.Buffer.String.
package strings ("strings")
Package strings implements simple functions to manipulate UTF-8 encoded strings.
Package strings implements simple functions to manipulate UTF-8 encoded strings. package strings ("strings"). Documented at pkg.go.dev, strings.
func Contains(s, substr string) bool
Contains reports whether substr is within s.
Contains reports whether substr is within s. func Contains(s, substr string) bool. Documented at pkg.go.dev, strings.Contains.
func (c *common) Error(args ...any)
Error is equivalent to Log followed by Fail.
Error is equivalent to Log followed by Fail. func (c *common) Error(args ...any). Documented at pkg.go.dev, testing.common.Error.
type inflight struct {
c *conn
wg sync.WaitGroup
mu sync.Mutex
cancel map[string]context.CancelCauseFunc
}
inflight holds the requests that are running, by ID, with the cancel function of each, so a notifications/cancelled can stop it.
inflight holds the requests that are running, by ID, with the cancel function of each, so a notifications/cancelled can stop it. type inflight struct { c *conn wg sync.WaitGroup mu sync.Mutex cancel map[string]context.CancelCauseFunc }. In ch15/mcp/server.go, line 125.
type conn struct {
r *bufio.Reader
mu sync.Mutex
w io.Writer
}
A conn reads and writes messages, one per line.
A conn reads and writes messages, one per line. type conn struct { r *bufio.Reader mu sync.Mutex w io.Writer }. In ch15/mcp/jsonrpc.go, line 84.
field wg sync.WaitGroup
field wg sync.WaitGroup. In ch15/mcp/server.go, line 127.
package sync ("sync")
Package sync provides basic synchronization primitives such as mutual exclusion locks.
Package sync provides basic synchronization primitives such as mutual exclusion locks. package sync ("sync"). Documented at pkg.go.dev, sync.
type Mutex struct {
_ noCopy
mu isync.Mutex
}
A Mutex is a mutual exclusion lock.
A Mutex is a mutual exclusion lock. type Mutex struct { _ noCopy mu isync.Mutex }. Documented at pkg.go.dev, sync.Mutex.
field cancel map[string]context.CancelCauseFunc
field cancel map[string]context.CancelCauseFunc. In ch15/mcp/server.go, line 130.
package context ("context")
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. package context ("context"). Documented at pkg.go.dev, context.
type CancelCauseFunc func(cause error)
A CancelCauseFunc behaves like a [CancelFunc] but additionally sets the cancellation cause.
A CancelCauseFunc behaves like a [CancelFunc] but additionally sets the cancellation cause. type CancelCauseFunc func(cause error). Documented at pkg.go.dev, context.CancelCauseFunc.
type Server struct {
Info Implementation
Instructions string // for the model, sent by server/discover
Tools []Tool // listed in this order, the same every time
// PageSize is how many tools one tools/list page holds; 0 means
// every tool on one page.
PageSize int
// Log receives the server's log lines. A server on standard input
// ...
}
A Server offers tools over the protocol.
A Server offers tools over the protocol. type Server struct { Info Implementation Instructions string // for the model, sent by server/discover Tools []Tool // listed in this order, the same every time // PageSize is how many tools one tools/list page holds; 0 means // every tool on one page. PageSize int // Log receives the server's log lines. A server on standard input // ... }. In ch15/mcp/server.go, line 45.
func (s *Server) start(ctx context.Context, m message,
calls *inflight)
start runs a request in a goroutine of its own.
start runs a request in a goroutine of its own. func (s *Server) start(ctx context.Context, m message, calls *inflight). In ch15/mcp/server.go, line 135.
var ctx context.Context
var ctx context.Context. In ch15/mcp/server.go, line 135.
type message struct {
JSONRPC string `json:"jsonrpc"` // always "2.0"
ID jsontext.Value `json:"id,omitzero"`
Method string `json:"method,omitzero"`
Params jsontext.Value `json:"params,omitzero"`
Result jsontext.Value `json:"result,omitzero"`
Error *Error `json:"error,omitzero"`
}
A message is one JSON-RPC 2.0 message: a request has an ID and a method, a notification has a method and no ID, and a response has the ID of the request it answers and either a result or an error.
A message is one JSON-RPC 2.0 message: a request has an ID and a method, a notification has a method and no ID, and a response has the ID of the request it answers and either a result or an error. type message struct { JSONRPC string `json:"jsonrpc"` // always "2.0" ID jsontext.Value `json:"id,omitzero"` Method string `json:"method,omitzero"` Params jsontext.Value `json:"params,omitzero"` Result jsontext.Value `json:"result,omitzero"` Error *Error `json:"error,omitzero"` }. In ch15/mcp/jsonrpc.go, line 31.
func canonicalID(id jsontext.Value) string
canonicalID is a request ID in one spelling, so 7 and 7 written with different spacing are the same request.
canonicalID is a request ID in one spelling, so 7 and 7 written with different spacing are the same request. func canonicalID(id jsontext.Value) string. In ch15/mcp/server.go, line 439.
field ID jsontext.Value `json:"id,omitzero"`
field ID jsontext.Value `json:"id,omitzero"`. In ch15/mcp/jsonrpc.go, line 33.
var cancel context.CancelCauseFunc
var cancel context.CancelCauseFunc. In ch15/mcp/server.go, line 138.
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
WithCancelCause behaves like [WithCancel] but returns a [CancelCauseFunc] instead of a [CancelFunc].
WithCancelCause behaves like [WithCancel] but returns a [CancelCauseFunc] instead of a [CancelFunc]. func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc). Documented at pkg.go.dev, context.WithCancelCause.
func (f *inflight) add(id string, cancel context.CancelCauseFunc) bool
func (f *inflight) add(id string, cancel context.CancelCauseFunc) bool. In ch15/mcp/server.go, line 155.
func (s *Server) reply(c *conn, id, result jsontext.Value,
rpcErr *Error)
reply sends a response, logging a failure to send: the client has gone, and Serve finds out when its input ends.
reply sends a response, logging a failure to send: the client has gone, and Serve finds out when its input ends. func (s *Server) reply(c *conn, id, result jsontext.Value, rpcErr *Error). In ch15/mcp/server.go, line 224.
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data jsontext.Value `json:"data,omitzero"`
}
An Error is a JSON-RPC error: the request itself was wrong, or the server couldn't handle it.
An Error is a JSON-RPC error: the request itself was wrong, or the server couldn't handle it. type Error struct { Code int `json:"code"` Message string `json:"message"` Data jsontext.Value `json:"data,omitzero"` }. In ch15/mcp/jsonrpc.go, line 47.
field Code int `json:"code"`
field Code int `json:"code"`. In ch15/mcp/jsonrpc.go, line 48.
const CodeInvalidRequest = -32600
JSON, but not a request
JSON, but not a request const CodeInvalidRequest = -32600. In ch15/mcp/jsonrpc.go, line 61.
field Message string `json:"message"`
field Message string `json:"message"`. In ch15/mcp/jsonrpc.go, line 49.
func (f *inflight) remove(id string)
func (f *inflight) remove(id string). In ch15/mcp/server.go, line 166.
var result jsontext.Value
var result jsontext.Value. In ch15/mcp/server.go, line 146.
func (s *Server) handle(ctx context.Context, m message) (
jsontext.Value, *Error)
handle answers one request.
handle answers one request. func (s *Server) handle(ctx context.Context, m message) ( jsontext.Value, *Error). In ch15/mcp/server.go, line 246.
func (Context) Err() error
If Done is not yet closed, Err returns nil.
If Done is not yet closed, Err returns nil. func (Context) Err() error. Documented at pkg.go.dev, context.Context.Err.
var cancel context.CancelCauseFunc
var cancel context.CancelCauseFunc. In ch15/mcp/server.go, line 155.
func (m *Mutex) Lock()
Lock locks m.
Lock locks m. func (m *Mutex) Lock(). Documented at pkg.go.dev, sync.Mutex.Lock.
func (m *Mutex) Unlock()
Unlock unlocks m.
Unlock unlocks m. func (m *Mutex) Unlock(). Documented at pkg.go.dev, sync.Mutex.Unlock.
const false = 0 != 0
Untyped bool.
Untyped bool. const false = 0 != 0. Documented at pkg.go.dev, builtin.false.
const true = 0 == 0
Untyped bool.
Untyped bool. const true = 0 == 0. Documented at pkg.go.dev, builtin.true.
func delete(m map[Type]Type1, key Type)
The delete built-in function deletes the element with the specified key (m[key]) from the map.
The delete built-in function deletes the element with the specified key (m[key]) from the map. func delete(m map[Type]Type1, key Type). Documented at pkg.go.dev, builtin.delete.
func (s *Server) Serve(ctx context.Context, r io.Reader,
w io.Writer) error
Serve answers requests from r on w until r ends, each request in a goroutine of its own, and waits for them all before it returns.
Serve answers requests from r on w until r ends, each request in a goroutine of its own, and waits for them all before it returns. func (s *Server) Serve(ctx context.Context, r io.Reader, w io.Writer) error. In ch15/mcp/server.go, line 75.
var ctx context.Context
var ctx context.Context. In ch15/mcp/server.go, line 75.
package io ("io")
Package io provides basic interfaces to I/O primitives.
Package io provides basic interfaces to I/O primitives. package io ("io"). Documented at pkg.go.dev, io.
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader is the interface that wraps the basic Read method.
Reader is the interface that wraps the basic Read method. type Reader interface { Read(p []byte) (n int, err error) }. Documented at pkg.go.dev, io.Reader.
type Writer interface {
Write(p []byte) (n int, err error)
}
Writer is the interface that wraps the basic Write method.
Writer is the interface that wraps the basic Write method. type Writer interface { Write(p []byte) (n int, err error) }. Documented at pkg.go.dev, io.Writer.
type error interface {
Error() string
}
The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error.
The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error. type error interface { Error() string }. Documented at pkg.go.dev, builtin.error.
var stop context.CancelCauseFunc
var stop context.CancelCauseFunc. In ch15/mcp/server.go, line 77.
func newConn(r io.Reader, w io.Writer) *conn
func newConn(r io.Reader, w io.Writer) *conn. In ch15/mcp/jsonrpc.go, line 91.
var errServerStopped = errors.New("mcp: server stopped")
errServerStopped is the cause of a request's cancellation when the server stops reading, such as when its input closes.
errServerStopped is the cause of a request's cancellation when the server stops reading, such as when its input closes. var errServerStopped = errors.New("mcp: server stopped"). In ch15/mcp/server.go, line 64.
func (c *conn) receive() (message, error)
receive reads the next line and decodes it.
receive reads the next line and decodes it. func (c *conn) receive() (message, error). In ch15/mcp/jsonrpc.go, line 120.
package errors ("errors")
Package errors implements functions to manipulate errors.
Package errors implements functions to manipulate errors. package errors ("errors"). Documented at pkg.go.dev, errors.
func Is(err, target error) bool
Is reports whether any error in err's tree matches target.
Is reports whether any error in err's tree matches target. func Is(err, target error) bool. Documented at pkg.go.dev, errors.Is.
var EOF = errors.New("EOF")
EOF is the error returned by Read when no more input is available.
EOF is the error returned by Read when no more input is available. var EOF = errors.New("EOF"). Documented at pkg.go.dev, io.EOF.
func (f *inflight) finish(d time.Duration)
finish waits up to d for the running requests to end.
finish waits up to d for the running requests to end. func (f *inflight) finish(d time.Duration). In ch15/mcp/server.go, line 178.
const drainGrace = 2 * time.Second
drainGrace is how long a server whose input has closed waits for the requests still running to answer, before it cancels them.
drainGrace is how long a server whose input has closed waits for the requests still running to answer, before it cancels them. const drainGrace = 2 * time.Second. In ch15/mcp/server.go, line 175.
func AsType[E error](err error) (E, bool)
AsType finds the first error in err's tree that matches the type E, and if one is found, returns that error value and true.
AsType finds the first error in err's tree that matches the type E, and if one is found, returns that error value and true. func AsType[E error](err error) (E, bool). Documented at pkg.go.dev, errors.AsType.
package jsontext ("encoding/json/jsontext")
Package jsontext implements syntactic processing of JSON as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785.
Package jsontext implements syntactic processing of JSON as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785. package jsontext ("encoding/json/jsontext"). Documented at pkg.go.dev, encoding/json/jsontext.
type Value []byte
Value represents a single raw JSON value, which may be one of the following:
Value represents a single raw JSON value, which may be one of the following: type Value []byte. Documented at pkg.go.dev, encoding/json/jsontext.Value.
package fmt ("fmt")
Package fmt implements formatted I/O with functions analogous to C's printf and scanf.
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. package fmt ("fmt"). Documented at pkg.go.dev, fmt.
func Errorf(format string, a ...any) (err error)
Errorf formats according to a format specifier and returns the string as a value that satisfies error.
Errorf formats according to a format specifier and returns the string as a value that satisfies error. func Errorf(format string, a ...any) (err error). Documented at pkg.go.dev, fmt.Errorf.
func (s *Server) dispatch(ctx context.Context, m message,
calls *inflight)
dispatch acts on one message: a notification at once, a request in a goroutine of its own, and anything else with an error.
dispatch acts on one message: a notification at once, a request in a goroutine of its own, and anything else with an error. func (s *Server) dispatch(ctx context.Context, m message, calls *inflight). In ch15/mcp/server.go, line 105.
package time ("time")
Package time provides functionality for measuring and displaying time.
Package time provides functionality for measuring and displaying time. package time ("time"). Documented at pkg.go.dev, time.
type Duration int64
A Duration represents the elapsed time between two instants as an int64 nanosecond count.
A Duration represents the elapsed time between two instants as an int64 nanosecond count. type Duration int64. Documented at pkg.go.dev, time.Duration.
var done chan struct{}
var done chan struct{}. In ch15/mcp/server.go, line 179.
func After(d Duration) <-chan Time
After waits for the duration to elapse and then sends the current time on the returned channel.
After waits for the duration to elapse and then sends the current time on the returned channel. func After(d Duration) <-chan Time. Documented at pkg.go.dev, time.After.