blob: fb5b35bc5ea7068084e30d3f8b16f50321ab4737 [file] [log] [blame]
// Copyright 2021 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::builtin::capability::BuiltinCapability,
::routing::capability_source::InternalCapability,
anyhow::Error,
async_trait::async_trait,
cm_rust::CapabilityName,
fidl_fuchsia_component as fcomponent, fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync,
fuchsia_zircon as zx,
futures::{lock::Mutex, TryStreamExt},
lazy_static::lazy_static,
moniker::AbsoluteMoniker,
std::sync::Arc,
};
/// Any stored data is removed after this amount of time
const CLEANUP_DEADLINE_SECONDS: i64 = 600;
lazy_static! {
static ref CRASH_RECORDS_CAPABILITY_NAME: CapabilityName =
"fuchsia.sys2.CrashIntrospect".into();
}
#[derive(Debug, Clone, PartialEq)]
pub struct ComponentCrashInfo {
pub url: String,
pub moniker: AbsoluteMoniker,
}
impl Into<fsys::ComponentCrashInfo> for ComponentCrashInfo {
fn into(self) -> fsys::ComponentCrashInfo {
fsys::ComponentCrashInfo {
url: Some(self.url),
moniker: Some(self.moniker.to_string()),
..fsys::ComponentCrashInfo::EMPTY
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Record {
// The point at which this record should be deleted
deadline: zx::Time,
// The koid of the thread a crash was observed on
koid: zx::Koid,
// The crash information
crash_info: ComponentCrashInfo,
}
#[derive(Clone)]
pub struct CrashRecords {
records: Arc<Mutex<Vec<Record>>>,
_cleanup_task: Arc<fasync::Task<()>>,
}
/// This task will inspect `records`, and sleep until either `records[0].deadline` or for
/// `CLEANUP_DEADLINE_SECONDS` seconds. Upon waking, it removes anything from `records` whose
/// deadline has passed, and then repeats.
///
/// If `records` is not sorted by `Record::deadline` in ascending order then this task will not
/// behave correctly.
async fn record_cleanup_task(records: Arc<Mutex<Vec<Record>>>) {
loop {
let sleep_until = {
let records_guard = records.lock().await;
if records_guard.is_empty() {
// If we have no records, then we can sleep for as long as the timeout and check
// again
zx::Time::after(zx::Duration::from_seconds(CLEANUP_DEADLINE_SECONDS))
} else {
// If there's an upcoming record to delete, sleep until then
records_guard[0].deadline.clone()
}
};
let timer = fasync::Timer::new(sleep_until);
timer.await;
let mut records_guard = records.lock().await;
while !records_guard.is_empty() && zx::Time::get_monotonic() > records_guard[0].deadline {
records_guard.remove(0);
}
}
}
impl CrashRecords {
pub fn new() -> Self {
let records = Arc::new(Mutex::new(vec![]));
CrashRecords {
records: records.clone(),
_cleanup_task: Arc::new(fasync::Task::spawn(record_cleanup_task(records))),
}
}
/// Adds a new report to CrashRecords, and schedules the new reports deletion in
/// `CLEANUP_DEADLINE_SECONDS`.
pub async fn add_report(&self, thread_koid: zx::Koid, report: ComponentCrashInfo) {
self.records.lock().await.push(Record {
deadline: zx::Time::after(zx::Duration::from_seconds(CLEANUP_DEADLINE_SECONDS)),
koid: thread_koid.clone(),
crash_info: report,
});
}
/// Removes the report (if any), and deletes it.
pub async fn take_report(&self, thread_koid: &zx::Koid) -> Option<ComponentCrashInfo> {
let mut records_guard = self.records.lock().await;
let index_to_remove = records_guard
.iter()
.enumerate()
.find(|(_, record)| &record.koid == thread_koid)
.map(|(i, _)| i);
index_to_remove.map(|i| records_guard.remove(i).crash_info)
}
}
pub struct CrashIntrospectSvc(CrashRecords);
impl CrashIntrospectSvc {
pub fn new(crash_records: CrashRecords) -> Arc<Self> {
Arc::new(Self(crash_records))
}
}
#[async_trait]
impl BuiltinCapability for CrashIntrospectSvc {
const NAME: &'static str = "CrashIntrospect";
type Marker = fsys::CrashIntrospectMarker;
async fn serve(
self: Arc<Self>,
mut stream: fsys::CrashIntrospectRequestStream,
) -> Result<(), Error> {
while let Some(request) = stream.try_next().await? {
match request {
fsys::CrashIntrospectRequest::FindComponentByThreadKoid {
thread_koid,
responder,
} => match self.0.take_report(&zx::Koid::from_raw(thread_koid)).await {
Some(report) => responder.send(&mut Ok(report.into()))?,
None => responder.send(&mut Err(fcomponent::Error::ResourceNotFound))?,
},
}
}
Ok(())
}
fn matches_routed_capability(&self, capability: &InternalCapability) -> bool {
capability.matches_protocol(&CRASH_RECORDS_CAPABILITY_NAME)
}
}
#[cfg(test)]
mod tests {
use {super::*, fidl::endpoints::create_proxy_and_stream, fuchsia_async as fasync};
#[fuchsia::test]
async fn get_crash_report() -> Result<(), Error> {
let crash_records = CrashRecords::new();
let crash_records_svc = CrashIntrospectSvc::new(crash_records.clone());
let (crash_records_proxy, crash_records_stream) =
create_proxy_and_stream::<fsys::CrashIntrospectMarker>()?;
let _task = fasync::Task::local(crash_records_svc.serve(crash_records_stream));
let koid_raw = 123;
let koid = zx::Koid::from_raw(koid_raw);
let url = "456".to_string();
let moniker = AbsoluteMoniker::from(vec!["a"]);
let crash_report = ComponentCrashInfo { url: url.clone(), moniker: moniker.clone() };
assert_eq!(
Err(fcomponent::Error::ResourceNotFound),
crash_records_proxy.find_component_by_thread_koid(koid_raw).await?
);
assert_eq!(None, crash_records.take_report(&koid).await);
crash_records.add_report(koid, crash_report).await;
assert_eq!(
Ok(fsys::ComponentCrashInfo {
url: Some(url.clone()),
moniker: Some(moniker.to_string()),
..fsys::ComponentCrashInfo::EMPTY
}),
crash_records_proxy.find_component_by_thread_koid(koid_raw).await?,
);
assert_eq!(
Err(fcomponent::Error::ResourceNotFound),
crash_records_proxy.find_component_by_thread_koid(koid_raw).await?
);
Ok(())
}
}