micro--go-micro
3480874c28
govulncheck / govulncheck (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
When caller and callee run in the same process, a unary Call pays the full network tax — pool.Get, dial, codec-over-socket, and the transport pump — even though the handler table is right there. This adds an opt-in fast-path that dispatches directly. - internal/network: a neutral registry (transport.Message in/out) so client and server wire up without importing each other. A running server registers a dispatcher under its name on Start, deregisters on Stop. - server: localDispatch serves a request in-process through the same router (identical wrappers/codecs/error mapping) over an in-memory socket — no dial, no pipe, no gob. - client: LocalDispatch() opt-in. In call(), a unary request whose body and response are raw frames (codec/bytes.Frame — the agent/MCP/flow shape) dispatches locally; everything else falls back to the network path unchanged. Correctness test proves the fast-path returns byte-identical replies to the network path; benchmark shows ~545µs -> ~28µs (~20x) and ~3.6x fewer allocations. Off by default. Covers #4817 (path b); the zero-copy typed path remains a follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CmdEY7pYmV5zzwCjNJ4ykL
61 行
1.7 KiB
Go
61 行
1.7 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
|
|
raw "go-micro.dev/v6/codec/bytes"
|
|
"go-micro.dev/v6/internal/network"
|
|
"go-micro.dev/v6/metadata"
|
|
"go-micro.dev/v6/transport"
|
|
"go-micro.dev/v6/transport/headers"
|
|
)
|
|
|
|
// localCall is the in-process fast-path for Call. When LocalDispatch is enabled
|
|
// and the callee runs in this same process, a unary request whose body and
|
|
// response are raw frames (codec/bytes.Frame) is dispatched straight to the
|
|
// server's handlers via internal/network — no dial, no codec-over-socket,
|
|
// no transport pump. It returns handled=false to fall back to the network path
|
|
// for anything it does not cover (disabled, streaming, non-frame bodies, or a
|
|
// service not registered in-process), so behavior is unchanged unless the
|
|
// fast-path fully applies.
|
|
func (r *rpcClient) localCall(ctx context.Context, req Request, resp interface{}) (handled bool, err error) {
|
|
if !r.opts.LocalDispatch || req.Stream() {
|
|
return false, nil
|
|
}
|
|
reqFrame, ok := req.Body().(*raw.Frame)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
respFrame, ok := resp.(*raw.Frame)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
dispatch, ok := network.Lookup(req.Service())
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
header := make(map[string]string)
|
|
if md, ok := metadata.FromContext(ctx); ok {
|
|
for k, v := range md {
|
|
if k == headers.Message { // pub/sub topic header, never forwarded
|
|
continue
|
|
}
|
|
header[k] = v
|
|
}
|
|
}
|
|
header[headers.Request] = req.Service()
|
|
header[headers.Endpoint] = req.Endpoint()
|
|
header["Content-Type"] = req.ContentType()
|
|
header["Accept"] = req.ContentType()
|
|
|
|
reply, err := dispatch(ctx, &transport.Message{Header: header, Body: reqFrame.Data})
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
if reply != nil {
|
|
respFrame.Data = reply.Body
|
|
}
|
|
return true, nil
|
|
}
|