Commit 5db52381 authored by Thomas Guilbert's avatar Thomas Guilbert Committed by Chromium LUCI CQ

Add SplitOnceCallback

This CL adds SplitOnceCallback, a utility function that allows to get
two OnceCallbacks out of one. Invoking any of the two split callbacks
will run the originally passed callback. Invoking the remaining callback
will result in a no-op.

Bug: 1156809
Change-Id: Iebf4542732fb6230af92fa27c4d7c5705933ad6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2586688
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Reviewed-by: default avatardanakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#838267}
parent 2dbe5247
......@@ -65,20 +65,23 @@ using EnableIfIsBaseCallback =
namespace internal {
template <typename... Args>
class AdaptCallbackForRepeatingHelper final {
class OnceCallbackHolder final {
public:
explicit AdaptCallbackForRepeatingHelper(OnceCallback<void(Args...)> callback)
: callback_(std::move(callback)) {
OnceCallbackHolder(OnceCallback<void(Args...)> 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>(args)...);
}
......@@ -86,6 +89,7 @@ class AdaptCallbackForRepeatingHelper final {
private:
volatile subtle::Atomic32 has_run_ = 0;
base::OnceCallback<void(Args...)> callback_;
const bool ignore_extra_runs_;
};
} // namespace internal
......@@ -100,9 +104,23 @@ class AdaptCallbackForRepeatingHelper final {
template <typename... Args>
RepeatingCallback<void(Args...)> AdaptCallbackForRepeating(
OnceCallback<void(Args...)> callback) {
using Helper = internal::AdaptCallbackForRepeatingHelper<Args...>;
return base::BindRepeating(&Helper::Run,
std::make_unique<Helper>(std::move(callback)));
using Helper = internal::OnceCallbackHolder<Args...>;
return base::BindRepeating(
&Helper::Run, std::make_unique<Helper>(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 <typename... Args>
std::pair<OnceCallback<void(Args...)>, OnceCallback<void(Args...)>>
SplitOnceCallback(OnceCallback<void(Args...)> callback) {
using Helper = internal::OnceCallbackHolder<Args...>;
auto wrapped_once = base::BindRepeating(
&Helper::Run, std::make_unique<Helper>(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
......
......@@ -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<void(int*)> cb =
base::BindOnce([](int* count) { ++*count; });
auto split = base::SplitOnceCallback(std::move(cb));
static_assert(std::is_same<decltype(split),
std::pair<base::OnceCallback<void(int*)>,
base::OnceCallback<void(int*)>>>::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<void(int*)> cb =
base::BindOnce([](int* count) { ++*count; });
auto split = base::SplitOnceCallback(std::move(cb));
static_assert(std::is_same<decltype(split),
std::pair<base::OnceCallback<void(int*)>,
base::OnceCallback<void(int*)>>>::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
......@@ -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<FooTask> CreateFooTask(base::OnceClosure task) {
std::pair<base::OnceClosure,base::OnceClosure> split
= base::SplitOnceCallback(std::move(task));
std::unique_ptr<FooTask> 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(FooStatus)>;
void DoOperation(StatusCallback done_cb) {
std::pair<StatusCallback, StatusCallback> 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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment