Skip to content

Refactor legacy SendMsg/RecvMsg to new type-safe Send/Recv interface#265

Merged
meling merged 19 commits intomasterfrom
meling/252/refactor-legacy-send-recv-stream-interface
Feb 11, 2026
Merged

Refactor legacy SendMsg/RecvMsg to new type-safe Send/Recv interface#265
meling merged 19 commits intomasterfrom
meling/252/refactor-legacy-send-recv-stream-interface

Conversation

@meling
Copy link
Member

@meling meling commented Feb 8, 2026

Fixes #252

Summary of Changes: Type-safe stream communication

This PR introduces a significant refactor to simplify the internal communication layer and message handling. Key improvements include replacing the Message wrapper with direct Metadata usage, enforcing type-safe stream operations, and removing legacy codec implementations.

Refactoring & Simplification

  • Streamlined Messaging: Replaced the intermediate Message wrapper with direct Metadata usage in the channel layer.
  • Type-Safety: Transitioned server implementations to use type-safe Send/Recv methods, deprecating the legacy SendMsg/RecvMsg interface.
  • Code Cleanup: Removed the custom gRPC codec and the msgType field from the Message struct, as they are no longer needed.
  • Response Handling: Simplified server response logic with a new responseWithError helper and unified error handling in the client interceptor.
  • Metadata: Consolidated metadata creation into a single NewMetadata function.

Internal Restructuring

  • Package Organization: Moved the stream package to internal/stream to better encapsulate internal logic.
  • Cleanup: Removed unused functions like NewRequest and unexported internal marshal/unmarshal helpers.

Maintenance

  • Tests: Fixed flakiness in multicast tests by sorting responses and updated AsProto documentation.
  • Generated Code: Regenerated proto files to align with the refactored codebase.

Replace the Message wrapper with ordering.Metadata in the request struct
and channel operations. This simplifies the wire protocol by using the
generated protobuf types directly.

Changes:
- Replace `msg *Message` with `md *ordering.Metadata` in request struct
- Update getStream() to return the typed Gorums_NodeStreamClient
- Use stream.Send()/Recv() with Metadata instead of SendMsg()/RecvMsg()
- Replace GetMessageID() with GetMessageSeqNo() for message routing
- Add UnmarshalResponse() for deserializing response messages
- Remove unused newRequestMessage() function from encoding.go
Replace legacy SendMsg/RecvMsg with type-safe Send/Recv methods in
server.go, continuing the work from issue #252.

Changes:
- Change finished channel from chan *Message to chan *ordering.Metadata
- Use srv.Recv() to receive *ordering.Metadata directly
- Use srv.Send(md) to send metadata directly
- Add UnmarshalRequest() to convert metadata to *Message for handlers
- Add MarshalResponseMetadata() to convert *Message to metadata for sending
- Update ServerCtx to use *ordering.Metadata channel internally
- Clone metadata in MarshalResponseMetadata to avoid race conditions

The Handler interface remains unchanged - conversion between *Message
and *ordering.Metadata happens at the server boundaries, so no changes
are needed to generated code.
Add responseWithError() helper function that creates a response message
if needed and sets the error status in a single call. This simplifies
the server's handler goroutine by:

- Properly handling UnmarshalRequest errors (send error response to client)
- Consolidating the nil message check and setError call into one line
- Reducing the handler goroutine from 26 lines to 21 lines

The helper is also useful for interceptors that need to return errors.
The custom Codec is no longer needed now that we use type-safe Send/Recv
methods with *ordering.Metadata. gRPC's default proto codec handles
serialization directly. We instead provide custom functions.

Removed:
- Codec struct and methods (Marshal, Unmarshal, gorumsMarshal, gorumsUnmarshal)
- NewCodec() constructor
- ContentSubtype constant
- init() function that registered the codec
- grpc.CallContentSubtype dial option
- Test init() functions that registered the codec
- mgr_test.go (only contained codec registration)

This simplifies the codebase by removing an abstraction layer that was
only needed for the legacy SendMsg/RecvMsg approach.
After removing the custom codec we no longer need the msgType field in
the gorums.Message struct.
We had only one remaining use of NewRequest in a test; this removes
NewRequest and replaces its use in TestAsProto with NewResponseMessage.
This replaces prior uses of NewGorumsMetadata.
@deepsource-io
Copy link
Contributor

deepsource-io bot commented Feb 8, 2026

Here's the code health analysis summary for commits b8a4608..3139e12. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Go LogoGo✅ SuccessView Check ↗
DeepSource Shell LogoShell✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Gorums’ per-node stream communication to use gRPC’s type-safe streaming interfaces (Send/Recv) with ordering.Metadata, removing the legacy SendMsg/RecvMsg usage and eliminating the custom gRPC codec.

Changes:

  • Switch client/server stream send/receive paths to typed ordering.Metadata (Send/Recv) and route responses by message_seq_no.
  • Introduce request/response marshal/unmarshal helpers that encode the application proto into Metadata.message_data and decode via protoregistry.
  • Remove codec registration/content subtype wiring and update tests accordingly.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
unicast.go Uses marshalRequest and enqueues metadata instead of *Message.
rpc.go Uses marshalRequest and enqueues metadata for RPC calls.
channel.go Reworks stream I/O to stream.Send(*Metadata) / stream.Recv() (*Metadata, error) and decodes responses from metadata.
server.go Reworks server stream I/O to typed Recv/Send, introduces request decoding and response marshaling in SendMessage.
encoding.go Removes codec implementation and adds (un)marshal helpers around Metadata.message_data + protoregistry.
ordering/gorums_metadata.go Consolidates metadata construction into NewMetadata (now includes optional marshaled message bytes).
client_interceptor.go Updates interceptor send path to clone metadata and set message_data per node/request.
channel_test.go Updates tests/benchmarks to create/enqueue metadata-based requests.
encoding_test.go Adjusts AsProto tests to reflect removal of NewRequest.
mgr.go Removes default call content subtype setup (codec no longer used).
server_test.go / rpc_test.go / config_test.go Removes codec registration from tests.
mgr_test.go Removes manager logging test (deleted).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

This commit refactors the internal messaging layer by replacing
the `ordering` package with `stream` and unifying the message
envelope structure.

Key changes:

- Rename the `ordering` package and proto files to `stream`.
- Update `stream.proto` to define a single `Message` type containing
  both metadata and payload (as `bytes`), replacing the previous
  split between `Metadata` and application message.
- Update `server.go` to:
  - Use the new `stream` package.
  - Implement explicit payload marshaling within `SendMessage`.
  - Send error messages to the client on marshaling failures instead
    of closing the stream.
- Update `encoding.go` to support packing/unpacking payloads into
  `stream.Message`.
- Update `client_interceptor.go` to work with the new
  `stream.Message` structure.
- Update `cmd/protoc-gen-gorums` templates to generate code compatible
  with the new stream package and message format.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

This is to avoid directly exposing the internal message types for the
Gorums runtime (NodeStream).

This comment was marked as outdated.

- Remove expectedReplies field and always send one response
  per node (success, error, or skip)
- Added ErrSkipNode for nodes skipped by request transformations
- Update quorum and multicast logic to treat skips as non-errors
- Simplify response iterator to use config size instead of expected
  count

This fixes the issue where marshal failures or skips could cause
the response iterator to stop early or block, ensuring exactly one
response is sent per node in the configuration.
Don't reuse the in message as the response message as explained in a
review comment; this can cause unnecessary bandwidth usage to send back
the request payload to the client for no good reason.

Review comment from Copilot:
messageWithError reuses the incoming request message (in) when out is nil.
That means error responses will echo the original request payload back to
the client (wasted bandwidth, and surprising semantics). Prefer constructing
a fresh response stream.Message (seqno+method+status, empty payload) or at
least clear Payload/Entry when building an error-only response.
This resolves a code review complaint about incorrect gRPC-style path.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

responses.go:203

  • Responses.Threshold currently treats ErrSkipNode as a successful response (it does not continue), which means skipped nodes increment count and can set resp to a zero value. This can cause First()/Majority()/All() to return a nil/zero response with nil error even though no node actually replied successfully. Skipped nodes should be ignored for success counting (e.g., if errors.Is(result.Err, ErrSkipNode) { continue }) and only count++ on result.Err == nil.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

This avoids the cmp.Or function here since it will always call the
transformAndMarshal() method even if there is nothing to transform.
@meling
Copy link
Member Author

meling commented Feb 11, 2026

@copilot can you please revise the top-level pull request description to match the current state of the pull request.

Copy link
Contributor

Copilot AI commented Feb 11, 2026

@meling I've opened a new pull request, #267, to work on those changes. Once the pull request is ready, I'll request review from you.

@meling meling merged commit 5deecbd into master Feb 11, 2026
5 checks passed
@meling meling deleted the meling/252/refactor-legacy-send-recv-stream-interface branch February 11, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Legacy use of RecvMsg and SendMsg in NodeStream; should use type-safe Recv and Send instead

2 participants