blob: 6acb8e74bdaf50dff04e50e7f5f14c3940708d88 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COBALT_ENCODER_SEND_RETRYER_H_
#define COBALT_ENCODER_SEND_RETRYER_H_
#include <memory>
#include "encoder/shuffler_client.h"
#include "util/clock.h"
namespace cobalt {
namespace encoder {
namespace send_retryer {
// An object that provides a way to cancel an invocation of SendToShuffler().
class CancelHandle;
// An abstract interface implemented by SendRetryer (below).
// This is abstracted so that it may be mocked in tests.
class SendRetryerInterface {
public:
virtual ~SendRetryerInterface() = default;
virtual grpc::Status SendToShuffler(
std::chrono::seconds initial_rpc_deadline,
std::chrono::seconds overerall_deadline,
send_retryer::CancelHandle* cancel_handle,
const EncryptedMessage& encrypted_message) = 0;
};
// This class wraps a ShufflerClient with retry logic. We categorize
// gRPC error statuses as either retryable or not. If an error is retryable
// then we retry with exponential backoff, otherwise we give up. If the
// returned error is DEADLINE_EXEEDED then we increase the deadline.
class SendRetryer : public SendRetryerInterface {
public:
// Does not take ownership of |shuffler_client|.
explicit SendRetryer(ShufflerClientInterface* shuffler_client);
virtual ~SendRetryer() = default;
// Uses the wrapped ShufflerClient to send the given |encrypted_message| to
// the Shuffler. It should be an encrypted Envelope as given by the output of
// EnvelopeMaker::MakeEncryptedEnvelope().
//
// |inital_rpc_deadline| is the gRPC deadline to use for the first send
// attempt. This must be positive or we will CHECK fail. The deadline will be
// increased in later attempts if a DEADLINE_EXCEEDED status code is returned
// from the Shuffler. We will not honor arbitrarily large values of this
// parameter: We will truncate to a reasonable upper bound for all
// RPC timeouts.
//
// |overall_deadline| is the overall deadline granted to the Retryer for its
// multiple attempts to send. This must be >= |initial_rpc_deadline| or
// we will CHECK fail. This may be set to std::chrono::seconds:max()
// and the Retryer will retry "forever". Normally this should not be set
// to less than about a minute in order to give the Retryer enough time
// to try multiple times with increasing timeouts.
//
// |cancel_handle|. An optional pointer to an object that allows for
// cancellation. This may be NULL but if it is not NULL then it must remain
// valid for the duration of the call. Does not take ownership of
// |cancel_handle|.
//
// This is a synchronous method that may take a long time to return
// as the Retryer performs multiple attempts to send with exponential
// backoff. This method will return when one of the following occurs:
//
// - A successful send. Returns OK.
//
// - A non-retryable status code is received from the shuffler. Returns
// that status code.
//
// - |overall_deadline| has been exceeded. Returns DEADLINE_EXCEEDED.
//
// - TryCancel() is invoked (from some other thread) on the provided
// |cancel_handle|. May return CANCELLED in this case if the call
// was successfully canceled. Other responses including OK are possible
// after a TryCancel() because the cancellation is not guaranteed.
grpc::Status SendToShuffler(
std::chrono::seconds initial_rpc_deadline,
std::chrono::seconds overerall_deadline,
send_retryer::CancelHandle* cancel_handle,
const EncryptedMessage& encrypted_message) override;
private:
friend class SendRetryerTest;
ShufflerClientInterface* shuffler_client_; // not owned
// The value with which we will initialize sleep_between_attempts. This
// is exposed to friend tests so that they can set it to a smaller value.
std::chrono::milliseconds initial_sleep_ = std::chrono::milliseconds(1000);
// The clock is abstracted so that friend tests can set a non-system clock.
std::unique_ptr<util::ClockInterface> clock_;
};
// An object that provides a way to cancel an invocation of SendToShuffler().
class CancelHandle {
public:
// Attempt to cancel the call. This may or may not succeed depending on
// the current state of the call. If the Retryer is currently blocked
// waiting for a retry then this will succeed. If a gRPC call is in-flight
// an attempt will be made to cancel it but this may not succeed.
void TryCancel();
private:
friend class SendRetryer;
friend class SendRetryerTest;
std::mutex mutex_;
bool cancelled_ = false;
std::condition_variable cancel_notifier_;
std::unique_ptr<grpc::ClientContext> context_;
// If this is not NULL then it will be invoked with the value
// |sleep_millis| just prior to a sleep of |sleep_millis| milliseconds
// commencing. This is only used for tests so far but may prove useful
// for other purposes in the future.
std::function<void(int)> sleep_notification_function_;
};
} // namespace send_retryer
} // namespace encoder
} // namespace cobalt
#endif // COBALT_ENCODER_SEND_RETRYER_H_