From 7776e0918c700c538f6f360f1358d4ca8e4aa6d3 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 19:04:41 +0100 Subject: [PATCH 1/8] gorums: route streamed responses through handler callback Generated streaming handlers called ServerContext.SendMessage and built their own envelopes with gorums.NewResponseMessage. Both were exported only for generated code, yet they sat in the application API. Each Handler and ServerInterceptor now receives a send callback. HandleRequest builds the envelope for each streamed response, and interceptors can wrap send to observe or change streamed responses. SendMessage is unexported, and NewResponseMessage moves to runtime/gorumsimpl for generated unary handlers. Code generated by earlier releases no longer compiles against this runtime, so the next release must run gorums-release prepare -bump-min. Updates: #424 --- benchkit/benchmark/benchmark_test.go | 7 +- benchkit/benchmark/symmetric_test.go | 6 +- call_async_test.go | 5 +- call_test.go | 9 +- .../gengorums/template_funcs.go | 1 + .../gengorums/template_server.go | 10 +- doc/dev-guide.md | 7 +- doc/user-guide.md | 22 ++-- examples/interceptors/server_interceptors.go | 20 ++-- gorumstest/gorumstest.go | 3 +- gorumstest/servers.go | 19 ++-- internal/stream/message.go | 9 ++ internal/tests/tls/peer_identity_test.go | 6 +- message.go | 24 +--- message_test.go | 7 +- runtime/gorumsimpl/response.go | 19 ++++ server.go | 35 +++--- server_internal_test.go | 12 +- server_test.go | 106 ++++++++++++------ stream_dedup_test.go | 16 +-- tree_integration_test.go | 14 ++- 21 files changed, 215 insertions(+), 142 deletions(-) create mode 100644 runtime/gorumsimpl/response.go diff --git a/benchkit/benchmark/benchmark_test.go b/benchkit/benchmark/benchmark_test.go index 7842f56c0..b80ffa7ca 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)) (*gorums.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) (*gorums.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..bf905fd39 100644 --- a/benchkit/benchmark/symmetric_test.go +++ b/benchkit/benchmark/symmetric_test.go @@ -19,6 +19,8 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/benchkit" "github.com/relab/gorums/gorumstest" + "github.com/relab/gorums/runtime/gorumsimpl" + proto "google.golang.org/protobuf/proto" ) // captureDiag redirects the probe-stall self-diagnosis to a buffer for the @@ -124,11 +126,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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, gorums.AsProto[*Echo](in)), nil }) } diff --git a/call_async_test.go b/call_async_test.go index 0ef8d1c0d..92cb0f07b 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)) (*gorums.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..d84dc47cf 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)) (*gorums.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)) (*gorums.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)) (*gorums.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)) (*gorums.Message, error) { received.Add(1) 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..ff2b7b146 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_server.go +++ b/cmd/protoc-gen-gorums/gengorums/template_server.go @@ -73,8 +73,9 @@ 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}} +{{$newMessage := use "gorumsimpl.NewResponseMessage" $genFile}} {{$gorumsServer := use "gorums.Server" $genFile}} {{range .Services -}} {{$names := .Names}} @@ -82,16 +83,15 @@ var registerInterface = ` 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 err send" $context $gorumsMessage $protoMessage $asProto $newMessage (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}})) (*{{$gorumsMessage}}, 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 }} diff --git a/doc/dev-guide.md b/doc/dev-guide.md index c78dd5d1c..223956795 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -626,6 +626,8 @@ 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. +Each handler receives a `send func(proto.Message)` callback for streaming replies; `Server.HandleRequest` builds and sends their response envelopes. +Generated unary handlers build their response envelopes through `runtime/gorumsimpl.NewResponseMessage`. ```mermaid classDiagram @@ -639,12 +641,11 @@ classDiagram } class ServerContext { Release() - SendMessage(Message) PeerConfig() Config ConnectedClients() Config } class Handler { - <> + <> } class ServerInterceptor { <> @@ -700,7 +701,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..ea699677b 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -1340,10 +1340,12 @@ 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) (*gorums.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. +Pass `send` to `next` so the handler can send streamed responses. Below are several examples based on the `examples/interceptors` package. @@ -1351,12 +1353,12 @@ 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) (*gorums.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) @@ -1371,20 +1373,20 @@ func LoggingInterceptor(addr string) gorums.ServerInterceptor { 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) (*gorums.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) (*gorums.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 +1396,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) (*gorums.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..ad6aaa25d 100644 --- a/examples/interceptors/server_interceptors.go +++ b/examples/interceptors/server_interceptors.go @@ -15,11 +15,11 @@ 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) (*gorums.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) @@ -29,10 +29,10 @@ func LoggingInterceptor(addr string) gorums.ServerInterceptor { } // 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) (*gorums.Message, error) { req := gorums.AsProto[proto.Message](in) log.Printf("LoggingSimpleInterceptor(incoming): Method=%s, Message=%v)", in.GetMethod(), req) - out, err := next(ctx, in) + out, err := next(ctx, in, send) resp := gorums.AsProto[proto.Message](out) log.Printf("LoggingSimpleInterceptor(outgoing): Method=%s, Err=%v, Message=%v", in.GetMethod(), err, resp) return out, err @@ -41,7 +41,7 @@ func LoggingSimpleInterceptor(ctx gorums.ServerContext, in *gorums.Message, next // 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) (*gorums.Message, error) { // delay based on sending node address delay := 0 * time.Millisecond p, ok := peer.FromContext(ctx) @@ -57,13 +57,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) (*gorums.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 +71,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) (*gorums.Message, error) { log.Printf("MetadataInterceptor: Adding custom metadata to message(customKey=customValue)") // Add a custom metadata field entry := &gorums.MetadataEntry{} @@ -85,7 +85,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/gorumstest/gorumstest.go b/gorumstest/gorumstest.go index ecf8063f4..cd1e74643 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)) (*gorums.Message, error) { srvCtx, ran = ctx, true return nil, nil }) diff --git a/gorumstest/servers.go b/gorumstest/servers.go index c1fad1979..ccdb2e292 100644 --- a/gorumstest/servers.go +++ b/gorumstest/servers.go @@ -8,7 +8,9 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/servers" + "github.com/relab/gorums/runtime/gorumsimpl" "go.uber.org/goleak" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -143,21 +145,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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) return srv } @@ -177,9 +179,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)) (*gorums.Message, error) { req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String(prefix+": "+req.GetValue())), nil + return gorumsimpl.NewResponseMessage(in, pb.String(prefix+": "+req.GetValue())), nil } } @@ -198,15 +200,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)) (*gorums.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/message.go b/internal/stream/message.go index 3dfa089e3..f9fb5965d 100644 --- a/internal/stream/message.go +++ b/internal/stream/message.go @@ -43,6 +43,15 @@ func NewMessageFromPayload(ctx context.Context, msgID uint64, method string, pay return msgBuilder.Build() } +// NewResponseEnvelope returns a fresh response envelope with the request's +// message ID and method. The payload and status are left empty. +func NewResponseEnvelope(in *Message) *Message { + return Message_builder{ + MessageSeqNo: in.GetMessageSeqNo(), + Method: in.GetMethod(), + }.Build() +} + // appendToIncomingContext appends client-specific metadata from the [Message] proto message // to the incoming gRPC context, allowing server implementations to extract and use said // metadata directly from the server method's context. It never modifies ctx's own metadata, diff --git a/internal/tests/tls/peer_identity_test.go b/internal/tests/tls/peer_identity_test.go index 766918421..ccfbb868a 100644 --- a/internal/tests/tls/peer_identity_test.go +++ b/internal/tests/tls/peer_identity_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/relab/gorums/internal/testutils/mockcall" + "github.com/relab/gorums/runtime/gorumsimpl" "github.com/relab/gorums" "github.com/relab/gorums/gorumstest" @@ -21,6 +22,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 +61,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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, pb.String(reply)), nil }) } return servers, calls diff --git a/message.go b/message.go index 5ecec9a65..8b37206c3 100644 --- a/message.go +++ b/message.go @@ -18,29 +18,15 @@ 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 { +// newResponseMessage creates a response envelope for the given request and +// proto message. The sender marshals its payload before transmission. +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(), + Message: stream.NewResponseEnvelope(in.Message), } } @@ -50,7 +36,7 @@ func NewResponseMessage(in *Message, resp proto.Message) *Message { // responses back to the client. func messageWithError(in, out *Message, err error) *Message { if out == nil { - out = NewResponseMessage(in, nil) + out = newResponseMessage(in, nil) } if err != nil { errStatus, ok := status.FromError(err) diff --git a/message_test.go b/message_test.go index 912e83933..aebcd9e84 100644 --- a/message_test.go +++ b/message_test.go @@ -8,6 +8,7 @@ import ( "github.com/relab/gorums/cmd/protoc-gen-gorums/dev" "github.com/relab/gorums/internal/stream" "github.com/relab/gorums/internal/testutils/mock" + "github.com/relab/gorums/runtime/gorumsimpl" "google.golang.org/protobuf/testing/protocmp" ) @@ -69,7 +70,7 @@ func TestNewResponseMessage(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := gorums.NewResponseMessage(tt.in, tt.resp) + got := gorumsimpl.NewResponseMessage(tt.in, tt.resp) if tt.want == nil { if got != nil { t.Errorf("NewResponseMessage returned %v, want nil", got) @@ -104,7 +105,7 @@ func TestAsProto(t *testing.T) { }{ { name: "Success", - msg: gorums.NewResponseMessage(&gorums.Message{}, dev.Response_builder{Result: 42}.Build()), + msg: gorumsimpl.NewResponseMessage(&gorums.Message{}, dev.Response_builder{Result: 42}.Build()), wantNil: false, wantResult: 42, }, @@ -115,7 +116,7 @@ func TestAsProto(t *testing.T) { }, { name: "WrongType", - msg: gorums.NewResponseMessage(&gorums.Message{}, dev.Request_builder{Value: "request"}.Build()), + msg: gorumsimpl.NewResponseMessage(&gorums.Message{}, dev.Request_builder{Value: "request"}.Build()), wantNil: true, }, } diff --git a/runtime/gorumsimpl/response.go b/runtime/gorumsimpl/response.go new file mode 100644 index 000000000..9a2a21a31 --- /dev/null +++ b/runtime/gorumsimpl/response.go @@ -0,0 +1,19 @@ +package gorumsimpl + +import ( + "github.com/relab/gorums" + "github.com/relab/gorums/internal/stream" + "google.golang.org/protobuf/proto" +) + +// NewResponseMessage returns a response envelope for generated unary handlers. +// The server marshals its payload before sending it. +func NewResponseMessage(in *gorums.Message, resp proto.Message) *gorums.Message { + if in == nil { + return nil + } + return &gorums.Message{ + Proto: resp, + Message: stream.NewResponseEnvelope(in.Message), + } +} diff --git a/server.go b/server.go index 3875c22f7..51e363091 100644 --- a/server.go +++ b/server.go @@ -17,12 +17,13 @@ import ( ) type ( - // Handler processes a request and returns a response. - Handler func(ServerContext, *Message) (*Message, error) + // Handler processes a request and returns a response. The send callback + // sends each response of a streaming call. + Handler func(ServerContext, *Message, func(proto.Message)) (*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 a Message and an error. + ServerInterceptor func(ServerContext, *Message, func(proto.Message), Handler) (*Message, error) ) // Server serves Gorums calls on registered handlers, and can call its peers @@ -174,8 +175,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 +192,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)) (*Message, error) { + return curr(ctx, in, send, next) } } return handler @@ -224,25 +225,27 @@ func (s *Server) HandleRequest(ctx context.Context, senderID ID, reqMsg *stream. 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()))) + srvCtx.sendMessage(messageWithError(in, 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)) + srvCtx.sendMessage(messageWithError(in, nil, err)) return } - out, err := handler(srvCtx, in) + out, err := handler(srvCtx, in, func(resp proto.Message) { + srvCtx.sendMessage(newResponseMessage(in, resp)) + }) // 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)) + srvCtx.sendMessage(messageWithError(in, out, err)) } // Serve serves on the externally supplied listener and records it so that @@ -429,12 +432,10 @@ func (ctx *ServerContext) Release() { } } -// SendMessage sends the given message to the client. +// 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) { +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) diff --git a/server_internal_test.go b/server_internal_test.go index 2f863266d..b8f7689e8 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)) (*Message, error) { handlerCalls.Add(1) req := AsProto[*pb.StringValue](in) - return NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return newResponseMessage(in, 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)) (*Message, error) { req := AsProto[*pb.StringValue](in) - return NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return newResponseMessage(in, 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)) (*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)) (*Message, error) { wg.Done() return nil, nil }) diff --git a/server_test.go b/server_test.go index 241508aaa..6fc7a416e 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) (*gorums.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,7 +61,7 @@ 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 } @@ -69,7 +69,7 @@ func appendStringInterceptor(inStr, outStr string) gorums.ServerInterceptor { // update the underlying response gorums.Message's message field (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 will marshal it before sending it on the wire. return out, err } } @@ -89,13 +89,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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) return s } @@ -115,6 +115,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) (*gorums.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)) (*gorums.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 +329,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)) (*gorums.Message, error) { + return gorumsimpl.NewResponseMessage(in, 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)) (*gorums.Message, error) { ctx.Release() - return gorums.NewResponseMessage(in, gorums.AsProto[*pb.StringValue](in)), nil + return gorumsimpl.NewResponseMessage(in, gorums.AsProto[*pb.StringValue](in)), nil }, wantReleases: 1, }, @@ -445,7 +485,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)) (*gorums.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 +506,7 @@ func outerChainedHandler( if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.String(req.GetValue()+" | "+res.GetValue())), nil + return gorumsimpl.NewResponseMessage(in, pb.String(req.GetValue()+" | "+res.GetValue())), nil } } @@ -533,7 +573,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)) (*gorums.Message, error) { wg.Done() return nil, nil }) @@ -558,7 +598,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)) (*gorums.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 +613,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)) (*gorums.Message, error) { t.Logf("Server %d received multicast on %v: %v", i+1, mock.StreamMethod, in.Proto) wg.Done() return nil, nil @@ -683,7 +723,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)) (*gorums.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 +735,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)) (*gorums.Message, error) { t.Log("CLIENT received inner multicast") wg.Done() return nil, nil @@ -724,9 +764,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)) (*gorums.Message, error) { req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return gorumsimpl.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil }) } gorumstest.WaitForPeers(t, servers) @@ -807,10 +847,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)) (*gorums.Message, error) { <-blocker req := gorums.AsProto[*pb.StringValue](in) - return gorums.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return gorumsimpl.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil }) } else { // Servers 1, 2 (remote): respond immediately. @@ -993,8 +1033,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)) (*gorums.Message, error) { + return gorumsimpl.NewResponseMessage(in, pb.String("echo")), nil }) cfg := gorumstest.Config(t, 1, func(int) gorumstest.ServerIface { return srv }) node := cfg.Nodes()[0] @@ -1029,8 +1069,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)) (*gorums.Message, error) { + return gorumsimpl.NewResponseMessage(in, pb.String("echo")), nil }) addrs := gorumstest.Servers(t, 1, func(int) gorumstest.ServerIface { return srv }) @@ -1041,7 +1081,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)) (*gorums.Message, error) { if handlers.Add(1) == 1 { close(firstStarted) <-secondInFlight @@ -1052,7 +1092,7 @@ func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return gorumsimpl.NewResponseMessage(in, resp), nil }) cfg, closeFn, err := gorums.NewConfig(gorums.WithNodeList(addrs), gorumstest.DialOptions(t), gorums.WithBackChannel(clientSrv)) if err != nil { @@ -1102,7 +1142,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)) (*gorums.Message, error) { clients := ctx.ConnectedClients() if clients.Size() == 0 { t.Error("ConnectedClients is empty") @@ -1114,13 +1154,13 @@ func TestServerHandlerNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, resp), nil + return gorumsimpl.NewResponseMessage(in, 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)) (*gorums.Message, error) { + return gorumsimpl.NewResponseMessage(in, pb.String("echo")), nil }) cfg, closeFn, err := gorums.NewConfig(gorums.WithNodeList(addrs), gorumstest.DialOptions(t), gorums.WithBackChannel(clientSrv)) if err != nil { @@ -1145,7 +1185,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)) (*gorums.Message, error) { records <- senderRecord{receiver: receiver, sender: ctx.SenderID()} return nil, nil } @@ -1368,9 +1408,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)) (*gorums.Message, error) { calls.Add(1) - return gorums.NewResponseMessage(in, pb.String("accepted")), nil + return gorumsimpl.NewResponseMessage(in, 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..8a94f5756 100644 --- a/stream_dedup_test.go +++ b/stream_dedup_test.go @@ -14,9 +14,11 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" + "github.com/relab/gorums/runtime/gorumsimpl" "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 +443,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)) (*gorums.Message, error) { started <- struct{}{} <-unblock // slow handler, no Release - return gorums.NewResponseMessage(in, pb.String("slow")), nil + return gorumsimpl.NewResponseMessage(in, pb.String("slow")), nil }) servers[0].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -486,10 +488,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)) (*gorums.Message, error) { started <- struct{}{} <-unblock // slow handler, no Release - return gorums.NewResponseMessage(in, pb.String("slow")), nil + return gorumsimpl.NewResponseMessage(in, pb.String("slow")), nil }) servers[1].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -529,7 +531,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)) (*gorums.Message, error) { n1 := gorumstest.PeerNode(t, ctx.PeerConfig(), 1) cctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() @@ -537,7 +539,7 @@ func TestStreamDedupNestedCallToRequesterWithoutRelease(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.String("outer | "+resp.GetValue())), nil + return gorumsimpl.NewResponseMessage(in, pb.String("outer | "+resp.GetValue())), nil }) servers[0].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -564,7 +566,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)) (*gorums.Message, error) { started <- struct{}{} select { case <-unblock: diff --git a/tree_integration_test.go b/tree_integration_test.go index 81cb35d4c..b3e79d652 100644 --- a/tree_integration_test.go +++ b/tree_integration_test.go @@ -11,6 +11,8 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" + "github.com/relab/gorums/runtime/gorumsimpl" + "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -68,7 +70,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)) (*gorums.Message, error) { ctx.Release() if children := tree.ChildrenOf(srv.NodeID()); len(children) > 0 { req := gorums.AsProto[*pb.StringValue](in) @@ -133,7 +135,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)) (*gorums.Message, error) { defer wg.Done() req := gorums.AsProto[*pb.StringValue](in) if len(children) == 0 { @@ -215,7 +217,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)) (*gorums.Message, error) { // Release before blocking on the children, so that the // next request on this stream can start meanwhile. ctx.Release() @@ -223,7 +225,7 @@ func TestTreeQuorumCall(t *testing.T) { if err != nil { return nil, err } - return gorums.NewResponseMessage(in, pb.Int32(total)), nil + return gorumsimpl.NewResponseMessage(in, pb.Int32(total)), nil }) } @@ -253,7 +255,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)) (*gorums.Message, error) { ctx.Release() total := int32(srv.NodeID()) if children := tree.ChildrenOf(srv.NodeID()); len(children) > 0 { @@ -264,7 +266,7 @@ func TestTreeExternalCaller(t *testing.T) { total += r.Value.GetValue() } } - return gorums.NewResponseMessage(in, pb.Int32(total)), nil + return gorumsimpl.NewResponseMessage(in, pb.Int32(total)), nil }) } From 7caf3f8867313cfb15508a1959a3582eb23a0443 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 19:04:41 +0100 Subject: [PATCH 2/8] all: regenerate server handlers for the send callback --- benchkit/benchmark/benchmark_gorums.pb.go | 11 ++--- benchkit/control_gorums.pb.go | 15 +++---- .../dev/zorums_server_gorums.pb.go | 40 +++++++++---------- examples/storage/proto/storage_gorums.pb.go | 34 ++++++++-------- .../importmsgs/importmsgs_gorums.pb.go.golden | 5 ++- .../multicast/multicast_gorums.pb.go.golden | 3 +- .../multiservice_gorums.pb.go.golden | 15 +++---- .../quorumcall/quorumcall_gorums.pb.go.golden | 5 ++- .../clientnames_gorums.pb.go.golden | 13 +++--- .../nodeonly/nodeonly_gorums.pb.go.golden | 7 ++-- .../correctable/correctable_gorums.pb.go | 10 ++--- internal/tests/metadata/metadata_gorums.pb.go | 9 +++-- .../multiservice/multiservice_gorums.pb.go | 17 ++++---- internal/tests/oneway/oneway_gorums.pb.go | 5 ++- internal/tests/ordering/ordering_gorums.pb.go | 9 +++-- internal/tests/tls/tls_gorums.pb.go | 5 ++- .../unresponsive/unresponsive_gorums.pb.go | 5 ++- 17 files changed, 110 insertions(+), 98 deletions(-) diff --git a/benchkit/benchmark/benchmark_gorums.pb.go b/benchkit/benchmark/benchmark_gorums.pb.go index a8394b10c..8470e686d 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,23 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.Message, error) { req := gorums.AsProto[*TimedMsg](in) impl.Multicast(ctx, req) return nil, nil diff --git a/benchkit/control_gorums.pb.go b/benchkit/control_gorums.pb.go index fe254b7ad..2d5f3377a 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,31 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.Message, error) { req := gorums.AsProto[*DoneRequest](in) impl.Done(ctx, req) 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..03fd57609 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,75 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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)) (*gorums.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)) (*gorums.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)) (*gorums.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)) (*gorums.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)) (*gorums.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)) (*gorums.Message, error) { req := gorums.AsProto[*Request](in) impl.Unicast(ctx, req) return nil, nil diff --git a/examples/storage/proto/storage_gorums.pb.go b/examples/storage/proto/storage_gorums.pb.go index 3d06881da..5c606318f 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,68 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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)) (*gorums.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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden index 1b8cb8ec9..feee8c066 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,12 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } 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..0bed4cbb1 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)) (*gorums.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..1356c24f7 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,34 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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..4e86478b7 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,12 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } diff --git a/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden b/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden index 5748fa428..45ae4291c 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,28 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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..0c2888702 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,15 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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..5e84b92cf 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,18 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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..0a3a7196f 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,20 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } diff --git a/internal/tests/multiservice/multiservice_gorums.pb.go b/internal/tests/multiservice/multiservice_gorums.pb.go index 26f8cda09..d00efbdd9 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,39 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } diff --git a/internal/tests/oneway/oneway_gorums.pb.go b/internal/tests/oneway/oneway_gorums.pb.go index 90ce5bb57..a6fac28ce 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)) (*gorums.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)) (*gorums.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..97bd9dc3a 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,20 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) - 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } diff --git a/internal/tests/tls/tls_gorums.pb.go b/internal/tests/tls/tls_gorums.pb.go index f37483c68..f8ba32750 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,12 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } diff --git a/internal/tests/unresponsive/unresponsive_gorums.pb.go b/internal/tests/unresponsive/unresponsive_gorums.pb.go index b6897b18f..e16cf22aa 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,12 @@ 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)) (*gorums.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 gorumsimpl.NewResponseMessage(in, resp), nil }) } From dcf028734578757bf1961b2f6cecf2dcab0012cc Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 19:04:52 +0100 Subject: [PATCH 3/8] AGENTS: leave version bumps to the release tool gorums-release prepare sets the module version, the runtime versions, the nested module requirements, and the go.work replace together. A hand-made bump on a feature branch updates only some of them, which leaves benchkit and examples requiring a runtime older than their generated code. --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) 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. From 202cd7ccff4a21c6a06128b624ff266f8020de49 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 19:10:06 +0100 Subject: [PATCH 4/8] gorums: return handler responses as proto messages Handlers returned a *Message envelope, so every unary handler, including hand-written test and back-channel handlers, had to build one with gorumsimpl.NewResponseMessage. That kept an envelope helper in the API that applications use. Handler and ServerInterceptor now return the response as a proto.Message. HandleRequest builds the envelope for unary and streamed responses alike, so gorumsimpl.NewResponseMessage and stream.NewResponseEnvelope are gone. Generated unary handlers return the implementation's result directly, and interceptors read the response without AsProto. Interceptors can no longer change the response envelope. Clients read only the payload and status of a response, so nothing depends on it. Updates: #424 --- benchkit/benchmark/benchmark_test.go | 4 +- benchkit/benchmark/symmetric_test.go | 5 +- call_async_test.go | 2 +- call_test.go | 8 +- .../gengorums/template_server.go | 13 +-- doc/dev-guide.md | 6 +- doc/user-guide.md | 14 +-- examples/interceptors/server_interceptors.go | 16 ++-- gorumstest/gorumstest.go | 2 +- gorumstest/servers.go | 15 ++- internal/stream/message.go | 9 -- internal/tests/tls/peer_identity_test.go | 5 +- message.go | 32 +++---- message_internal_test.go | 68 ++++++++++++++ message_test.go | 91 +------------------ runtime/gorumsimpl/response.go | 19 ---- server.go | 44 ++++----- server_internal_test.go | 12 +-- server_test.go | 73 ++++++++------- stream_dedup_test.go | 15 ++- tree_integration_test.go | 13 ++- 21 files changed, 195 insertions(+), 271 deletions(-) create mode 100644 message_internal_test.go delete mode 100644 runtime/gorumsimpl/response.go diff --git a/benchkit/benchmark/benchmark_test.go b/benchkit/benchmark/benchmark_test.go index b80ffa7ca..21e28a3ac 100644 --- a/benchkit/benchmark/benchmark_test.go +++ b/benchkit/benchmark/benchmark_test.go @@ -250,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, _ func(proto.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) }) } @@ -646,7 +646,7 @@ 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, send func(proto.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) } diff --git a/benchkit/benchmark/symmetric_test.go b/benchkit/benchmark/symmetric_test.go index bf905fd39..d81ced67f 100644 --- a/benchkit/benchmark/symmetric_test.go +++ b/benchkit/benchmark/symmetric_test.go @@ -19,7 +19,6 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/benchkit" "github.com/relab/gorums/gorumstest" - "github.com/relab/gorums/runtime/gorumsimpl" proto "google.golang.org/protobuf/proto" ) @@ -126,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, gorums.AsProto[*Echo](in)), nil + return gorums.AsProto[*Echo](in), nil }) } diff --git a/call_async_test.go b/call_async_test.go index 92cb0f07b..479bf497b 100644 --- a/call_async_test.go +++ b/call_async_test.go @@ -181,7 +181,7 @@ 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, send func(proto.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, send) }) diff --git a/call_test.go b/call_test.go index d84dc47cf..34d3fcbae 100644 --- a/call_test.go +++ b/call_test.go @@ -29,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, _ func(proto.Message)) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { return nil, nil }) } @@ -63,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, _ func(proto.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 }) @@ -87,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, _ func(proto.Message)) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { return nil, nil }) } @@ -138,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, _ func(proto.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/gengorums/template_server.go b/cmd/protoc-gen-gorums/gengorums/template_server.go index ff2b7b146..8fc97780e 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_server.go +++ b/cmd/protoc-gen-gorums/gengorums/template_server.go @@ -75,16 +75,15 @@ var registerInterface = ` {{$gorumsMessage := use "gorums.Message" .GenFile}} {{$protoMessage := use "proto.Message" .GenFile}} {{$asProto := use "gorums.AsProto" .GenFile}} -{{$newMessage := use "gorumsimpl.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 send" $context $gorumsMessage $protoMessage $asProto $newMessage (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}})) (*{{$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}}) @@ -95,11 +94,7 @@ func {{$names.Register}}({{$reg.srv}} *{{$gorumsServer}}, {{$reg.impl}} {{$names }) 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 223956795..5711864b5 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -625,9 +625,9 @@ 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. -Each handler receives a `send func(proto.Message)` callback for streaming replies; `Server.HandleRequest` builds and sends their response envelopes. -Generated unary handlers build their response envelopes through `runtime/gorumsimpl.NewResponseMessage`. +A `gorums.Message` wraps a request's `stream.Message` together with its decoded proto payload. +A handler returns its response as a `proto.Message`, and sends each response of a streaming call through its `send func(proto.Message)` callback. +`Server.HandleRequest` builds the response envelope for both, so handlers never touch envelopes. ```mermaid classDiagram diff --git a/doc/user-guide.md b/doc/user-guide.md index ea699677b..84cd6f684 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -1340,12 +1340,13 @@ 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, send func(proto.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. Pass `send` to `next` so the handler can send streamed responses. +An interceptor can wrap `send` to observe or change each streamed response, and it receives a unary response as the `proto.Message` that `next` returns. Below are several examples based on the `examples/interceptors` package. @@ -1353,7 +1354,7 @@ 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, send func(proto.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) @@ -1361,8 +1362,7 @@ func LoggingInterceptor(addr string) gorums.ServerInterceptor { 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 } } @@ -1373,13 +1373,13 @@ func LoggingInterceptor(addr string) gorums.ServerInterceptor { 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, send func(proto.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, send) } -func MetadataInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func(proto.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") @@ -1396,7 +1396,7 @@ 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, send func(proto.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") diff --git a/examples/interceptors/server_interceptors.go b/examples/interceptors/server_interceptors.go index ad6aaa25d..5f9b2c979 100644 --- a/examples/interceptors/server_interceptors.go +++ b/examples/interceptors/server_interceptors.go @@ -15,33 +15,31 @@ 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, send func(proto.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, 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, send func(proto.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, send) - resp := gorums.AsProto[proto.Message](out) - log.Printf("LoggingSimpleInterceptor(outgoing): Method=%s, Err=%v, Message=%v", in.GetMethod(), err, resp) + log.Printf("LoggingSimpleInterceptor(outgoing): Method=%s, Err=%v, Message=%v", in.GetMethod(), err, out) return out, err } // 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, send func(proto.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) @@ -63,7 +61,7 @@ func DelayedInterceptor(ctx gorums.ServerContext, in *gorums.Message, send func( } // NoFooAllowedInterceptor rejects requests for messages with key "foo". -func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerContext, in *gorums.Message, send func(proto.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" { @@ -76,7 +74,7 @@ func NoFooAllowedInterceptor[T interface{ GetKey() string }](ctx gorums.ServerCo // 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, send func(proto.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{} diff --git a/gorumstest/gorumstest.go b/gorumstest/gorumstest.go index cd1e74643..56ca2c3be 100644 --- a/gorumstest/gorumstest.go +++ b/gorumstest/gorumstest.go @@ -43,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, _ func(proto.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 ccdb2e292..469c86655 100644 --- a/gorumstest/servers.go +++ b/gorumstest/servers.go @@ -8,7 +8,6 @@ import ( "github.com/relab/gorums" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/servers" - "github.com/relab/gorums/runtime/gorumsimpl" "go.uber.org/goleak" "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" @@ -145,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return resp, nil }) - srv.RegisterHandler(mock.GetValueMethod, func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return resp, nil }) return srv } @@ -179,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, _ func(proto.Message)) (*gorums.Message, error) { + return func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { req := gorums.AsProto[*pb.StringValue](in) - return gorumsimpl.NewResponseMessage(in, pb.String(prefix+": "+req.GetValue())), nil + return pb.String(prefix + ": " + req.GetValue()), nil } } @@ -200,7 +199,7 @@ func EchoServer(_ int) ServerIface { func StreamServer(delay time.Duration) func(int) ServerIface { return func(int) ServerIface { srv := gorums.NewServer() - srv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message, send func(proto.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() diff --git a/internal/stream/message.go b/internal/stream/message.go index f9fb5965d..3dfa089e3 100644 --- a/internal/stream/message.go +++ b/internal/stream/message.go @@ -43,15 +43,6 @@ func NewMessageFromPayload(ctx context.Context, msgID uint64, method string, pay return msgBuilder.Build() } -// NewResponseEnvelope returns a fresh response envelope with the request's -// message ID and method. The payload and status are left empty. -func NewResponseEnvelope(in *Message) *Message { - return Message_builder{ - MessageSeqNo: in.GetMessageSeqNo(), - Method: in.GetMethod(), - }.Build() -} - // appendToIncomingContext appends client-specific metadata from the [Message] proto message // to the incoming gRPC context, allowing server implementations to extract and use said // metadata directly from the server method's context. It never modifies ctx's own metadata, diff --git a/internal/tests/tls/peer_identity_test.go b/internal/tests/tls/peer_identity_test.go index ccfbb868a..712ae7310 100644 --- a/internal/tests/tls/peer_identity_test.go +++ b/internal/tests/tls/peer_identity_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/relab/gorums/internal/testutils/mockcall" - "github.com/relab/gorums/runtime/gorumsimpl" "github.com/relab/gorums" "github.com/relab/gorums/gorumstest" @@ -61,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, pb.String(reply)), nil + return pb.String(reply), nil }) } return servers, calls diff --git a/message.go b/message.go index 8b37206c3..c9e2bc1d0 100644 --- a/message.go +++ b/message.go @@ -18,25 +18,19 @@ type Message struct { // MetadataEntry is a type alias for [stream.MetadataEntry]. type MetadataEntry = stream.MetadataEntry -// newResponseMessage creates a response envelope for the given request and -// proto message. The sender marshals its payload before transmission. -func newResponseMessage(in *Message, resp proto.Message) *Message { - if in == nil { - return nil - } - return &Message{ - Proto: resp, - Message: stream.NewResponseEnvelope(in.Message), - } -} - -// 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 aebcd9e84..4191c2be9 100644 --- a/message_test.go +++ b/message_test.go @@ -3,97 +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" - "github.com/relab/gorums/runtime/gorumsimpl" - "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 := gorumsimpl.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() @@ -105,7 +18,7 @@ func TestAsProto(t *testing.T) { }{ { name: "Success", - msg: gorumsimpl.NewResponseMessage(&gorums.Message{}, dev.Response_builder{Result: 42}.Build()), + msg: &gorums.Message{Proto: dev.Response_builder{Result: 42}.Build()}, wantNil: false, wantResult: 42, }, @@ -116,7 +29,7 @@ func TestAsProto(t *testing.T) { }, { name: "WrongType", - msg: gorumsimpl.NewResponseMessage(&gorums.Message{}, dev.Request_builder{Value: "request"}.Build()), + msg: &gorums.Message{Proto: dev.Request_builder{Value: "request"}.Build()}, wantNil: true, }, } diff --git a/runtime/gorumsimpl/response.go b/runtime/gorumsimpl/response.go deleted file mode 100644 index 9a2a21a31..000000000 --- a/runtime/gorumsimpl/response.go +++ /dev/null @@ -1,19 +0,0 @@ -package gorumsimpl - -import ( - "github.com/relab/gorums" - "github.com/relab/gorums/internal/stream" - "google.golang.org/protobuf/proto" -) - -// NewResponseMessage returns a response envelope for generated unary handlers. -// The server marshals its payload before sending it. -func NewResponseMessage(in *gorums.Message, resp proto.Message) *gorums.Message { - if in == nil { - return nil - } - return &gorums.Message{ - Proto: resp, - Message: stream.NewResponseEnvelope(in.Message), - } -} diff --git a/server.go b/server.go index 51e363091..fbfe0e7f5 100644 --- a/server.go +++ b/server.go @@ -17,13 +17,14 @@ import ( ) type ( - // Handler processes a request and returns a response. The send callback - // sends each response of a streaming call. - Handler func(ServerContext, *Message, func(proto.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, a streaming response - // sender, and the next Handler. It returns a Message and an error. - ServerInterceptor func(ServerContext, *Message, func(proto.Message), Handler) (*Message, error) + // 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 @@ -192,7 +193,7 @@ func chainInterceptors(final Handler, interceptors ...ServerInterceptor) Handler handler := final for _, curr := range slices.Backward(interceptors) { next := handler - handler = func(ctx ServerContext, in *Message, send func(proto.Message)) (*Message, error) { + handler = func(ctx ServerContext, in *Message, send func(proto.Message)) (proto.Message, error) { return curr(ctx, in, send, next) } } @@ -224,20 +225,19 @@ func (s *Server) HandleRequest(ctx context.Context, senderID ID, reqMsg *stream. 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()))) + srvCtx.reply(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)) + srvCtx.reply(reqMsg, nil, err) return } + in := &Message{Proto: msg, Message: reqMsg} out, err := handler(srvCtx, in, func(resp proto.Message) { - srvCtx.sendMessage(newResponseMessage(in, resp)) + srvCtx.reply(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 @@ -245,7 +245,7 @@ func (s *Server) HandleRequest(ctx context.Context, senderID ID, reqMsg *stream. if out == nil && err == nil { return } - srvCtx.sendMessage(messageWithError(in, out, err)) + srvCtx.reply(reqMsg, out, err) } // Serve serves on the externally supplied listener and records it so that @@ -432,22 +432,12 @@ 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. -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) - } - } +// reply sends resp, or the status of err, to the client as the response to +// the request in. A marshal failure is sent as the response status; the +// stream is not closed. +func (ctx *ServerContext) reply(in *stream.Message, resp proto.Message, err error) { if ctx.send != nil { - ctx.send(out.Message) + ctx.send(newResponse(in, resp, err)) } } diff --git a/server_internal_test.go b/server_internal_test.go index b8f7689e8..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, _ func(proto.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, _ func(proto.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, _ func(proto.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, _ func(proto.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 6fc7a416e..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, send func(proto.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 @@ -65,11 +65,10 @@ func appendStringInterceptor(inStr, outStr string) gorums.ServerInterceptor { 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. - // The server will 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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return resp, nil }) return s } @@ -119,7 +118,7 @@ func TestServerInterceptorsChain(t *testing.T) { // 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) (*gorums.Message, error) { + 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)) }) @@ -127,7 +126,7 @@ func TestServerInterceptorsStream(t *testing.T) { } 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)) (*gorums.Message, error) { + 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))) @@ -329,17 +328,17 @@ func TestServerHandleRequestRelease(t *testing.T) { { name: "HandlerReturns", method: mock.TestMethod, - handler: func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (*gorums.Message, error) { - return gorumsimpl.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, _ func(proto.Message)) (*gorums.Message, error) { + handler: func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { ctx.Release() - return gorumsimpl.NewResponseMessage(in, gorums.AsProto[*pb.StringValue](in)), nil + return gorums.AsProto[*pb.StringValue](in), nil }, wantReleases: 1, }, @@ -485,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, _ func(proto.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 @@ -506,7 +505,7 @@ func outerChainedHandler( if err != nil { return nil, err } - return gorumsimpl.NewResponseMessage(in, pb.String(req.GetValue()+" | "+res.GetValue())), nil + return pb.String(req.GetValue() + " | " + res.GetValue()), nil } } @@ -573,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, _ func(proto.Message)) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, _ *gorums.Message, _ func(proto.Message)) (proto.Message, error) { wg.Done() return nil, nil }) @@ -598,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, _ func(proto.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 @@ -613,7 +612,7 @@ func TestServerHandlerCanMulticastViaConfig(t *testing.T) { return nil, nil // one-way }) - srv.RegisterHandler(mock.StreamMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.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 @@ -723,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, _ func(proto.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() @@ -735,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, _ func(proto.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 @@ -764,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) } gorumstest.WaitForPeers(t, servers) @@ -847,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, pb.String("echo: "+req.GetValue())), nil + return pb.String("echo: " + req.GetValue()), nil }) } else { // Servers 1, 2 (remote): respond immediately. @@ -1033,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, _ func(proto.Message)) (*gorums.Message, error) { - return gorumsimpl.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] @@ -1069,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, _ func(proto.Message)) (*gorums.Message, error) { - return gorumsimpl.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 }) @@ -1081,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, _ func(proto.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 @@ -1092,7 +1091,7 @@ func TestServerBackChannelNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorumsimpl.NewResponseMessage(in, resp), nil + return resp, nil }) cfg, closeFn, err := gorums.NewConfig(gorums.WithNodeList(addrs), gorumstest.DialOptions(t), gorums.WithBackChannel(clientSrv)) if err != nil { @@ -1142,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, _ func(proto.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") @@ -1154,13 +1153,13 @@ func TestServerHandlerNestedCallBeforeRelease(t *testing.T) { if err != nil { return nil, err } - return gorumsimpl.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, _ func(proto.Message)) (*gorums.Message, error) { - return gorumsimpl.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 { @@ -1185,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, _ func(proto.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 } @@ -1408,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, _ func(proto.Message)) (*gorums.Message, error) { + srv.RegisterHandler(mock.TestMethod, func(_ gorums.ServerContext, in *gorums.Message, _ func(proto.Message)) (proto.Message, error) { calls.Add(1) - return gorumsimpl.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 8a94f5756..fb0a9f77a 100644 --- a/stream_dedup_test.go +++ b/stream_dedup_test.go @@ -14,7 +14,6 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" - "github.com/relab/gorums/runtime/gorumsimpl" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" @@ -443,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, pb.String("slow")), nil + return pb.String("slow"), nil }) servers[0].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -488,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, pb.String("slow")), nil + return pb.String("slow"), nil }) servers[1].RegisterHandler(mock.EchoMethod, gorumstest.EchoHandler("echo")) gorumstest.WaitForPeers(t, servers) @@ -531,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, _ func(proto.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() @@ -539,7 +538,7 @@ func TestStreamDedupNestedCallToRequesterWithoutRelease(t *testing.T) { if err != nil { return nil, err } - return gorumsimpl.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) @@ -566,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, _ func(proto.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 b3e79d652..5b5cc33ad 100644 --- a/tree_integration_test.go +++ b/tree_integration_test.go @@ -11,7 +11,6 @@ import ( "github.com/relab/gorums/gorumstest" "github.com/relab/gorums/internal/testutils/mock" "github.com/relab/gorums/internal/testutils/mockcall" - "github.com/relab/gorums/runtime/gorumsimpl" "google.golang.org/protobuf/proto" pb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -70,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, _ func(proto.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) @@ -135,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, _ func(proto.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 { @@ -217,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, _ func(proto.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() @@ -225,7 +224,7 @@ func TestTreeQuorumCall(t *testing.T) { if err != nil { return nil, err } - return gorumsimpl.NewResponseMessage(in, pb.Int32(total)), nil + return pb.Int32(total), nil }) } @@ -255,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, _ func(proto.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 { @@ -266,7 +265,7 @@ func TestTreeExternalCaller(t *testing.T) { total += r.Value.GetValue() } } - return gorumsimpl.NewResponseMessage(in, pb.Int32(total)), nil + return pb.Int32(total), nil }) } From a91528b056fe4955cca46a4af07564ad6c893350 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 19:10:06 +0100 Subject: [PATCH 5/8] all: regenerate server handlers that return proto messages --- benchkit/benchmark/benchmark_gorums.pb.go | 18 ++----- benchkit/control_gorums.pb.go | 26 +++------ .../dev/zorums_server_gorums.pb.go | 46 ++++++---------- examples/storage/proto/storage_gorums.pb.go | 54 ++++++------------- .../importmsgs/importmsgs_gorums.pb.go.golden | 8 +-- .../multicast/multicast_gorums.pb.go.golden | 2 +- .../multiservice_gorums.pb.go.golden | 26 +++------ .../quorumcall/quorumcall_gorums.pb.go.golden | 8 +-- .../clientnames_gorums.pb.go.golden | 20 +++---- .../nodeonly/nodeonly_gorums.pb.go.golden | 10 ++-- .../correctable/correctable_gorums.pb.go | 10 ++-- internal/tests/metadata/metadata_gorums.pb.go | 16 ++---- .../multiservice/multiservice_gorums.pb.go | 32 +++-------- internal/tests/oneway/oneway_gorums.pb.go | 4 +- internal/tests/ordering/ordering_gorums.pb.go | 16 ++---- internal/tests/tls/tls_gorums.pb.go | 8 +-- .../unresponsive/unresponsive_gorums.pb.go | 8 +-- 17 files changed, 88 insertions(+), 224 deletions(-) diff --git a/benchkit/benchmark/benchmark_gorums.pb.go b/benchkit/benchmark/benchmark_gorums.pb.go index 8470e686d..05279fb3b 100644 --- a/benchkit/benchmark/benchmark_gorums.pb.go +++ b/benchkit/benchmark/benchmark_gorums.pb.go @@ -85,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("benchmark.Benchmark.SlowServer", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.SlowServer(ctx, req) }) - srv.RegisterHandler("benchmark.Benchmark.Multicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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/control_gorums.pb.go b/benchkit/control_gorums.pb.go index 2d5f3377a..4c7edf03c 100644 --- a/benchkit/control_gorums.pb.go +++ b/benchkit/control_gorums.pb.go @@ -102,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Start(ctx, req) }) - srv.RegisterHandler("benchkit.Control.Stop", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Stop(ctx, req) }) - srv.RegisterHandler("benchkit.Control.ClockSync", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.ClockSync(ctx, req) }) - srv.RegisterHandler("benchkit.Control.Done", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go index 03fd57609..cf834bcb2 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go @@ -45,75 +45,59 @@ type ZorumsServiceServer interface { } func RegisterZorumsServiceServer(srv *gorums.Server, impl ZorumsServiceServer) { - srv.RegisterHandler("dev.ZorumsService.RemoteCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.RemoteCall(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCall", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.QuorumCallWithEmptyRequest(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.QuorumCallWithEmptyResponse(ctx, req) }) - srv.RegisterHandler("dev.ZorumsService.MulticastWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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, _ func(proto.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, _ func(proto.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, send func(proto.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) { send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyResponse", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.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) { send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.QuorumCallStreamWithEmptyRequest", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.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) { send(resp) }) return nil, nil }) - srv.RegisterHandler("dev.ZorumsService.Unicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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/examples/storage/proto/storage_gorums.pb.go b/examples/storage/proto/storage_gorums.pb.go index 5c606318f..f16a0ebad 100644 --- a/examples/storage/proto/storage_gorums.pb.go +++ b/examples/storage/proto/storage_gorums.pb.go @@ -165,65 +165,41 @@ type StorageServer interface { } func RegisterStorageServer(srv *gorums.Server, impl StorageServer) { - srv.RegisterHandler("proto.Storage.ReadRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.ReadRPC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.WriteRPC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteUnicast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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, _ func(proto.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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.ReadQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteQC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.WriteQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.ReadNestedQC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.ReadNestedQC(ctx, req) }) - srv.RegisterHandler("proto.Storage.WriteNestedMulticast", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.WriteNestedMulticast(ctx, req) }) - srv.RegisterHandler("proto.Storage.ReadCorrectable", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.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) { send(resp) 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 feee8c066..916cee2d9 100644 --- a/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden @@ -55,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, _ func(proto.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 gorumsimpl.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 0bed4cbb1..274d8671b 100644 --- a/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden @@ -65,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, _ func(proto.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 1356c24f7..2cb5432c3 100644 --- a/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden @@ -141,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("internal.testprotos.calltypes.multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 4e86478b7..c7d109720 100644 --- a/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden @@ -54,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, _ func(proto.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 gorumsimpl.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 45ae4291c..4872ba1ee 100644 --- a/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden +++ b/internal/testprotos/testdata/clientnames/clientnames_gorums.pb.go.golden @@ -110,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Config(ctx, req) }) - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.WithConfig", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Node(ctx, req) }) - srv.RegisterHandler("internal.testprotos.clientnames.ClientNames.ForNode", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 0c2888702..ff1c290c0 100644 --- a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden +++ b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden @@ -79,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Read(ctx, req) }) - srv.RegisterHandler("internal.testprotos.nodeonly.NodeOnly.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 5e84b92cf..5c3e9e09d 100644 --- a/internal/tests/correctable/correctable_gorums.pb.go +++ b/internal/tests/correctable/correctable_gorums.pb.go @@ -70,15 +70,11 @@ type CorrectableTestServer interface { } func RegisterCorrectableTestServer(srv *gorums.Server, impl CorrectableTestServer) { - srv.RegisterHandler("correctable.CorrectableTest.Correctable", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Correctable(ctx, req) }) - srv.RegisterHandler("correctable.CorrectableTest.CorrectableStream", func(ctx gorums.ServerContext, in *gorums.Message, send func(proto.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) { send(resp) diff --git a/internal/tests/metadata/metadata_gorums.pb.go b/internal/tests/metadata/metadata_gorums.pb.go index 0a3a7196f..966afb8eb 100644 --- a/internal/tests/metadata/metadata_gorums.pb.go +++ b/internal/tests/metadata/metadata_gorums.pb.go @@ -58,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.IDFromMD(ctx, req) }) - srv.RegisterHandler("metadata.MetadataTest.WhatIP", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.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 d00efbdd9..a65358a63 100644 --- a/internal/tests/multiservice/multiservice_gorums.pb.go +++ b/internal/tests/multiservice/multiservice_gorums.pb.go @@ -130,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("multiservice.Alpha.Read", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.Heartbeat(ctx, req) }) - srv.RegisterHandler("multiservice.Beta.Write", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.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 a6fac28ce..3e3334afb 100644 --- a/internal/tests/oneway/oneway_gorums.pb.go +++ b/internal/tests/oneway/oneway_gorums.pb.go @@ -102,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, _ func(proto.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, _ func(proto.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 97bd9dc3a..084d236d6 100644 --- a/internal/tests/ordering/ordering_gorums.pb.go +++ b/internal/tests/ordering/ordering_gorums.pb.go @@ -83,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.QuorumCall(ctx, req) }) - srv.RegisterHandler("ordering.Ordering.UnaryRPC", func(ctx gorums.ServerContext, in *gorums.Message, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.UnaryRPC(ctx, req) }) } diff --git a/internal/tests/tls/tls_gorums.pb.go b/internal/tests/tls/tls_gorums.pb.go index f8ba32750..789d8a528 100644 --- a/internal/tests/tls/tls_gorums.pb.go +++ b/internal/tests/tls/tls_gorums.pb.go @@ -54,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, _ func(proto.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 gorumsimpl.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 e16cf22aa..22c764810 100644 --- a/internal/tests/unresponsive/unresponsive_gorums.pb.go +++ b/internal/tests/unresponsive/unresponsive_gorums.pb.go @@ -54,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, _ func(proto.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 gorumsimpl.NewResponseMessage(in, resp), nil + return impl.TestUnresponsive(ctx, req) }) } From 3ad0653c37e4854e8627368e997be98f64214069 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 22:38:38 +0100 Subject: [PATCH 6/8] gorums: send handler responses without a ServerContext field ServerContext kept the stream's send function only so that its reply method could build and send a response envelope. Each handler and interceptor copied this field, but none could use it. The send callback that HandleRequest passes to the handler also captured the ServerContext, which moved it to the heap on every request. HandleRequest now calls send with the envelope from newResponse directly. A HandleRequest microbenchmark drops from 8 to 7 allocations and from 358 to 310 bytes per request. The nil check is gone, because the stream package always supplies a non-nil send; the RequestHandler contract now says so. Updates: #424 --- internal/stream/channel.go | 12 ++++++------ server.go | 19 ++++--------------- 2 files changed, 10 insertions(+), 21 deletions(-) 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/server.go b/server.go index fbfe0e7f5..e83c00d46 100644 --- a/server.go +++ b/server.go @@ -219,25 +219,24 @@ 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 { - srvCtx.reply(reqMsg, 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) if err != nil { - srvCtx.reply(reqMsg, nil, err) + send(newResponse(reqMsg, nil, err)) return } in := &Message{Proto: msg, Message: reqMsg} out, err := handler(srvCtx, in, func(resp proto.Message) { - srvCtx.reply(reqMsg, resp, nil) + 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 @@ -245,7 +244,7 @@ func (s *Server) HandleRequest(ctx context.Context, senderID ID, reqMsg *stream. if out == nil && err == nil { return } - srvCtx.reply(reqMsg, out, err) + send(newResponse(reqMsg, out, err)) } // Serve serves on the externally supplied listener and records it so that @@ -381,7 +380,6 @@ type ServerContext struct { context.Context senderID ID release func() - send func(*stream.Message) srv *Server } @@ -432,15 +430,6 @@ func (ctx *ServerContext) Release() { } } -// reply sends resp, or the status of err, to the client as the response to -// the request in. A marshal failure is sent as the response status; the -// stream is not closed. -func (ctx *ServerContext) reply(in *stream.Message, resp proto.Message, err error) { - if ctx.send != nil { - ctx.send(newResponse(in, resp, err)) - } -} - // 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. From af801cf401c475ffcb30157034fb0aa059ff39fc Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 22:38:38 +0100 Subject: [PATCH 7/8] doc: explain how handlers and interceptors deliver responses The guides described unary and streamed responses in one sentence, and did not say what wrapping send means. They now list each call type: a unary handler returns its response, a streaming handler calls send once for each response, and a one-way handler sends nothing. The user guide shows how an interceptor gives next a new send function to see streamed responses, and adds StreamLoggingInterceptor to the interceptors example as a working pattern. Updates: #424 --- doc/dev-guide.md | 9 ++++-- doc/user-guide.md | 32 ++++++++++++++++++-- examples/interceptors/server_interceptors.go | 11 +++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/doc/dev-guide.md b/doc/dev-guide.md index 5711864b5..0e38bc0d1 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -626,8 +626,13 @@ 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 request's `stream.Message` together with its decoded proto payload. -A handler returns its response as a `proto.Message`, and sends each response of a streaming call through its `send func(proto.Message)` callback. -`Server.HandleRequest` builds the response envelope for both, so handlers never touch envelopes. +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 diff --git a/doc/user-guide.md b/doc/user-guide.md index 84cd6f684..4488750db 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -1345,8 +1345,19 @@ type ServerInterceptor func(ctx gorums.ServerContext, in *gorums.Message, send f 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. -Pass `send` to `next` so the handler can send streamed responses. -An interceptor can wrap `send` to observe or change each streamed response, and it receives a unary response as the `proto.Message` that `next` returns. + +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. @@ -1368,6 +1379,23 @@ func LoggingInterceptor(addr string) gorums.ServerInterceptor { } ``` +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: diff --git a/examples/interceptors/server_interceptors.go b/examples/interceptors/server_interceptors.go index 5f9b2c979..51c691bfc 100644 --- a/examples/interceptors/server_interceptors.go +++ b/examples/interceptors/server_interceptors.go @@ -36,6 +36,17 @@ func LoggingSimpleInterceptor(ctx gorums.ServerContext, in *gorums.Message, send 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. From b2585fb884b0451222c0b695807081ee2cf1c46d Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Mon, 2 Nov 2026 22:38:42 +0100 Subject: [PATCH 8/8] doc: use asterisk list markers in the error style rules The markdown linter requires asterisk list markers, and every other list in the developer guide already uses them. --- doc/dev-guide.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/dev-guide.md b/doc/dev-guide.md index 0e38bc0d1..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.