Repository navigation
gengorums: generate service clients - #379
Conversation
c4907b3 to
f473a59
Compare
f473a59 to
5779ceb
Compare
5779ceb to
763d279
Compare
|
[P3] Include nested protobuf types in the client-name check The callers of this check visit only the top-level Location: cmd/protoc-gen-gorums/gengorums/generate.go:113. Validation: the existing test suites pass in the root, examples, and benchkit modules. |
The generated service clients for #308 hold their configuration or node and call the runtime with a plain context.Context. The ConfigContext and NodeContext wrappers no longer carry their weight, so the call constructors now take the context and the target as separate arguments, and the wrapper types and the Config.Context and Node.Context methods are removed. The empty-configuration and nil-node checks move from the removed Context methods into the constructors, and the panic names the constructor that was called. Generated code still uses the old signatures; later commits update the generator and regenerate it.
The runtime constructors now take the context and the target directly, so the generated package-level call functions that took a ConfigContext or NodeContext no longer compile. Generate, for each service S, an SClient bound to a gorums.Config and an SClientNode bound to a *gorums.Node, and generate each call as a method on the client that matches its target: quorum calls, streaming quorum calls, and multicasts on SClient; remote calls and unicasts on SClientNode. The client types live in their own service-level template, so dev mode writes them to zorums_client_gorums.pb.go. The ConfigContext and NodeContext aliases are gone from the static code, which also removes them from the reserved identifiers.
Each service now generates SClient and SClientNode types with helper methods, so an RPC method collides only with the helpers of the client type that receives it: Config, WithConfig, and ForNode on SClient, and Node on SClientNode. A remote call named Config is therefore valid, and the generator no longer checks RPC names against the type aliases. The client types and their constructors are package-level names, so messages and enums in the Go package must not use them either. Option validation now runs before the name checks, because the call type of a method selects its receiver.
The generated package-level call functions and the ConfigContext and NodeContext types are gone, so every caller now builds a generated service client from its configuration or node and passes the context to each call. Nested calls in server handlers build the client from the handler's peer configuration and pass the handler's context. The benchkit helpers StartRemote and StopRemote take a context and a configuration instead of a ConfigContext, and the setup callbacks of MeasureLatency, ClientMeasured, and ServerMeasured receive the run's context and configuration, so a benchmark builds its own client. New dev tests check the client target accessors, that ForNode and NewZorumsServiceClientNode reach the same node, including a node outside the client's configuration, and that constructing and converting clients allocates nothing.
The call functions that took a ConfigContext or NodeContext are gone; each service now has SClient and SClientNode types whose methods take a context.Context. Update the user guide, the developer guide, and the package docs to show the client types, and add a migration section for code written against the function API.
Regenerate with make dev and make genproto for the generated service clients.
The generated doc comments of unicast and multicast methods named the service and method, which the context already shows. They also folded Send and Async into one long sentence. State the call target, then say how Send and Async dispatch the request and where each reports the send error, in short sentences.
The name check visited only top-level messages and enums. But protoc-gen-go declares nested types at package level, with names such as Outer_InnerClient. A nested type could thus take the name of a generated client type for a service such as Outer_Inner. Generation then succeeded and the package failed to compile with a redeclared type. Walk nested messages and enums recursively, so such a schema gets the generator's error instead.
The doc comment said asyncQCFunc dispatches one quorum call "with client", which does not say what client does. Name the QuorumCall method of the BenchmarkClient and the nodes it calls.
The service-client migration reworded these doc comments to say that cfg holds the remote servers. The possessive wording from before reads better, and cfg needs no extra explanation. Restore the original text; only the signatures changed.
MeasureLatency, ClientMeasured, and ServerMeasured each spelled out the same setup function type inline, with parameter names, which made the signatures long. Declare it once as a named type. It is exported, because these exported functions take it as a parameter, so callers and godoc need to see it. SetupFunc names its role: it runs once per configuration, outside the measured window, and builds the operation that the harness then times.
The doc comments of MeasureLatency, ClientMeasured, and ServerMeasured walked through the lifecycle step by step. State only what each function takes, what it returns, and what it measures. The steps stay visible in the code and its inline comments.
The benchkit package has a generated Config alias for gorums.Config. Use it in EstimateOffsets, and drop the gorums import that this file then no longer needs.
Regenerate with the clarified unicast and multicast doc comments.
763d279 to
4988d84
Compare
|
Re: nested protobuf types in the client-name check (comment above). Fixed in ae99a61: the check now walks nested messages and enums recursively by GoIdent.GoName. New failing cases reproduce |
Each service had two client types: SClient for configuration calls and SClientNode for node calls, with ForNode, NewSClientNode, and Node to move between them. Generate only SClient. Remote calls and unicasts become methods on SClient that take the target node after the context, as the runtime constructors do. The method signature still keeps node calls and configuration calls apart at compile time, and the node can be any node, also one outside the client's configuration. A node call does not use the client's configuration, so the zero SClient is ready for node calls; the type's doc comment says so. The helper methods are now Config and WithConfig on one receiver, so an RPC with either name collides whatever its call type, and Node and ForNode are legal RPC names.
The generated SClientNode type is gone; remote calls and unicasts are
methods on SClient that take the node after the context. Migrate the
tests and the storage example: client.ForNode(n).M(ctx, in) and
NewSClientNode(n).M(ctx, in) become client.M(ctx, n, in), or
SClient{}.M(ctx, n, in) where no configuration is at hand.
The generated-code tests drop the ForNode, NewSClientNode, and Node
checks and their allocation checks. They now show that a zero client
makes node calls, that a node outside the client's configuration
works, and that a quorum call on a zero client panics.
WithConfig ignored its receiver and returned the same client as NewSClient. Remove it, so that a client for another configuration comes from NewSClient alone. An RPC method may now be named WithConfig; only Config remains a reserved method name.
A node call ignores the client's configuration, so its doc comment example now calls it on the zero client, qualified by the generated package name. The comment also says why a node call is a client method: the client type keeps the service's calls together and avoids package-level name conflicts. The one-way call comments describe Send and Async in separate sentences, with Wait on the handle that Async returns.
A call on an empty configuration or a nil node panics. The panic message now names the full method name of the call next to its call type, so the message points at the failing call site.
The test protos under internal/testprotos use the internal.testprotos proto package. The clientnames proto and the inline test protos used gorums.testprotos instead.
A node call does not use the client's configuration. The client type keeps the service's calls together and avoids package-level name conflicts, so the zero client is ready for node calls. Say this in the package doc, the user guide, and the developer guide. Describe Send and Async in separate sentences, with Wait on the handle that Async returns. Say what the Call handle provides instead of saying that it adds Intercept. Split long sentences, and use execute and remote call in the package doc.
Match the package doc, which says that a call executes on a configuration or a node.
A node client may be built from the nil node that Config.Node returns for an unknown ID. A call on that client should fail like a call to a node that is down, not panic. RemoteCall now returns ErrNoNode, and Unicast returns a handle whose Send and Wait report ErrNoNode without sending. ErrNoNode is a gRPC status error with the Unavailable code, like ErrNodeClosed and ErrStreamDown. Quorum calls and multicasts still panic on an empty configuration.
The single client type made node calls take a node argument and look different from configuration calls, and its zero value needed its own documentation. Each service now gets up to two client interfaces, as in gRPC: SClient with the quorum calls and multicasts, built from a configuration by NewSClient, and SNodeClient with the remote calls and unicasts, built from a node by NewSNodeClient. Every call has the shape Method(ctx, in). A service with only one kind of call gets only that client. Unexported structs implement the interfaces. Each call type template renders either the documented interface method or the implementing method, so the signature is written once. In dev mode, the client file holds the interfaces, constructors, and structs, and each call type file holds the implementing methods. The clients have no helper methods, so an RPC may be named Config. The collision check now covers the names of the generated interfaces, constructors, and structs, for the clients that a service has.
The generated node calls moved from SClient, where they took the node as an argument, to SNodeClient, which is built from the node. Tests and the storage example now call NewSNodeClient(node).Method(ctx, in), and the generated code tests use the caller's configuration instead of the removed Config method. The generated code tests also check that a node client of a nil node reports ErrNoNode, that a quorum call on an empty configuration still panics, and that building a node client does not allocate.
Replace the one-client-type text, including the zero-value client and the namespace explanation, with the two client interfaces. The docs now state the rules for their use: build node clients where they are needed and do not keep them, a nil node returns ErrNoNode, any node works, an empty configuration panics, and a new configuration needs a new SClient. The dev guide describes the two rendering modes of the call type templates and the extended name checks.
|
Pushed five new commits that replace the one-client-type design with two client interfaces per service.
The first four commits do not pass all tests alone; the last commit regenerates the generated files. |
A nil node is a caller error that retrying cannot fix, unlike the transient conditions that the Unavailable code describes.
A service named TLS now gets the struct tlsNodeClient, not tLSNodeClient.
Test .Decl negatively in the method body templates, and separate the synchronous and asynchronous examples of one-way calls. Document the two rendering modes where renderMethod is defined.
meling
left a comment
There was a problem hiding this comment.
Reviewed the current tip, ef8e0df, against master (723472a).
Two generator name collisions are reproduced below: the oneof-wrapper collision also persists at #380's current tip; the client/Correctable-alias collision is already rejected by #380 and needs its guard moved into this prerequisite PR.
Validation: go test ./... passes in the root, examples, and benchkit modules.
Both reproducer schemas compile at master; the inline comments describe their results at the two PR tips.
The earlier nested-message, enum-value, import-shadowing, and nil-node fixes are present.
The PR description still describes the superseded single-client design, including node arguments, a zero-value client, Config(), zero allocations, and a panic for a nil node.
Please update it to the current SClient/SNodeClient interfaces and ErrNoNode behavior.
protoc-gen-go declares a type at package level for each field of a oneof, named Message_Field, with a lowercase first letter in the opaque API. Such a type can take the name of a generated client interface or struct.
Closes #308. The generic terminal methods from the original issue are already on
master(#378); this PR adds the generated service clients.The design went through two revisions in review; the current one is the design update on #308: two client interfaces per service, one constructor each.
Important
This is a breaking change for users of the function-based API that v0.12.0 ships, so it targets v0.13.
Prepare the v0.13 release with
gorums-release prepare -bump-min: the generated code no longer fits the v0.12 runtime, soMinVersionandGenVersionmust become 13.Without it, code generated by v0.12 passes
EnforceVersionand then fails withundefined: gorums.ConfigContext.Problem
Generated calls are package-level functions that take a
*gorums.ConfigContextor a*gorums.NodeContext.A caller must first wrap its context:
pb.ReadQC(cfg.Context(ctx), req).Go does not allow
pbto declare methods ongorums.Config, socfg.ReadQC(...)is not possible.Package-level functions also block several services in one Go package (#349), because two services with the same RPC name collide.
Changes
For each service
S, the generator emits up to two client interfaces, as gRPC does.The call type in the proto file decides which interface a method goes to:
SClientNewSClient(cfg gorums.Config)SNodeClientNewSNodeClient(node *gorums.Node)client.Method(ctx, req).The target is fixed when the client is built.
sClient,sNodeClient), and the doc comments are on the interface methods.A struct name is the interface name with its leading acronym lowercased, for example
tlsNodeClientfor serviceTLS.Config(),WithConfig(),Node(), orForNode().An RPC may have any name.
A node client holds one pointer, so building one allocates nothing.
Building a configuration client allocates once, because the
Configslice moves into an interface.Config,ID, andNode, as before.gorumsimplconstructors take(ctx, config, req, method)or(ctx, node, req, method).ConfigContext,NodeContext,Config.Context, andNode.Contextare removed.A quorum call or multicast on an empty configuration panics when the call is built (see Return an error instead of panicking on an empty configuration #395 for a possible change).
A remote call or unicast on a nil node does not panic: it returns the new
gorums.ErrNoNode, a gRPC status error with theInvalidArgumentcode.So
pb.NewStorageNodeClient(cfg.Node(id))is safe for an ID that is not incfg.genClientsgenerates each interface, constructor, and struct.Each call-type template renders a method either as an interface declaration or as the struct method, so a signature is written once.
Only the clients that a service has are reserved.
examples, andbenchkituse the clients.benchkit's
StartRemote,StopRemote, and the setup callbacks ofMeasureLatency,ClientMeasured, andServerMeasurednow take(ctx context.Context, cfg gorums.Config)instead of a*gorums.ConfigContext.Breaking changes
gorums.ConfigContext,gorums.NodeContext,Config.Context, andNode.Contextare removed, and so are the generatedConfigContextandNodeContextaliases.gorumsimplconstructor signatures change (generated code only).gorums.ErrNoNodeinstead of panicking.StorageClientorNewStorageNodeClient, no longer compiles.Notes
The tip passes.
Testing
Config,WithConfig,Node, andForNode, and for a service with only node calls (nodeonly).TestGeneratedCodeClient: node calls through a node client reach the given node, also a node outside the client's configuration; a nil node reportsErrNoNode; an empty configuration panics.TestGeneratedCodeClientAllocs: one allocation for a configuration client, none for a node client.TestCallNilNodeandTestCallConstructorsPanicininternal/impl.go test ./...in the root,examples, andbenchkitmodules, with and without-tags=integration;make test,make goplscheck,make modernize, andgolangci-lintare clean.QuorumCall, count 6, benchstat) were measured on an earlier version of this PR, before the clients became interfaces. They were not run again.