blob: 58fc92d40b2decfd89da8714c84598c912d0d601 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_BASE_CALLBACK_TIMEOUT_HELPERS_H_
#define MEDIA_BASE_CALLBACK_TIMEOUT_HELPERS_H_
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
// This file provides callback wrappers to help handle the case where a
// base::OnceCallback has not run before a given timeout. This can help identify
// programming errors (e.g. a callback was saved without ever running), as well
// as performance issues (e.g. slow operation).
//
// The timeout timer starts when the callback is wrapped. If the wrapped
// callback runs before the timeout, the original callback will run exactly the
// same as if without wrapping. Otherwise:
// - `WrapCallbackWithTimeoutHandler`: On timeout, the `timeout_callback` runs.
// WARNING: The original callback is not affected: it will still run if the
// wrapped callback runs after the timeout. Do NOT handle the result in both
// the original callback and the `timeout_callback`.
// - `WrapCallbackWithDefaultInvokeIfTimeout`: On timeout, the original callback
// runs with the default arguments passed when wrapping the callback. It will be
// a no-op if the wrapped callback runs after the timeout.
//
// Example:
// // If `OnResult()` doesn't run after 10 seconds, it'll be run with "false".
// foo->DoWorkAndReturnResult(
// WrapCallbackWithDefaultInvokeIfTimeout(
// base::BindOnce(&Foo::OnResult, this), base::Seconds(10), false));
//
// // If `OnResult()` doesn't run after 10 seconds, LogError() will run.
// // `OnResult()` may still run after that.
// foo->DoWorkAndReturnResult(
// WrapCallbackWithTimeoutHandler(
// base::BindOnce(&Foo::OnResult, this),
// base::BindOnce(&Foo::LogError, this, TIMEOUT)));
namespace media {
// Callback time for the timeout handler in `WrapCallbackWithTimeoutHandler`.
// If `called_on_destruction` is true, the timeout callback was called because
// the original callback was destructed without running. Otherwise, it was
// called because the original callback timed out.
using TimeoutCallback = base::OnceCallback<void(bool called_on_destruction)>;
namespace internal {
// First, tell the compiler CallbackWithTimeoutHelper is a class template with
// one type parameter. Then define specializations where the type is a function
// returning void and taking zero or more arguments.
template <typename Signature>
class CallbackWithTimeoutHelper;
// Only support callbacks that return void because otherwise it is odd to call
// the callback in the destructor and drop the return value immediately.
template <typename... Args>
class CallbackWithTimeoutHelper<void(Args...)> {
public:
using CallbackType = base::OnceCallback<void(Args...)>;
// Bound arguments may be different to the callback signature when wrappers
// are used, e.g. in base::Owned and base::Unretained case, they are
// OwnedWrapper and UnretainedWrapper. Use BoundArgs to help handle this.
template <typename... BoundArgs>
explicit CallbackWithTimeoutHelper(CallbackType callback,
base::TimeDelta timeout_delay,
BoundArgs&&... args)
: callback_(std::move(callback)) {
timeout_callback_ = base::BindOnce(
&CallbackWithTimeoutHelper::RunWithReason, base::Unretained(this),
std::forward<BoundArgs>(args)...);
ScheduleTimeoutCallback(timeout_delay);
}
// The first int param acts to disambiguate this constructor from the template
// constructor above. The precedent is C++'s own operator++(int) vs
// operator++() to distinguish post-increment and pre-increment.
CallbackWithTimeoutHelper(int ignored,
CallbackType callback,
base::TimeDelta timeout_delay,
TimeoutCallback timeout_callback)
: callback_(std::move(callback)),
timeout_callback_(std::move(timeout_callback)) {
ScheduleTimeoutCallback(timeout_delay);
}
CallbackWithTimeoutHelper(const CallbackWithTimeoutHelper&) = delete;
CallbackWithTimeoutHelper& operator=(const CallbackWithTimeoutHelper&) =
delete;
~CallbackWithTimeoutHelper() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the callback is destructed without running, it will time out for sure.
// Therefore, fire the timeout closure now.
if (timeout_callback_)
std::move(timeout_callback_).Run(/*called_on_destruction=*/true);
}
void Run(Args... args) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
timeout_callback_.Reset();
// The callback could already run as part of `OnTimeout()`.
if (callback_)
std::move(callback_).Run(std::forward<Args>(args)...);
}
private:
void RunWithReason(Args... args, bool /*called_on_destruction*/) {
Run(std::forward<Args>(args)...);
}
void ScheduleTimeoutCallback(base::TimeDelta timeout_delay) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CallbackWithTimeoutHelper::OnTimeout,
weak_factory_.GetWeakPtr()),
timeout_delay);
}
void OnTimeout() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (timeout_callback_)
std::move(timeout_callback_).Run(/*called_on_destruction=*/false);
}
CallbackType callback_;
TimeoutCallback timeout_callback_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<CallbackWithTimeoutHelper> weak_factory_{this};
};
} // namespace internal
template <typename T, typename... Args>
inline base::OnceCallback<T> WrapCallbackWithTimeoutHandler(
base::OnceCallback<T> callback,
base::TimeDelta timeout_delay,
TimeoutCallback timeout_callback) {
return base::BindOnce(
&internal::CallbackWithTimeoutHelper<T>::Run,
std::make_unique<internal::CallbackWithTimeoutHelper<T>>(
/*ignored=*/0, std::move(callback), timeout_delay,
std::move(timeout_callback)));
}
template <typename T, typename... Args>
inline base::OnceCallback<T> WrapCallbackWithDefaultInvokeIfTimeout(
base::OnceCallback<T> callback,
base::TimeDelta timeout_delay,
Args&&... args) {
return base::BindOnce(
&internal::CallbackWithTimeoutHelper<T>::Run,
std::make_unique<internal::CallbackWithTimeoutHelper<T>>(
std::move(callback), timeout_delay, std::forward<Args>(args)...));
}
} // namespace media
#endif // MEDIA_BASE_CALLBACK_TIMEOUT_HELPERS_H_