Easy-to-enable, first-class API for having automatic retry any type of call. It would be good if this API supported hedging, where you preemptively issue a second RPC if the first is taking a while. Supporting hedging means that hedging would not need additional buffering.
This is larger in scope than #1122, which is only unary. This is related to #1570 and #1586, but more concerned with the API to expose to applications and the cross-language feature set. I've not yet looked at the policy in those PRs, so this says nothing about how I feel about the policy presented there. We can end up copying some of the policy in the PRs into this discussion.
I spoke with @ctiller and have this rough design that could suit both Java and C core.
I think any "policy" should probably be set on CallOptions (with maybe a default specified in ManagedChannelBuilder).
The design here is based on the same basic approach taken with the Load Balancer, have an object that can direct the core what to do, which seems to have worked well. The proposal is to have something like (very pseudo-code; just intended to describe features available, not concrete methods/arguments):
RetryPolicy {
int maxBytesToBuffer;
void start(Listener, method);
void inboundProgress(headers|message|trailers);
void fail(how);
Listener {
void event(pick|end);
}
}
(There is likely a factory to create a RetryPolicy instance each request; not too concerned at this point.) When a request is started, start() would be called and provided an object to provide later (possibly async) events. "pick" instructs the runtime to choose a transport and start the stream on that transport. "end" means there will be no further retry events.
The runtime will buffer outgoing messages up to maxBytesToBuffer (rounded up to the nearest message), until the "end" event. When the too much is buffered, flow control will push-back on the application.
You can see we have inboundProgress(), but no outboundProgress(). That's not a design feature; we were okay with having an outboundProgress() if it proved useful. We do however, want to separate the internal "streams" from this API, for the same reasons why "transports" aren't exposed concretely to LoadBalancers.
Noop Retry, just issues the request like normal:
void start(Listener, method) {
listener.event(pick);
listener.event(end);
}
Retry once:
void start(Listener, method) {
this.listener = listener;
listener.event(pick);
}
void fail(how) {
if (!retried && retryable(how)) {
retried = true;
schedule(1, SECONDS, lambda: listener.event(pick));
} else {
listener.event(end);
}
}
Hedging:
void start(Listener, method) {
this.listener = listener;
listener.event(pick);
// There would be some way to pass attributes to the LB so the pick would be
// guaranteed to go to a different transport.
scheduled = schedule(10, MILLISECONDS, lambda: listener.event(pick));
}
void inboundProgress(headers|message|trailers) {
scheduled.cancel();
listener.event(end);
}
@lukaszx0, @elandau, FYI
@ejona86 I like this design. Regarding the factory aspect how about returning a RetryContext from start()? I think it would make it clearer as to what tracks state.
RetryPolicy {
RetryContext start(Listener, method);
Listener {
void event(pick|end);
}
RetryContext {
void inboundProgress(headers|message|trailers);
void fail(how);
}
}
@elandau, I think returning the context from start is a good idea, since it would allow using final/constructor. Other places we can't do that (like Call, Stream, *Transport), but I do think we can do so here. We may want to tweak the name start(), but that is bike shedding that can be done later.
Thanks for putting this together Eric.
I've not yet looked at the policy in those PRs
FYI, in our PR (https://github.com/grpc/grpc-java/pull/1570), there's RetryPolicy which looks like this:
public class IdempotentRetryPolicy extends RetryPolicy {
public static final class Provider implements RetryPolicy.Provider {
@Override
public RetryPolicy get() {
return new IdempotentRetryPolicy();
}
}
@Override
public boolean isRetryable(Status status, MethodDescriptor method, CallOptions callOptions) {
return method.isIdempotent();
}
}
(with maybe a default specified in ManagedChannelBuilder).
It is now possible to do this after my changes in https://github.com/grpc/grpc-java/pull/1587
cc @jhump @ericzundel
Quick update. There's now an RFC design for retires which was aproved and I'm hoping to be the one who'll take on implementing it in my free time.
I've met with @ejona86 and we've discussed the java specific designs. We took some nodes, they're very raw, but I thought it'd be good to share them so that we can continue brainstorming and iterating on the design here.
Two fundamental options for what to buffer:
InputStream)ClientCallImplInProcessTransport without issueProblems:
byte[] copyProblems:
ClientTransportFactory to get the allocator so that ClientCallImpl can pre-serialize and save the buffersProblems:
MessageFramer; which makes it harder to do in ClientCallImpl. Maybe could make use of MessageFramer interface being added in https://github.com/grpc/grpc-java/pull/2839ClientCallImpl - Seems the most naturalAbstractStream - Sees the serialized outputInProcessTransport to work? How useful?I was planning to spend some time playing with it to get some proof of concept.
@ejona86 have you thought about it more? Got anything to add/update, comment?
What is the status of this feature? Still planned to be implemented?
@ggarber, yes, still planned for implementation, based on the gRFC. It's still basically in design stage for Java.
@ejona86 I see retries are in design phase. How do i configure retries in grpc android ? Is there a way to see the configure retries. I see right now grpc is not attempting to retry even when the network is back and available .
@kraghu, retries aren't implemented, so there isn't any way to turn them on. Reconnecting _is_ implemented though, mostly following the connection backoff spec. You may be interested in #2169.
For the record, there has been some activity in this area recently: #3656, #3818, #3840
Tracking progress in the project Retry Support
Hi, this is a very nice feature. When will this be ready in release?
@mackeyzheng It is not ready right now because service config is not usable. This will soon be fixed and hopefully retry will be available in release v1.12.
Hi, how is the progress on the retry mechanism going? It will simplify the client code (StreamObserver) a lot as of now our clients need to handle Status.UNAVAILABLE and resubscribe manually. Is there something that blocks the progress? I will be happy to help/contribute if additional work needs to be done.
Library level retry feature is available in the master branch and next release (v1.17.0). In v1.16, it is also available but there is a constraint that users must manage to disable census before enabling retry.
Most helpful comment
Library level retry feature is available in the master branch and next release (v1.17.0). In v1.16, it is also available but there is a constraint that users must manage to disable census before enabling retry.