[CallbackList] Allow removal from within callback.

This makes moving from sigslot to CallbackList slightly simpler in some
situations.

Bug: webrtc:11943
Change-Id: I5c6dafb8ac597a119b90b64f369fa9e6316e38da
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/258560
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36515}
This commit is contained in:
Tomas Gunnarsson 2022-04-09 17:49:00 +02:00 committed by WebRTC LUCI CQ
parent 06e4fc548c
commit 8bd5d484fe
3 changed files with 40 additions and 5 deletions

View File

@ -22,8 +22,7 @@ CallbackListReceivers::~CallbackListReceivers() {
} }
void CallbackListReceivers::RemoveReceivers(const void* removal_tag) { void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
RTC_CHECK(!send_in_progress_); RTC_DCHECK(removal_tag);
RTC_DCHECK(removal_tag != nullptr);
// We divide the receivers_ vector into three regions: from right to left, the // We divide the receivers_ vector into three regions: from right to left, the
// "keep" region, the "todo" region, and the "remove" region. The "todo" // "keep" region, the "todo" region, and the "remove" region. The "todo"
@ -42,8 +41,13 @@ void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
} else if (receivers_[first_remove - 1].removal_tag == removal_tag) { } else if (receivers_[first_remove - 1].removal_tag == removal_tag) {
// The last element of the "todo" region should be removed. Move the // The last element of the "todo" region should be removed. Move the
// "todo"/"remove" boundary. // "todo"/"remove" boundary.
if (send_in_progress_) {
// Tag this receiver for removal, which will be done when `ForEach`
// has completed.
receivers_[first_remove - 1].removal_tag = pending_removal_tag();
}
--first_remove; --first_remove;
} else { } else if (!send_in_progress_) {
// The first element of the "todo" region should be removed, and the last // The first element of the "todo" region should be removed, and the last
// element of the "todo" region should be kept. Swap them, and then shrink // element of the "todo" region should be kept. Swap them, and then shrink
// the "todo" region from both ends. // the "todo" region from both ends.
@ -57,18 +61,28 @@ void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
} }
} }
if (!send_in_progress_) {
// Discard the remove region. // Discard the remove region.
receivers_.resize(first_remove); receivers_.resize(first_remove);
}
} }
void CallbackListReceivers::Foreach( void CallbackListReceivers::Foreach(
rtc::FunctionView<void(UntypedFunction&)> fv) { rtc::FunctionView<void(UntypedFunction&)> fv) {
RTC_CHECK(!send_in_progress_); RTC_CHECK(!send_in_progress_);
bool removals_detected = false;
send_in_progress_ = true; send_in_progress_ = true;
for (auto& r : receivers_) { for (auto& r : receivers_) {
RTC_DCHECK_NE(r.removal_tag, pending_removal_tag());
fv(r.function); fv(r.function);
if (r.removal_tag == pending_removal_tag()) {
removals_detected = true;
}
} }
send_in_progress_ = false; send_in_progress_ = false;
if (removals_detected) {
RemoveReceivers(pending_removal_tag());
}
} }
template void CallbackListReceivers::AddReceiver( template void CallbackListReceivers::AddReceiver(

View File

@ -51,10 +51,18 @@ class CallbackListReceivers {
void Foreach(rtc::FunctionView<void(UntypedFunction&)> fv); void Foreach(rtc::FunctionView<void(UntypedFunction&)> fv);
private: private:
// Special protected pointer value that's used as a removal_tag for
// receivers that want to unsubscribe from within a callback.
// Note we could use `&receivers_` too, but since it's the first member
// variable of the class, its address will be the same as the instance
// CallbackList instance, so we take an extra step to avoid collision.
const void* pending_removal_tag() const { return &send_in_progress_; }
struct Callback { struct Callback {
const void* removal_tag; const void* removal_tag;
UntypedFunction function; UntypedFunction function;
}; };
std::vector<Callback> receivers_; std::vector<Callback> receivers_;
bool send_in_progress_ = false; bool send_in_progress_ = false;
}; };

View File

@ -252,5 +252,18 @@ TEST(CallbackList, RemoveManyReceivers) {
EXPECT_EQ(accumulator, 1212); EXPECT_EQ(accumulator, 1212);
} }
TEST(CallbackList, RemoveFromSend) {
int removal_tag = 0;
CallbackList<> c;
c.AddReceiver(&removal_tag, [&] {
c.RemoveReceivers(&removal_tag);
// Do after RemoveReceivers to make sure the lambda is still valid.
++removal_tag;
});
c.Send();
c.Send();
EXPECT_EQ(removal_tag, 1);
}
} // namespace } // namespace
} // namespace webrtc } // namespace webrtc