salesforce / reactive-grpc

Reactive stubs for gRPC
BSD 3-Clause "New" or "Revised" License
832 stars 118 forks source link
grpc-java reactor rxjava2

CircleCI

What is reactive-grpc?

Reactive gRPC is a suite of libraries for using gRPC with Reactive Streams programming libraries. Using a protocol buffers compiler plugin, Reactive gRPC generates alternative gRPC bindings for each reactive technology. The reactive bindings support unary and streaming operations in both directions. Reactive gRPC also builds on top of gRPC's back-pressure support, to deliver end-to-end back-pressure-based flow control in line with Reactive Streams back-pressure model.

Reactive gRPC supports the following reactive programming models:

Akka gRPC is now mature and production ready. Use that for Akka-based services.

Usage

See the readme in each technology-specific sub-directory for usage details.

Demos

Android support

Reactive gRPC supports Android to the same level of the underlying reactive technologies.

Back-pressure

Reactive gRPC stubs support bi-directional streaming with back-pressure. Under the hood, Reactive gRPC is built atop the vanilla gRPC service stubs generated by protoc. As such, they inherit gRPC's HTTP/2-based back-pressure model.

Internally, gRPC and Reactive gRPC implement a pull-based back-pressure strategy. At the HTTP/2 layer, gRPC maintains a buffer of serialized protocol buffer messages. As frames are consumed on the consumer side, the producer is signaled to transmit more frames. If this producer-side transmit buffer fills, the HTTP/2 layer signals to the gRPC messaging layer to stop producing new messages in the stream. Reactive gRPC handles this signal, applying back-pressure to Reactive Streams using the Publisher api. Reactive gRPC also implements Publisher back-pressure on the consumer side of a stream. As messages are consumed by the consumer-side Publisher, signals are sent down through gRPC and HTTP/2 to request more data.

An example of back-pressure in action can be found in BackpressureIntegrationTest.java.

backpressure

Understanding Reactive gRPC Flow Control

For simple unary request and response services, Reactive gRPC's flow control model is transparent. However, Reactive gRPC is built on top of three different interacting flow control models, and, as a result, backpressure doesn't always behave exactly as you would expect. For streaming services, flow control isn't always intuitive, especially when infinite streams are involved.

When reasoning about flow control with Reactive gRPC, you cannot assume everything works like Reactive Streams. A call to request(1) on the consuming side of the wire will not necessarially result in a request(1) call on the producing side. Zero or more messages may be requested from the producer based on the state of the HTTP/2 flow control window, and the serialized size of each protobuf message. Instead, you need to think about how each stage in processing interacts with the stage before and after it.

ReactivegRPCHTTP/2...HTTP/2gRPCReactive

flow control

Exception Handling

Exception handling with Reactive gRPC is a little strange due to the way gRPC deals with errors. Servers that produce an error by calling onError(Throwable) will terminate the call with a StatusRuntimeException. The client will have its onError(Throwable) subscription handler called as expected.

Exceptions going from client to server are a little less predictable. Depending on the timing, gRPC may cancel the request before sending any messages due to an exception in the outbound stream.

Contributing

Found a bug? Think you've got an awesome feature you want to add? We welcome contributions!

Submitting a Contribution

  1. Search for an existing issue. If none exists, create a new issue so that other contributors can keep track of what you are trying to add/fix and offer suggestions (or let you know if there is already an effort in progress). Be sure to clearly state the problem you are trying to solve and an explanation of why you want to use the strategy you're proposing to solve it.
  2. Fork this repository on GitHub and create a branch for your feature.
  3. Clone your fork and branch to your local machine.
  4. Commit changes to your branch.
  5. Push your work up to GitHub.
  6. Submit a pull request so that we can review your changes.

Make sure that you rebase your branch off of master before opening a new pull request. We might also ask you to rebase it if master changes after you open your pull request.

Acceptance Criteria

We love contributions, but it's important that your pull request adhere to the standards that we maintain in this repository.