From 1f284b1dcf5d756b9f0e8f459118b2648d37c34e Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Sun, 1 Nov 2026 22:58:44 +0100 Subject: [PATCH 1/5] gorums: align call and generator documentation with behavior --- cmd/protoc-gen-gorums/dev/aliases.go | 14 ++------- .../gengorums/template_multicast.go | 5 +-- .../gengorums/template_quorumcall.go | 12 +++---- .../gengorums/template_unicast.go | 2 +- doc.go | 23 +++++++------- doc/dev-guide.md | 6 ++-- doc/user-guide.md | 31 +++++++++---------- errors.go | 13 ++++---- internal/impl/errors.go | 7 ++--- internal/impl/multicast.go | 15 ++------- internal/impl/quorumcall.go | 29 +++-------------- internal/impl/remotecall.go | 6 ++-- internal/impl/unicast.go | 13 ++------ 13 files changed, 63 insertions(+), 113 deletions(-) diff --git a/cmd/protoc-gen-gorums/dev/aliases.go b/cmd/protoc-gen-gorums/dev/aliases.go index 0fcab47e0..c1fbcb221 100644 --- a/cmd/protoc-gen-gorums/dev/aliases.go +++ b/cmd/protoc-gen-gorums/dev/aliases.go @@ -2,18 +2,8 @@ package dev import gorums "github.com/relab/gorums" -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/cmd/protoc-gen-gorums/gengorums/template_multicast.go b/cmd/protoc-gen-gorums/gengorums/template_multicast.go index c79b8236c..0dc760f10 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_multicast.go +++ b/cmd/protoc-gen-gorums/gengorums/template_multicast.go @@ -14,8 +14,9 @@ var multicastComment = ` {{if ne $comments ""}} {{$comments -}} {{else}} -// {{$method}} starts a one-way call on the client's configuration; no reply -// is returned. Use gorums.MapRequest to send a different message to each node. +// {{$method}} returns a one-way call handle for the client's configuration. +// No reply is returned. Use gorums.MapRequest to send a different message to +// each node. // // Send blocks until every send completes and returns any send errors. // Async returns a handle at once; Wait on the handle returns the send errors. diff --git a/cmd/protoc-gen-gorums/gengorums/template_quorumcall.go b/cmd/protoc-gen-gorums/gengorums/template_quorumcall.go index 850a9eb7d..dca214b2a 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_quorumcall.go +++ b/cmd/protoc-gen-gorums/gengorums/template_quorumcall.go @@ -17,9 +17,9 @@ var quorumCallComment = ` {{if ne $comments ""}} {{$comments -}} {{else}} -// {{$method}} executes a quorum call on the client's configuration. Use -// terminal methods like Majority(), First(), or Threshold(n) to retrieve the -// aggregated result. +// {{$method}} returns a quorum-call handle for the client's configuration. +// A terminal method such as Majority(), First(), or Threshold(n) dispatches +// the call and retrieves the aggregated result. // // Example: // @@ -59,9 +59,9 @@ var quorumCallStreamComment = ` {{if ne $comments ""}} {{$comments -}} {{else}} -// {{$method}} executes a streaming quorum call on the client's configuration. -// The server can send multiple responses, and the response iterator continues -// until the context is canceled. +// {{$method}} returns a streaming quorum-call handle for the client's +// configuration. The server can send multiple responses, and the response +// iterator continues until the context is canceled. // // Example: // diff --git a/cmd/protoc-gen-gorums/gengorums/template_unicast.go b/cmd/protoc-gen-gorums/gengorums/template_unicast.go index ea6ea816a..b2aa198d9 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_unicast.go +++ b/cmd/protoc-gen-gorums/gengorums/template_unicast.go @@ -14,7 +14,7 @@ var unicastComment = ` {{if ne $comments ""}} {{$comments -}} {{else}} -// {{$method}} starts a one-way call on the client's node; no reply is +// {{$method}} returns a one-way call handle for the client's node; no reply is // returned. // // Send blocks until the send completes and returns any send error. diff --git a/doc.go b/doc.go index f7353148e..5b6bb4105 100644 --- a/doc.go +++ b/doc.go @@ -18,9 +18,8 @@ // - multicast: a one-way call to every node in a configuration. // - unicast: a one-way call to a single node. // -// Calls are dispatched lazily: the request is sent on the first consuming -// operation, such as a terminal method, [Call.Async], or a range over the -// response sequence. +// Quorum calls and one-way calls return handles that dispatch when consumed. +// Remote calls execute immediately. // Lazy dispatch is what lets a call site attach client interceptors before the // request is sent. [Call.Async] sends the request at once and leaves the // replies to a later terminal method, and [Responses.Correctable] reports @@ -42,13 +41,13 @@ // leader := pb.NewStorageNodeClient(cfg.Node(leaderID)) // reply, err = leader.ReadRPC(ctx, req) // -// A node client holds only its node, so building one allocates nothing. -// Build a node client where you need it, and do not keep it. The node may be -// any node, also one outside cfg. If the node is nil, every call returns -// [ErrNoNode]. A quorum call or multicast on an empty configuration sends -// nothing and returns [ErrEmptyConfig]. To call another configuration, build -// a new SClient from it. A client does not own its configuration or node; -// the close function returned by [NewConfig] closes the connections. +// A node client holds only its node, which may be outside cfg. +// If the node is nil, remote calls return [ErrNoNode] and unicast handles +// report it when consumed. A quorum call or multicast on an empty +// configuration sends nothing and reports [ErrEmptyConfig] when consumed. +// To call another configuration, build a new SClient from it. +// A client does not own its configuration or node; the close function returned +// by [NewConfig] closes the connections. // // A Go package may hold several services, in one proto file or in several. // Each service gets its own clients, server interface, and register function. @@ -59,8 +58,8 @@ // // # Configurations and nodes // -// A [Node] is a client-side handle to a remote (or co-located) peer, not the -// local process. A configuration is an immutable, ordered set of nodes built +// A [Node] is a client-side handle to a peer reached through a network or +// local transport. A configuration is an immutable, ordered set of nodes built // with [NewConfig] from a node source, e.g., [WithNodes] or [WithNodeList]. // Set operations such as [Config.Add], [Config.Remove], and [Config.Union] // derive new configurations that share the underlying node connections, so diff --git a/doc/dev-guide.md b/doc/dev-guide.md index fb4067fed..85780ae1f 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -512,13 +512,13 @@ flowchart LR ### Call Engine (`internal/impl`) A call constructor takes the call's `context.Context` and its target directly: a `Config` for quorum calls and multicasts, and a `*Node` for remote calls and unicasts. -On an empty `Config`, the `CallContext` holds `ErrEmptyConfig`: a quorum call sends nothing, its terminal methods return the error, and its response iterator yields one response that holds the error; `Multicast` returns a `OnewayCall` that sends nothing and reports the error. -On a nil `*Node`, `RemoteCall` returns `ErrNoNode`, and `Unicast` returns a `OnewayCall` whose `CallContext` holds `ErrNoNode` and that sends nothing. +On an empty `Config`, the `CallContext` holds `ErrEmptyConfig`: a quorum call sends nothing, its terminal methods return the error, and its response iterator yields one response that holds the error; `Multicast` returns an `OnewayCall` that sends nothing and reports the error. +On a nil `*Node`, `RemoteCall` returns `ErrNoNode`, and `Unicast` returns an `OnewayCall` whose `CallContext` holds `ErrNoNode` and that sends nothing. Each call constructor except `RemoteCall` builds a `CallContext` that holds the request, the target `Config`, the response channel, and the response iterator (`ResponseSeq`). A unicast's `CallContext` holds a one-node `Config`. A quorum call returns a `Call`, which embeds `Responses` and its terminal methods (`First`, `Majority`, `All`, `Threshold`, `Aggregate`, `Reduce`, `Matching`), as well as the `Correctable` future. `Call.Async` sends the requests at once and returns the dispatched `Responses`, so any terminal method collects the replies later. -A one-way call (`Multicast`, `Unicast`) returns a `OnewayCall`, whose `Send` waits for the send confirmations and whose `Async` returns an `OnewayAsync`. +A one-way call (`Multicast`, `Unicast`) returns an `OnewayCall`, whose `Send` waits for local send confirmations and whose `Async` returns an `OnewayAsync`. A `ClientInterceptor` wraps the response iterator and may register per-node request transformations before the call is dispatched. `CallContext` reaches each node's send path through `conn.NodeTransport`. diff --git a/doc/user-guide.md b/doc/user-guide.md index 4488750db..436e2c746 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -211,7 +211,7 @@ protoc -I="$(go list -m -f {{.Dir}} github.com/relab/gorums):." \ The above step should produce three files named `storage.pb.go`, `storage_gorums.pb.go`, and `gorums.pb.go` in your package directory. The first contains the Protobuf definitions of our messages. The second contains the Gorums generated client and server interfaces. -The third declares the `Config`, `ID`, and `Node` type aliases of the Go package. +The third declares the `Config`, `ID`, `Node`, and `Correctable` type aliases of the Go package. ### Examining the Gorums Generated Code @@ -228,7 +228,7 @@ func NewStorageClient(config gorums.Config) StorageClient func NewStorageNodeClient(node *gorums.Node) StorageNodeClient ``` -The `StorageClient` methods send a request to every node in the configuration. +The `StorageClient` methods target every node in the configuration. A quorum call method returns a `*gorums.Call[*ReadRequest, *ReadResponse]` handle. The handle embeds `*gorums.Responses[*ReadResponse]`, which provides the terminal methods. The `Call` type also has an `Intercept` method, which registers client interceptors before the call is sent. @@ -246,7 +246,7 @@ type StorageClient interface { } ``` -The `StorageNodeClient` methods send a request to the node of the client: +The `StorageNodeClient` methods target the client's node: ```go type StorageNodeClient interface { @@ -280,16 +280,17 @@ err = leader.WriteUnicast(ctx, req).Send() Follow these rules when you use the clients: * **Build node clients where you need them.** - A node client holds one pointer and owns nothing, so building one allocates nothing. - Do not keep node clients in a struct or a map. + A node client holds one pointer and owns nothing. * **A nil node does not panic.** `cfg.Node(id)` returns nil if the configuration has no node with that ID. - `NewStorageNodeClient(nil)` is legal, and every call on that client returns `gorums.ErrNoNode`. + `NewStorageNodeClient(nil)` is legal. + Remote calls return `gorums.ErrNoNode`; unicast handles report it from `Send` or `Wait` after `Async`. This error is a gRPC status error with the `InvalidArgument` code, because retrying cannot fix it. * **Any node works.** The node can also be a node that is not in a configuration that you use for quorum calls. * **An empty configuration does not panic.** - A quorum call or multicast on an empty configuration sends nothing and returns `gorums.ErrEmptyConfig`. + A quorum call or multicast on an empty configuration sends nothing. + Its handle reports `gorums.ErrEmptyConfig` when consumed. This error is a gRPC status error with the `InvalidArgument` code. `Results()` yields one response whose `Err` is `gorums.ErrEmptyConfig`, and `Correctable` completes at once with this error. * **Build a new client for a new configuration.** @@ -322,10 +323,8 @@ type StorageServer interface { } ``` -It is worth noting that unicast and multicast methods do not return a response or error, as they are one-way calls. -On the client side, `Send` returns an error if the request could not be sent. -`Async` returns a handle at once; `Wait` on that handle returns the same send errors. -Neither reports the success or failure of the request at the server side. +Unicast and multicast server methods return no response or error. +The client-side `Send` method and `Wait` after `Async` report local send errors, but do not report the server handler's result. **Note:** You may decide to keep the `.proto` file and the generated `.pb.go` files in a separate directory/package and import that package (the generated Gorums API) into your application. @@ -831,9 +830,9 @@ Collecting the replies one call after the other takes about as long as the slowe The call is not dispatched until the caller calls `.Send()` or `.Async()` on the handle. If the caller drops the handle without calling either method, the call is never dispatched. -`.Send()` and the `.Wait()` of an `.Async()` handle report every send failure, so a one-way call cannot fail silently. -Each send completes when the message reaches the stream of its node. -A one-way call has no reply, so there is nothing more to wait for. +`.Send()` and `.Wait()` after `.Async()` report whether the local transport accepted each send. +They cannot report errors that occur later in the server handler. +A one-way call has no reply to await. A server handler that multicasts calls `.Async()` before `ServerContext.Release()` and `.Wait()` after it. The sends then keep the order of the requests. @@ -2306,7 +2305,7 @@ Nested calls follow the rule in [Request Ordering and ServerContext.Release](#re `ReadNestedQC` below calls `Release` first, because the nested reads of different requests may run in any order; the next request starts while the nested calls are in flight. `WriteNestedMulticast` dispatches its multicast before `Release`, so the peers receive the writes in the order the requests arrived. -A quorum call or a multicast on an empty configuration returns `gorums.ErrEmptyConfig`. +A quorum call or a multicast on an empty configuration reports `gorums.ErrEmptyConfig` when its handle is consumed. The `len(config) == 0` checks below return a clearer error for a server that has no peers. ```go @@ -2553,7 +2552,7 @@ This is how a protocol can use a new tree for each view, or fall back to a star The root is simply the first node of the configuration, and it is a node like any other: it holds the data and runs the same handlers. A caller that is not in the configuration starts a tree call by sending to the root, with a one-node configuration, for example `gorums.Config{tree.Root()}`. The root's handler then relays to `tree.ChildrenOf(root.ID())` like every other internal node, so the root's own work is done too. -Do not start at `tree.ChildrenOf(tree.Root().ID())` from outside: that skips the root, and a call on an empty configuration, which is what `ChildrenOf` returns for a one-node tree, returns `gorums.ErrEmptyConfig`. +Do not start at `tree.ChildrenOf(tree.Root().ID())` from outside: that skips the root, and a call on an empty configuration, which is what `ChildrenOf` returns for a one-node tree, reports `gorums.ErrEmptyConfig` when consumed. When the process that starts the call is itself the root, it does its local work and then sends to `tree.ChildrenOf(id)` with its own ID. ### Relaying in a Server diff --git a/errors.go b/errors.go index 298cbfbf4..7e9bc824b 100644 --- a/errors.go +++ b/errors.go @@ -27,16 +27,17 @@ var ErrStreamDown = stream.ErrStreamDown // is a gRPC status error with the Unavailable code; match it with [errors.Is]. var ErrNodeClosed = stream.ErrNodeClosed -// ErrNoNode is returned by a remote call or unicast on a nil node, such as -// the node that [Config.Node] returns for an ID outside the configuration. +// ErrNoNode is returned by a remote call on a nil node, or reported when a +// unicast handle for a nil node is consumed. [Config.Node] returns nil for an +// ID outside the configuration. // It is a gRPC status error with the InvalidArgument code; match it with // [errors.Is]. var ErrNoNode = impl.ErrNoNode -// ErrEmptyConfig is returned by a quorum call or multicast on an empty -// configuration, such as one that [Config.Remove] or [Config.WithoutErrors] -// emptied. It is a gRPC status error with the InvalidArgument code; match it -// with [errors.Is]. +// ErrEmptyConfig is reported when a quorum call or multicast handle on an +// empty configuration is consumed. [Config.Remove] and +// [Config.WithoutErrors] can produce an empty configuration. It is a gRPC +// status error with the InvalidArgument code; match it with [errors.Is]. var ErrEmptyConfig = impl.ErrEmptyConfig // ErrSendQueueFull is returned for a two-way call enqueued while the node's diff --git a/internal/impl/errors.go b/internal/impl/errors.go index ec36ee817..7fcf1b2a4 100644 --- a/internal/impl/errors.go +++ b/internal/impl/errors.go @@ -27,8 +27,7 @@ var ErrTypeMismatch = errors.New("response type mismatch") // cannot succeed until the caller passes a node. var ErrNoNode = status.Error(codes.InvalidArgument, "no such node") -// ErrEmptyConfig is returned by a quorum call or multicast on an empty -// configuration, such as one that [Config.Remove] or [Config.WithoutErrors] -// emptied. It is a gRPC status error with the InvalidArgument code, because -// the call cannot succeed until the caller passes a node. +// ErrEmptyConfig is reported when a quorum call or multicast handle on an +// empty configuration is consumed. It is a gRPC status error with the +// InvalidArgument code. var ErrEmptyConfig = status.Error(codes.InvalidArgument, "empty configuration") diff --git a/internal/impl/multicast.go b/internal/impl/multicast.go index b9d3add44..c0c8e14c7 100644 --- a/internal/impl/multicast.go +++ b/internal/impl/multicast.go @@ -7,18 +7,9 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// Multicast is a one-way call to every node in config; no replies are -// returned to the client. It returns an -// [OnewayCall] handle that dispatches the request only when it is consumed: -// [OnewayCall.Send] blocks until the send completes for every node and reports -// any send failures, while [OnewayCall.Async] dispatches without waiting and -// defers those failures to [OnewayAsync.Wait]. If config is empty, the handle -// sends nothing and reports [ErrEmptyConfig]. -// -// Register per-node request transforms with [OnewayCall.Intercept] and -// [MapRequest] before consuming the handle. -// -// This function should be used by generated code only. +// Multicast returns a lazy [OnewayCall] for every node in config. +// On an empty config, it sends nothing and reports [ErrEmptyConfig] when +// consumed. func Multicast[Req proto.Message](ctx context.Context, config Config, req Req, method string) *OnewayCall[Req] { callCtx := &CallContext[Req, *emptypb.Empty]{ Context: ctx, diff --git a/internal/impl/quorumcall.go b/internal/impl/quorumcall.go index f3f9c3b81..b2fbab63f 100644 --- a/internal/impl/quorumcall.go +++ b/internal/impl/quorumcall.go @@ -6,35 +6,16 @@ import ( "google.golang.org/protobuf/proto" ) -// QuorumCall performs a quorum call on the nodes in config and returns a -// [Call] handle that provides access to node responses via terminal methods -// and fluent iteration. If config is empty, the call sends nothing and -// reports [ErrEmptyConfig]. -// -// Type parameters: -// - Req: The request message type -// - Resp: The response message type from individual nodes -// -// Register interceptors with [Call.Intercept] before invoking a terminal -// method. Messages are sent to nodes when a terminal method (like -// [Responses.Majority] or [Responses.First]) or an iterator method (like -// [Responses.Results]) is first called, applying any registered request -// transformations. -// -// This function should only be used by generated code. +// QuorumCall returns a lazy [Call] for the nodes in config. +// Consuming a call on an empty config sends nothing and reports +// [ErrEmptyConfig]. func QuorumCall[Req, Resp proto.Message](ctx context.Context, config Config, req Req, method string) *Call[Req, Resp] { return invokeQuorumCall[Req, Resp](ctx, config, req, method, false) } -// QuorumCallStream performs a streaming quorum call on the nodes in config and -// returns a [Call] handle. If config is empty, the call sends nothing and +// QuorumCallStream returns a lazy [Call] that accepts multiple responses per +// node until ctx ends. Consuming a call on an empty config sends nothing and // reports [ErrEmptyConfig]. -// This is used for correctable stream methods where the server sends multiple responses. -// -// In streaming mode, the response iterator continues indefinitely until the context -// is canceled, allowing the server to send multiple responses over time. -// -// This function should only be used by generated code. func QuorumCallStream[Req, Resp proto.Message](ctx context.Context, config Config, req Req, method string) *Call[Req, Resp] { return invokeQuorumCall[Req, Resp](ctx, config, req, method, true) } diff --git a/internal/impl/remotecall.go b/internal/impl/remotecall.go index 6c723d295..fded1e6cd 100644 --- a/internal/impl/remotecall.go +++ b/internal/impl/remotecall.go @@ -8,10 +8,8 @@ import ( "google.golang.org/protobuf/proto" ) -// RemoteCall executes a remote procedure call on node. It returns -// [ErrNoNode] if node is nil. -// -// This method should be used by generated code only. +// RemoteCall executes a remote procedure call on node. +// It returns [ErrNoNode] if node is nil. func RemoteCall[Req, Resp proto.Message](ctx context.Context, node *Node, req Req, method string) (Resp, error) { if node == nil { var zero Resp diff --git a/internal/impl/unicast.go b/internal/impl/unicast.go index b2a81285c..e038ad848 100644 --- a/internal/impl/unicast.go +++ b/internal/impl/unicast.go @@ -7,17 +7,8 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// Unicast is a one-way call to node; no reply is returned to the client. It -// returns an [OnewayCall] handle that dispatches the request only when it is -// consumed: [OnewayCall.Send] blocks until the send completes and reports any -// send error, while [OnewayCall.Async] dispatches without waiting and defers -// that error to [OnewayAsync.Wait]. If node is nil, the handle sends nothing -// and reports [ErrNoNode]. -// -// Register request transforms with [OnewayCall.Intercept] and [MapRequest] -// before consuming the handle. -// -// This function should be used by generated code only. +// Unicast returns a lazy [OnewayCall] for node. +// If node is nil, it sends nothing and reports [ErrNoNode] when consumed. func Unicast[Req proto.Message](ctx context.Context, node *Node, req Req, method string) *OnewayCall[Req] { callCtx := &CallContext[Req, *emptypb.Empty]{ Context: ctx, From f02d1dcba645c329781c5d392c34b9d8e66d5b0c Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Sun, 1 Nov 2026 22:58:47 +0100 Subject: [PATCH 2/5] protoc-gen-gorums: regenerate documented API comments --- benchkit/benchmark/gorums.pb.go | 14 ++------------ benchkit/gorums.pb.go | 14 ++------------ .../gengorums/template_static.go | 14 ++------------ examples/storage/proto/gorums.pb.go | 14 ++------------ .../calltypes/importmsgs/gorums.pb.go.golden | 14 ++------------ .../importmsgs/importmsgs_gorums.pb.go.golden | 6 +++--- .../calltypes/multicast/gorums.pb.go.golden | 14 ++------------ .../multicast/multicast_gorums.pb.go.golden | 5 +++-- .../calltypes/multiservice/gorums.pb.go.golden | 14 ++------------ .../multiservice_gorums.pb.go.golden | 17 +++++++++-------- .../calltypes/quorumcall/gorums.pb.go.golden | 14 ++------------ .../quorumcall/quorumcall_gorums.pb.go.golden | 6 +++--- .../testdata/clientnames/gorums.pb.go.golden | 14 ++------------ .../testdata/nodeonly/gorums.pb.go.golden | 14 ++------------ .../nodeonly/nodeonly_gorums.pb.go.golden | 2 +- .../tests/correctable/correctable_gorums.pb.go | 12 ++++++------ internal/tests/correctable/gorums.pb.go | 14 ++------------ internal/tests/metadata/gorums.pb.go | 14 ++------------ internal/tests/multiservice/gorums.pb.go | 14 ++------------ .../multiservice/multiservice_gorums.pb.go | 18 +++++++++--------- internal/tests/oneway/gorums.pb.go | 14 ++------------ internal/tests/oneway/oneway_gorums.pb.go | 7 ++++--- internal/tests/ordering/gorums.pb.go | 14 ++------------ internal/tests/ordering/ordering_gorums.pb.go | 6 +++--- internal/tests/tls/gorums.pb.go | 14 ++------------ internal/tests/unresponsive/gorums.pb.go | 14 ++------------ 26 files changed, 75 insertions(+), 242 deletions(-) diff --git a/benchkit/benchmark/gorums.pb.go b/benchkit/benchmark/gorums.pb.go index 1b1e4f886..19e45afd8 100644 --- a/benchkit/benchmark/gorums.pb.go +++ b/benchkit/benchmark/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/benchkit/gorums.pb.go b/benchkit/gorums.pb.go index fbb69a0e3..c73059c98 100644 --- a/benchkit/gorums.pb.go +++ b/benchkit/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/cmd/protoc-gen-gorums/gengorums/template_static.go b/cmd/protoc-gen-gorums/gengorums/template_static.go index a57349325..d61a76ca9 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_static.go +++ b/cmd/protoc-gen-gorums/gengorums/template_static.go @@ -11,18 +11,8 @@ var pkgIdentMap = map[string]string{"github.com/relab/gorums": "Config"} // These identifiers cannot be used to define message types in a proto file. var reservedIdents = []string{"Config", "Correctable", "ID", "Node"} -var staticCode = `// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +var staticCode = `// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/examples/storage/proto/gorums.pb.go b/examples/storage/proto/gorums.pb.go index 106a7183d..0a1d7c871 100644 --- a/examples/storage/proto/gorums.pb.go +++ b/examples/storage/proto/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/testprotos/testdata/calltypes/importmsgs/gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/importmsgs/gorums.pb.go.golden index 68b3404ad..e8da55702 100644 --- a/internal/testprotos/testdata/calltypes/importmsgs/gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/importmsgs/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config 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 916cee2d9..bdf85f93d 100644 --- a/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/importmsgs/importmsgs_gorums.pb.go.golden @@ -25,9 +25,9 @@ const ( // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type QuorumCallClient interface { - // QuorumCall executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // QuorumCall returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // diff --git a/internal/testprotos/testdata/calltypes/multicast/gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/multicast/gorums.pb.go.golden index 153f085af..9bde0e93d 100644 --- a/internal/testprotos/testdata/calltypes/multicast/gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multicast/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config 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 274d8671b..65e219f61 100644 --- a/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multicast/multicast_gorums.pb.go.golden @@ -28,8 +28,9 @@ var _ emptypb.Empty // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type MulticastClient interface { - // Multicast starts a one-way call on the client's configuration; no reply - // is returned. Use gorums.MapRequest to send a different message to each node. + // Multicast returns a one-way call handle for the client's configuration. + // No reply is returned. Use gorums.MapRequest to send a different message to + // each node. // // Send blocks until every send completes and returns any send errors. // Async returns a handle at once; Wait on the handle returns the send errors. diff --git a/internal/testprotos/testdata/calltypes/multiservice/gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/multiservice/gorums.pb.go.golden index 858cf3066..736f1e936 100644 --- a/internal/testprotos/testdata/calltypes/multiservice/gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multiservice/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config 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 2cb5432c3..f3448a6c5 100644 --- a/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/multiservice/multiservice_gorums.pb.go.golden @@ -28,9 +28,9 @@ var _ emptypb.Empty // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type AlphaClient interface { - // Heartbeat executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Heartbeat returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // @@ -76,17 +76,18 @@ type alphaNodeClient struct { // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type BetaClient interface { - // Heartbeat executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Heartbeat returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // // resp, err := client.Heartbeat(ctx, in).Majority() Heartbeat(ctx context.Context, in *Request) *gorums.Call[*Request, *Response] - // Write starts a one-way call on the client's configuration; no reply - // is returned. Use gorums.MapRequest to send a different message to each node. + // Write returns a one-way call handle for the client's configuration. + // No reply is returned. Use gorums.MapRequest to send a different message to + // each node. // // Send blocks until every send completes and returns any send errors. // Async returns a handle at once; Wait on the handle returns the send errors. diff --git a/internal/testprotos/testdata/calltypes/quorumcall/gorums.pb.go.golden b/internal/testprotos/testdata/calltypes/quorumcall/gorums.pb.go.golden index 1951a6e78..a0b7aea14 100644 --- a/internal/testprotos/testdata/calltypes/quorumcall/gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/quorumcall/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config 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 c7d109720..443df3129 100644 --- a/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden +++ b/internal/testprotos/testdata/calltypes/quorumcall/quorumcall_gorums.pb.go.golden @@ -24,9 +24,9 @@ const ( // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type QuorumCallClient interface { - // QuorumCall executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // QuorumCall returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // diff --git a/internal/testprotos/testdata/clientnames/gorums.pb.go.golden b/internal/testprotos/testdata/clientnames/gorums.pb.go.golden index 7403f5a52..ab76090bd 100644 --- a/internal/testprotos/testdata/clientnames/gorums.pb.go.golden +++ b/internal/testprotos/testdata/clientnames/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/testprotos/testdata/nodeonly/gorums.pb.go.golden b/internal/testprotos/testdata/nodeonly/gorums.pb.go.golden index 241edac57..ddf499a9e 100644 --- a/internal/testprotos/testdata/nodeonly/gorums.pb.go.golden +++ b/internal/testprotos/testdata/nodeonly/gorums.pb.go.golden @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden index ff1c290c0..2c0353f86 100644 --- a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden +++ b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden @@ -36,7 +36,7 @@ type NodeOnlyNodeClient interface { // resp, err := nodeClient.Read(ctx, in) Read(ctx context.Context, in *Request) (*Response, error) - // Write starts a one-way call on the client's node; no reply is + // Write returns a one-way call handle for the client's node; no reply is // returned. // // Send blocks until the send completes and returns any send error. diff --git a/internal/tests/correctable/correctable_gorums.pb.go b/internal/tests/correctable/correctable_gorums.pb.go index 5c3e9e09d..072923c17 100644 --- a/internal/tests/correctable/correctable_gorums.pb.go +++ b/internal/tests/correctable/correctable_gorums.pb.go @@ -24,18 +24,18 @@ const ( // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type CorrectableTestClient interface { - // Correctable executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Correctable returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // // resp, err := client.Correctable(ctx, in).Majority() Correctable(ctx context.Context, in *Request) *gorums.Call[*Request, *Response] - // CorrectableStream executes a streaming quorum call on the client's configuration. - // The server can send multiple responses, and the response iterator continues - // until the context is canceled. + // CorrectableStream returns a streaming quorum-call handle for the client's + // configuration. The server can send multiple responses, and the response + // iterator continues until the context is canceled. // // Example: // diff --git a/internal/tests/correctable/gorums.pb.go b/internal/tests/correctable/gorums.pb.go index 8eb00c7b9..b743b49d9 100644 --- a/internal/tests/correctable/gorums.pb.go +++ b/internal/tests/correctable/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/metadata/gorums.pb.go b/internal/tests/metadata/gorums.pb.go index c2e9c73f3..2e85c8824 100644 --- a/internal/tests/metadata/gorums.pb.go +++ b/internal/tests/metadata/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/multiservice/gorums.pb.go b/internal/tests/multiservice/gorums.pb.go index 39df8b1b9..80bd34295 100644 --- a/internal/tests/multiservice/gorums.pb.go +++ b/internal/tests/multiservice/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/multiservice/multiservice_gorums.pb.go b/internal/tests/multiservice/multiservice_gorums.pb.go index a65358a63..e5387662e 100644 --- a/internal/tests/multiservice/multiservice_gorums.pb.go +++ b/internal/tests/multiservice/multiservice_gorums.pb.go @@ -24,9 +24,9 @@ const ( // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type AlphaClient interface { - // Heartbeat executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Heartbeat returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // @@ -72,18 +72,18 @@ type alphaNodeClient struct { // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type BetaClient interface { - // Heartbeat executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Heartbeat returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // // resp, err := client.Heartbeat(ctx, in).Majority() Heartbeat(ctx context.Context, in *Request) *gorums.Call[*Request, *Response] - // Write executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // Write returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // diff --git a/internal/tests/oneway/gorums.pb.go b/internal/tests/oneway/gorums.pb.go index cf4fed667..83bbc7ee1 100644 --- a/internal/tests/oneway/gorums.pb.go +++ b/internal/tests/oneway/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/oneway/oneway_gorums.pb.go b/internal/tests/oneway/oneway_gorums.pb.go index 3e3334afb..1560df39f 100644 --- a/internal/tests/oneway/oneway_gorums.pb.go +++ b/internal/tests/oneway/oneway_gorums.pb.go @@ -28,8 +28,9 @@ var _ emptypb.Empty // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type OnewayTestClient interface { - // Multicast starts a one-way call on the client's configuration; no reply - // is returned. Use gorums.MapRequest to send a different message to each node. + // Multicast returns a one-way call handle for the client's configuration. + // No reply is returned. Use gorums.MapRequest to send a different message to + // each node. // // Send blocks until every send completes and returns any send errors. // Async returns a handle at once; Wait on the handle returns the send errors. @@ -59,7 +60,7 @@ type onewayTestClient struct { // unicasts on the client's node. If the node is nil, no message is sent and the // call fails with [gorums.ErrNoNode]. type OnewayTestNodeClient interface { - // Unicast starts a one-way call on the client's node; no reply is + // Unicast returns a one-way call handle for the client's node; no reply is // returned. // // Send blocks until the send completes and returns any send error. diff --git a/internal/tests/ordering/gorums.pb.go b/internal/tests/ordering/gorums.pb.go index aebd3ceef..23f326dd6 100644 --- a/internal/tests/ordering/gorums.pb.go +++ b/internal/tests/ordering/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/ordering/ordering_gorums.pb.go b/internal/tests/ordering/ordering_gorums.pb.go index 084d236d6..3b4bd504b 100644 --- a/internal/tests/ordering/ordering_gorums.pb.go +++ b/internal/tests/ordering/ordering_gorums.pb.go @@ -24,9 +24,9 @@ const ( // multicasts on the client's configuration. If the configuration is empty, no // message is sent and the call fails with [gorums.ErrEmptyConfig]. type OrderingClient interface { - // QuorumCall executes a quorum call on the client's configuration. Use - // terminal methods like Majority(), First(), or Threshold(n) to retrieve the - // aggregated result. + // QuorumCall returns a quorum-call handle for the client's configuration. + // A terminal method such as Majority(), First(), or Threshold(n) dispatches + // the call and retrieves the aggregated result. // // Example: // diff --git a/internal/tests/tls/gorums.pb.go b/internal/tests/tls/gorums.pb.go index 9697e0b14..ef98cfd8b 100644 --- a/internal/tests/tls/gorums.pb.go +++ b/internal/tests/tls/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config diff --git a/internal/tests/unresponsive/gorums.pb.go b/internal/tests/unresponsive/gorums.pb.go index 0f05e1a54..bd3b5f849 100644 --- a/internal/tests/unresponsive/gorums.pb.go +++ b/internal/tests/unresponsive/gorums.pb.go @@ -9,18 +9,8 @@ import ( gorums "github.com/relab/gorums" ) -// The type aliases below are useful Gorums types that we make accessible -// from generated code. These names therefore become reserved identifiers, -// meaning that proto message types with these names would collide with the -// generated aliases and cause a compile error. -// -// The protoc-gen-gorums bundler is responsible for discovering these -// aliases and any other identifiers defined herein, and adding them to -// the reserved identifiers list. -// -// If necessary, additional aliases and other identifiers should be added in -// the generator's cmd/protoc-gen-gorums/dev directory, and the bundler will -// automatically discover them and add them to the reserved identifiers list. +// These aliases make common Gorums types available to application code. +// Their names are reserved in packages with generated Gorums code. type ( Config = gorums.Config From 0e9091b67e820b7fadf11e2a1652ea80990f7277 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Tue, 3 Nov 2026 00:03:13 +0100 Subject: [PATCH 3/5] gorums: document re-exported identifiers only in exported packages The gorums and runtime/gorumsimpl packages re-export 47 identifiers from internal/impl, internal/conn, and internal/stream. Each one had its contract documented twice, and several copies disagreed, such as impl.ErrNoNode. Users only see the exported declaration. Each internal declaration now keeps a one-line, plain-text pointer to its exported name. A doc link cannot work there, because an internal package cannot import gorums. User-relevant details that only the internal comments had now appear on the exported declarations: AggregateFunc, ByLastError, DefaultSendBufferSize, and ErrSendQueueFull. MetadataEntry no longer defers to the internal stream package. --- call.go | 5 +++-- config.go | 6 ++++-- dial_options.go | 1 + errors.go | 4 ++-- internal/conn/config.go | 36 ++--------------------------------- internal/conn/dial_options.go | 8 +++----- internal/conn/errors.go | 7 ++----- internal/conn/node.go | 20 +++++-------------- internal/conn/node_source.go | 19 +++++------------- internal/impl/call.go | 18 ++++++------------ internal/impl/call_context.go | 2 +- internal/impl/correctable.go | 5 ++--- internal/impl/errors.go | 19 ++++++------------ internal/impl/multicast.go | 4 +--- internal/impl/quorumcall.go | 8 ++------ internal/impl/remotecall.go | 3 +-- internal/impl/responses.go | 4 ++-- internal/impl/terminals.go | 8 ++------ internal/impl/unicast.go | 3 +-- internal/stream/errors.go | 13 ++++--------- internal/stream/request.go | 2 +- message.go | 2 +- 22 files changed, 57 insertions(+), 140 deletions(-) diff --git a/call.go b/call.go index 608381208..330868338 100644 --- a/call.go +++ b/call.go @@ -59,8 +59,9 @@ type Correctable[Resp any] = impl.Correctable[Resp] type ResponseSeq[T proto.Message] = impl.ResponseSeq[T] // AggregateFunc aggregates the responses of a quorum call into a result of -// type Out, which may differ from the response type. [Responses.Aggregate] -// runs an AggregateFunc. +// type Out, which may differ from the response type. It receives the call's +// single-use response sequence, decides how to handle each node error, and +// decides when to stop. [Responses.Aggregate] runs an AggregateFunc. type AggregateFunc[Resp proto.Message, Out any] = impl.AggregateFunc[Resp, Out] // NodeResponse contains a node's response value or error. diff --git a/config.go b/config.go index 31b126cdd..084d7714d 100644 --- a/config.go +++ b/config.go @@ -65,8 +65,10 @@ type Node = conn.Node // It is compatible with [slices.SortFunc] and [Config.Sort]. var ByID = conn.ByID -// ByLastError compares nodes by their LastErr status. -// Nodes with no error sort before nodes with an error. +// ByLastError compares nodes by their [Node.LastErr] status. +// Nodes with no error sort before nodes with an error. LastErr reverts to nil +// once traffic flows again, so this orders nodes by whether they are failing +// now. // It is compatible with [slices.SortFunc] and [Config.Sort]. var ByLastError = conn.ByLastError diff --git a/dial_options.go b/dial_options.go index 0d71e7a51..e30d7eb18 100644 --- a/dial_options.go +++ b/dial_options.go @@ -56,6 +56,7 @@ func WithBackoff(backoff backoff.Config) DialOption { // DefaultSendBufferSize is the per-node send queue capacity Gorums uses when // [WithDialBufferSizes] or [WithBufferSizes] is applied with a send size of 0. +// Each node allocates the full capacity up front. const DefaultSendBufferSize = conn.DefaultSendBufferSize // DefaultRecvBufferSize is the number of received requests per channel that diff --git a/errors.go b/errors.go index 7e9bc824b..da2761d02 100644 --- a/errors.go +++ b/errors.go @@ -42,8 +42,8 @@ var ErrEmptyConfig = impl.ErrEmptyConfig // ErrSendQueueFull is returned for a two-way call enqueued while the node's // send queue is full, so quorum logic counts the peer as failed; see -// [WithDialBufferSizes]. It is a gRPC status error with the Unavailable code; -// match it with [errors.Is]. +// [WithDialBufferSizes]. A one-way call waits for space instead. It is a gRPC +// status error with the Unavailable code; match it with [errors.Is]. var ErrSendQueueFull = stream.ErrSendQueueFull // ErrSendStalled is reported by [Node.LastErr] while a send to the node has diff --git a/internal/conn/config.go b/internal/conn/config.go index 48f5b5ff1..dba904686 100644 --- a/internal/conn/config.go +++ b/internal/conn/config.go @@ -8,42 +8,10 @@ import ( "time" ) -// Config represents a static set of nodes on which multicast or -// quorum calls may be invoked. A configuration is created using [NewConfig]. -// A configuration should be treated as immutable. Therefore, methods that -// operate on a configuration always return a new Config instance. +// Config is exported and documented as gorums.Config. type Config []*Node -// NewConfig returns a new [Config] based on the provided nodes and dial -// options, and a function that closes the configuration's connection pool. -// -// The returned configuration and every configuration derived from it, for -// example with [Config.Extend], [Config.Remove], or [Config.Sort], share one -// connection pool. Only the returned close function closes that pool, and it -// closes every node in it, including nodes that [Config.Extend] added later. -// After the close function returns: -// - calls to the pool's nodes fail with an Unavailable "node closed" error; -// - [Config.Extend] on any configuration in the pool returns an error. -// -// One exception: a node that runs in-process, or that reuses a server's -// inbound stream, owns no connection, so calls to it still work. -// -// The close function is idempotent and safe for concurrent use; every call -// returns after the nodes are closed. It returns no error, because closing a -// connection has no failure that a caller can act on. -// -// On error, NewConfig returns a nil configuration and a nil close function. -// -// Example: -// -// cfg, closeFn, err := NewConfig( -// gorums.WithNodeList([]string{"localhost:8080", "localhost:8081", "localhost:8082"}), -// gorums.WithGRPCDialOptions(grpc.WithTransportCredentials(insecure.NewCredentials())), -// ) -// if err != nil { -// return err -// } -// defer closeFn() +// NewConfig is exported and documented as gorums.NewConfig. func NewConfig(nodes NodeSource, opts ...DialOption) (Config, func(), error) { if nodes == nil { return nil, nil, fmt.Errorf("gorums: missing required node list") diff --git a/internal/conn/dial_options.go b/internal/conn/dial_options.go index 14e11216e..46103370c 100644 --- a/internal/conn/dial_options.go +++ b/internal/conn/dial_options.go @@ -11,7 +11,7 @@ import ( "github.com/relab/gorums/internal/stream" ) -// DialOption sets an option on a configuration's [DialOptions]. +// DialOption is exported and documented as gorums.DialOption. type DialOption func(*DialOptions) // DialOptions holds the accumulated dial configuration for a [Config]'s @@ -45,12 +45,10 @@ func (o *DialOptions) borrowsStream(id ID) bool { return o.StreamDedup && o.InboundManager != nil && id < o.LocalNodeID } -// DefaultSendBufferSize is the per-node send queue capacity used when no -// explicit size is configured. Each node allocates the full capacity up front. +// DefaultSendBufferSize is exported and documented as gorums.DefaultSendBufferSize. const DefaultSendBufferSize = 4096 -// DefaultRecvBufferSize is the number of received requests per channel that can -// wait for the running handler when no explicit size is configured. +// DefaultRecvBufferSize is exported and documented as gorums.DefaultRecvBufferSize. const DefaultRecvBufferSize = 4096 // NewDialOptions returns a DialOptions initialized with default values. diff --git a/internal/conn/errors.go b/internal/conn/errors.go index 8a6ef48b0..ead0d44d7 100644 --- a/internal/conn/errors.go +++ b/internal/conn/errors.go @@ -6,13 +6,10 @@ import ( "strings" ) -// ErrStopped is returned by the server wait methods ([InboundManager.WaitForPeers], -// [InboundManager.WaitForClients]) when the server is stopped before the -// condition is met. +// ErrStopped is exported and documented as gorums.ErrStopped. var ErrStopped = errors.New("server stopped") -// QuorumCallError reports on a failed quorum call. -// It provides detailed information about which nodes failed. +// QuorumCallError is exported and documented as gorums.QuorumCallError. type QuorumCallError struct { cause error errors []NodeError diff --git a/internal/conn/node.go b/internal/conn/node.go index b2329b977..48d4edc73 100644 --- a/internal/conn/node.go +++ b/internal/conn/node.go @@ -29,14 +29,10 @@ func sharedNodeTransport(peer *Node) *stream.Transport { return stream.NewSharedTransport(transport) } -// ID identifies a node. ID 0 is reserved for a node that has no ID: a -// handler-only server, or a client that announces no ID. Dynamic client IDs -// start at [ClientIDStart] and skip configured IDs. Under stream -// deduplication, the peer with the lower ID dials. +// ID is exported and documented as gorums.ID. type ID = stream.ID -// Node encapsulates the state of a node on which a remote procedure call -// can be performed. +// Node is exported and documented as gorums.Node. type Node struct { // Only assigned at creation. id ID @@ -342,16 +338,12 @@ func (n *Node) Latency() time.Duration { return NodeTransport(n).Latency().Load() } -// ByID compares nodes by their identifier in increasing order. -// It is compatible with [slices.SortFunc] and [Config.Sort]. +// ByID is exported and documented as gorums.ByID. var ByID = func(a, b *Node) int { return cmp.Compare(a.id, b.id) } -// ByLastError compares nodes by their [Node.LastErr] status, sorting the nodes -// with no recorded error first. Since LastErr reverts to nil once traffic flows -// again, this orders nodes by whether they are currently failing. -// It is compatible with [slices.SortFunc] and [Config.Sort]. +// ByLastError is exported and documented as gorums.ByLastError. var ByLastError = func(a, b *Node) int { aErr := a.LastErr() bErr := b.LastErr() @@ -365,9 +357,7 @@ var ByLastError = func(a, b *Node) int { } } -// ByLatency compares nodes by their current latency estimate in ascending order. -// Nodes with no measurement yet (negative latency value) sort after nodes with a -// measurement. It is compatible with [slices.SortFunc] and [Config.Sort]. +// ByLatency is exported and documented as gorums.ByLatency. var ByLatency = func(a, b *Node) int { la, lb := a.Latency(), b.Latency() // Note: cmp.Compare alone would sort negative sentinel values first diff --git a/internal/conn/node_source.go b/internal/conn/node_source.go index 55c470551..ec0d5d557 100644 --- a/internal/conn/node_source.go +++ b/internal/conn/node_source.go @@ -7,9 +7,7 @@ import ( "slices" ) -// NodeSource identifies the set of nodes to build a [Config] from. Create one -// with [WithNodes] or [WithNodeList]; the interface is sealed so it can only -// be implemented within this package. +// NodeSource is exported and documented as gorums.NodeSource. type NodeSource interface { newConfig(nodeRegistry) (Config, error) // nodeMap returns the captured, explicitly numbered node records, or nil @@ -26,21 +24,17 @@ type nodeRegistry interface { newNode(id ID, addr string, verify PeerVerifier) (*Node, error) } -// NodeAddress must be implemented by types that can be used as node addresses. +// NodeAddress is exported and documented as gorums.NodeAddress. type NodeAddress interface { Addr() string } -// TLSIdentity supplies the expected URI SAN or DNS SAN of a peer's verified -// TLS certificate. A node source captures it alongside the node's address. +// TLSIdentity is exported and documented as gorums.TLSIdentity. type TLSIdentity interface { TLSIdentity() string } -// WithNodes returns a NodeSource containing the provided mapping from -// application-specific IDs to types implementing NodeAddress. -// Node IDs must be greater than 0. It captures each node's address and, when -// the node implements [TLSIdentity], its expected certificate identity. +// WithNodes is exported and documented as gorums.WithNodes. func WithNodes[T NodeAddress](nodes map[ID]T) NodeSource { nm := make(nodeMap, len(nodes)) for _, id := range slices.Sorted(maps.Keys(nodes)) { @@ -96,10 +90,7 @@ func (nm nodeMap) newConfig(registry nodeRegistry) (Config, error) { return builder.configuration(), nil } -// WithNodeList returns a NodeSource for the provided list of node addresses. -// Unique Node IDs are generated sequentially starting from the maximum existing -// node ID plus one, or from 1 if no nodes exist, preventing conflicts with -// existing nodes. +// WithNodeList is exported and documented as gorums.WithNodeList. func WithNodeList(addrsList []string) NodeSource { return nodeList(addrsList) } diff --git a/internal/impl/call.go b/internal/impl/call.go index c006a1f8f..8a0ab387c 100644 --- a/internal/impl/call.go +++ b/internal/impl/call.go @@ -10,19 +10,16 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// Call represents a lazily dispatched quorum call. -// Register interceptors with [Call.Intercept] before the call is dispatched. -// A Call may be dispatched once. +// Call is exported and documented as gorums.Call. type Call[Req, Resp proto.Message] struct { *Responses[Resp] ctx *CallContext[Req, Resp] } -// ClientInterceptor transforms a call's request or response sequence. +// ClientInterceptor is exported and documented as gorums.ClientInterceptor. type ClientInterceptor[Req, Resp proto.Message] func(ctx *CallContext[Req, Resp], next ResponseSeq[Resp]) ResponseSeq[Resp] -// MapRequest returns an interceptor that transforms the request for each node. -// Returning nil or an invalid message skips the node with [ErrSkipNode]. +// MapRequest is exported and documented as gorums.MapRequest. func MapRequest[Req, Resp proto.Message](fn func(Req, *Node) Req) ClientInterceptor[Req, Resp] { return func(ctx *CallContext[Req, Resp], next ResponseSeq[Resp]) ResponseSeq[Resp] { if fn != nil { @@ -32,7 +29,7 @@ func MapRequest[Req, Resp proto.Message](fn func(Req, *Node) Req) ClientIntercep } } -// MapResponse returns an interceptor that transforms each successful response. +// MapResponse is exported and documented as gorums.MapResponse. func MapResponse[Req, Resp proto.Message](fn func(Resp, *Node) Resp) ClientInterceptor[Req, Resp] { return func(ctx *CallContext[Req, Resp], next ResponseSeq[Resp]) ResponseSeq[Resp] { if fn == nil { @@ -98,9 +95,7 @@ func (c *Call[Req, Resp]) Async() *Responses[Resp] { return c.Responses } -// OnewayCall represents a lazily dispatched multicast or unicast call. -// [OnewayCall.Send] and [OnewayCall.Async] each dispatch the call; invoking -// either after the call was dispatched panics. +// OnewayCall is exported and documented as gorums.OnewayCall. type OnewayCall[Req proto.Message] struct { ctx *CallContext[Req, *emptypb.Empty] unicast bool @@ -218,8 +213,7 @@ func (c *OnewayCall[Req]) receive() (NodeResponse[*stream.Message], error) { } } -// OnewayAsync is the send-completion handle of a one-way call dispatched with -// [OnewayCall.Async]. +// OnewayAsync is exported and documented as gorums.OnewayAsync. type OnewayAsync struct { collect func() error once sync.Once diff --git a/internal/impl/call_context.go b/internal/impl/call_context.go index a4ebfd3bb..ed77a9650 100644 --- a/internal/impl/call_context.go +++ b/internal/impl/call_context.go @@ -11,7 +11,7 @@ import ( "google.golang.org/protobuf/proto" ) -// CallContext provides an interceptor with the context and state of a call. +// CallContext is exported and documented as gorums.CallContext. type CallContext[Req, Resp proto.Message] struct { context.Context config Config diff --git a/internal/impl/correctable.go b/internal/impl/correctable.go index 9e06e72e7..601d7a670 100644 --- a/internal/impl/correctable.go +++ b/internal/impl/correctable.go @@ -7,8 +7,7 @@ import ( "github.com/relab/gorums/internal/conn" ) -// LevelNotSet is the zero value level used to indicate that no level -// (and thereby no reply) has been set for a correctable quorum call. +// LevelNotSet is exported and documented as gorums.LevelNotSet. const LevelNotSet = -1 type watcher struct { @@ -16,7 +15,7 @@ type watcher struct { ch chan struct{} } -// Correctable represents the progressive result of a correctable quorum call. +// Correctable is exported and documented as gorums.Correctable. type Correctable[Resp any] struct { mu sync.Mutex reply Resp diff --git a/internal/impl/errors.go b/internal/impl/errors.go index 7fcf1b2a4..a750878aa 100644 --- a/internal/impl/errors.go +++ b/internal/impl/errors.go @@ -7,27 +7,20 @@ import ( "google.golang.org/grpc/status" ) -// ErrIncomplete is the error returned by a quorum call when the call cannot be completed -// due to insufficient non-error replies to form a quorum according to the quorum function. +// ErrIncomplete is exported and documented as gorums.ErrIncomplete. var ErrIncomplete = errors.New("incomplete call") -// ErrSendFailure is the error returned by a multicast call when message sending fails for one or more nodes. +// ErrSendFailure is exported and documented as gorums.ErrSendFailure. var ErrSendFailure = errors.New("send failure") -// ErrSkipNode is returned when a node is skipped by request transformations. -// This allows the response iterator to account for all nodes without blocking. +// ErrSkipNode is exported and documented as gorums.ErrSkipNode. var ErrSkipNode = errors.New("skip node") -// ErrTypeMismatch is returned when a response cannot be cast to the expected type. +// ErrTypeMismatch is exported and documented as gorums.ErrTypeMismatch. var ErrTypeMismatch = errors.New("response type mismatch") -// ErrNoNode is returned by a remote call or unicast on a nil node, such as -// the node that [Config.Node] returns for an ID outside the configuration. -// It is a gRPC status error with the InvalidArgument code, because the call -// cannot succeed until the caller passes a node. +// ErrNoNode is exported and documented as gorums.ErrNoNode. var ErrNoNode = status.Error(codes.InvalidArgument, "no such node") -// ErrEmptyConfig is reported when a quorum call or multicast handle on an -// empty configuration is consumed. It is a gRPC status error with the -// InvalidArgument code. +// ErrEmptyConfig is exported and documented as gorums.ErrEmptyConfig. var ErrEmptyConfig = status.Error(codes.InvalidArgument, "empty configuration") diff --git a/internal/impl/multicast.go b/internal/impl/multicast.go index c0c8e14c7..2268923f3 100644 --- a/internal/impl/multicast.go +++ b/internal/impl/multicast.go @@ -7,9 +7,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// Multicast returns a lazy [OnewayCall] for every node in config. -// On an empty config, it sends nothing and reports [ErrEmptyConfig] when -// consumed. +// Multicast is exported and documented as gorumsimpl.Multicast. func Multicast[Req proto.Message](ctx context.Context, config Config, req Req, method string) *OnewayCall[Req] { callCtx := &CallContext[Req, *emptypb.Empty]{ Context: ctx, diff --git a/internal/impl/quorumcall.go b/internal/impl/quorumcall.go index b2fbab63f..9e9c30958 100644 --- a/internal/impl/quorumcall.go +++ b/internal/impl/quorumcall.go @@ -6,16 +6,12 @@ import ( "google.golang.org/protobuf/proto" ) -// QuorumCall returns a lazy [Call] for the nodes in config. -// Consuming a call on an empty config sends nothing and reports -// [ErrEmptyConfig]. +// QuorumCall is exported and documented as gorumsimpl.QuorumCall. func QuorumCall[Req, Resp proto.Message](ctx context.Context, config Config, req Req, method string) *Call[Req, Resp] { return invokeQuorumCall[Req, Resp](ctx, config, req, method, false) } -// QuorumCallStream returns a lazy [Call] that accepts multiple responses per -// node until ctx ends. Consuming a call on an empty config sends nothing and -// reports [ErrEmptyConfig]. +// QuorumCallStream is exported and documented as gorumsimpl.QuorumCallStream. func QuorumCallStream[Req, Resp proto.Message](ctx context.Context, config Config, req Req, method string) *Call[Req, Resp] { return invokeQuorumCall[Req, Resp](ctx, config, req, method, true) } diff --git a/internal/impl/remotecall.go b/internal/impl/remotecall.go index fded1e6cd..3189bc971 100644 --- a/internal/impl/remotecall.go +++ b/internal/impl/remotecall.go @@ -8,8 +8,7 @@ import ( "google.golang.org/protobuf/proto" ) -// RemoteCall executes a remote procedure call on node. -// It returns [ErrNoNode] if node is nil. +// RemoteCall is exported and documented as gorumsimpl.RemoteCall. func RemoteCall[Req, Resp proto.Message](ctx context.Context, node *Node, req Req, method string) (Resp, error) { if node == nil { var zero Resp diff --git a/internal/impl/responses.go b/internal/impl/responses.go index b61449771..028d73987 100644 --- a/internal/impl/responses.go +++ b/internal/impl/responses.go @@ -38,7 +38,7 @@ func mapToCallResponse[Resp proto.Message](channelResp NodeResponse[*stream.Mess // Iterator Helpers // ------------------------------------------------------------------------- -// ResponseSeq yields the responses from a quorum call. +// ResponseSeq is exported and documented as gorums.ResponseSeq. type ResponseSeq[T proto.Message] iter.Seq[NodeResponse[T]] // IgnoreErrors returns a sequence containing only successful responses. @@ -58,7 +58,7 @@ func (seq ResponseSeq[Resp]) IgnoreErrors() ResponseSeq[Resp] { // Response Methods // ------------------------------------------------------------------------- -// Responses provides response iteration and aggregation for a quorum call. +// Responses is exported and documented as gorums.Responses. type Responses[Resp proto.Message] struct { seq ResponseSeq[Resp] size int diff --git a/internal/impl/terminals.go b/internal/impl/terminals.go index 75005ef35..cd5863056 100644 --- a/internal/impl/terminals.go +++ b/internal/impl/terminals.go @@ -47,10 +47,7 @@ func (t *nodeTally) incomplete() error { return conn.NewQuorumCallError(ErrIncomplete, t.errs) } -// AggregateFunc aggregates the responses of a quorum call into a result of -// type Out, which may differ from the response type. It receives the call's -// single-use response sequence, decides how to handle each node error, and -// decides when to stop. [Responses.Aggregate] runs an AggregateFunc. +// AggregateFunc is exported and documented as gorums.AggregateFunc. type AggregateFunc[Resp proto.Message, Out any] func(ResponseSeq[Resp]) (Out, error) // Aggregate applies fn to the single-use response sequence and returns its @@ -126,8 +123,7 @@ func (r *Responses[Resp]) Reduce[Acc any]( return acc, tally.incomplete() } -// Agreement contains a representative value and the successful node -// responses that established it. Responses are retained in arrival order. +// Agreement is exported and documented as gorums.Agreement. type Agreement[Resp proto.Message] struct { Value Resp Responses []NodeResponse[Resp] diff --git a/internal/impl/unicast.go b/internal/impl/unicast.go index e038ad848..dd782b220 100644 --- a/internal/impl/unicast.go +++ b/internal/impl/unicast.go @@ -7,8 +7,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// Unicast returns a lazy [OnewayCall] for node. -// If node is nil, it sends nothing and reports [ErrNoNode] when consumed. +// Unicast is exported and documented as gorumsimpl.Unicast. func Unicast[Req proto.Message](ctx context.Context, node *Node, req Req, method string) *OnewayCall[Req] { callCtx := &CallContext[Req, *emptypb.Empty]{ Context: ctx, diff --git a/internal/stream/errors.go b/internal/stream/errors.go index 2ed2d1717..cf3e2eacb 100644 --- a/internal/stream/errors.go +++ b/internal/stream/errors.go @@ -8,18 +8,13 @@ import ( ) var ( - // ErrNodeClosed is returned for requests enqueued after the node closed. + // ErrNodeClosed is exported and documented as gorums.ErrNodeClosed. ErrNodeClosed = status.Error(codes.Unavailable, "node closed") - // ErrStreamDown is returned for requests that cannot be delivered or - // retried because the node's stream is not available. + // ErrStreamDown is exported and documented as gorums.ErrStreamDown. ErrStreamDown = status.Error(codes.Unavailable, "stream is down") - // ErrSendQueueFull is returned for two-way requests enqueued while the - // node's send queue is full; one-way requests wait for space instead (see - // [Channel]). + // ErrSendQueueFull is exported and documented as gorums.ErrSendQueueFull. ErrSendQueueFull = status.Error(codes.Unavailable, "send queue full") - // ErrSendStalled is reported by [Channel.LastErr] while a send on the - // node's stream has been blocked for [stallReportDelay] or longer, as when - // the peer has stopped reading the stream. + // ErrSendStalled is exported and documented as gorums.ErrSendStalled. ErrSendStalled = status.Error(codes.Unavailable, "send stalled") ) diff --git a/internal/stream/request.go b/internal/stream/request.go index a98150100..889cfd0ec 100644 --- a/internal/stream/request.go +++ b/internal/stream/request.go @@ -55,7 +55,7 @@ func (r Request) sendErrorResponse(nodeID ID, err error) { // ID identifies a node. A channel with ID 0 belongs to a peer that has no ID. type ID = uint32 -// NodeResponse wraps a response value from node ID, and an error if any. +// NodeResponse is exported and documented as gorums.NodeResponse. type NodeResponse[T any] struct { NodeID ID Value T diff --git a/message.go b/message.go index c9e2bc1d0..162616160 100644 --- a/message.go +++ b/message.go @@ -15,7 +15,7 @@ type Message struct { *stream.Message } -// MetadataEntry is a type alias for [stream.MetadataEntry]. +// MetadataEntry is a key-value pair in a message's metadata. type MetadataEntry = stream.MetadataEntry // newResponse returns the response envelope to the request in. It carries the From 0b1f45cab2d43c82eba271bc9fdede4a3d379dc9 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Tue, 3 Nov 2026 00:03:13 +0100 Subject: [PATCH 4/5] gorums: say where call errors are returned in docs The docs said that call handles report errors "when consumed", which does not name a method. They now say that a call "fails with" an error, and doc.go and the user guide define this once: the method that collects the result returns the error. For a quorum call, that is a terminal method. For a one-way call, it is Send, or Wait on the OnewayAsync that Async returns. Restore three correct statements that an earlier rewrite removed: building a node client allocates nothing, node clients should not be kept, and a one-way send completes when the message reaches the node's stream. Use "a OnewayCall" in the dev guide, since one-way starts with a /w/ sound, and give the unicast method template the same sentence structure as the multicast template. --- call.go | 2 +- .../gengorums/template_unicast.go | 4 +-- doc.go | 21 ++++++++++----- doc/dev-guide.md | 6 ++--- doc/user-guide.md | 26 +++++++++++-------- errors.go | 13 +++++----- runtime/gorumsimpl/call.go | 21 ++++++++------- 7 files changed, 53 insertions(+), 40 deletions(-) diff --git a/call.go b/call.go index 330868338..d23183773 100644 --- a/call.go +++ b/call.go @@ -20,7 +20,7 @@ type Call[Req, Resp proto.Message] = impl.Call[Req, Resp] type OnewayCall[Req proto.Message] = impl.OnewayCall[Req] // OnewayAsync is the send-completion handle of a one-way call dispatched with -// [OnewayCall.Async]; [OnewayAsync.Wait] reports the send error. +// [OnewayCall.Async]; [OnewayAsync.Wait] returns the send error. type OnewayAsync = impl.OnewayAsync // CallContext provides an interceptor with the context and state of a call. diff --git a/cmd/protoc-gen-gorums/gengorums/template_unicast.go b/cmd/protoc-gen-gorums/gengorums/template_unicast.go index b2aa198d9..aa14109eb 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_unicast.go +++ b/cmd/protoc-gen-gorums/gengorums/template_unicast.go @@ -14,8 +14,8 @@ var unicastComment = ` {{if ne $comments ""}} {{$comments -}} {{else}} -// {{$method}} returns a one-way call handle for the client's node; no reply is -// returned. +// {{$method}} returns a one-way call handle for the client's node. +// No reply is returned. // // Send blocks until the send completes and returns any send error. // Async returns a handle at once; Wait on the handle returns the send error. diff --git a/doc.go b/doc.go index 5b6bb4105..71b1ea6cc 100644 --- a/doc.go +++ b/doc.go @@ -18,13 +18,21 @@ // - multicast: a one-way call to every node in a configuration. // - unicast: a one-way call to a single node. // -// Quorum calls and one-way calls return handles that dispatch when consumed. -// Remote calls execute immediately. +// A quorum call or one-way call returns a handle and sends nothing until +// the caller invokes a terminal method, [Call.Async], [Responses.Correctable], +// [OnewayCall.Send], or [OnewayCall.Async], or ranges over +// [Responses.Results]. A remote call sends at once. // Lazy dispatch is what lets a call site attach client interceptors before the // request is sent. [Call.Async] sends the request at once and leaves the // replies to a later terminal method, and [Responses.Correctable] reports // progressively better results. // +// A call that fails with an error returns it from the method that collects +// its result: a terminal method of a quorum call, or [OnewayCall.Send] of a +// one-way call. Async returns a handle that collects the result later: +// [Call.Async] returns [Responses], whose terminal methods return the error, +// and [OnewayCall.Async] returns [OnewayAsync], whose Wait returns it. +// // # Service clients // // For each service S, protoc-gen-gorums generates up to two client @@ -41,10 +49,11 @@ // leader := pb.NewStorageNodeClient(cfg.Node(leaderID)) // reply, err = leader.ReadRPC(ctx, req) // -// A node client holds only its node, which may be outside cfg. -// If the node is nil, remote calls return [ErrNoNode] and unicast handles -// report it when consumed. A quorum call or multicast on an empty -// configuration sends nothing and reports [ErrEmptyConfig] when consumed. +// A node client holds only its node, so building one allocates nothing. +// Build a node client where you need it, and do not keep it. The node may be +// outside cfg. If the node is nil, a remote call or unicast fails with +// [ErrNoNode]. On an empty configuration, a quorum call or multicast sends +// nothing and fails with [ErrEmptyConfig]. // To call another configuration, build a new SClient from it. // A client does not own its configuration or node; the close function returned // by [NewConfig] closes the connections. diff --git a/doc/dev-guide.md b/doc/dev-guide.md index 85780ae1f..ebf28f80e 100644 --- a/doc/dev-guide.md +++ b/doc/dev-guide.md @@ -512,13 +512,13 @@ flowchart LR ### Call Engine (`internal/impl`) A call constructor takes the call's `context.Context` and its target directly: a `Config` for quorum calls and multicasts, and a `*Node` for remote calls and unicasts. -On an empty `Config`, the `CallContext` holds `ErrEmptyConfig`: a quorum call sends nothing, its terminal methods return the error, and its response iterator yields one response that holds the error; `Multicast` returns an `OnewayCall` that sends nothing and reports the error. -On a nil `*Node`, `RemoteCall` returns `ErrNoNode`, and `Unicast` returns an `OnewayCall` whose `CallContext` holds `ErrNoNode` and that sends nothing. +On an empty `Config`, the `CallContext` holds `ErrEmptyConfig`: a quorum call sends nothing, its terminal methods return the error, and its response iterator yields one response that holds the error; `Multicast` returns a `OnewayCall` that sends nothing and reports the error. +On a nil `*Node`, `RemoteCall` returns `ErrNoNode`, and `Unicast` returns a `OnewayCall` whose `CallContext` holds `ErrNoNode` and that sends nothing. Each call constructor except `RemoteCall` builds a `CallContext` that holds the request, the target `Config`, the response channel, and the response iterator (`ResponseSeq`). A unicast's `CallContext` holds a one-node `Config`. A quorum call returns a `Call`, which embeds `Responses` and its terminal methods (`First`, `Majority`, `All`, `Threshold`, `Aggregate`, `Reduce`, `Matching`), as well as the `Correctable` future. `Call.Async` sends the requests at once and returns the dispatched `Responses`, so any terminal method collects the replies later. -A one-way call (`Multicast`, `Unicast`) returns an `OnewayCall`, whose `Send` waits for local send confirmations and whose `Async` returns an `OnewayAsync`. +A one-way call (`Multicast`, `Unicast`) returns a `OnewayCall`, whose `Send` waits for local send confirmations and whose `Async` returns a `OnewayAsync`. A `ClientInterceptor` wraps the response iterator and may register per-node request transformations before the call is dispatched. `CallContext` reaches each node's send path through `conn.NodeTransport`. diff --git a/doc/user-guide.md b/doc/user-guide.md index 436e2c746..08e6aca3c 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -234,9 +234,13 @@ The handle embeds `*gorums.Responses[*ReadResponse]`, which provides the termina The `Call` type also has an `Intercept` method, which registers client interceptors before the call is sent. The one-way `WriteMulticast` method returns a `*gorums.OnewayCall[*WriteRequest]` handle. `Send` blocks until every send completes and returns any send errors. -`Async` sends the request and returns a handle at once. +`Async` sends the request and returns a `*gorums.OnewayAsync` handle at once. `Wait` on that handle returns the send errors. +When this guide says that a call *fails with* an error, the method that collects the call's result returns that error. +For a quorum call, this is a terminal method, also on the `*gorums.Responses` that `Async` returns. +For a one-way call, this is `Send`, or `Wait` on the `*gorums.OnewayAsync` that `Async` returns. + ```go type StorageClient interface { ReadQC(ctx context.Context, in *ReadRequest) *gorums.Call[*ReadRequest, *ReadResponse] @@ -280,17 +284,17 @@ err = leader.WriteUnicast(ctx, req).Send() Follow these rules when you use the clients: * **Build node clients where you need them.** - A node client holds one pointer and owns nothing. + A node client holds one pointer and owns nothing, so building one allocates nothing. + Do not keep node clients in a struct or a map. * **A nil node does not panic.** `cfg.Node(id)` returns nil if the configuration has no node with that ID. `NewStorageNodeClient(nil)` is legal. - Remote calls return `gorums.ErrNoNode`; unicast handles report it from `Send` or `Wait` after `Async`. + A remote call or unicast on that client fails with `gorums.ErrNoNode`. This error is a gRPC status error with the `InvalidArgument` code, because retrying cannot fix it. * **Any node works.** The node can also be a node that is not in a configuration that you use for quorum calls. * **An empty configuration does not panic.** - A quorum call or multicast on an empty configuration sends nothing. - Its handle reports `gorums.ErrEmptyConfig` when consumed. + A quorum call or multicast on an empty configuration sends nothing and fails with `gorums.ErrEmptyConfig`. This error is a gRPC status error with the `InvalidArgument` code. `Results()` yields one response whose `Err` is `gorums.ErrEmptyConfig`, and `Correctable` completes at once with this error. * **Build a new client for a new configuration.** @@ -324,7 +328,7 @@ type StorageServer interface { ``` Unicast and multicast server methods return no response or error. -The client-side `Send` method and `Wait` after `Async` report local send errors, but do not report the server handler's result. +On the client side, a one-way call fails only if a request could not be sent; it does not return the result of the server handler. **Note:** You may decide to keep the `.proto` file and the generated `.pb.go` files in a separate directory/package and import that package (the generated Gorums API) into your application. @@ -830,9 +834,9 @@ Collecting the replies one call after the other takes about as long as the slowe The call is not dispatched until the caller calls `.Send()` or `.Async()` on the handle. If the caller drops the handle without calling either method, the call is never dispatched. -`.Send()` and `.Wait()` after `.Async()` report whether the local transport accepted each send. -They cannot report errors that occur later in the server handler. -A one-way call has no reply to await. +`.Send()` returns every send failure, and so does `.Wait()` on the `*OnewayAsync` that `.Async()` returns, so a one-way call cannot fail silently. +Each send completes when the message reaches the stream of its node. +Neither can return errors that occur later in the server handler, because a one-way call has no reply. A server handler that multicasts calls `.Async()` before `ServerContext.Release()` and `.Wait()` after it. The sends then keep the order of the requests. @@ -2305,7 +2309,7 @@ Nested calls follow the rule in [Request Ordering and ServerContext.Release](#re `ReadNestedQC` below calls `Release` first, because the nested reads of different requests may run in any order; the next request starts while the nested calls are in flight. `WriteNestedMulticast` dispatches its multicast before `Release`, so the peers receive the writes in the order the requests arrived. -A quorum call or a multicast on an empty configuration reports `gorums.ErrEmptyConfig` when its handle is consumed. +A quorum call or a multicast on an empty configuration sends nothing and fails with `gorums.ErrEmptyConfig`. The `len(config) == 0` checks below return a clearer error for a server that has no peers. ```go @@ -2552,7 +2556,7 @@ This is how a protocol can use a new tree for each view, or fall back to a star The root is simply the first node of the configuration, and it is a node like any other: it holds the data and runs the same handlers. A caller that is not in the configuration starts a tree call by sending to the root, with a one-node configuration, for example `gorums.Config{tree.Root()}`. The root's handler then relays to `tree.ChildrenOf(root.ID())` like every other internal node, so the root's own work is done too. -Do not start at `tree.ChildrenOf(tree.Root().ID())` from outside: that skips the root, and a call on an empty configuration, which is what `ChildrenOf` returns for a one-node tree, reports `gorums.ErrEmptyConfig` when consumed. +Do not start at `tree.ChildrenOf(tree.Root().ID())` from outside: that skips the root, and a call on an empty configuration, which is what `ChildrenOf` returns for a one-node tree, fails with `gorums.ErrEmptyConfig`. When the process that starts the call is itself the root, it does its local work and then sends to `tree.ChildrenOf(id)` with its own ID. ### Relaying in a Server diff --git a/errors.go b/errors.go index da2761d02..80f466af1 100644 --- a/errors.go +++ b/errors.go @@ -27,17 +27,16 @@ var ErrStreamDown = stream.ErrStreamDown // is a gRPC status error with the Unavailable code; match it with [errors.Is]. var ErrNodeClosed = stream.ErrNodeClosed -// ErrNoNode is returned by a remote call on a nil node, or reported when a -// unicast handle for a nil node is consumed. [Config.Node] returns nil for an -// ID outside the configuration. +// ErrNoNode is the error of a remote call or unicast on a nil node. +// [Config.Node] returns nil for an ID outside the configuration. // It is a gRPC status error with the InvalidArgument code; match it with // [errors.Is]. var ErrNoNode = impl.ErrNoNode -// ErrEmptyConfig is reported when a quorum call or multicast handle on an -// empty configuration is consumed. [Config.Remove] and -// [Config.WithoutErrors] can produce an empty configuration. It is a gRPC -// status error with the InvalidArgument code; match it with [errors.Is]. +// ErrEmptyConfig is the error of a quorum call or multicast on an empty +// configuration, such as one that [Config.Remove] or [Config.WithoutErrors] +// emptied. Such a call sends nothing. It is a gRPC status error with the +// InvalidArgument code; match it with [errors.Is]. var ErrEmptyConfig = impl.ErrEmptyConfig // ErrSendQueueFull is returned for a two-way call enqueued while the node's diff --git a/runtime/gorumsimpl/call.go b/runtime/gorumsimpl/call.go index 63caf3be9..032924fe9 100644 --- a/runtime/gorumsimpl/call.go +++ b/runtime/gorumsimpl/call.go @@ -12,33 +12,34 @@ import ( ) // QuorumCall returns a quorum-call handle for the nodes in config. -// The call is dispatched when the handle is consumed. If config is empty, -// the handle sends nothing and reports [gorums.ErrEmptyConfig]. +// A terminal method or [gorums.Call.Async] dispatches the call. If config is +// empty, the call sends nothing and fails with [gorums.ErrEmptyConfig]. // It is used by generated code. func QuorumCall[Req, Resp proto.Message](ctx context.Context, config gorums.Config, req Req, method string) *gorums.Call[Req, Resp] { return impl.QuorumCall[Req, Resp](ctx, config, req, method) } // QuorumCallStream returns a streaming quorum-call handle for the nodes in -// config. The call is dispatched when the handle is consumed. If config is -// empty, the handle sends nothing and reports [gorums.ErrEmptyConfig]. +// config. It accepts multiple responses per node until ctx ends. A terminal +// method or [gorums.Call.Async] dispatches the call. If config is empty, the +// call sends nothing and fails with [gorums.ErrEmptyConfig]. // It is used by generated code. func QuorumCallStream[Req, Resp proto.Message](ctx context.Context, config gorums.Config, req Req, method string) *gorums.Call[Req, Resp] { return impl.QuorumCallStream[Req, Resp](ctx, config, req, method) } // Multicast returns a one-way call handle for every node in config. -// [gorums.OnewayCall.Send] or [gorums.OnewayCall.Async] dispatches the call; -// Send errors are reported by Send or [gorums.OnewayAsync.Wait]. If config is -// empty, the handle sends nothing and reports [gorums.ErrEmptyConfig]. +// [gorums.OnewayCall.Send] or [gorums.OnewayCall.Async] dispatches the call. +// If config is empty, the call sends nothing and fails with +// [gorums.ErrEmptyConfig]. // It is used by generated code. func Multicast[Req proto.Message](ctx context.Context, config gorums.Config, req Req, method string) *gorums.OnewayCall[Req] { return impl.Multicast(ctx, config, req, method) } -// Unicast executes a one-way call on node. No reply is returned. -// Send errors are reported by [gorums.OnewayCall.Send] or -// [gorums.OnewayAsync.Wait]. If node is nil, they report [gorums.ErrNoNode]. +// Unicast returns a one-way call handle for node. +// [gorums.OnewayCall.Send] or [gorums.OnewayCall.Async] dispatches the call. +// If node is nil, the call sends nothing and fails with [gorums.ErrNoNode]. // It is used by generated code. func Unicast[Req proto.Message](ctx context.Context, node *gorums.Node, req Req, method string) *gorums.OnewayCall[Req] { return impl.Unicast(ctx, node, req, method) From eb3883235751b6c795aab8a8209209107f350d85 Mon Sep 17 00:00:00 2001 From: Hein Meling Date: Tue, 3 Nov 2026 00:03:13 +0100 Subject: [PATCH 5/5] protoc-gen-gorums: regenerate unicast method comments --- .../testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden | 4 ++-- internal/tests/oneway/oneway_gorums.pb.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden index 2c0353f86..e063fb314 100644 --- a/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden +++ b/internal/testprotos/testdata/nodeonly/nodeonly_gorums.pb.go.golden @@ -36,8 +36,8 @@ type NodeOnlyNodeClient interface { // resp, err := nodeClient.Read(ctx, in) Read(ctx context.Context, in *Request) (*Response, error) - // Write returns a one-way call handle for the client's node; no reply is - // returned. + // Write returns a one-way call handle for the client's node. + // No reply is returned. // // Send blocks until the send completes and returns any send error. // Async returns a handle at once; Wait on the handle returns the send error. diff --git a/internal/tests/oneway/oneway_gorums.pb.go b/internal/tests/oneway/oneway_gorums.pb.go index 1560df39f..3bd2fa24d 100644 --- a/internal/tests/oneway/oneway_gorums.pb.go +++ b/internal/tests/oneway/oneway_gorums.pb.go @@ -60,8 +60,8 @@ type onewayTestClient struct { // unicasts on the client's node. If the node is nil, no message is sent and the // call fails with [gorums.ErrNoNode]. type OnewayTestNodeClient interface { - // Unicast returns a one-way call handle for the client's node; no reply is - // returned. + // Unicast returns a one-way call handle for the client's node. + // No reply is returned. // // Send blocks until the send completes and returns any send error. // Async returns a handle at once; Wait on the handle returns the send error.