mirror of https://github.com/grpc/grpc.io.git
C++ Best Practices (#1309)
This commit is contained in:
parent
27a2b1acd5
commit
4709643416
|
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
title: Best Practices
|
||||
linkTitle: Best Practices for gRPC C++ API and FAQ
|
||||
weight: 60
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
* Please **use the callback API**.
|
||||
* **Look for header files with comments** in
|
||||
[third_party/grpc/include/grpcpp](https://github.com/grpc/grpc/tree/master/include/grpcpp).
|
||||
* **Always set a deadline on RPCs.** Here's a
|
||||
[blog post](https://grpc.io/blog/deadlines/) with some explanation. It's
|
||||
harder to do so for long-lasting streaming RPCs, but applications can
|
||||
implement custom logic to add deadlines for messages.
|
||||
<!-- TODO(yashykt): Add example. -->
|
||||
|
||||
## Streaming RPCs
|
||||
|
||||
* **Read all messages until failure** if you need all sent messages. Read
|
||||
until the reaction is called with bool `ok=false` for callback API or the
|
||||
tag has `ok=false` for the async API, or Read fails for the sync API. This
|
||||
is more reliable than counting messages.
|
||||
* **There can only be one read and one write in flight at a time** This is an
|
||||
API requirement rather than a best practice, but worth mentioning again.
|
||||
* **If your application has a two-way stream of data, use bi-directional
|
||||
streaming rather a client-server and server-client model.** This will allow
|
||||
for consistent load balancing and is better supported in gRPC.
|
||||
|
||||
## Callback API Specific
|
||||
|
||||
In this section, "operations" here are defined as `StartRead`, `StartWrite` (and
|
||||
variants), and `SendInitialMetadata`. `Finish` is also an operation, but often
|
||||
not relevant to the discussion.
|
||||
|
||||
"Reactions" are defined as the overridable callbacks within the reactor, such as
|
||||
`OnReadDone`, `OnWriteDone`, `OnCancel`, and `OnInitialMetadataDone`. `OnDone`
|
||||
is also a reaction, but as the final reaction, the directions here may not be
|
||||
relevant to it.
|
||||
|
||||
Best practices:
|
||||
|
||||
* **Reactions should be fast.** Do not do blocking or long-running/heavy
|
||||
weight tasks or sleep. It could impact other RPCs within the process.
|
||||
|
||||
### Streaming
|
||||
|
||||
* **Use Holds for starting operations outside reactions.** If you are starting
|
||||
operations on the client from outside reactions, you may need to use
|
||||
[holds](https://github.com/grpc/grpc/blob/24b050b593a435a625e10672fb9b55e8c6501eea/include/grpcpp/support/client_callback.h#L307).
|
||||
This prevents OnDone() from running until holds are removed, preventing
|
||||
races for final cleanup with outstanding operations if the stream has an
|
||||
error. The `bool ok` value in the reactions will reflect if the stream has
|
||||
ended, and operations started after this will all have `ok=false`.
|
||||
|
||||
* **Synchronize reactions.** Reactions can run in parallel. For example,
|
||||
`OnReadDone` may run at the same time as `OnWriteDone`. Synchronize
|
||||
accordingly.
|
||||
|
||||
* **Read until false** Rather than counting number of messages, etc. read
|
||||
until `OnReadDone(ok=false)`.
|
||||
|
||||
On the server side, note that this requires the client to call writes done,
|
||||
which is recommended. The server side does not have to do anything special -
|
||||
`Finish` signals the end of stream.
|
||||
|
||||
A status sent by the application through the `Finish` call may not be
|
||||
visible to the client until all incoming messages have been consumed.
|
||||
|
||||
## FAQ
|
||||
|
||||
### General
|
||||
|
||||
1. How do I debug gRPC issues?
|
||||
|
||||
See
|
||||
[troubleshooting](https://github.com/grpc/grpc/blob/master/TROUBLESHOOTING.md).
|
||||
|
||||
### Callback Streaming API
|
||||
|
||||
1. Is a client half-close required or expected?
|
||||
|
||||
A client half-close is strongly recommended so that the server can continue
|
||||
to read until `OnReadDone(ok=false)`, which is recommended on both the
|
||||
server and client side. However, it is not required -- a server could also
|
||||
always choose to Finish() before consuming all client data.
|
||||
|
||||
1. How do I cancel operations on the client side?
|
||||
|
||||
[`ClientContext::TryCancel()`](https://github.com/grpc/grpc/blob/24b050b593a435a625e10672fb9b55e8c6501eea/include/grpcpp/client_context.h#L392)
|
||||
|
||||
1. Does the client need to read the data on the wire before `OnDone` is called?
|
||||
|
||||
The best practice is always to read until `ok=false` on the client side.
|
||||
|
||||
The client must read all incoming data before it can see an OK status from
|
||||
server `Finish`. However, an error status such as from cancellation or
|
||||
deadline or stream abort may arrive anytime.
|
||||
|
||||
There are no guarantees about whether an explicit server `Finish` with an
|
||||
error status will be queued behind server writes or delivered immediately.
|
||||
Therefore, the client should always consume all incoming messages by reading
|
||||
until `ok=false` to guarantee delivery of the status.
|
||||
|
||||
Since messages are not guaranteed to be delivered if the server calls
|
||||
`Finish` with an error status, the error status should not be used to
|
||||
communicate success and further directions to a client; trailing metadata
|
||||
should be used for this purpose instead.
|
||||
|
||||
1. When is `OnDone` called on the client?
|
||||
|
||||
It is called when there is a status available for the client (all incoming
|
||||
data is read and the server has called `Finish` OR the status is an error
|
||||
that will be delivered immediately), all user reactions have finished
|
||||
running, and all holds are removed.
|
||||
|
||||
1. When is `OnDone` called on the server?
|
||||
|
||||
All reactions must finish running (including `OnCancel` when relevant) and
|
||||
server must have called `Finish`.
|
||||
|
||||
1. What is "within-reactor flow" vs. "outside-reactor flow" and why does it
|
||||
matter?
|
||||
|
||||
Within-reactor flow is when operations are started from inside reactions (or
|
||||
the reactor constructor) such as `OnWriteDone` starting another write. These
|
||||
make sense since there can only be one read and one write in flight and
|
||||
starting them from the reaction can help maintain that.
|
||||
|
||||
Outside-reactor flow is when operations on the stream are started from
|
||||
outside reactions. This makes sense since reactions aren't supposed to block
|
||||
and the application may not be ready to perform its next read or write. Note
|
||||
that outside-reactor flows require the use of holds to synchronize on the
|
||||
client side. Server side uses Finish to synchronize outside-reactor calls;
|
||||
the application should not start more operations after calling Finish.
|
||||
|
||||
1. What are holds and how and when do I use them?
|
||||
|
||||
They are used to synchronize when OnDone is called and are only needed with
|
||||
outside-reactor flow is used. Note that holds are only on the client side.
|
||||
|
||||
1. What if the server calls `Finish` but the client keeps starting new
|
||||
operations, such as with `StartWrite`?
|
||||
|
||||
`OnWriteDone(ok=false)` will be called each time the write is started up
|
||||
until `OnDone` is called.
|
||||
|
||||
1. How do we know when a reactor can be deleted?
|
||||
|
||||
The reactor can be deleted in `OnDone`. No methods on the reactor base class
|
||||
may be invoked from `OnDone`, and the reactor object will not be accessed by
|
||||
gRPC after `OnDone` is called. It is the application's responsibility to
|
||||
ensure no operations are started when OnDone is running.
|
||||
|
||||
1. Can reactions run at the same time, e.g. can `OnReadInitialMetadataDone` run
|
||||
at the same time as `OnReadDone`?
|
||||
|
||||
Yes, most reactions may run in parallel. Only `OnDone` runs by itself as the
|
||||
final operation.
|
||||
|
||||
1. Is `OnReadInitialMetadataDone` called every time, even if the metadata is
|
||||
empty?
|
||||
|
||||
Yes, this is used to communicate to the client that the metadata is empty.
|
||||
As with all reactions, the user application need not override this reaction.
|
||||
|
||||
1. Is `OnSendInitialMetadataDone` called on the server if the initial metadata
|
||||
goes out with the first write rather than because of an explicit
|
||||
`SendInitialMetadata`?
|
||||
|
||||
No, it has to be explicitly requested. Implicit calls don’t get callbacks.
|
||||
|
||||
1. If a client calls `WriteLast`, do they get both `OnWriteDone` and
|
||||
`OnWritesDoneDone` callbacks invoked? What happens if they call
|
||||
`Write(options.set_last_message = true)`?
|
||||
|
||||
If there’s a payload, only `OnWriteDone()` will be called.
|
||||
`OnWritesDoneDone` will be called only in response to `WritesDone()`.
|
||||
|
||||
1. Can you call `WritesDone()` while you have a write outstanding (i.e.
|
||||
`OnWriteDone()` hasn’t been called yet)?
|
||||
|
||||
Yes, the transport orders these.
|
||||
|
||||
1. When does `OnReadInitialMetadataDone` get called with `ok=false` for the
|
||||
client?
|
||||
|
||||
This would be called only if there is an error.
|
||||
|
||||
1. Can a user call `SendInitialMetadata`, `StartWrite` without waiting for
|
||||
`OnSendInitialMetadataDone` in the middle?
|
||||
|
||||
This is similar to `StartWrite` and `WritesDone`. We do not need to enforce
|
||||
ordering if the transport orders it, but the user may get callbacks invoked
|
||||
in any order.
|
||||
|
||||
1. When does server `OnCancel` run?
|
||||
|
||||
Note that this is not specific to streaming. It is called for an out-of-band
|
||||
cancellation, i.e. when there is an error on the stream (such as connection
|
||||
abort), a cancellation requested on the client or server side, or a deadline
|
||||
that expires. It can be used as a signal that the client is no longer
|
||||
processing any data.
|
||||
|
||||
It may run in parallel with other reactions. Operations called after
|
||||
`OnCancel` or started within `OnCancel` will have their reactions called
|
||||
with `ok=false`.
|
||||
|
||||
Note that `OnCancel` no longer runs if the server calls `Finish` specifying
|
||||
an error status because that is not considered an out-of-band cancellation.
|
||||
|
||||
Depending on ordering, `OnCancel` may or may not run if `Finish` is called.
|
||||
However, `OnDone` is always the final callback.
|
||||
|
||||
1. Does server still need to call `Finish` if `OnCancel` is run?
|
||||
|
||||
Yes, although the status passed to `Finish` is ignored.
|
||||
|
||||
1. Will `OnDone` still be called if `OnCancel` is called?
|
||||
|
||||
Yes, `OnDone` is the final callback and will be called once the server has
|
||||
also called `Finish` and all other reactions have completed running.
|
||||
|
||||
1. Can the user call additional operations (Start*) after `OnCancel`?
|
||||
|
||||
Yes, but they will all have reactions run with `ok=false`. It is not valid
|
||||
to call them after calling server `Finish`.
|
||||
|
||||
1. When does `IsCancelled()` return true on the `context`?
|
||||
|
||||
This is set when there is an error on the stream, a cancellation requested
|
||||
on the client or server side, or a deadline that expires. Once b/138186533
|
||||
is resolved, if this kind of error is the reason why reactions are called
|
||||
with `ok=false`, it will be set *before* these reactions are called.
|
||||
|
||||
1. Is there a per operation (e.g. `StartRead`) deadline, or only per RPC?
|
||||
|
||||
It is only per RPC.
|
||||
|
||||
1. If a user does `stub->MyBidiStreamRPC(); context->TryCancel()`, does the
|
||||
user still need to call `StartCall`?
|
||||
|
||||
Yes, it is required once `stub->MyBidiStreamRPC()` is called.
|
||||
|
||||
1. Is it legal for the server to call `Finish` while there is a Read or Write
|
||||
outstanding?
|
||||
|
||||
This is fine for reads and `OnReadDone(ok=false)` is called. Calling
|
||||
`Finish` with a write outstanding is not valid API usage, since a Finish can
|
||||
be considered a final write with no data, which would violate the
|
||||
one-write-in-flight rule.
|
||||
|
||||
1. What happens when the server calls `Finish` with a read outstanding?
|
||||
|
||||
`OnReadDone(ok=false)` is called.
|
||||
|
||||
1. Can you start another operation (e.g. read or write) in the client reactor’s
|
||||
`OnDone`?
|
||||
|
||||
No, that is not a legal use of the API.
|
||||
|
||||
1. Can you start operations on the server after calling `Finish`?
|
||||
|
||||
This is not a good practice. However, if you start new operations within
|
||||
reactions, their corresponding reactions will be called with `ok=false`.
|
||||
Starting them with outside-reaction flow is illegal and problematic since
|
||||
the operations may race with `OnDone`.
|
||||
Loading…
Reference in New Issue