update_client: Introduce a helper function to make timeout callbacks.
This wrapper guarantees that the callback will eventually be called even if the recipient of the callback forgets about it. Bug: 402773563 Change-Id: I48d23681d8b570972537d40c56d03591edea4e85 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4282299 Auto-Submit: Joshua Pawlicki <waffles@chromium.org> Commit-Queue: Joshua Pawlicki <waffles@chromium.org> Reviewed-by: Sorin Jianu <sorin@chromium.org> Cr-Commit-Position: refs/heads/main@{#1431756}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
463e0d9dde
commit
d25bb2f9eb
components/update_client
@@ -136,6 +136,7 @@ static_library("update_client") {
|
|||||||
"task_traits.h",
|
"task_traits.h",
|
||||||
"task_update.cc",
|
"task_update.cc",
|
||||||
"task_update.h",
|
"task_update.h",
|
||||||
|
"timed_callback.h",
|
||||||
"unpacker.cc",
|
"unpacker.cc",
|
||||||
"unpacker.h",
|
"unpacker.h",
|
||||||
"unzipper.h",
|
"unzipper.h",
|
||||||
@@ -296,6 +297,7 @@ source_set("unit_tests") {
|
|||||||
"protocol_serializer_json_unittest.cc",
|
"protocol_serializer_json_unittest.cc",
|
||||||
"protocol_serializer_unittest.cc",
|
"protocol_serializer_unittest.cc",
|
||||||
"request_sender_unittest.cc",
|
"request_sender_unittest.cc",
|
||||||
|
"timed_callback_unittest.cc",
|
||||||
"unpacker_unittest.cc",
|
"unpacker_unittest.cc",
|
||||||
"update_checker_unittest.cc",
|
"update_checker_unittest.cc",
|
||||||
"update_client_unittest.cc",
|
"update_client_unittest.cc",
|
||||||
|
77
components/update_client/timed_callback.h
Normal file
77
components/update_client/timed_callback.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_UPDATE_CLIENT_TIMED_CALLBACK_H_
|
||||||
|
#define COMPONENTS_UPDATE_CLIENT_TIMED_CALLBACK_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/cancelable_callback.h"
|
||||||
|
#include "base/functional/callback.h"
|
||||||
|
#include "base/functional/callback_helpers.h"
|
||||||
|
#include "base/task/sequenced_task_runner.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
|
||||||
|
namespace update_client {
|
||||||
|
|
||||||
|
// MakeTimedCallback posts a task to run `callback` after `timeout` with
|
||||||
|
// `timeout_args` on the current sequence, and returns a new callback to the
|
||||||
|
// caller that also calls `callback`. The returned callback must only be run on
|
||||||
|
// the current sequence.
|
||||||
|
//
|
||||||
|
// If the timeout occurs before the returned callback is
|
||||||
|
// run, the returned callback does nothing. It is safe to discard the returned
|
||||||
|
// callback in this case, or to call it.
|
||||||
|
//
|
||||||
|
// If the returned callback is run before the timeout, the timeout will do
|
||||||
|
// nothing.
|
||||||
|
//
|
||||||
|
// In either case, `callback` is guaranteed to run exactly once.
|
||||||
|
//
|
||||||
|
// For example, given:
|
||||||
|
// void HandleDecryptedContents(std::optional<std::string> plaintext);
|
||||||
|
// decryptAsync(ciphertext, base::BindOnce(&HandleDecryptedContents));
|
||||||
|
//
|
||||||
|
// It's possible to add a 30-second timeout:
|
||||||
|
// void HandleDecryptedContents(std::optional<std::string> plaintext);
|
||||||
|
// decryptAsync(
|
||||||
|
// cipher,
|
||||||
|
// MakeTimedCallback(
|
||||||
|
// base::BindOnce(&HandleDecryptedContents),
|
||||||
|
// base::Seconds(30),
|
||||||
|
// std::nullopt));
|
||||||
|
//
|
||||||
|
template <typename ReturnType, typename... Args>
|
||||||
|
base::OnceCallback<ReturnType(Args...)> MakeTimedCallback(
|
||||||
|
base::OnceCallback<ReturnType(Args...)> callback,
|
||||||
|
base::TimeDelta timeout,
|
||||||
|
Args... timeout_args) {
|
||||||
|
auto wrapper =
|
||||||
|
std::make_unique<base::CancelableOnceCallback<ReturnType(Args...)>>(
|
||||||
|
std::move(callback));
|
||||||
|
// Both callbacks must be constructed before either has a chance to run.
|
||||||
|
// `callback_1` will be run with the `timeout_args` after `timeout`.
|
||||||
|
// When either callback is run, they invalidate CancelableCallback's internal
|
||||||
|
// weak pointers, and the other callback becomes a no-op.
|
||||||
|
base::OnceCallback<ReturnType()> callback_1 =
|
||||||
|
base::BindOnce(wrapper->callback(), std::move(timeout_args)...);
|
||||||
|
base::OnceCallback<ReturnType(Args...)> callback_2 = wrapper->callback();
|
||||||
|
|
||||||
|
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
|
||||||
|
FROM_HERE,
|
||||||
|
base::BindOnce(
|
||||||
|
[](std::unique_ptr<base::CancelableOnceCallback<ReturnType(Args...)>>
|
||||||
|
/* wrapper */, // This callback takes ownership of `wrapper`.
|
||||||
|
base::OnceCallback<ReturnType()> callback_1) {
|
||||||
|
std::move(callback_1).Run();
|
||||||
|
},
|
||||||
|
std::move(wrapper), std::move(callback_1)),
|
||||||
|
timeout);
|
||||||
|
return callback_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace update_client
|
||||||
|
|
||||||
|
#endif // COMPONENTS_UPDATE_CLIENT_TIMED_CALLBACK_H_
|
62
components/update_client/timed_callback_unittest.cc
Normal file
62
components/update_client/timed_callback_unittest.cc
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2025 The Chromium Authors
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "components/update_client/timed_callback.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/functional/callback.h"
|
||||||
|
#include "base/run_loop.h"
|
||||||
|
#include "base/task/sequenced_task_runner.h"
|
||||||
|
#include "base/test/bind.h"
|
||||||
|
#include "base/test/mock_callback.h"
|
||||||
|
#include "base/test/task_environment.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace update_client {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
TEST(TimedCallbackTest, CallNoTimeout) {
|
||||||
|
base::test::TaskEnvironment task_environment;
|
||||||
|
base::MockOnceClosure mock;
|
||||||
|
EXPECT_CALL(mock, Run());
|
||||||
|
std::move(MakeTimedCallback(mock.Get(), base::Seconds(55))).Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TimedCallbackTest, TimeoutThenCall) {
|
||||||
|
base::test::TaskEnvironment task_environment;
|
||||||
|
base::MockOnceCallback<void(int)> mock;
|
||||||
|
base::RunLoop loop;
|
||||||
|
EXPECT_CALL(mock, Run(1));
|
||||||
|
base::OnceCallback<void(int)> callback = MakeTimedCallback(
|
||||||
|
base::BindOnce(mock.Get().Then(loop.QuitClosure())), base::Seconds(1), 1);
|
||||||
|
loop.Run();
|
||||||
|
std::move(callback).Run(2); // Should have no effect.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TimedCallbackTest, CallThenTimeout) {
|
||||||
|
base::test::TaskEnvironment task_environment;
|
||||||
|
base::MockOnceClosure mock;
|
||||||
|
base::RunLoop loop;
|
||||||
|
EXPECT_CALL(mock, Run());
|
||||||
|
MakeTimedCallback(mock.Get(), base::Seconds(0)).Run();
|
||||||
|
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
|
||||||
|
FROM_HERE, loop.QuitClosure(), base::Milliseconds(10));
|
||||||
|
loop.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(TimedCallbackTest, MoveOnlyParameter) {
|
||||||
|
base::test::TaskEnvironment task_environment;
|
||||||
|
base::MockOnceClosure mock;
|
||||||
|
EXPECT_CALL(mock, Run());
|
||||||
|
std::move(MakeTimedCallback(base::BindOnce([](base::OnceClosure closure) {
|
||||||
|
std::move(closure).Run();
|
||||||
|
}),
|
||||||
|
base::Seconds(55), base::BindOnce([] {})))
|
||||||
|
.Run(mock.Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace update_client
|
Reference in New Issue
Block a user