Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions benchkit/benchmark/gorums.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 2 additions & 12 deletions benchkit/gorums.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions call.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 2 additions & 12 deletions cmd/protoc-gen-gorums/dev/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
meling marked this conversation as resolved.
Resolved

type (
Config = gorums.Config
Expand Down
5 changes: 3 additions & 2 deletions cmd/protoc-gen-gorums/gengorums/template_multicast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions cmd/protoc-gen-gorums/gengorums/template_quorumcall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand Down Expand Up @@ -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:
//
Expand Down
14 changes: 2 additions & 12 deletions cmd/protoc-gen-gorums/gengorums/template_static.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions cmd/protoc-gen-gorums/gengorums/template_unicast.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ var unicastComment = `
{{if ne $comments ""}}
{{$comments -}}
{{else}}
// {{$method}} starts a one-way call on 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.
Expand Down
6 changes: 4 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions dial_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 18 additions & 10 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@
// - 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.
// 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
Expand All @@ -44,11 +51,12 @@
//
// 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.
// 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.
//
// 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.
Expand All @@ -59,8 +67,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
Expand Down
2 changes: 1 addition & 1 deletion doc/dev-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ Each call constructor except `RemoteCall` builds a `CallContext` that holds the
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 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`.

Expand Down
31 changes: 17 additions & 14 deletions doc/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -228,15 +228,19 @@ 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.
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]
Expand All @@ -246,7 +250,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 {
Expand Down Expand Up @@ -284,12 +288,13 @@ Follow these rules when you use the clients:
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, and every call on that client returns `gorums.ErrNoNode`.
`NewStorageNodeClient(nil)` is legal.
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 and returns `gorums.ErrEmptyConfig`.
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.**
Expand Down Expand Up @@ -322,10 +327,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.
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.
Expand Down Expand Up @@ -831,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 the `.Wait()` of an `.Async()` handle report every send failure, so a one-way call cannot fail silently.
`.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.
A one-way call has no reply, so there is nothing more to wait for.
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.
Expand Down Expand Up @@ -2306,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 returns `gorums.ErrEmptyConfig`.
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
Expand Down Expand Up @@ -2553,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, 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, 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
Expand Down
Loading
Loading