Skip to content

Commit

Permalink
Add SplitOnceCallback
Browse files Browse the repository at this point in the history
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: danakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#838267}
  • Loading branch information
tguilbert-google authored and Chromium LUCI CQ committed Dec 17, 2020
1 parent 2dbe524 commit 5db5238
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 11 deletions.
40 changes: 29 additions & 11 deletions base/callback_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,31 @@ 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)...);
}

private:
volatile subtle::Atomic32 has_run_ = 0;
base::OnceCallback<void(Args...)> callback_;
const bool ignore_extra_runs_;
};

} // namespace internal
Expand All @@ -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
Expand Down
43 changes: 43 additions & 0 deletions base/callback_helpers_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
43 changes: 43 additions & 0 deletions docs/callback.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5db5238

Please sign in to comment.