Commit 66f6323f authored by Abhishek Bhardwaj's avatar Abhishek Bhardwaj Committed by Commit Bot

power: Add wrapper class for wake up alarms API on Chrome OS

This change adds a class that makes the wake up alarm D-Bus API on
Chrome OS easy to use by clients. The D-Bus API was merged in
aec92f8b.

BUG=chromium:913318
TEST=Unit tests and end-to-end alarms via ARC++ applications.

Change-Id: I045af2c6ffbf93b3d44ac09ca217159fcdae18fe
Reviewed-on: https://chromium-review.googlesource.com/c/1372848
Commit-Queue: Abhishek Bhardwaj <abhishekbh@chromium.org>
Reviewed-by: default avatarDan Erat <derat@chromium.org>
Cr-Commit-Position: refs/heads/master@{#634959}
parent 15f51e3e
......@@ -184,6 +184,8 @@ component("dbus") {
"media_analytics_client.h",
"modem_messaging_client.cc",
"modem_messaging_client.h",
"native_timer.cc",
"native_timer.h",
"oobe_configuration_client.cc",
"oobe_configuration_client.h",
"permission_broker_client.cc",
......@@ -303,6 +305,7 @@ source_set("unit_tests") {
"fake_power_manager_client_unittest.cc",
"gsm_sms_client_unittest.cc",
"modem_messaging_client_unittest.cc",
"native_timer_unittest.cc",
"oobe_configuration_client_unittest.cc",
"pipe_reader_unittest.cc",
"power_manager_client_unittest.cc",
......
......@@ -252,6 +252,13 @@ void FakePowerManagerClient::CreateArcTimers(
const std::string& tag,
std::vector<std::pair<clockid_t, base::ScopedFD>> arc_timer_requests,
DBusMethodCallback<std::vector<TimerId>> callback) {
// Return error if tag is empty.
if (tag.empty()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<TimerId>()));
return;
}
// Check if client tag already exists. Return error iff it does.
if (base::ContainsKey(client_timer_ids_, tag)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
......
// Copyright 2018 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.
#include "chromeos/dbus/native_timer.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/unix_domain_socket.h"
#include "base/rand_util.h"
#include "base/task_runner_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/power_manager_client.h"
namespace chromeos {
namespace {
// Value of |timer_id_| when it's not initialized.
const PowerManagerClient::TimerId kNotCreatedId = -1;
// Value of |timer_id_| when creation was attempted but failed.
const PowerManagerClient::TimerId kErrorId = -2;
} // namespace
NativeTimer::NativeTimer(const std::string& tag)
: timer_id_(kNotCreatedId), tag_(tag), weak_factory_(this) {
// Create a socket pair, one end will be sent to the power daemon the other
// socket will be used to listen for the timer firing.
base::ScopedFD powerd_fd;
base::ScopedFD expiration_fd;
base::CreateSocketPair(&powerd_fd, &expiration_fd);
if (!powerd_fd.is_valid() || !expiration_fd.is_valid()) {
LOG(ERROR) << "Invalid file descriptor";
timer_id_ = kErrorId;
return;
}
// Send create timer request to the power daemon.
std::vector<std::pair<clockid_t, base::ScopedFD>> create_timers_request;
create_timers_request.push_back(
std::make_pair(CLOCK_BOOTTIME_ALARM, std::move(powerd_fd)));
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->CreateArcTimers(
tag, std::move(create_timers_request),
base::BindOnce(&NativeTimer::OnCreateTimer, weak_factory_.GetWeakPtr(),
std::move(expiration_fd)));
}
NativeTimer::~NativeTimer() {
// Delete the timer if it was created.
if (timer_id_ < 0) {
return;
}
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->DeleteArcTimers(
tag_, base::DoNothing());
}
struct NativeTimer::StartTimerParams {
StartTimerParams() = default;
StartTimerParams(base::TimeTicks absolute_expiration_time,
base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback)
: absolute_expiration_time(absolute_expiration_time),
timer_expiration_callback(std::move(timer_expiration_callback)),
result_callback(std::move(result_callback)) {}
StartTimerParams(StartTimerParams&&) = default;
~StartTimerParams() = default;
base::TimeTicks absolute_expiration_time;
base::OnceClosure timer_expiration_callback;
OnStartNativeTimerCallback result_callback;
DISALLOW_COPY_AND_ASSIGN(StartTimerParams);
};
void NativeTimer::Start(base::TimeTicks absolute_expiration_time,
base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If the timer creation didn't succeed then tell the caller immediately.
if (timer_id_ == kErrorId) {
std::move(result_callback).Run(false);
return;
}
// If the timer creation is in flight then save the parameters for this
// method. |OnCreateTimer| will issue the start call.
if (timer_id_ == kNotCreatedId) {
// In normal scenarios of two back to back |Start| calls, the first one is
// returned true in it's result callback and is overridden by the second
// |Start| call. In the case of two back to back in flight |Start| calls
// follow the same semantics and return true to the first caller.
if (in_flight_start_timer_params_) {
std::move(in_flight_start_timer_params_->result_callback).Run(true);
}
in_flight_start_timer_params_ = std::make_unique<StartTimerParams>(
absolute_expiration_time, std::move(timer_expiration_callback),
std::move(result_callback));
return;
}
// Start semantics guarantee that it will override any old timer set. Reset
// state to ensure this.
ResetState();
DCHECK_GE(timer_id_, 0);
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->StartArcTimer(
timer_id_, absolute_expiration_time,
base::BindOnce(&NativeTimer::OnStartTimer, weak_factory_.GetWeakPtr(),
std::move(timer_expiration_callback),
std::move(result_callback)));
}
void NativeTimer::OnCreateTimer(
base::ScopedFD expiration_fd,
base::Optional<std::vector<int32_t>> timer_ids) {
DCHECK(expiration_fd.is_valid());
if (!timer_ids.has_value()) {
LOG(ERROR) << "No timers returned";
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
// Only one timer is being created.
std::vector<int32_t> result = timer_ids.value();
if (result.size() != 1) {
LOG(ERROR) << "powerd created " << result.size() << " timers instead of 1";
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
// If timer creation failed and a |Start| call is pending then notify its
// result callback an error.
if (result[0] < 0) {
LOG(ERROR) << "Error timer ID " << result[0];
timer_id_ = kErrorId;
ProcessAndResetInFlightStartParams(false);
return;
}
// If timer creation succeeded and a |Start| call is pending then use the
// stored parameters to schedule a timer.
timer_id_ = result[0];
expiration_fd_ = std::move(expiration_fd);
ProcessAndResetInFlightStartParams(true);
}
void NativeTimer::OnStartTimer(base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback,
bool result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!result) {
LOG(ERROR) << "Starting timer ID " << timer_id_ << " failed";
std::move(result_callback).Run(false);
return;
}
// At this point the timer has started, watch for its expiration and tell the
// client that the start operation succeeded.
timer_expiration_callback_ = std::move(timer_expiration_callback);
expiration_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
expiration_fd_.get(), base::BindRepeating(&NativeTimer::OnExpiration,
weak_factory_.GetWeakPtr()));
std::move(result_callback).Run(true);
}
void NativeTimer::OnExpiration() {
// Write to the |expiration_fd_| to indicate to the instance that the timer
// has expired. The instance expects 8 bytes on the read end similar to what
// happens on a timerfd expiration. The timerfd API expects this to be the
// number of expirations, however, more than one expiration isn't tracked
// currently. This can block in the unlikely scenario of multiple writes
// happening but the instance not reading the data. When the send queue is
// full (64Kb), a write attempt here will block.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(expiration_fd_.is_valid());
uint64_t timer_data;
std::vector<base::ScopedFD> fds;
if (!base::UnixDomainSocket::RecvMsg(expiration_fd_.get(), &timer_data,
sizeof(timer_data), &fds)) {
PLOG(ERROR) << "Bad data in expiration fd";
}
// If this isn't done then this task will keep running forever. Hence, clean
// state regardless of any error above.
ResetState();
std::move(timer_expiration_callback_).Run();
}
void NativeTimer::ResetState() {
weak_factory_.InvalidateWeakPtrs();
expiration_fd_watcher_.reset();
in_flight_start_timer_params_.reset();
}
void NativeTimer::ProcessAndResetInFlightStartParams(bool result) {
if (!in_flight_start_timer_params_) {
return;
}
// Run the result callback if |result| is false. Else schedule a timer with
// the parameters stored.
if (!result) {
DCHECK_LT(timer_id_, 0);
std::move(in_flight_start_timer_params_->result_callback).Run(false);
in_flight_start_timer_params_.reset();
return;
}
// The |in_flight_start_timer_params_->result_callback| will be called in
// |OnStartTimer|.
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->StartArcTimer(
timer_id_, in_flight_start_timer_params_->absolute_expiration_time,
base::BindOnce(
&NativeTimer::OnStartTimer, weak_factory_.GetWeakPtr(),
std::move(in_flight_start_timer_params_->timer_expiration_callback),
std::move(in_flight_start_timer_params_->result_callback)));
// This state has been processed and must be reset to indicate that.
in_flight_start_timer_params_.reset();
}
} // namespace chromeos
// Copyright 2018 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.
#ifndef CHROMEOS_DBUS_NATIVE_TIMER_H_
#define CHROMEOS_DBUS_NATIVE_TIMER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/dbus/power_manager_client.h"
namespace chromeos {
using OnStartNativeTimerCallback = base::OnceCallback<void(bool)>;
// Sets timers that can also wake up the device from suspend by making D-Bus
// calls to the power daemon.
class COMPONENT_EXPORT(CHROMEOS_DBUS) NativeTimer {
public:
explicit NativeTimer(const std::string& tag);
~NativeTimer();
// Starts a timer to expire at |absolute_expiration_time|. Runs
// |timer_expiration_callback| on timer expiration. Runs |result_callback|
// with the result of the start operation. If starting the timer failed then
// |timer_expiration_callback| will not be called.
//
// Consecutive |Start| calls override the previous |Start| call.
void Start(base::TimeTicks absolute_expiration_time,
base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback);
private:
struct StartTimerParams;
// D-Bus callback for a create timer D-Bus call.
void OnCreateTimer(base::ScopedFD expiration_fd,
base::Optional<std::vector<int32_t>> timer_ids);
// D-Bus callback for a start timer D-Bus call.
void OnStartTimer(base::OnceClosure timer_expiration_callback,
OnStartNativeTimerCallback result_callback,
bool result);
// Callback for timer expiration.
void OnExpiration();
// Resets the |expiration_fd_watcher_| and cancels any inflight callbacks.
void ResetState();
// Calls the result callback for a pending |Start| operation with false iff
// |result| is false. Else, schedules a timer using the D-Bus API and calls
// the result callback for a pending |Start| operation with true. Resets
// |in_flight_start_timer_params_| in all cases.
void ProcessAndResetInFlightStartParams(bool result);
// Stores the parameters for |Start| when timer is not yet created i.e.
// |timer_id_| is uninitialized. Since |Start| calls override each other at
// any point only the latest |Start| call's parameters are stored in this.
std::unique_ptr<StartTimerParams> in_flight_start_timer_params_;
// Timer id returned by the power daemon, to be used as a handle for the timer
// APIs.
PowerManagerClient::TimerId timer_id_;
// Tag associated with the D-Bus API. Cached for deleting the timer in the
// destructor.
std::string tag_;
// File descriptor that will be written to when a Chrome OS alarm fires.
base::ScopedFD expiration_fd_;
// Callback to run when the timer expires.
base::OnceClosure timer_expiration_callback_;
// Watches |expiration_fd_| for an event.
std::unique_ptr<base::FileDescriptorWatcher::Controller>
expiration_fd_watcher_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<NativeTimer> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NativeTimer);
};
} // namespace chromeos
#endif // CHROMEOS_DBUS_NATIVE_TIMER_H_
// Copyright 2018 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.
#include "chromeos/dbus/native_timer.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
// Returns true iff |Start| on |timer| succeeds and timer expiration occurs too.
// The underlying fake power manager implementation expires the timer
// immediately and the test doesn't sleep.
bool CheckStartTimerAndExpiration(NativeTimer* timer,
base::TimeTicks absolute_expiration_ticks) {
base::RunLoop start_timer_loop;
base::RunLoop expiration_loop;
bool start_timer_result = false;
bool expiration_result = false;
timer->Start(
absolute_expiration_ticks,
base::BindOnce(
[](bool* result_out, base::OnceClosure quit_closure) {
*result_out = true;
std::move(quit_closure).Run();
},
&expiration_result, expiration_loop.QuitClosure()),
base::BindOnce(
[](bool* result_out, base::OnceClosure quit_closure, bool result) {
*result_out = result;
std::move(quit_closure).Run();
},
&start_timer_result, start_timer_loop.QuitClosure()));
start_timer_loop.Run();
if (!start_timer_result) {
return false;
}
// Run until timer expiration and check for the result.
expiration_loop.Run();
if (!expiration_result) {
return false;
}
return true;
}
} // namespace
class NativeTimerTest : public testing::Test {
public:
NativeTimerTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO) {
fake_power_manager_client_ = new chromeos::FakePowerManagerClient;
chromeos::DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient(
base::WrapUnique(fake_power_manager_client_));
}
~NativeTimerTest() override = default;
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
// Owned by chromeos::DBusThreadManager.
chromeos::FakePowerManagerClient* fake_power_manager_client_;
private:
DISALLOW_COPY_AND_ASSIGN(NativeTimerTest);
};
TEST_F(NativeTimerTest, CheckCreateFailure) {
// Create the timer. It queues async operations; enclose it in a run loop.
// This should fail internally as an empty tag is provided.
base::RunLoop create_timer_loop;
NativeTimer timer("");
create_timer_loop.RunUntilIdle();
// Starting the timer should fail as timer creation failed.
EXPECT_FALSE(CheckStartTimerAndExpiration(
&timer, base::TimeTicks() + base::TimeDelta::FromMilliseconds(1000)));
}
TEST_F(NativeTimerTest, CheckCreateAndStartTimer) {
// Create the timer. It queues async operations; enclose it in a run loop.
base::RunLoop create_timer_loop;
NativeTimer timer("Assistant");
create_timer_loop.RunUntilIdle();
// Start timer and check if starting the timer and its expiration succeeded.
EXPECT_TRUE(CheckStartTimerAndExpiration(
&timer, base::TimeTicks() + base::TimeDelta::FromMilliseconds(1000)));
// Start another timer and check if starting the timer and its expiration
// succeeded.
EXPECT_TRUE(CheckStartTimerAndExpiration(
&timer, base::TimeTicks() + base::TimeDelta::FromMilliseconds(1000)));
}
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment