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_update.cc",
|
||||
"task_update.h",
|
||||
"timed_callback.h",
|
||||
"unpacker.cc",
|
||||
"unpacker.h",
|
||||
"unzipper.h",
|
||||
@ -296,6 +297,7 @@ source_set("unit_tests") {
|
||||
"protocol_serializer_json_unittest.cc",
|
||||
"protocol_serializer_unittest.cc",
|
||||
"request_sender_unittest.cc",
|
||||
"timed_callback_unittest.cc",
|
||||
"unpacker_unittest.cc",
|
||||
"update_checker_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