/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/devtools/actors/timeline.rs
361 строка
11 KB
eri
devtools: Avoid panics when borrowing in `ActorRegistry` (#45248)
04 июн 2026, 13:57
Не верифицирован
04 июн 2026, 13:57
84fa008
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![expect(dead_code)] use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use atomic_refcell::AtomicRefCell; use devtools_traits::DevtoolScriptControlMsg::{DropTimelineMarkers, SetTimelineMarkers}; use devtools_traits::{DevtoolScriptControlMsg, TimelineMarker, TimelineMarkerType}; use malloc_size_of_derive::MallocSizeOf; use serde::{Serialize, Serializer}; use serde_json::{Map, Value}; use servo_base::cross_process_instant::CrossProcessInstant; use servo_base::generic_channel::{self, GenericReceiver, GenericSender}; use servo_base::id::PipelineId; use crate::StreamId; use crate::actor::{Actor, ActorError, ActorRegistry}; use crate::actors::framerate::FramerateActor; use crate::actors::memory::{MemoryActor, TimelineMemoryReply}; use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream}; #[derive(MallocSizeOf)] pub(crate) struct TimelineActor { name: String, script_sender: GenericSender<DevtoolScriptControlMsg>, marker_types: Vec<TimelineMarkerType>, pipeline_id: PipelineId, #[conditional_malloc_size_of] is_recording: Arc<Mutex<bool>>, framerate_actor: AtomicRefCell<Option<String>>, memory_actor: AtomicRefCell<Option<String>>, #[conditional_malloc_size_of] registry: Arc<Mutex<ActorRegistry>>, start_stamp: CrossProcessInstant, } struct Emitter { from: String, stream: DevtoolsConnection, registry: Arc<Mutex<ActorRegistry>>, start_stamp: CrossProcessInstant, framerate_actor: Option<String>, memory_actor: Option<String>, } #[derive(Serialize)] struct IsRecordingReply { from: String, value: bool, } #[derive(Serialize)] struct StartReply { from: String, value: HighResolutionStamp, } #[derive(Serialize)] struct StopReply { from: String, value: HighResolutionStamp, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct TimelineMarkerReply { name: String, start: HighResolutionStamp, end: HighResolutionStamp, stack: Option<Vec<()>>, end_stack: Option<Vec<()>>, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct MarkersEmitterReply { #[serde(rename = "type")] type_: String, markers: Vec<TimelineMarkerReply>, from: String, end_time: HighResolutionStamp, } #[derive(Serialize)] struct MemoryEmitterReply { #[serde(rename = "type")] type_: String, from: String, delta: HighResolutionStamp, measurement: TimelineMemoryReply, } #[derive(Serialize)] struct FramerateEmitterReply { #[serde(rename = "type")] type_: String, from: String, delta: HighResolutionStamp, timestamps: Vec<HighResolutionStamp>, } /// HighResolutionStamp is struct that contains duration in milliseconds /// with accuracy to microsecond that shows how much time has passed since /// actor registry inited /// analog <https://w3c.github.io/hr-time/#sec-DOMHighResTimeStamp> #[derive(MallocSizeOf)] pub(crate) struct HighResolutionStamp(f64); impl HighResolutionStamp { pub fn new(start_stamp: CrossProcessInstant, time: CrossProcessInstant) -> HighResolutionStamp { let duration = (time - start_stamp).whole_microseconds(); HighResolutionStamp(duration as f64 / 1000_f64) } pub fn wrap(time: f64) -> HighResolutionStamp { HighResolutionStamp(time) } } impl Serialize for HighResolutionStamp { fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { self.0.serialize(s) } } static DEFAULT_TIMELINE_DATA_PULL_TIMEOUT: u64 = 200; // ms impl TimelineActor { pub fn new( name: String, pipeline_id: PipelineId, script_sender: GenericSender<DevtoolScriptControlMsg>, registry: Arc<Mutex<ActorRegistry>>, ) -> TimelineActor { let marker_types = vec![TimelineMarkerType::Reflow, TimelineMarkerType::DOMEvent]; TimelineActor { name, pipeline_id, marker_types, script_sender, is_recording: Arc::new(Mutex::new(false)), framerate_actor: AtomicRefCell::new(None), memory_actor: AtomicRefCell::new(None), start_stamp: CrossProcessInstant::now(), registry, } } fn pull_timeline_data( &self, receiver: GenericReceiver<Option<TimelineMarker>>, mut emitter: Emitter, ) { let is_recording = self.is_recording.clone(); if !*is_recording.lock().unwrap() { return; } thread::Builder::new() .name("PullTimelineData".to_owned()) .spawn(move || { loop { if !*is_recording.lock().unwrap() { break; } let mut markers = vec![]; while let Ok(Some(marker)) = receiver.try_recv() { markers.push(emitter.marker(marker)); } if emitter.send(markers).is_err() { break; } thread::sleep(Duration::from_millis(DEFAULT_TIMELINE_DATA_PULL_TIMEOUT)); } }) .expect("Thread spawning failed"); } } impl Actor for TimelineActor { fn name(&self) -> &str { &self.name } fn handle_message( &self, request: ClientRequest, registry: &ActorRegistry, msg_type: &str, msg: &Map<String, Value>, _id: StreamId, ) -> Result<(), ActorError> { match msg_type { "start" => { **self.is_recording.lock().as_mut().unwrap() = true; let (tx, rx) = generic_channel::channel::<Option<TimelineMarker>>().unwrap(); self.script_sender .send(SetTimelineMarkers( self.pipeline_id, self.marker_types.clone(), tx, )) .unwrap(); // init memory actor if let Some(with_memory) = msg.get("withMemory") && let Some(true) = with_memory.as_bool() { *self.memory_actor.borrow_mut() = Some(MemoryActor::create(registry)); } // init framerate actor if let Some(with_ticks) = msg.get("withTicks") && let Some(true) = with_ticks.as_bool() { let framerate_actor = Some(FramerateActor::create( registry, self.pipeline_id, self.script_sender.clone(), )); *self.framerate_actor.borrow_mut() = framerate_actor; } let emitter = Emitter::new( self.name().into(), self.registry.clone(), self.start_stamp, request.stream(), self.memory_actor.borrow().clone(), self.framerate_actor.borrow().clone(), ); self.pull_timeline_data(rx, emitter); let msg = StartReply { from: self.name().into(), value: HighResolutionStamp::new(self.start_stamp, CrossProcessInstant::now()), }; request.reply_final(&msg)? }, "stop" => { let msg = StopReply { from: self.name().into(), value: HighResolutionStamp::new(self.start_stamp, CrossProcessInstant::now()), }; self.script_sender .send(DropTimelineMarkers( self.pipeline_id, self.marker_types.clone(), )) .unwrap(); // TODO: move this to the cleanup method. if let Some(ref actor_name) = *self.framerate_actor.borrow() { registry.remove(actor_name.clone()); } if let Some(ref actor_name) = *self.memory_actor.borrow() { registry.remove(actor_name.clone()); } **self.is_recording.lock().as_mut().unwrap() = false; request.reply_final(&msg)? }, "isRecording" => { let msg = IsRecordingReply { from: self.name().into(), value: *self.is_recording.lock().unwrap(), }; request.reply_final(&msg)? }, _ => return Err(ActorError::UnrecognizedPacketType), }; Ok(()) } } impl Emitter { pub fn new( name: String, registry: Arc<Mutex<ActorRegistry>>, start_stamp: CrossProcessInstant, stream: DevtoolsConnection, memory_actor_name: Option<String>, framerate_actor_name: Option<String>, ) -> Emitter { Emitter { from: name, stream, registry, start_stamp, framerate_actor: framerate_actor_name, memory_actor: memory_actor_name, } } fn marker(&self, payload: TimelineMarker) -> TimelineMarkerReply { TimelineMarkerReply { name: payload.name, start: HighResolutionStamp::new(self.start_stamp, payload.start_time), end: HighResolutionStamp::new(self.start_stamp, payload.end_time), stack: payload.start_stack, end_stack: payload.end_stack, } } fn send(&mut self, markers: Vec<TimelineMarkerReply>) -> Result<(), ActorError> { let end_time = CrossProcessInstant::now(); let reply = MarkersEmitterReply { type_: "markers".to_owned(), markers, from: self.from.clone(), end_time: HighResolutionStamp::new(self.start_stamp, end_time), }; self.stream.write_json_packet(&reply)?; if let Some(ref actor_name) = self.framerate_actor { let mut lock = self.registry.lock(); let registry = lock.as_mut().unwrap(); let framerate_actor = registry.find::<FramerateActor>(actor_name); let framerate_reply = FramerateEmitterReply { type_: "framerate".to_owned(), from: framerate_actor.name().into(), delta: HighResolutionStamp::new(self.start_stamp, end_time), timestamps: framerate_actor.take_pending_ticks(), }; self.stream.write_json_packet(&framerate_reply)?; } if let Some(ref actor_name) = self.memory_actor { let registry = self.registry.lock().unwrap(); let memory_actor = registry.find::<MemoryActor>(actor_name); let memory_reply = MemoryEmitterReply { type_: "memory".to_owned(), from: memory_actor.name().into(), delta: HighResolutionStamp::new(self.start_stamp, end_time), measurement: memory_actor.measure(), }; self.stream.write_json_packet(&memory_reply)?; } Ok(()) } }