blob: 2d650dfac906056122635908ec4beb43d4deca02 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_APP_MODE_RETRY_RUNNER_H_
#define CHROME_BROWSER_ASH_APP_MODE_RETRY_RUNNER_H_
#include <memory>
#include <optional>
#include <variant>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_mode/cancellable_job.h"
namespace ash {
template <typename Result>
using RetryResultCallback = base::OnceCallback<void(Result result)>;
template <typename Result>
using RetryJob = base::RepeatingCallback<std::unique_ptr<CancellableJob>(
RetryResultCallback<Result> on_result)>;
template <typename Result>
using VoidRetryJob =
base::RepeatingCallback<void(RetryResultCallback<Result> on_result)>;
template <typename Result>
using RetryPredicate = base::RepeatingCallback<bool(const Result& result)>;
// Runs the given `job` up to `n` times.
//
// `job` will be run `n` times or until `should_retry` returns false, whichever
// happens first.
//
// `should_retry` will be invoked with the result whenever `job` finishes and
// there are attempts remaining. The result of `should_retry` determines if we
// must retry the job, or if we're done and we can pass the result to `on_done`.
//
// `on_done` will be called with the result of the last `job`.
//
// Attempts at `job` are delayed with exponential backoff after failure.
//
// Destroying the returned `std::unique_ptr` cancels this task. In that case
// `on_done` will not be called.
template <typename Result>
[[nodiscard]] std::unique_ptr<CancellableJob> RunUpToNTimes(
int n,
VoidRetryJob<Result> job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done);
// Same as above but accepts jobs that return instances of `CancellableJob`.
//
// Each `CancellableJob` instance generated by attempts at `job` are eventually
// destroyed after they return. This guarantees resources allocated by the
// execution of `job` are not kept around indefinitely after the `job` ends.
template <typename Result>
[[nodiscard]] std::unique_ptr<CancellableJob> RunUpToNTimes(
int n,
RetryJob<Result> job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done);
// Returns a predicate suitable for `RunUpToNTimes()` jobs that return an
// `std::optional` and should retry when the result is `std::nullopt`.
template <typename T>
base::RepeatingCallback<bool(const std::optional<T>&)> RetryIfNullopt();
namespace internal {
// Computes the delay for `attempt_count` with exponential backoff.
base::TimeDelta DelayForAttempt(int attempt_count);
// Runs `task` after the given `delay` in the current sequence.
void PostDelayedTask(base::OnceClosure task, base::TimeDelta delay);
// Helper to retry tasks that can fail.
template <typename Result, typename HandleType = std::monostate>
class RetryRunner : public CancellableJob {
public:
using Job = base::RepeatingCallback<std::unique_ptr<HandleType>(
RetryResultCallback<Result> on_result)>;
[[nodiscard]] static std::unique_ptr<CancellableJob> Run(
int max_attempts,
Job job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done) {
auto handle = base::WrapUnique(new RetryRunner<Result, HandleType>(
max_attempts, job, should_retry, std::move(on_done)));
handle->RunAndRetryOnFailure();
return handle;
}
RetryRunner(const RetryRunner&) = delete;
RetryRunner& operator=(const RetryRunner&) = delete;
~RetryRunner() override = default;
private:
RetryRunner(int max_attempts,
Job job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done)
: max_attempts_(max_attempts),
job_(job),
should_retry_(should_retry),
on_done_(std::move(on_done)) {}
void RunAndRetryOnFailure(int attempt_count = 1) {
current_run_ = job_.Run(base::BindOnce(
[](base::WeakPtr<RetryRunner> self, int attempt_count, Result result) {
if (!self) {
return;
}
self->current_run_.reset();
if (attempt_count >= self->max_attempts_ ||
!self->should_retry_.Run(result)) {
return std::move(self->on_done_).Run(std::move(result));
}
internal::PostDelayedTask(
base::BindOnce(&RetryRunner::RunAndRetryOnFailure, self,
attempt_count + 1),
internal::DelayForAttempt(attempt_count));
},
weak_ptr_factory_.GetWeakPtr(), attempt_count));
if (on_done_.is_null()) {
// `job_` already called `on_done_` synchronously, reset `current_run_`.
current_run_.reset();
}
}
int max_attempts_;
Job job_;
RetryPredicate<Result> should_retry_;
RetryResultCallback<Result> on_done_;
std::unique_ptr<HandleType> current_run_;
base::WeakPtrFactory<RetryRunner> weak_ptr_factory_{this};
};
} // namespace internal
template <typename Result>
[[nodiscard]] std::unique_ptr<CancellableJob> RunUpToNTimes(
int n,
VoidRetryJob<Result> job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done) {
return internal::RetryRunner<Result>::Run(
/*max_attempts=*/n, /*job=*/
base::BindRepeating(
[](VoidRetryJob<Result> job,
RetryResultCallback<Result> result_callback) {
job.Run(std::move(result_callback));
return std::make_unique<std::monostate>();
},
job),
should_retry, std::move(on_done));
}
template <typename Result>
[[nodiscard]] std::unique_ptr<CancellableJob> RunUpToNTimes(
int n,
RetryJob<Result> job,
RetryPredicate<Result> should_retry,
RetryResultCallback<Result> on_done) {
return internal::RetryRunner<Result, CancellableJob>::Run(
/*max_attempts=*/n, job, should_retry, std::move(on_done));
}
template <typename T>
base::RepeatingCallback<bool(const std::optional<T>&)> RetryIfNullopt() {
return base::BindRepeating(
[](const std::optional<T>& optional) { return !optional.has_value(); });
}
} // namespace ash
#endif // CHROME_BROWSER_ASH_APP_MODE_RETRY_RUNNER_H_