Create quiche_callbacks.h quiche_callbacks.h introduces the following type aliases: - quiche::UnretainedCallback (alias for absl::FunctionRef) - quiche::SingleUseCallback (alias for absl::AnyInvocable with && qualifier) - quiche::MultiUseCallback (alias for absl::AnyInvocable with const qualifier) Those are meant to replace existing usage of std::function in QUICHE. PiperOrigin-RevId: 536477899
diff --git a/build/source_list.bzl b/build/source_list.bzl index 70cd31f..fe5dee7 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -47,6 +47,7 @@ "common/platform/api/quiche_url_utils.h", "common/print_elements.h", "common/quiche_buffer_allocator.h", + "common/quiche_callbacks.h", "common/quiche_circular_deque.h", "common/quiche_crypto_logging.h", "common/quiche_data_reader.h", @@ -1046,6 +1047,7 @@ "common/platform/api/quiche_url_utils_test.cc", "common/print_elements_test.cc", "common/quiche_buffer_allocator_test.cc", + "common/quiche_callbacks_test.cc", "common/quiche_circular_deque_test.cc", "common/quiche_data_reader_test.cc", "common/quiche_data_writer_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index 2cf1a78..5f25812 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -47,6 +47,7 @@ "src/quiche/common/platform/api/quiche_url_utils.h", "src/quiche/common/print_elements.h", "src/quiche/common/quiche_buffer_allocator.h", + "src/quiche/common/quiche_callbacks.h", "src/quiche/common/quiche_circular_deque.h", "src/quiche/common/quiche_crypto_logging.h", "src/quiche/common/quiche_data_reader.h", @@ -1046,6 +1047,7 @@ "src/quiche/common/platform/api/quiche_url_utils_test.cc", "src/quiche/common/print_elements_test.cc", "src/quiche/common/quiche_buffer_allocator_test.cc", + "src/quiche/common/quiche_callbacks_test.cc", "src/quiche/common/quiche_circular_deque_test.cc", "src/quiche/common/quiche_data_reader_test.cc", "src/quiche/common/quiche_data_writer_test.cc",
diff --git a/build/source_list.json b/build/source_list.json index c501c60..ae24505 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -46,6 +46,7 @@ "quiche/common/platform/api/quiche_url_utils.h", "quiche/common/print_elements.h", "quiche/common/quiche_buffer_allocator.h", + "quiche/common/quiche_callbacks.h", "quiche/common/quiche_circular_deque.h", "quiche/common/quiche_crypto_logging.h", "quiche/common/quiche_data_reader.h", @@ -1045,6 +1046,7 @@ "quiche/common/platform/api/quiche_url_utils_test.cc", "quiche/common/print_elements_test.cc", "quiche/common/quiche_buffer_allocator_test.cc", + "quiche/common/quiche_callbacks_test.cc", "quiche/common/quiche_circular_deque_test.cc", "quiche/common/quiche_data_reader_test.cc", "quiche/common/quiche_data_writer_test.cc",
diff --git a/quiche/common/quiche_callbacks.h b/quiche/common/quiche_callbacks.h new file mode 100644 index 0000000..15eb1a9 --- /dev/null +++ b/quiche/common/quiche_callbacks.h
@@ -0,0 +1,151 @@ +// Copyright 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// quiche_callbacks.h provides definitions for the callback types used by +// QUICHE. Those aliases should be used instead of the function types provided +// by the standard library (std::function) or Abseil (absl::FunctionRef, +// absl::AnyInvocable). +// +// The aliases defined in this class are: +// - quiche::UnretainedCallback +// - quiche::SingleUseCallback +// - quiche::MultiUseCallback +// Each is documented below near its definition. +// +// As a general principle, there are following ways of constructing a callback: +// - Using a lambda expression (preferred) +// - Using absl::bind_front +// - Passing an already defined local function +// +// The following methods, however, should be avoided: +// - std::bind (<https://abseil.io/tips/108>) +// - Binding or taking a pointer to a function outside of the current module, +// especially the methods provided by the C++ standard library +// (use lambda instead; see go/totw/133 internally for motivation) + +#ifndef QUICHE_COMMON_QUICHE_CALLBACKS_H_ +#define QUICHE_COMMON_QUICHE_CALLBACKS_H_ + +#include <type_traits> + +#include "absl/functional/any_invocable.h" +#include "absl/functional/function_ref.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace quiche { + +namespace callbacks_internal { +template <class Sig> +class QUICHE_EXPORT SignatureChanger {}; + +template <typename ReturnType, typename... Args> +class QUICHE_EXPORT SignatureChanger<ReturnType(Args...)> { + public: + using Rvalue = ReturnType(Args...) &&; + using Const = ReturnType(Args...) const; +}; +} // namespace callbacks_internal + +// UnretainedCallback is the QUICHE alias for absl::FunctionRef. +// +// UnretainedCallback should be used when a function needs another function +// passed into it, but it will not retain any pointers to it long-term. +// +// For example, a QuicSession class may have function: +// void DoForAllStreams(quiche::UnretainedCallback<void(QuicStream*)>); +// +// Then a method could call it like this: +// int num_bidi_streams = 0; +// DoForAllStreams([&num_bidi_streams](QuicStream* stream) { +// if (stream->is_bidirectional()) { +// ++num_bidi_streams; +// } +// }); +// +// Note that similarly to absl::string_view, FunctionRef/UnretainedCallback does +// not own the underlying memory. This means that the code below will not work: +// +// quiche::UnretainedCallback f = [&i]() { ++i; } // <- INVALID CODE +// +// The code above allocates a lambda object that stores a pointer to `i` in it, +// stores a reference to that object inside `f`, and then immediately frees the +// lambda object. Attempting to compile the code above will fail with an error +// that says "Temporary whose address is used as value of local variable 'f' +// will be destroyed at the end of the full-expression". +template <class T> +using UnretainedCallback = absl::FunctionRef<T>; + +// SingleUseCallback<T(...)> is the QUICHE alias for +// absl::AnyInvocable<T(...) &&>. +// +// SingleUseCallback is meant to be used for callbacks that may be called at +// most once. For instance, a class may have a method that looks like this: +// +// void SetOnSessionDestroyed(quiche::SingleUseCallback<void()> callback) { +// on_session_destroyed_callback_ = std::move(callback); +// } +// +// Then it can execute the callback like this: +// +// ~Session() { +// std::move(on_session_destroyed_callback_ )(); +// } +// +// Note that as with other types of similar nature, calling the callback after +// it has been moved is undefined behavior (it will result in an +// ABSL_HARDENING_ASSERT() call). +template <class T> +using SingleUseCallback = absl::AnyInvocable< + typename callbacks_internal::SignatureChanger<T>::Rvalue>; + +static_assert(std::is_same_v<SingleUseCallback<void(int, int &, int &&)>, + absl::AnyInvocable<void(int, int &, int &&) &&>>); + +// MultiUseCallback<T(...)> is the QUICHE alias for +// absl::AnyInvocable<T(...) const>. +// +// MultiUseCallback is intended for situations where a callback may be invoked +// multiple times. It is probably the closest equivalent to std::function +// in this file, notable differences being that MultiUseCallback is move-only +// and immutable (meaning that it cannot have an internal state that mutates; it +// can still point to things that are mutable). +template <class T> +using MultiUseCallback = + absl::AnyInvocable<typename callbacks_internal::SignatureChanger<T>::Const>; + +static_assert( + std::is_same_v<MultiUseCallback<void()>, absl::AnyInvocable<void() const>>); + +// Use cases that are intentionally not covered by this header file: +// +// (a) Mutable callbacks. +// +// In C++, it's possible for a lambda to mutate its own state, e.g.: +// +// absl::AnyInvocable<void()> inc = [i = 0]() mutable { +// std::cout << (i++) << std::endl; +// }; +// inc(); +// inc(); +// inc(); +// +// The code above will output numbers 0, 1, 2. The fact that a callback can +// mutate its internal representation is counterintuitive, and thus not +// supported. Note that this limitation can be trivially worked around by +// passing a pointer (e.g., in the example below, `i = 0` could be replaced with +// `i = std::make_unique<int>(0)` to the similar effect). +// +// (b) Copyable callbacks. +// +// In C++, this would typically achieved by using std::function. This file +// could contain an alias for std::function; it currently does not, since this +// use case is expected to be fairly rare. +// +// (c) noexpect support. +// +// We do not use C++ exceptions in QUICHE. + +} // namespace quiche + +#endif // QUICHE_COMMON_QUICHE_CALLBACKS_H_
diff --git a/quiche/common/quiche_callbacks_test.cc b/quiche/common/quiche_callbacks_test.cc new file mode 100644 index 0000000..4d83d35 --- /dev/null +++ b/quiche/common/quiche_callbacks_test.cc
@@ -0,0 +1,82 @@ +#include "quiche/common/quiche_callbacks.h" + +#include <memory> + +#include "quiche/common/platform/api/quiche_test.h" + +namespace quiche { +namespace { + +void Apply(const std::vector<int>& container, + UnretainedCallback<void(int)> function) { + for (int n : container) { + function(n); + } +} + +TEST(QuicheCallbacksTest, UnretainedCallback) { + std::vector<int> nums = {1, 2, 3, 4}; + int sum = 0; + Apply(nums, [&sum](int n) { sum += n; }); + EXPECT_EQ(sum, 10); +} + +TEST(QuicheCallbacksTest, SingleUseCallback) { + int called = 0; + SingleUseCallback<void()> callback = [&called]() { called++; }; + EXPECT_EQ(called, 0); + + SingleUseCallback<void()> new_callback = std::move(callback); + EXPECT_EQ(called, 0); + + std::move(new_callback)(); + EXPECT_EQ(called, 1); + EXPECT_QUICHE_DEBUG_DEATH( + std::move(new_callback)(), // NOLINT(bugprone-use-after-move) + "AnyInvocable"); +} + +class SetFlagOnDestruction { + public: + SetFlagOnDestruction(bool* flag) : flag_(flag) {} + ~SetFlagOnDestruction() { *flag_ = true; } + + private: + bool* flag_; +}; + +TEST(QuicheCallbacksTest, SingleUseCallbackOwnership) { + bool deleted = false; + auto flag_setter = std::make_unique<SetFlagOnDestruction>(&deleted); + { + SingleUseCallback<void()> callback = [setter = std::move(flag_setter)]() {}; + EXPECT_FALSE(deleted); + } + EXPECT_TRUE(deleted); +} + +TEST(QuicheCallbacksTest, MultiUseCallback) { + int called = 0; + MultiUseCallback<void()> callback = [&called]() { called++; }; + EXPECT_EQ(called, 0); + + callback(); + EXPECT_EQ(called, 1); + + callback(); + callback(); + EXPECT_EQ(called, 3); +} + +TEST(QuicheCallbacksTest, MultiUseCallbackOwnership) { + bool deleted = false; + auto flag_setter = std::make_unique<SetFlagOnDestruction>(&deleted); + { + MultiUseCallback<void()> callback = [setter = std::move(flag_setter)]() {}; + EXPECT_FALSE(deleted); + } + EXPECT_TRUE(deleted); +} + +} // namespace +} // namespace quiche