diff --git a/base/callback_helpers.h b/base/callback_helpers.h index 54aabe518c7030..8ba5e6273a29e5 100644 --- a/base/callback_helpers.h +++ b/base/callback_helpers.h @@ -65,20 +65,23 @@ using EnableIfIsBaseCallback = namespace internal { template -class AdaptCallbackForRepeatingHelper final { +class OnceCallbackHolder final { public: - explicit AdaptCallbackForRepeatingHelper(OnceCallback callback) - : callback_(std::move(callback)) { + OnceCallbackHolder(OnceCallback callback, + bool ignore_extra_runs) + : callback_(std::move(callback)), ignore_extra_runs_(ignore_extra_runs) { DCHECK(callback_); } - AdaptCallbackForRepeatingHelper(const AdaptCallbackForRepeatingHelper&) = - delete; - AdaptCallbackForRepeatingHelper& operator=( - const AdaptCallbackForRepeatingHelper&) = delete; + OnceCallbackHolder(const OnceCallbackHolder&) = delete; + OnceCallbackHolder& operator=(const OnceCallbackHolder&) = delete; void Run(Args... args) { - if (subtle::NoBarrier_AtomicExchange(&has_run_, 1)) + if (subtle::NoBarrier_AtomicExchange(&has_run_, 1)) { + CHECK(ignore_extra_runs_) << "Both OnceCallbacks returned by " + "base::SplitOnceCallback() were run. " + "At most one of the pair should be run."; return; + } DCHECK(callback_); std::move(callback_).Run(std::forward(args)...); } @@ -86,6 +89,7 @@ class AdaptCallbackForRepeatingHelper final { private: volatile subtle::Atomic32 has_run_ = 0; base::OnceCallback callback_; + const bool ignore_extra_runs_; }; } // namespace internal @@ -100,9 +104,23 @@ class AdaptCallbackForRepeatingHelper final { template RepeatingCallback AdaptCallbackForRepeating( OnceCallback callback) { - using Helper = internal::AdaptCallbackForRepeatingHelper; - return base::BindRepeating(&Helper::Run, - std::make_unique(std::move(callback))); + using Helper = internal::OnceCallbackHolder; + return base::BindRepeating( + &Helper::Run, std::make_unique(std::move(callback), + /*ignore_extra_runs=*/true)); +} + +// Wraps the given OnceCallback and returns two OnceCallbacks with an identical +// signature. On first invokation of either returned callbacks, the original +// callback is invoked. Invoking the remaining callback results in a crash. +template +std::pair, OnceCallback> +SplitOnceCallback(OnceCallback callback) { + using Helper = internal::OnceCallbackHolder; + auto wrapped_once = base::BindRepeating( + &Helper::Run, std::make_unique(std::move(callback), + /*ignore_extra_runs=*/false)); + return std::make_pair(wrapped_once, wrapped_once); } // ScopedClosureRunner is akin to std::unique_ptr<> for Closures. It ensures diff --git a/base/callback_helpers_unittest.cc b/base/callback_helpers_unittest.cc index b88c1c2385e07c..bfc276c67646e9 100644 --- a/base/callback_helpers_unittest.cc +++ b/base/callback_helpers_unittest.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/test/gtest_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -193,4 +194,46 @@ TEST(CallbackHelpersTest, AdaptCallbackForRepeating) { EXPECT_EQ(1, count); } +TEST(CallbackHelpersTest, SplitOnceCallback_FirstCallback) { + int count = 0; + base::OnceCallback cb = + base::BindOnce([](int* count) { ++*count; }); + + auto split = base::SplitOnceCallback(std::move(cb)); + + static_assert(std::is_same, + base::OnceCallback>>::value, + ""); + + EXPECT_EQ(0, count); + std::move(split.first).Run(&count); + EXPECT_EQ(1, count); + +#if GTEST_HAS_DEATH_TEST + EXPECT_CHECK_DEATH(std::move(split.second).Run(&count)); +#endif // GTEST_HAS_DEATH_TEST +} + +TEST(CallbackHelpersTest, SplitOnceCallback_SecondCallback) { + int count = 0; + base::OnceCallback cb = + base::BindOnce([](int* count) { ++*count; }); + + auto split = base::SplitOnceCallback(std::move(cb)); + + static_assert(std::is_same, + base::OnceCallback>>::value, + ""); + + EXPECT_EQ(0, count); + std::move(split.second).Run(&count); + EXPECT_EQ(1, count); + +#if GTEST_HAS_DEATH_TEST + EXPECT_CHECK_DEATH(std::move(split.first).Run(&count)); +#endif // GTEST_HAS_DEATH_TEST +} + } // namespace diff --git a/docs/callback.md b/docs/callback.md index 412242c3ad5648..8e038a87efa765 100644 --- a/docs/callback.md +++ b/docs/callback.md @@ -232,6 +232,49 @@ OnceClosure task = other_task_runner->PostTask(FROM_HERE, std::move(task)); ``` +### Splitting a OnceCallback in two + +If a callback is only run once, but two references need to be held to the +callback, using a `base::OnceCallback` can be clearer than a +`base::RepeatingCallback`, from an intent and semantics point of view. +`base::SplitOnceCallback()` takes a `base::OnceCallback` and returns a pair of +callbacks with the same signature. When either of the returned callback is run, +the original callback is invoked. Running the leftover callback will result in a +crash. +This can be useful when passing a `base::OnceCallback` to a function that may or +may not take ownership of the callback. E.g, when an object creation could fail: + +```cpp +std::unique_ptr CreateFooTask(base::OnceClosure task) { + std::pair split + = base::SplitOnceCallback(std::move(task)); + + std::unique_ptr foo = TryCreateFooTask(std::move(split.first)); + if (foo) + return foo; + + return CreateFallbackFooTask(std::move(split.second)); +} +``` + +While it is best to use a single callback to report success/failure, some APIs +already take multiple callbacks. `base::SplitOnceCallback()` can be used to +split a completion callback and help in such a case: + +```cpp +using StatusCallback = base::OnceCallback; +void DoOperation(StatusCallback done_cb) { + std::pair split + = base::SplitOnceCallback(std::move(done_cb)); + + InnerWork(BindOnce(std::move(split.first), STATUS_OK), + BindOnce(std::move(split.second), STATUS_ABORTED)); +} + +void InnerWork(base::OnceClosure work_done_cb, + base::OnceClosure work_aborted_cb); +``` + ## Quick reference for basic stuff ### Binding A Bare Function