diff --git a/AGENTS.md b/AGENTS.md index 24f94204c..672f68f44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,16 @@ Generated `zorums_*_gorums.pb.go` files in `cmd/protoc-gen-gorums/dev/` are outp Never edit any generated file to satisfy a linter; fix the generator or its inputs and regenerate. +### Versions + +Never bump versions on a feature branch, even for a breaking generator or runtime change. +This covers `internal/version/version.go`, `GenVersion` and `MinVersion` in `runtime/gorumsimpl/version.go`, the gorums requirement in `benchkit/go.mod` and `examples/go.mod`, and the `replace` in `go.work`. +`gorums-release prepare` changes all of them together at release time; a partial bump breaks the nested modules. +Instead, state in the PR description which flag the next release needs: + +- `-bump-gen`: the generated code needs a new runtime feature. +- `-bump-min`: code generated by an earlier release no longer compiles against the runtime. + ## Tests Follow TDD: write a failing test, watch it fail, make it pass, refactor. diff --git a/benchkit/benchmark/benchmark_gorums.pb.go b/benchkit/benchmark/benchmark_gorums.pb.go index a8394b10c..05279fb3b 100644 --- a/benchkit/benchmark/benchmark_gorums.pb.go +++ b/benchkit/benchmark/benchmark_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -84,23 +85,15 @@ type BenchmarkServer interface { } func RegisterBenchmarkServer(srv *gorums.Server, impl BenchmarkServer) { - srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Echo](in) - resp, err := impl.QuorumCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("benchmark.Benchmark.SlowServer", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchmark.Benchmark.SlowServer", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Echo](in) - resp, err := impl.SlowServer(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.SlowServer(ctx, req) }) - srv.RegisterHandler("benchmark.Benchmark.Multicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchmark.Benchmark.Multicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*TimedMsg](in) impl.Multicast(ctx, req) return nil, nil diff --git a/benchkit/benchmark/benchmark_test.go b/benchkit/benchmark/benchmark_test.go index 7842f56c0..21e28a3ac 100644 --- a/benchkit/benchmark/benchmark_test.go +++ b/benchkit/benchmark/benchmark_test.go @@ -13,6 +13,7 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/benchkit" "github.com/relab/gorums/gorumstest" + proto "google.golang.org/protobuf/proto" ) func TestDescriptions(t *testing.T) { @@ -249,7 +250,7 @@ func TestRunAsyncQCBenchmarkRejectsRateRamp(t *testing.T) { // to registerReplyDroppingPeer's silent drop, which instead times out). Call // before serving srv, since it registers a service. func registerFailingQuorumCallPeer(srv *gorums.Server, errMsg string) { - srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { return nil, errors.New(errMsg) }) } @@ -645,11 +646,11 @@ func TestServerMeasuredVerify(t *testing.T) { // leak deterministically detectable. func TestServerMeasuredWindowExcludesClockSync(t *testing.T) { const clockSyncDelay = 2 * time.Millisecond - delayClockSync := func(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { + delayClockSync := func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { if in.GetMethod() == "benchkit.Control.ClockSync" { time.Sleep(clockSyncDelay) } - return next(ctx, in) + return next(ctx, in, send) } target := symmetricServers(t, 3, gorums.WithServerInterceptors(delayClockSync)) diff --git a/benchkit/benchmark/symmetric_test.go b/benchkit/benchmark/symmetric_test.go index ab82b18f1..d81ced67f 100644 --- a/benchkit/benchmark/symmetric_test.go +++ b/benchkit/benchmark/symmetric_test.go @@ -19,6 +19,7 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/benchkit" "github.com/relab/gorums/gorumstest" + proto "google.golang.org/protobuf/proto" ) // captureDiag redirects the probe-stall self-diagnosis to a buffer for the @@ -124,11 +125,11 @@ func symmetricServers(t *testing.T, n int, serverOpts ...gorums.ServerOption) *S func registerReplyDroppingPeer(srv *gorums.Server, drop int64) { var remaining atomic.Int64 remaining.Store(drop) - srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchmark.Benchmark.QuorumCall", func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { if remaining.Add(-1) >= 0 { return nil, nil // no response and no error: nothing is sent back } - return gorums.NewResponseMessage(in, gorums.AsProto[*Echo](in)), nil + return gorums.AsProto[*Echo](in), nil }) } diff --git a/benchkit/control_gorums.pb.go b/benchkit/control_gorums.pb.go index fe254b7ad..4c7edf03c 100644 --- a/benchkit/control_gorums.pb.go +++ b/benchkit/control_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -101,31 +102,19 @@ type ControlServer interface { } func RegisterControlServer(srv *gorums.Server, impl ControlServer) { - srv.RegisterHandler("benchkit.Control.Start", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchkit.Control.Start", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*StartRequest](in) - resp, err := impl.Start(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Start(ctx, req) }) - srv.RegisterHandler("benchkit.Control.Stop", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchkit.Control.Stop", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*StopRequest](in) - resp, err := impl.Stop(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Stop(ctx, req) }) - srv.RegisterHandler("benchkit.Control.ClockSync", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchkit.Control.ClockSync", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*ClockSyncRequest](in) - resp, err := impl.ClockSync(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.ClockSync(ctx, req) }) - srv.RegisterHandler("benchkit.Control.Done", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("benchkit.Control.Done", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*DoneRequest](in) impl.Done(ctx, req) return nil, nil diff --git a/call_async_test.go b/call_async_test.go index 0ef8d1c0d..479bf497b 100644 --- a/call_async_test.go +++ b/call_async_test.go @@ -10,6 +10,7 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -180,9 +181,9 @@ func TestCallAsyncSendsBeforeReturn(t *testing.T) { config := gorumstest.Config(t, numNodes, func(int) gorumstest.ServerIface { srv := gorums.NewServer() echo := gorumstest.EchoHandler("echo") - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { received <- struct{}{} - return echo(ctx, in) + return echo(ctx, in, send) }) return srv }) diff --git a/call_test.go b/call_test.go index d3e137955..34d3fcbae 100644 --- a/call_test.go +++ b/call_test.go @@ -18,6 +18,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -28,7 +29,7 @@ import ( func TestOnewayNoResourceLeak(t *testing.T) { servers := gorumstest.LocalServers(t, 3) for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { return nil, nil }) } @@ -62,7 +63,7 @@ func TestOnewayDroppedHandleDoesNotDispatch(t *testing.T) { var received atomic.Int32 servers := gorumstest.LocalServers(t, 3) for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { received.Add(1) return nil, nil }) @@ -86,7 +87,7 @@ func TestOnewayDroppedHandleDoesNotDispatch(t *testing.T) { func TestOnewayCallDoubleDispatchPanics(t *testing.T) { servers := gorumstest.LocalServers(t, 3) for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { return nil, nil }) } @@ -137,7 +138,7 @@ func TestOnewayCallAsync(t *testing.T) { var received atomic.Int32 servers := gorumstest.LocalServers(t, 3) for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { received.Add(1) return nil, nil }) diff --git a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go index 0d7d59fcb..cf834bcb2 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go @@ -9,6 +9,7 @@ package dev import ( gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -44,78 +45,59 @@ type ZorumsServiceServer interface { } func RegisterZorumsServiceServer(srv *gorums.Server, impl ZorumsServiceServer) { - srv.RegisterHandler("dev.ZorumsService.RemoteCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.RemoteCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.RemoteCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.RemoteCall(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.QuorumCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*emptypb.Empty](in) - resp, err := impl.QuorumCallWithEmptyRequest(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCallWithEmptyRequest(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.QuorumCallWithEmptyResponse(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCallWithEmptyResponse(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.MulticastWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.MulticastWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.MulticastWithEmptyResponse(ctx, req) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.MulticastSecondMethod", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.MulticastSecondMethod", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.MulticastSecondMethod(ctx, req) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.MulticastWithEmptyRequestAndResponse", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.MulticastWithEmptyRequestAndResponse", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*emptypb.Empty](in) impl.MulticastWithEmptyRequestAndResponse(ctx, req) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallStream", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCallStream", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.QuorumCallStream(ctx, req, func(resp *Response) { - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.QuorumCallStreamWithEmptyResponse(ctx, req, func(resp *emptypb.Empty) { - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*emptypb.Empty](in) impl.QuorumCallStreamWithEmptyRequest(ctx, req, func(resp *Response) { - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.Unicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("dev.ZorumsService.Unicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Unicast(ctx, req) return nil, nil diff --git a/cmd/protoc-gen-gorums/gengorums/template_funcs.go b/cmd/protoc-gen-gorums/gengorums/template_funcs.go index 7c0c43404..bda751ee4 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_funcs.go +++ b/cmd/protoc-gen-gorums/gengorums/template_funcs.go @@ -18,6 +18,7 @@ var importMap = map[string]protogen.GoImportPath{ "context": protogen.GoImportPath("context"), "gorums": protogen.GoImportPath("github.com/relab/gorums"), "gorumsimpl": protogen.GoImportPath("github.com/relab/gorums/runtime/gorumsimpl"), + "proto": protogen.GoImportPath("google.golang.org/protobuf/proto"), } func addImport(path, ident string, g *protogen.GeneratedFile) string { diff --git a/cmd/protoc-gen-gorums/gengorums/template_server.go b/cmd/protoc-gen-gorums/gengorums/template_server.go index b7252a950..8fc97780e 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_server.go +++ b/cmd/protoc-gen-gorums/gengorums/template_server.go @@ -73,33 +73,28 @@ var serverInterface = ` var registerInterface = ` {{$genFile := .GenFile}} {{$gorumsMessage := use "gorums.Message" .GenFile}} +{{$protoMessage := use "proto.Message" .GenFile}} {{$asProto := use "gorums.AsProto" .GenFile}} -{{$newMessage := use "gorums.NewResponseMessage" $genFile}} {{$gorumsServer := use "gorums.Server" $genFile}} {{range .Services -}} {{$names := .Names}} -{{$reg := localNames "srv impl" $context $gorumsMessage $asProto $newMessage (methodTypes $genFile .Service)}} +{{$reg := localNames "srv impl" $context $gorumsMessage $protoMessage $asProto (methodTypes $genFile .Service)}} func {{$names.Register}}({{$reg.srv}} *{{$gorumsServer}}, {{$reg.impl}} {{$names.Interface}}) { {{- range .Service.Methods}} - {{- $local := localNames "ctx in req resp err out" $context $gorumsMessage $asProto $newMessage (in $genFile .) (out $genFile .) $reg.impl}} - {{$reg.srv}}.RegisterHandler("{{.Desc.FullName}}", func({{$local.ctx}} {{$context}}, {{$local.in}} *{{$gorumsMessage}}) (*{{$gorumsMessage}}, error) { + {{- $local := localNames "ctx in req resp send" $context $gorumsMessage $protoMessage $asProto (in $genFile .) (out $genFile .) $reg.impl}} + {{$reg.srv}}.RegisterHandler("{{.Desc.FullName}}", func({{$local.ctx}} {{$context}}, {{$local.in}} *{{$gorumsMessage}}, {{if isStreamingServer .}}{{$local.send}}{{else}}_{{end}} func({{$protoMessage}})) ({{$protoMessage}}, error) { {{$local.req}} := {{$asProto}}[*{{in $genFile .}}]({{$local.in}}) {{- if isOneway .}} {{$reg.impl}}.{{.GoName}}({{$local.ctx}}, {{$local.req}}) return nil, nil {{- else if isStreamingServer .}} {{$reg.impl}}.{{.GoName}}({{$local.ctx}}, {{$local.req}}, func({{$local.resp}} *{{out $genFile .}}) { - {{$local.out}} := {{$newMessage}}({{$local.in}}, {{$local.resp}}) - {{$local.ctx}}.SendMessage({{$local.out}}) + {{$local.send}}({{$local.resp}}) }) return nil, nil {{- else }} - {{$local.resp}}, {{$local.err}} := {{$reg.impl}}.{{.GoName}}({{$local.ctx}}, {{$local.req}}) - if {{$local.err}} != nil { - return nil, {{$local.err}} - } - return {{$newMessage}}({{$local.in}}, {{$local.resp}}), nil + return {{$reg.impl}}.{{.GoName}}({{$local.ctx}}, {{$local.req}}) {{- end}} }) {{- end}} diff --git a/doc/dev-guide.md b/doc/dev-guide.md index c78dd5d1c..fb4067fed 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -195,16 +195,16 @@ Golden files replace the tool versions in the header with `VERSION`, so a releas An error string describes the operation that was attempted, not the failure. Wrapped errors then read like prose: `dialing node 3: creating stream: connection refused`. -- Do not start an error with `failed to`, `could not`, `unable to`, or `error`. +* Do not start an error with `failed to`, `could not`, `unable to`, or `error`. Write `creating node %d: %w`, not `failed to create node %d: %w`. -- An error that wraps nothing names the condition: `no nodes in configuration`. -- A precondition has the form `cannot X without Y`. -- Do not add an annotation that only says something failed; return the error unchanged. -- Use a lowercase first letter, no trailing punctuation, and no newlines. +* An error that wraps nothing names the condition: `no nodes in configuration`. +* A precondition has the form `cannot X without Y`. +* Do not add an annotation that only says something failed; return the error unchanged. +* Use a lowercase first letter, no trailing punctuation, and no newlines. Sentinel errors (`ErrXxx`) name the condition. -- Only the layer that creates a Gorums error adds the `gorums: ` prefix. +* Only the layer that creates a Gorums error adds the `gorums: ` prefix. A caller that wraps such an error does not add it again. -- Messages that an application end user reads directly, such as `protoc-gen-gorums` and command-line diagnostics, may keep explicit failure wording. +* Messages that an application end user reads directly, such as `protoc-gen-gorums` and command-line diagnostics, may keep explicit failure wording. `TestErrorStringsDescribeOperation` in `internal/lint` rejects the banned prefixes in `errors.New` and `fmt.Errorf` calls. @@ -625,7 +625,14 @@ sequenceDiagram The root `gorums.Server` ties the layers together on the server side. It registers a `stream.Server` with its gRPC server, uses its `conn.InboundManager` as the server's `stream.PeerAcceptor`, and serves as the `stream.RequestHandler` for inbound streams, for its local node, and for back-channel requests on its outbound peer configuration. `Server.HandleRequest` creates a `ServerContext` per request, records the sender ID that the channel passes in, and runs the registered `Handler`, wrapped by any `ServerInterceptor`s. -A `gorums.Message` wraps a `stream.Message` together with its decoded proto payload. +A `gorums.Message` wraps a request's `stream.Message` together with its decoded proto payload. +How a handler delivers its responses depends on the call type: + +* A unary handler returns its response as a `proto.Message`. +* A streaming handler calls its `send func(proto.Message)` callback once for each response, and returns `nil, nil`. +* A one-way handler returns `nil, nil`, and the server sends no response. + +`Server.HandleRequest` builds the response envelope for the returned response and for each sent response, so handlers never touch envelopes. ```mermaid classDiagram @@ -639,12 +646,11 @@ classDiagram } class ServerContext { Release() - SendMessage(Message) PeerConfig() Config ConnectedClients() Config } class Handler { - <> + <> } class ServerInterceptor { <> @@ -700,7 +706,7 @@ sequenceDiagram P->>IC: request Message IC->>D: push handler job D->>H: HandleRequest(ctx, senderID, msg, release, send) - H->>H: Handler(ServerContext, Message) + H->>H: Handler(ServerContext, Message, send) H->>IC: send(reply): push on sendQueue IC->>P: Send(reply) on send loop ``` diff --git a/doc/user-guide.md b/doc/user-guide.md index 7c26affce..4488750db 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -1340,10 +1340,24 @@ Gorums also supports server-side interceptors that wrap inbound RPC handlers, si A server-side interceptor implements the `gorums.ServerInterceptor` signature: ```go -type ServerInterceptor func(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) +type ServerInterceptor func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) ``` -You can pass multiple interceptors when starting a Gorums server. They can perform logging, latency injection, metadata insertion, and request validation before sending the request to the handler. +You can pass multiple interceptors when starting a Gorums server. +They can perform logging, latency injection, metadata insertion, and request validation before sending the request to the handler. + +How an interceptor sees the handler's responses depends on the call type: + +* Unary call: `next` returns the response. + The interceptor can inspect or replace the response before it returns it. +* Streaming call: the handler calls `send` once for each response, and `next` returns `nil, nil`. + To see each response, the interceptor gives `next` a new `send` function. + This function inspects or replaces the response, and then calls the original `send`. + The [stream logging interceptor](#server-side-stream-logging-interceptor) below shows this pattern. +* One-way call: the handler sends no response, and `next` returns `nil, nil`. + +An interceptor that does not need the streamed responses passes its `send` argument to `next` unchanged. +It must not pass `nil`, because then a streaming handler cannot send its responses. Below are several examples based on the `examples/interceptors` package. @@ -1351,40 +1365,56 @@ Below are several examples based on the `examples/interceptors` package. ```go func LoggingInterceptor(addr string) gorums.ServerInterceptor { - return func(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { + return func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { req := gorums.AsProto[proto.Message](in) log.Printf("[%s]: LoggingInterceptor(incoming): Method=%s, Message=%s", addr, in.GetMethod(), req) start := time.Now() - out, err := next(ctx, in) + out, err := next(ctx, in, send) duration := time.Since(start) - resp := gorums.AsProto[proto.Message](out) - log.Printf("[%s]: LoggingInterceptor(outgoing): Method=%s, Duration=%s, Err=%v, Message=%v", addr, in.GetMethod(), duration, err, resp) + log.Printf("[%s]: LoggingInterceptor(outgoing): Method=%s, Duration=%s, Err=%v, Message=%v", addr, in.GetMethod(), duration, err, out) return out, err } } ``` +The logging interceptor above sees only unary responses, because `next` returns `nil` for a streaming call. + +#### Server-Side Stream Logging Interceptor + +This interceptor logs each response of a streaming call. +It passes `next` a new `send` function, which logs the response and then calls the original `send`: + +```go +func StreamLoggingInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { + logAndSend := func(resp proto.Message) { + log.Printf("StreamLoggingInterceptor: Method=%s, Message=%v", in.GetMethod(), resp) + send(resp) + } + return next(ctx, in, logAndSend) +} +``` + #### Delay and Metadata Interceptors Interceptors can inject arbitrary delays based on client properties or attach metadata to the incoming requests: ```go -func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { delay := 50 * time.Millisecond time.Sleep(delay) - return next(ctx, in) + return next(ctx, in, send) } -func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { // Inject a custom metadata field for the handler entry := &gorums.MetadataEntry{} entry.SetKey("customKey") entry.SetValue("customValue") in.SetEntry([]*gorums.MetadataEntry{entry}) - return next(ctx, in) + return next(ctx, in, send) } ``` @@ -1394,13 +1424,13 @@ A server interceptor can also stop a request from reaching the handler entirely. ```go // NoFooAllowedInterceptor rejects requests for messages with key "foo". -func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { if req, ok := gorums.AsProto[proto.Message](in).(T); ok { if req.GetKey() == "foo" { return nil, fmt.Errorf("requests for key 'foo' are not allowed") } } - return next(ctx, in) + return next(ctx, in, send) } ``` diff --git a/examples/interceptors/server_interceptors.go b/examples/interceptors/server_interceptors.go index 39f851a1a..51c691bfc 100644 --- a/examples/interceptors/server_interceptors.go +++ b/examples/interceptors/server_interceptors.go @@ -15,33 +15,42 @@ import ( // LoggingInterceptor returns an interceptor that logs each request and its // response, tagged with addr, together with the time the handler took. func LoggingInterceptor(addr string) gorums.ServerInterceptor { - return func(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { + return func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { req := gorums.AsProto[proto.Message](in) log.Printf("[%s]: LoggingInterceptor(incoming): Method=%s, Message=%s", addr, in.GetMethod(), req) start := time.Now() - out, err := next(ctx, in) + out, err := next(ctx, in, send) duration := time.Since(start) - resp := gorums.AsProto[proto.Message](out) - log.Printf("[%s]: LoggingInterceptor(outgoing): Method=%s, Duration=%s, Err=%v, Message=%v, Type=%T", addr, in.GetMethod(), duration, err, resp, resp) + log.Printf("[%s]: LoggingInterceptor(outgoing): Method=%s, Duration=%s, Err=%v, Message=%v, Type=%T", addr, in.GetMethod(), duration, err, out, out) return out, err } } // LoggingSimpleInterceptor logs each request and its response. -func LoggingSimpleInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func LoggingSimpleInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { req := gorums.AsProto[proto.Message](in) log.Printf("LoggingSimpleInterceptor(incoming): Method=%s, Message=%v)", in.GetMethod(), req) - out, err := next(ctx, in) - resp := gorums.AsProto[proto.Message](out) - log.Printf("LoggingSimpleInterceptor(outgoing): Method=%s, Err=%v, Message=%v", in.GetMethod(), err, resp) + out, err := next(ctx, in, send) + log.Printf("LoggingSimpleInterceptor(outgoing): Method=%s, Err=%v, Message=%v", in.GetMethod(), err, out) return out, err } +// StreamLoggingInterceptor logs each response that a streaming handler sends. +// It passes next a send function that logs the response and then calls the +// original send. +func StreamLoggingInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { + logAndSend := func(resp proto.Message) { + log.Printf("StreamLoggingInterceptor: Method=%s, Message=%v", in.GetMethod(), resp) + send(resp) + } + return next(ctx, in, logAndSend) +} + // DelayedInterceptor delays each request before passing it on. The delay grows // with the length of the sender's network address; without a known sender // address there is no delay. -func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { // delay based on sending node address delay := 0 * time.Millisecond p, ok := peer.FromContext(ctx) @@ -57,13 +66,13 @@ func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorum time.Sleep(delay) // Call the next handler in the chain - out, err := next(ctx, in) + out, err := next(ctx, in, send) log.Printf("DelayedInterceptor: Finished processing message after %s", delay) return out, err } // NoFooAllowedInterceptor rejects requests for messages with key "foo". -func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { if req, ok := gorums.AsProto[proto.Message](in).(T); ok { log.Printf("NoFooAllowedInterceptor: Received request for key '%s'", req.GetKey()) if req.GetKey() == "foo" { @@ -71,12 +80,12 @@ func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerCo return nil, errors.New("requests for key 'foo' are not allowed") } } - return next(ctx, in) + return next(ctx, in, send) } // MetadataInterceptor sets each request's metadata to the single entry // customKey=customValue before passing the request on. -func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { +func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { log.Printf("MetadataInterceptor: Adding custom metadata to message(customKey=customValue)") // Add a custom metadata field entry := &gorums.MetadataEntry{} @@ -85,7 +94,7 @@ func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, next goru in.SetEntry([]*gorums.MetadataEntry{entry}) // Call the next handler in the chain - out, err := next(ctx, in) + out, err := next(ctx, in, send) log.Printf("MetadataInterceptor: Finished processing message with custom metadata") return out, err } diff --git a/examples/storage/proto/storage_gorums.pb.go b/examples/storage/proto/storage_gorums.pb.go index 3d06881da..f16a0ebad 100644 --- a/examples/storage/proto/storage_gorums.pb.go +++ b/examples/storage/proto/storage_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -164,69 +165,44 @@ type StorageServer interface { } func RegisterStorageServer(srv *gorums.Server, impl StorageServer) { - srv.RegisterHandler("proto.Storage.ReadRPC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.ReadRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*ReadRequest](in) - resp, err := impl.ReadRPC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.ReadRPC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteRPC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.WriteRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*WriteRequest](in) - resp, err := impl.WriteRPC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.WriteRPC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteUnicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.WriteUnicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*WriteRequest](in) impl.WriteUnicast(ctx, req) return nil, nil }) - srv.RegisterHandler("proto.Storage.WriteMulticast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.WriteMulticast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*WriteRequest](in) impl.WriteMulticast(ctx, req) return nil, nil }) - srv.RegisterHandler("proto.Storage.ReadQC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.ReadQC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*ReadRequest](in) - resp, err := impl.ReadQC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.ReadQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteQC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.WriteQC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*WriteRequest](in) - resp, err := impl.WriteQC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.WriteQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.ReadNestedQC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.ReadNestedQC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*ReadRequest](in) - resp, err := impl.ReadNestedQC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.ReadNestedQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteNestedMulticast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.WriteNestedMulticast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*WriteRequest](in) - resp, err := impl.WriteNestedMulticast(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.WriteNestedMulticast(ctx, req) }) - srv.RegisterHandler("proto.Storage.ReadCorrectable", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("proto.Storage.ReadCorrectable", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*ReadRequest](in) impl.ReadCorrectable(ctx, req, func(resp *ReadResponse) { - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) }) return nil, nil }) diff --git a/gorumstest/gorumstest.go b/gorumstest/gorumstest.go index ecf8063f4..56ca2c3be 100644 --- a/gorumstest/gorumstest.go +++ b/gorumstest/gorumstest.go @@ -19,6 +19,7 @@ import ( "go.uber.org/goleak" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" ) // Context creates a context with timeout for testing. @@ -42,7 +43,7 @@ func ServerContext(t testing.TB, senderID gorums.ID) gorums.ServerContext { ran bool ) srv := gorums.NewServer() - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { srvCtx, ran = ctx, true return nil, nil }) diff --git a/gorumstest/servers.go b/gorumstest/servers.go index c1fad1979..469c86655 100644 --- a/gorumstest/servers.go +++ b/gorumstest/servers.go @@ -9,6 +9,7 @@ import ( "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/servers" "go.uber.org/goleak" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -143,21 +144,21 @@ func WaitForPeers(t testing.TB, srvs []*gorums.Server) { func newDefaultServer(i int, opts ...gorums.ServerOption) ServerIface { srv := gorums.NewServer(opts...) ts := testSrv{val: int32((i + 1) * 10)} - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) resp, err := ts.Test(ctx, req) if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return resp, nil }) - srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.Int32Value](in) resp, err := ts.GetValue(ctx, req) if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return resp, nil }) return srv } @@ -177,9 +178,9 @@ func (ts testSrv) GetValue(_ gorums.ServerContext, _ *pb.Int32Value) (*pb.Int32V // EchoHandler returns a handler that replies to a string-valued request with // prefix+": "+value. func EchoHandler(prefix string) gorums.Handler { - return func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + return func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String(prefix+": "+req.GetValue())), nil + return pb.String(prefix + ": " + req.GetValue()), nil } } @@ -198,15 +199,14 @@ func EchoServer(_ int) ServerIface { func StreamServer(delay time.Duration) func(int) ServerIface { return func(int) ServerIface { srv := gorums.NewServer() - srv.RegisterHandler(mock.StreamMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) val := req.GetValue() // Send 3 responses for i := 1; i <= 3; i++ { resp := pb.String(fmt.Sprintf("echo: %s-%d", val, i)) - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) if delay > 0 { time.Sleep(delay) } diff --git a/internal/stream/channel.go b/internal/stream/channel.go index 0c2aeaa06..c5d48d37c 100644 --- a/internal/stream/channel.go +++ b/internal/stream/channel.go @@ -47,12 +47,12 @@ type Channel interface { // the stream start before HandleRequest returns; otherwise the next request // starts when HandleRequest returns. // -// The send function delivers a response message to the peer. On a stream, it -// does not wait for send queue space, except on an [InboundChannel] created -// with [InboundOptions.WaitingReplies]. For two-way call types, send may be -// called zero or more times (e.g., for streaming correctable calls). For -// one-way call types, the peer has no pending call to receive a response, so -// it is dropped. +// The send function must be non-nil and delivers a response message to the +// peer. On a stream, the send function does not wait for queue space, except +// on an [InboundChannel] created with [InboundOptions.WaitingReplies]. +// For two-way call types, send may be called zero or more times (e.g., for +// streaming correctable calls). For one-way call types, the peer has no +// pending call to receive a response, so it is dropped. type RequestHandler interface { HandleRequest(ctx context.Context, senderID ID, msg *Message, release func(), send func(*Message)) } diff --git a/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden index 1b8cb8ec9..916cee2d9 100644 --- a/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden @@ -11,6 +11,7 @@ import ( gorums "github.com/relab/gorums" msgs "github.com/relab/gorums/internal/testprotos/msgs" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -54,12 +55,8 @@ type QuorumCallServer interface { } func RegisterQuorumCallServer(srv *gorums.Server, impl QuorumCallServer) { - srv.RegisterHandler("internal.testprotos.calltypes.importmsgs.QuorumCall.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.importmsgs.QuorumCall.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*msgs.Request](in) - resp, err := impl.QuorumCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) } diff --git a/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden index 62d715a6f..274d8671b 100644 --- a/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -64,7 +65,7 @@ type MulticastServer interface { } func RegisterMulticastServer(srv *gorums.Server, impl MulticastServer) { - srv.RegisterHandler("internal.testprotos.calltypes.multicast.Multicast.Multicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.multicast.Multicast.Multicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Multicast(ctx, req) return nil, nil diff --git a/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden index ca783b29b..2cb5432c3 100644 --- a/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -140,34 +141,22 @@ type BetaServer interface { } func RegisterAlphaServer(srv *gorums.Server, impl AlphaServer) { - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Alpha.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Alpha.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Heartbeat(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Read(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Read(ctx, req) }) } func RegisterBetaServer(srv *gorums.Server, impl BetaServer) { - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Beta.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Beta.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Heartbeat(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Write(ctx, req) return nil, nil diff --git a/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden index 22631f43f..c7d109720 100644 --- a/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -53,12 +54,8 @@ type QuorumCallServer interface { } func RegisterQuorumCallServer(srv *gorums.Server, impl QuorumCallServer) { - srv.RegisterHandler("internal.testprotos.calltypes.quorumcall.QuorumCall.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.calltypes.quorumcall.QuorumCall.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.QuorumCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) } diff --git a/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden b/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden index 5748fa428..4872ba1ee 100644 --- a/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden +++ b/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -109,28 +110,20 @@ type ClientNamesServer interface { } func RegisterClientNamesServer(srv *gorums.Server, impl ClientNamesServer) { - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.Config", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.Config", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Config(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Config(ctx, req) }) - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.WithConfig", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.WithConfig", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.WithConfig(ctx, req) return nil, nil }) - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.Node", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.Node", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Node(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Node(ctx, req) }) - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.ForNode", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.ForNode", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.ForNode(ctx, req) return nil, nil diff --git a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden index cafdbd9e5..ff1c290c0 100644 --- a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden +++ b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -78,15 +79,11 @@ type NodeOnlyServer interface { } func RegisterNodeOnlyServer(srv *gorums.Server, impl NodeOnlyServer) { - srv.RegisterHandler("internal.testprotos.nodeonly.NodeOnly.Read", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.nodeonly.NodeOnly.Read", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Read(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Read(ctx, req) }) - srv.RegisterHandler("internal.testprotos.nodeonly.NodeOnly.Write", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("internal.testprotos.nodeonly.NodeOnly.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Write(ctx, req) return nil, nil diff --git a/internal/tests/correctable/correctable_gorums.pb.go b/internal/tests/correctable/correctable_gorums.pb.go index 2719667e4..5c3e9e09d 100644 --- a/internal/tests/correctable/correctable_gorums.pb.go +++ b/internal/tests/correctable/correctable_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -69,19 +70,14 @@ type CorrectableTestServer interface { } func RegisterCorrectableTestServer(srv *gorums.Server, impl CorrectableTestServer) { - srv.RegisterHandler("correctable.CorrectableTest.Correctable", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("correctable.CorrectableTest.Correctable", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Correctable(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Correctable(ctx, req) }) - srv.RegisterHandler("correctable.CorrectableTest.CorrectableStream", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("correctable.CorrectableTest.CorrectableStream", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.CorrectableStream(ctx, req, func(resp *Response) { - out := gorums.NewResponseMessage(in, resp) - ctx.SendMessage(out) + send(resp) }) return nil, nil }) diff --git a/internal/tests/metadata/metadata_gorums.pb.go b/internal/tests/metadata/metadata_gorums.pb.go index bc98de149..966afb8eb 100644 --- a/internal/tests/metadata/metadata_gorums.pb.go +++ b/internal/tests/metadata/metadata_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -57,20 +58,12 @@ type MetadataTestServer interface { } func RegisterMetadataTestServer(srv *gorums.Server, impl MetadataTestServer) { - srv.RegisterHandler("metadata.MetadataTest.IDFromMD", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("metadata.MetadataTest.IDFromMD", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*emptypb.Empty](in) - resp, err := impl.IDFromMD(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.IDFromMD(ctx, req) }) - srv.RegisterHandler("metadata.MetadataTest.WhatIP", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("metadata.MetadataTest.WhatIP", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*emptypb.Empty](in) - resp, err := impl.WhatIP(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.WhatIP(ctx, req) }) } diff --git a/internal/tests/multiservice/multiservice_gorums.pb.go b/internal/tests/multiservice/multiservice_gorums.pb.go index 26f8cda09..a65358a63 100644 --- a/internal/tests/multiservice/multiservice_gorums.pb.go +++ b/internal/tests/multiservice/multiservice_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -129,39 +130,23 @@ type BetaServer interface { } func RegisterAlphaServer(srv *gorums.Server, impl AlphaServer) { - srv.RegisterHandler("multiservice.Alpha.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("multiservice.Alpha.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Heartbeat(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Read(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Read(ctx, req) }) } func RegisterBetaServer(srv *gorums.Server, impl BetaServer) { - srv.RegisterHandler("multiservice.Beta.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("multiservice.Beta.Heartbeat", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Heartbeat(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.Write(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.Write(ctx, req) }) } diff --git a/internal/tests/oneway/oneway_gorums.pb.go b/internal/tests/oneway/oneway_gorums.pb.go index 90ce5bb57..3e3334afb 100644 --- a/internal/tests/oneway/oneway_gorums.pb.go +++ b/internal/tests/oneway/oneway_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" emptypb "google.golang.org/protobuf/types/known/emptypb" ) @@ -101,12 +102,12 @@ type OnewayTestServer interface { } func RegisterOnewayTestServer(srv *gorums.Server, impl OnewayTestServer) { - srv.RegisterHandler("oneway.OnewayTest.Unicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("oneway.OnewayTest.Unicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Unicast(ctx, req) return nil, nil }) - srv.RegisterHandler("oneway.OnewayTest.Multicast", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("oneway.OnewayTest.Multicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) impl.Multicast(ctx, req) return nil, nil diff --git a/internal/tests/ordering/ordering_gorums.pb.go b/internal/tests/ordering/ordering_gorums.pb.go index d21467780..084d236d6 100644 --- a/internal/tests/ordering/ordering_gorums.pb.go +++ b/internal/tests/ordering/ordering_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -82,20 +83,12 @@ type OrderingServer interface { } func RegisterOrderingServer(srv *gorums.Server, impl OrderingServer) { - srv.RegisterHandler("ordering.Ordering.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("ordering.Ordering.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.QuorumCall(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("ordering.Ordering.UnaryRPC", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("ordering.Ordering.UnaryRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.UnaryRPC(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.UnaryRPC(ctx, req) }) } diff --git a/internal/tests/tls/peer_identity_test.go b/internal/tests/tls/peer_identity_test.go index 766918421..712ae7310 100644 --- a/internal/tests/tls/peer_identity_test.go +++ b/internal/tests/tls/peer_identity_test.go @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + proto "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -59,11 +60,11 @@ func peerGroup(t *testing.T, ca *testCA, n int, certIDs []gorums.ID, opts ...gor count := new(atomic.Int32) calls = append(calls, count) self := srv.NodeID() - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { count.Add(1) req := gorums.AsProto[*pb.StringValue](in) reply := fmt.Sprintf("server %d from %d: %s", self, ctx.SenderID(), req.GetValue()) - return gorums.NewResponseMessage(in, pb.String(reply)), nil + return pb.String(reply), nil }) } return servers, calls diff --git a/internal/tests/tls/tls_gorums.pb.go b/internal/tests/tls/tls_gorums.pb.go index f37483c68..789d8a528 100644 --- a/internal/tests/tls/tls_gorums.pb.go +++ b/internal/tests/tls/tls_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -53,12 +54,8 @@ type TLSServer interface { } func RegisterTLSServer(srv *gorums.Server, impl TLSServer) { - srv.RegisterHandler("tls.TLS.TestTLS", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("tls.TLS.TestTLS", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Request](in) - resp, err := impl.TestTLS(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.TestTLS(ctx, req) }) } diff --git a/internal/tests/unresponsive/unresponsive_gorums.pb.go b/internal/tests/unresponsive/unresponsive_gorums.pb.go index b6897b18f..22c764810 100644 --- a/internal/tests/unresponsive/unresponsive_gorums.pb.go +++ b/internal/tests/unresponsive/unresponsive_gorums.pb.go @@ -10,6 +10,7 @@ import ( context "context" gorums "github.com/relab/gorums" gorumsimpl "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) const ( @@ -53,12 +54,8 @@ type UnresponsiveServer interface { } func RegisterUnresponsiveServer(srv *gorums.Server, impl UnresponsiveServer) { - srv.RegisterHandler("unresponsive.Unresponsive.TestUnresponsive", func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler("unresponsive.Unresponsive.TestUnresponsive", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*Empty](in) - resp, err := impl.TestUnresponsive(ctx, req) - if err != nil { - return nil, err - } - return gorums.NewResponseMessage(in, resp), nil + return impl.TestUnresponsive(ctx, req) }) } diff --git a/message.go b/message.go index 5ecec9a65..c9e2bc1d0 100644 --- a/message.go +++ b/message.go @@ -18,39 +18,19 @@ type Message struct { // MetadataEntry is a type alias for [stream.MetadataEntry]. type MetadataEntry = stream.MetadataEntry -// NewResponseMessage creates a new response envelope based on the provided proto -// message. The response includes the message ID and method from the request -// to facilitate routing the response back to the caller on the client side. -// The payload, error status, and metadata entries are left empty; -// [ServerContext.SendMessage] marshals the payload. This function is safe for -// concurrent use. -// -// This function should only be used in generated code. -func NewResponseMessage(in *Message, resp proto.Message) *Message { - if in == nil { - return nil - } - // Create a new Message to avoid race conditions when the sender - // goroutine marshals while the handler creates the next response. - msgBuilder := stream.Message_builder{ - MessageSeqNo: in.GetMessageSeqNo(), // needed in RouteResponse to lookup the response channel - Method: in.GetMethod(), // needed in UnmarshalResponse to look up the response type in the proto registry - // Payload is left empty; SendMessage will marshal resp into the payload when sending the message - // Status is left empty; it can be set by messageWithError if needed - } - return &Message{ - Proto: resp, - Message: msgBuilder.Build(), - } -} - -// messageWithError ensures a response envelope exists and sets the error status. -// If out is nil, a new response is created based on the in request envelope; -// otherwise, out is modified in place. This is used by the server to send error -// responses back to the client. -func messageWithError(in, out *Message, err error) *Message { - if out == nil { - out = NewResponseMessage(in, nil) +// newResponse returns the response envelope to the request in. It carries the +// request's message ID and method, and either the status of err or the +// marshaled resp. A marshal failure becomes the response status. +func newResponse(in *stream.Message, resp proto.Message, err error) *stream.Message { + out := stream.Message_builder{ + MessageSeqNo: in.GetMessageSeqNo(), // routes the response to the pending call + Method: in.GetMethod(), // selects the response type on the client + }.Build() + if err == nil && resp != nil { + var payload []byte + if payload, err = proto.Marshal(resp); err == nil { + out.SetPayload(payload) + } } if err != nil { errStatus, ok := status.FromError(err) diff --git a/message_internal_test.go b/message_internal_test.go new file mode 100644 index 000000000..aa0584a26 --- /dev/null +++ b/message_internal_test.go @@ -0,0 +1,68 @@ +package gorums + +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/relab/gorums/internal/stream" + "github.com/relab/gorums/internal/testutils/mock" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + pb "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestMessageNewResponse(t *testing.T) { + in := stream.Message_builder{ + MessageSeqNo: 100, + Method: mock.GetValueMethod, + Payload: []byte("request payload"), + Entry: []*stream.MetadataEntry{stream.MetadataEntry_builder{Key: "key1", Value: "val1"}.Build()}, + }.Build() + payload, err := proto.Marshal(pb.String("response")) + if err != nil { + t.Fatal(err) + } + // response returns the expected envelope; it carries only the request's + // message ID and method, never its payload or metadata. + response := func(payload []byte, code codes.Code, msg string) *stream.Message { + out := stream.Message_builder{MessageSeqNo: 100, Method: mock.GetValueMethod, Payload: payload}.Build() + if code != codes.OK { + out.SetStatus(status.New(code, msg).Proto()) + } + return out + } + + tests := []struct { + name string + resp proto.Message + err error + want *stream.Message + }{ + {name: "NilResp", resp: nil, err: nil, want: response(nil, codes.OK, "")}, + {name: "Resp", resp: pb.String("response"), err: nil, want: response(payload, codes.OK, "")}, + {name: "StatusErr", resp: nil, err: status.Error(codes.NotFound, "missing"), want: response(nil, codes.NotFound, "missing")}, + {name: "PlainErr", resp: nil, err: errors.New("failed"), want: response(nil, codes.Unknown, "failed")}, + {name: "RespAndErr", resp: pb.String("response"), err: errors.New("failed"), want: response(nil, codes.Unknown, "failed")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := newResponse(in, tt.resp, tt.err) + if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" { + t.Errorf("newResponse mismatch (-want, +got):\n%s", diff) + } + }) + } + + t.Run("MarshalErr", func(t *testing.T) { + got := newResponse(in, pb.String("\xff"), nil) // invalid UTF-8 fails to marshal + if len(got.GetPayload()) != 0 { + t.Errorf("payload = %q, want empty", got.GetPayload()) + } + if code := status.FromProto(got.GetStatus()).Code(); code != codes.Unknown { + t.Errorf("status code = %v, want %v", code, codes.Unknown) + } + }) +} diff --git a/message_test.go b/message_test.go index 912e83933..4191c2be9 100644 --- a/message_test.go +++ b/message_test.go @@ -3,96 +3,10 @@ package gorums_test import ( "testing" - "github.com/google/go-cmp/cmp" "github.com/relab/gorums" "github.com/relab/gorums/cmd/protoc-gen-gorums/dev" - "github.com/relab/gorums/internal/stream" - "github.com/relab/gorums/internal/testutils/mock" - "google.golang.org/protobuf/testing/protocmp" ) -func TestNewResponseMessage(t *testing.T) { - req := dev.Request_builder{Value: "request"}.Build() - resp := dev.Response_builder{Result: 42}.Build() - - streamIn := stream.Message_builder{ - MessageSeqNo: 100, - Method: mock.GetValueMethod, - Payload: []byte("request payload"), - Entry: []*stream.MetadataEntry{stream.MetadataEntry_builder{Key: "key1", Value: "val1"}.Build()}, - }.Build() - streamOut := stream.Message_builder{MessageSeqNo: 100, Method: mock.GetValueMethod}.Build() - - tests := []struct { - name string - in *gorums.Message - resp *dev.Response - want *gorums.Message - }{ - { - name: "NilIn/NilResp/NilOut", - in: nil, - resp: nil, - want: nil, - }, - { - name: "NilIn/Resp/NilOut", - in: nil, - resp: resp, - want: nil, - }, - { - name: "NilReq/NilResp/StreamIn/StreamOut", - in: &gorums.Message{Proto: nil, Message: streamIn}, - resp: nil, - want: &gorums.Message{Proto: (*dev.Response)(nil), Message: streamOut}, - }, - { - name: "NilReq/Resp/StreamIn/StreamOut", - in: &gorums.Message{Proto: nil, Message: streamIn}, - resp: resp, - want: &gorums.Message{Proto: resp, Message: streamOut}, - }, - { - name: "Req/NilResp/StreamIn/StreamOut", - in: &gorums.Message{Proto: req, Message: streamIn}, - resp: nil, - want: &gorums.Message{Proto: (*dev.Response)(nil), Message: streamOut}, - }, - { - name: "Req/Resp/StreamIn/StreamOut", - in: &gorums.Message{Proto: req, Message: streamIn}, - resp: resp, - want: &gorums.Message{Proto: resp, Message: streamOut}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := gorums.NewResponseMessage(tt.in, tt.resp) - if tt.want == nil { - if got != nil { - t.Errorf("NewResponseMessage returned %v, want nil", got) - } - return - } - if got == nil { - t.Fatalf("NewResponseMessage returned nil, want non-nil") - } - if (tt.want.Proto == nil) != (got.Proto == nil) { - t.Errorf("Msg field: want nil=%v, got nil=%v", tt.want.Proto == nil, got.Proto == nil) - } else if tt.want.Proto != nil && got.Proto != nil { - if diff := cmp.Diff(tt.want.Proto, got.Proto, protocmp.Transform()); diff != "" { - t.Errorf("Msg field mismatch (-want, +got):\n%s", diff) - } - } - if diff := cmp.Diff(tt.want.Message, got.Message, protocmp.Transform()); diff != "" { - t.Errorf("Message field mismatch (-want, +got):\n%s", diff) - } - }) - } -} - func TestAsProto(t *testing.T) { t.Parallel() @@ -104,7 +18,7 @@ func TestAsProto(t *testing.T) { }{ { name: "Success", - msg: gorums.NewResponseMessage(&gorums.Message{}, dev.Response_builder{Result: 42}.Build()), + msg: &gorums.Message{Proto: dev.Response_builder{Result: 42}.Build()}, wantNil: false, wantResult: 42, }, @@ -115,7 +29,7 @@ func TestAsProto(t *testing.T) { }, { name: "WrongType", - msg: gorums.NewResponseMessage(&gorums.Message{}, dev.Request_builder{Value: "request"}.Build()), + msg: &gorums.Message{Proto: dev.Request_builder{Value: "request"}.Build()}, wantNil: true, }, } diff --git a/server.go b/server.go index 3875c22f7..e83c00d46 100644 --- a/server.go +++ b/server.go @@ -17,12 +17,14 @@ import ( ) type ( - // Handler processes a request and returns a response. - Handler func(ServerContext, *Message) (*Message, error) + // Handler processes a request and returns its response. The send callback + // sends each response of a streaming call. A handler that returns a nil + // response and a nil error sends no reply. + Handler func(ServerContext, *Message, func(proto.Message)) (proto.Message, error) // ServerInterceptor intercepts and may modify incoming requests and outgoing responses. - // It receives a ServerContext, the incoming Message, and a Handler representing - // the next element in the chain. It returns a Message and an error. - ServerInterceptor func(ServerContext, *Message, Handler) (*Message, error) + // It receives a ServerContext, the incoming Message, a streaming response + // sender, and the next Handler. It returns the response and an error. + ServerInterceptor func(ServerContext, *Message, func(proto.Message), Handler) (proto.Message, error) ) // Server serves Gorums calls on registered handlers, and can call its peers @@ -174,8 +176,8 @@ func (s *Server) newPeerConfig(nodes NodeSource, dialOpts []DialOption) (Config, } // RegisterHandler registers a request handler for the specified method name. -// -// This function should only be used by generated code. +// Generated registration functions use it; applications can also register +// handlers for back-channel calls. func (s *Server) RegisterHandler(method string, handler Handler) { s.handlers[method] = chainInterceptors(handler, s.interceptors...) } @@ -191,8 +193,8 @@ func chainInterceptors(final Handler, interceptors ...ServerInterceptor) Handler handler := final for _, curr := range slices.Backward(interceptors) { next := handler - handler = func(ctx ServerContext, in *Message) (*Message, error) { - return curr(ctx, in, next) + handler = func(ctx ServerContext, in *Message, send func(proto.Message)) (proto.Message, error) { + return curr(ctx, in, send, next) } } return handler @@ -217,32 +219,32 @@ func (s *Server) HandleRequest(ctx context.Context, senderID ID, reqMsg *stream. Context: ctx, senderID: senderID, release: release, - send: send, srv: s, } handler, ok := s.handlers[reqMsg.GetMethod()] if !ok { - in := &Message{Message: reqMsg} - srvCtx.SendMessage(messageWithError(in, nil, status.Errorf(codes.Unimplemented, "no handler registered for method %s", reqMsg.GetMethod()))) + send(newResponse(reqMsg, nil, status.Errorf(codes.Unimplemented, "no handler registered for method %s", reqMsg.GetMethod()))) return } msg, err := impl.UnmarshalRequest(reqMsg) - in := &Message{Proto: msg, Message: reqMsg} if err != nil { - srvCtx.SendMessage(messageWithError(in, nil, err)) + send(newResponse(reqMsg, nil, err)) return } - out, err := handler(srvCtx, in) + in := &Message{Proto: msg, Message: reqMsg} + out, err := handler(srvCtx, in, func(resp proto.Message) { + send(newResponse(reqMsg, resp, nil)) + }) // If there is no response and no error, we do not send anything back to the client. // This corresponds to a unidirectional message from client to server, where clients // are not expected to receive a response. if out == nil && err == nil { return } - srvCtx.SendMessage(messageWithError(in, out, err)) + send(newResponse(reqMsg, out, err)) } // Serve serves on the externally supplied listener and records it so that @@ -378,7 +380,6 @@ type ServerContext struct { context.Context senderID ID release func() - send func(*stream.Message) srv *Server } @@ -429,27 +430,6 @@ func (ctx *ServerContext) Release() { } } -// SendMessage sends the given message to the client. -// If marshaling fails, the error is encoded into the response envelope -// and sent to the client; the stream is not closed. -// -// This function should only be used by generated code. -func (ctx *ServerContext) SendMessage(out *Message) { - // If Proto is set, marshal it to payload before sending. - if out.Proto != nil && len(out.GetPayload()) == 0 { - payload, err := proto.Marshal(out.Proto) - if err == nil { - out.SetPayload(payload) - } else { - // Encode the marshal error into the response envelope; don't close the stream. - out = messageWithError(nil, out, err) - } - } - if ctx.send != nil { - ctx.send(out.Message) - } -} - // PeerConfig returns the [Config] of the peers configured with [WithPeers], // or nil if [WithPeers] was not used. It is the same configuration as // [Server.PeerConfig], so a handler can fan out calls to the server's peers. diff --git a/server_internal_test.go b/server_internal_test.go index 2f863266d..cae15333d 100644 --- a/server_internal_test.go +++ b/server_internal_test.go @@ -257,10 +257,10 @@ func TestSelfNodeIDStreamRejectedEndToEnd(t *testing.T) { var srv *Server addrs := testStartServers(t, 1, func(_ int) servers.ServerIface { srv = NewServer(WithPeers(1, peerNodes(), insecureDialOpts)) - srv.RegisterHandler(mock.TestMethod, func(_ ServerContext, in *Message) (*Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ ServerContext, in *Message, _ func(proto.Message)) (proto.Message, error) { handlerCalls.Add(1) req := AsProto[*pb.StringValue](in) - return NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) return srv }) @@ -372,9 +372,9 @@ func TestKnownPeerServerCallsClient(t *testing.T) { // Client connects as peer 2 with handlers registered on a server via withServer. clientSrv := NewServer() - clientSrv.RegisterHandler(mock.TestMethod, func(_ ServerContext, in *Message) (*Message, error) { + clientSrv.RegisterHandler(mock.TestMethod, func(_ ServerContext, in *Message, _ func(proto.Message)) (proto.Message, error) { req := AsProto[*pb.StringValue](in) - return NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) peerMD := conn.MetadataWithNodeID(2) _, closeFn, err := NewConfig(WithNodeList(addrs), testDialOptions(t), WithMetadata(peerMD), withServer(clientSrv)) @@ -537,7 +537,7 @@ func TestConnectedClientsMixedMode(t *testing.T) { func TestConnectedClientsServerCallsClient(t *testing.T) { // Register the server handler before starting so it is present before clients arrive. srv := NewServer() - srv.RegisterHandler(mock.TestMethod, func(ctx ServerContext, _ *Message) (*Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx ServerContext, _ *Message, _ func(proto.Message)) (proto.Message, error) { if cfg := ctx.ConnectedClients(); len(cfg) > 0 { // Release before the back-channel send: Wait blocks until every // client's send completes, and holding the dispatch lock across @@ -557,7 +557,7 @@ func TestConnectedClientsServerCallsClient(t *testing.T) { // Client: a Server whose back-channel mock.StreamMethod handler is wired in via withServer. clientSrv := NewServer() - clientSrv.RegisterHandler(mock.StreamMethod, func(_ ServerContext, _ *Message) (*Message, error) { + clientSrv.RegisterHandler(mock.StreamMethod, func(_ ServerContext, _ *Message, _ func(proto.Message)) (proto.Message, error) { wg.Done() return nil, nil }) diff --git a/server_test.go b/server_test.go index 241508aaa..1cb4b45e0 100644 --- a/server_test.go +++ b/server_test.go @@ -52,7 +52,7 @@ func TestServerConnectCallback(t *testing.T) { } func appendStringInterceptor(inStr, outStr string) gorums.ServerInterceptor { - return func(ctx gorums.ServerContext, in *gorums.Message, next gorums.Handler) (*gorums.Message, error) { + return func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) // update the underlying request gorums.Message's message field (pb.StringValue in this case) req.Value += inStr @@ -61,15 +61,14 @@ func appendStringInterceptor(inStr, outStr string) gorums.ServerInterceptor { // The next handler in the chain will access req via gorums.AsProto(in) which reads in.Proto. // call the next handler - out, err := next(ctx, in) + out, err := next(ctx, in, send) if err != nil { return nil, err } - resp := gorums.AsProto[*pb.StringValue](out) - // update the underlying response gorums.Message's message field (pb.StringValue in this case) + resp := out.(*pb.StringValue) + // update the response (pb.StringValue in this case) resp.Value += outStr - // We do not need to re-marshal the response into the payload either. - // SendMessage will lazily marshal it before sending it on the wire. + // The server marshals the response before sending it on the wire. return out, err } } @@ -89,13 +88,13 @@ func TestServerInterceptorsChain(t *testing.T) { appendStringInterceptor("i2in-", "i2out-"), )) // register final handler which appends "final-" to the request value - s.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + s.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) resp, err := interceptorSrv.Test(ctx, req) if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return resp, nil }) return s } @@ -115,6 +114,46 @@ func TestServerInterceptorsChain(t *testing.T) { } } +// TestServerInterceptorsStream verifies that streamed responses pass through +// the send callbacks of the interceptor chain before they reach the client. +func TestServerInterceptorsStream(t *testing.T) { + wrapSend := func(suffix string) gorums.ServerInterceptor { + return func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.Message), next gorums.Handler) (proto.Message, error) { + return next(ctx, in, func(resp proto.Message) { + send(pb.String(resp.(*pb.StringValue).GetValue() + suffix)) + }) + } + } + streamServerFn := func(_ int) gorumstest.ServerIface { + s := gorums.NewServer(gorums.WithServerInterceptors(wrapSend("-i1"), wrapSend("-i2"))) + s.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message, send func(proto.Message)) (proto.Message, error) { + req := gorums.AsProto[*pb.StringValue](in) + for i := 1; i <= 3; i++ { + send(pb.String(fmt.Sprintf("%s-%d", req.GetValue(), i))) + } + return nil, nil + }) + return s + } + config := gorumstest.Config(t, 1, streamServerFn) + ctx := gorumstest.Context(t, 5*time.Second) + + var got []string + for resp := range mockcall.QuorumCallStream(ctx, config, "stream").Results() { + if resp.Err != nil { + t.Fatalf("unexpected error: %v", resp.Err) + } + got = append(got, resp.Value.GetValue()) + if len(got) == 3 { + break + } + } + want := []string{"stream-1-i2-i1", "stream-2-i2-i1", "stream-3-i2-i1"} + if !slices.Equal(got, want) { + t.Errorf("responses = %q, want %q", got, want) + } +} + // TestServerBufferSizesProcessRequests verifies that the server processes // concurrent requests for each combination of receive and send buffer sizes, // including size 0, which selects the default size. @@ -289,17 +328,17 @@ func TestServerHandleRequestRelease(t *testing.T) { { name: "HandlerReturns", method: mock.TestMethod, - handler: func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { - return gorums.NewResponseMessage(in, gorums.AsProto[*pb.StringValue](in)), nil + handler: func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { + return gorums.AsProto[*pb.StringValue](in), nil }, wantReleases: 0, }, { name: "HandlerReleases", method: mock.TestMethod, - handler: func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + handler: func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { ctx.Release() - return gorums.NewResponseMessage(in, gorums.AsProto[*pb.StringValue](in)), nil + return gorums.AsProto[*pb.StringValue](in), nil }, wantReleases: 1, }, @@ -445,7 +484,7 @@ func outerChainedHandler( respFn func(*gorums.Responses[*pb.StringValue]) (*pb.StringValue, error), ) gorums.Handler { t.Helper() - return func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + return func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) t.Logf("Server %d received outer request: %s", myID, req.GetValue()) // Release before making the inner quorum call, so the next request on @@ -466,7 +505,7 @@ func outerChainedHandler( if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.String(req.GetValue()+" | "+res.GetValue())), nil + return pb.String(req.GetValue() + " | " + res.GetValue()), nil } } @@ -533,7 +572,7 @@ func TestServerSymmetricConfigurationRoutesMulticast(t *testing.T) { // Register mock handler to each server for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { wg.Done() return nil, nil }) @@ -558,7 +597,7 @@ func TestServerHandlerCanMulticastViaConfig(t *testing.T) { wg.Add(9) for i, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { t.Logf("Server %d received multicast on %v: %v", i+1, mock.TestMethod, in.Proto) // Release before the nested multicast: the peer configuration // includes the local node, whose in-process dispatch waits for @@ -573,7 +612,7 @@ func TestServerHandlerCanMulticastViaConfig(t *testing.T) { return nil, nil // one-way }) - srv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { t.Logf("Server %d received multicast on %v: %v", i+1, mock.StreamMethod, in.Proto) wg.Done() return nil, nil @@ -683,7 +722,7 @@ func TestServerHandlerCanMulticastViaConnectedClients(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { t.Logf("SERVER received multicast: %v", in.Proto) if cfg := ctx.ConnectedClients(); cfg.Size() == 1 { err := gorumsimpl.Multicast(t.Context(), cfg, pb.String("inner-call"), mock.StreamMethod).Send() @@ -695,7 +734,7 @@ func TestServerHandlerCanMulticastViaConnectedClients(t *testing.T) { }) // Client handles the back-channel multicast dispatched by the server. - clientSrv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + clientSrv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { t.Log("CLIENT received inner multicast") wg.Done() return nil, nil @@ -724,9 +763,9 @@ func TestServerLocalDispatchContention(t *testing.T) { t.Helper() servers := gorumstest.LocalServers(t, 3) for _, srv := range servers { - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) } gorumstest.WaitForPeers(t, servers) @@ -807,10 +846,10 @@ func TestServerLocalDispatchContentionSlowReplica(t *testing.T) { for i, srv := range servers { if i == 0 { // Server 0 (self-node): block until signaled. - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { <-blocker req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) } else { // Servers 1, 2 (remote): respond immediately. @@ -993,8 +1032,8 @@ func TestServerGracefulStopReleasesPeerConfig(t *testing.T) { // during the drain. func TestServerGracefulStopWithOpenClientStream(t *testing.T) { srv := gorums.NewServer() - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { - return gorums.NewResponseMessage(in, pb.String("echo")), nil + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { + return pb.String("echo"), nil }) cfg := gorumstest.Config(t, 1, func(int) gorumstest.ServerIface { return srv }) node := cfg.Nodes()[0] @@ -1029,8 +1068,8 @@ func TestServerGracefulStopWithOpenClientStream(t *testing.T) { // second in-flight request does not stop the reply from being read. func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { srv := gorums.NewServer() - srv.RegisterHandler(mock.EchoMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { - return gorums.NewResponseMessage(in, pb.String("echo")), nil + srv.RegisterHandler(mock.EchoMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { + return pb.String("echo"), nil }) addrs := gorumstest.Servers(t, 1, func(int) gorumstest.ServerIface { return srv }) @@ -1041,7 +1080,7 @@ func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { secondInFlight = make(chan struct{}) ) clientSrv := gorums.NewServer() - clientSrv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + clientSrv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { if handlers.Add(1) == 1 { close(firstStarted) <-secondInFlight @@ -1052,7 +1091,7 @@ func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return resp, nil }) cfg, closeFn, err := gorums.NewConfig(gorums.WithNodeList(addrs), gorumstest.DialOptions(t), gorums.WithBackChannel(clientSrv)) if err != nil { @@ -1102,7 +1141,7 @@ func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { // arrives on the same stream the handler's request was read from. func TestServerHandlerNestedCallBeforeRelease(t *testing.T) { srv := gorums.NewServer() - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { clients := ctx.ConnectedClients() if clients.Size() == 0 { t.Error("ConnectedClients is empty") @@ -1114,13 +1153,13 @@ func TestServerHandlerNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return resp, nil }) addrs := gorumstest.Servers(t, 1, func(int) gorumstest.ServerIface { return srv }) clientSrv := gorums.NewServer() - clientSrv.RegisterHandler(mock.EchoMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { - return gorums.NewResponseMessage(in, pb.String("echo")), nil + clientSrv.RegisterHandler(mock.EchoMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { + return pb.String("echo"), nil }) cfg, closeFn, err := gorums.NewConfig(gorums.WithNodeList(addrs), gorumstest.DialOptions(t), gorums.WithBackChannel(clientSrv)) if err != nil { @@ -1145,7 +1184,7 @@ type senderRecord struct { // senderRecorder returns a one-way handler that reports the request's // [gorums.ServerContext.SenderID] as observed by receiver. func senderRecorder(receiver gorums.ID, records chan<- senderRecord) gorums.Handler { - return func(ctx gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + return func(ctx gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { records <- senderRecord{receiver: receiver, sender: ctx.SenderID()} return nil, nil } @@ -1368,9 +1407,9 @@ func TestServerIdentityRequiresTLS(t *testing.T) { }), gorumstest.DialOptions(t), gorums.WithTransportCredentials(insecure.NewCredentials())), gorums.WithConnectCallback(func(_ context.Context) { callbacks.Add(1) }), ) - srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { calls.Add(1) - return gorums.NewResponseMessage(in, pb.String("accepted")), nil + return pb.String("accepted"), nil }) return srv }, gorums.WithMetadata(metadata.Pairs("gorums-node-id", "2"))) diff --git a/stream_dedup_test.go b/stream_dedup_test.go index 5026c5b03..fb0a9f77a 100644 --- a/stream_dedup_test.go +++ b/stream_dedup_test.go @@ -17,6 +17,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -441,10 +442,10 @@ func TestStreamDedupBorrowerSlowHandlerDoesNotBlockOwnCalls(t *testing.T) { servers := newServerPair(t, dedup) started := make(chan struct{}, 1) unblock := make(chan struct{}) - servers[1].RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + servers[1].RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { started <- struct{}{} <-unblock // slow handler, no Release - return gorums.NewResponseMessage(in, pb.String("slow")), nil + return pb.String("slow"), nil }) servers[0].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -486,10 +487,10 @@ func TestStreamDedupOwnerSlowHandlerDoesNotBlockOwnCalls(t *testing.T) { servers := newServerPair(t, dedup) started := make(chan struct{}, 4) unblock := make(chan struct{}) - servers[0].RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + servers[0].RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { started <- struct{}{} <-unblock // slow handler, no Release - return gorums.NewResponseMessage(in, pb.String("slow")), nil + return pb.String("slow"), nil }) servers[1].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -529,7 +530,7 @@ func TestStreamDedupNestedCallToRequesterWithoutRelease(t *testing.T) { } t.Run(name, func(t *testing.T) { servers := newServerPair(t, dedup) - servers[1].RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + servers[1].RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { n1 := gorumstest.PeerNode(t, ctx.PeerConfig(), 1) cctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -537,7 +538,7 @@ func TestStreamDedupNestedCallToRequesterWithoutRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.String("outer | "+resp.GetValue())), nil + return pb.String("outer | " + resp.GetValue()), nil }) servers[0].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -564,7 +565,7 @@ func TestStreamDedupInflightCallFailsWithStreamDown(t *testing.T) { started := make(chan struct{}, 1) unblock := make(chan struct{}) defer close(unblock) - owner.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, _ *gorums.Message) (*gorums.Message, error) { + owner.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { started <- struct{}{} select { case <-unblock: diff --git a/tree_integration_test.go b/tree_integration_test.go index 81cb35d4c..5b5cc33ad 100644 --- a/tree_integration_test.go +++ b/tree_integration_test.go @@ -11,6 +11,7 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -68,7 +69,7 @@ func TestTreeMulticast(t *testing.T) { for i, srv := range servers { tree := trees[i] - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { ctx.Release() if children := tree.ChildrenOf(srv.NodeID()); len(children) > 0 { req := gorums.AsProto[*pb.StringValue](in) @@ -133,7 +134,7 @@ func TestTreeMulticastRelayOrder(t *testing.T) { tree := trees[i] children := tree.ChildrenOf(srv.NodeID()) relayedOne := make(chan struct{}) // closed when this node has relayed "1" - srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { defer wg.Done() req := gorums.AsProto[*pb.StringValue](in) if len(children) == 0 { @@ -215,7 +216,7 @@ func TestTreeQuorumCall(t *testing.T) { for i, srv := range servers { tree := trees[i] - srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { // Release before blocking on the children, so that the // next request on this stream can start meanwhile. ctx.Release() @@ -223,7 +224,7 @@ func TestTreeQuorumCall(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.Int32(total)), nil + return pb.Int32(total), nil }) } @@ -253,7 +254,7 @@ func TestTreeExternalCaller(t *testing.T) { for i, srv := range servers { tree := trees[i] - srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message) (*gorums.Message, error) { + srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { ctx.Release() total := int32(srv.NodeID()) if children := tree.ChildrenOf(srv.NodeID()); len(children) > 0 { @@ -264,7 +265,7 @@ func TestTreeExternalCaller(t *testing.T) { total += r.Value.GetValue() } } - return gorums.NewResponseMessage(in, pb.Int32(total)), nil + return pb.Int32(total), nil }) }