1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use futures::lock::Mutex;
6use log::*;
7use moniker::Moniker;
8use std::sync::Arc;
9use {fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync};
1011/// Any stored data is removed after this amount of time
12const CLEANUP_DEADLINE_SECONDS: i64 = 600;
1314#[derive(Debug, Clone, PartialEq)]
15pub struct ComponentCrashInfo {
16pub url: String,
17pub moniker: Moniker,
18}
1920impl Into<fsys::ComponentCrashInfo> for ComponentCrashInfo {
21fn into(self) -> fsys::ComponentCrashInfo {
22 fsys::ComponentCrashInfo {
23 url: Some(self.url),
24 moniker: Some(self.moniker.to_string()),
25 ..Default::default()
26 }
27 }
28}
2930#[derive(Debug, Clone, PartialEq)]
31pub(crate) struct Record {
32// The point at which this record should be deleted
33deadline: zx::MonotonicInstant,
34// The koid of the thread a crash was observed on
35koid: zx::Koid,
36// The crash information
37crash_info: ComponentCrashInfo,
38}
3940#[derive(Clone)]
41pub struct CrashRecords {
42 records: Arc<Mutex<Vec<Record>>>,
43 _cleanup_task: Arc<fasync::Task<()>>,
44}
4546/// This task will inspect `records`, and sleep until either `records[0].deadline` or for
47/// `CLEANUP_DEADLINE_SECONDS` seconds. Upon waking, it removes anything from `records` whose
48/// deadline has passed, and then repeats.
49///
50/// If `records` is not sorted by `Record::deadline` in ascending order then this task will not
51/// behave correctly.
52async fn record_cleanup_task(records: Arc<Mutex<Vec<Record>>>) {
53loop {
54let sleep_until = {
55let records_guard = records.lock().await;
56if records_guard.is_empty() {
57// If we have no records, then we can sleep for as long as the timeout and check
58 // again
59zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
60 CLEANUP_DEADLINE_SECONDS,
61 ))
62 } else {
63// If there's an upcoming record to delete, sleep until then
64records_guard[0].deadline.clone()
65 }
66 };
67let timer = fasync::Timer::new(sleep_until);
68 timer.await;
69let mut records_guard = records.lock().await;
70while !records_guard.is_empty() && zx::MonotonicInstant::get() > records_guard[0].deadline {
71 records_guard.remove(0);
72 }
73 }
74}
7576impl CrashRecords {
77pub fn new() -> Self {
78let records = Arc::new(Mutex::new(vec![]));
79 CrashRecords {
80 records: records.clone(),
81 _cleanup_task: Arc::new(fasync::Task::spawn(record_cleanup_task(records))),
82 }
83 }
8485/// Adds a new report to CrashRecords, and schedules the new reports deletion in
86 /// `CLEANUP_DEADLINE_SECONDS`.
87pub async fn add_report(&self, thread_koid: zx::Koid, report: ComponentCrashInfo) {
88self.records.lock().await.push(Record {
89 deadline: zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
90 CLEANUP_DEADLINE_SECONDS,
91 )),
92 koid: thread_koid.clone(),
93 crash_info: report,
94 });
95 }
9697/// Removes the report (if any), and deletes it.
98pub async fn take_report(&self, thread_koid: &zx::Koid) -> Option<ComponentCrashInfo> {
99let mut records_guard = self.records.lock().await;
100let index_to_remove = records_guard
101 .iter()
102 .enumerate()
103 .find(|(_, record)| &record.koid == thread_koid)
104 .map(|(i, _)| i);
105if index_to_remove.is_none() {
106warn!("crash introspection failed to provide attribution for the crashed thread with koid {:?}", thread_koid);
107 }
108 index_to_remove.map(|i| records_guard.remove(i).crash_info)
109 }
110}