blob: 4e39a273ffd8f1f5219ddb4f9fde7764c8d27388 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::error::PowerManagerError;
use crate::message::{Message, MessageReturn};
use anyhow::format_err;
use async_trait::async_trait;
use futures::future::join_all;
use std::rc::Rc;
/// A trait that all nodes in the PowerManager must implement
#[async_trait(?Send)]
pub trait Node {
/// Return a string to indicate the name of this node
///
/// Each node should use this function to indicate a meaningful name. The name may be used for
/// logging and/or debugging purposes.
fn name(&self) -> String;
/// Handle a new message
///
/// All nodes must implement this message to support communication between nodes. This is the
/// entry point for a Node to receive new messages.
async fn handle_message(&self, _msg: &Message) -> Result<MessageReturn, PowerManagerError> {
Err(PowerManagerError::Unsupported)
}
/// Send a message to another node
///
/// This is implemented as a future to support scenarios where a node wishes to send messages to
/// multiple other nodes. Errors are logged automatically.
async fn send_message(
&self,
node: &Rc<dyn Node>,
msg: &Message,
) -> Result<MessageReturn, PowerManagerError> {
// TODO(fxbug.dev/44484): Ideally we'd use a duration event here. But due to a limitation in
// the Rust tracing library, that would require creating any formatted strings (such as the
// "message" value) unconditionally, even when the tracing category is disabled. To avoid
// that unnecessary computation, just use an instant event.
fuchsia_trace::instant!(
"power_manager:messages",
"message_start",
fuchsia_trace::Scope::Thread,
"message" => format!("{:?}", msg).as_str(),
"source_node" => self.name().as_str(),
"dest_node" => node.name().as_str()
);
let result = node.handle_message(msg).await;
fuchsia_trace::instant!(
"power_manager:messages",
"message_result",
fuchsia_trace::Scope::Thread,
"message" => format!("{:?}", msg).as_str(),
"source_node" => self.name().as_str(),
"dest_node" => node.name().as_str(),
"result" => format!("{:?}", result).as_str()
);
result
}
/// Send a message to a list of other nodes. The message is sent to each node in a separate
/// Future and all are joined before returning. The results from all nodes are returned in a
/// vector in the same node-ordering that was provided.
async fn send_message_to_many(
&self,
nodes: &Vec<Rc<dyn Node>>,
msg: &Message,
) -> Vec<Result<MessageReturn, PowerManagerError>> {
join_all(nodes.iter().map(|node| async move {
self.send_message(node, msg).await.map_err(|e| {
PowerManagerError::GenericError(format_err!(
"Failed to send message to {}: {}",
node.name(),
e
))
})
}))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::mock_node::{MessageMatcher, MockNodeMaker};
use crate::{msg_eq, msg_ok_return};
use assert_matches::assert_matches;
use fuchsia_async as fasync;
struct TestNode;
impl Node for TestNode {
fn name(&self) -> String {
"TestNode".to_string()
}
}
/// Tests that `send_message_to_many` sends a message to all provided nodes and the results are
/// returned correctly.
#[fasync::run_singlethreaded(test)]
async fn test_send_message_to_many() {
let mut mock_maker = MockNodeMaker::new();
let sending_node = mock_maker.make("sending_node", vec![]);
let receiving_node_1 = mock_maker.make(
"receiving_node_1",
vec![(msg_eq!(GetPerformanceState), msg_ok_return!(GetPerformanceState(1)))],
);
let receiving_node_2 = mock_maker.make(
"receiving_node_1",
vec![(msg_eq!(GetPerformanceState), msg_ok_return!(GetPerformanceState(2)))],
);
let results = sending_node
.send_message_to_many(
&vec![receiving_node_1, receiving_node_2],
&Message::GetPerformanceState,
)
.await;
assert_eq!(results.len(), 2);
assert_matches!(results[0], Ok(MessageReturn::GetPerformanceState(1)));
assert_matches!(results[1], Ok(MessageReturn::GetPerformanceState(2)));
}
}