1use crate::{StarnixKernel, generate_kernel_name};
6use anyhow::Error;
7use fidl::endpoints::{Proxy, ServerEnd};
8use fidl_fuchsia_component as fcomponent;
9use fidl_fuchsia_component_runner as frunner;
10use fidl_fuchsia_power_system as fpower;
11use frunner::{ComponentControllerMarker, ComponentStartInfo};
12use fuchsia_async as fasync;
13use fuchsia_component::client::connect_to_protocol;
14use futures::FutureExt;
15use futures::channel::oneshot;
16use starnix_sync::{
17 ActivityGovernorProxyLock, KernelsLock, LockDepMutex, StarnixKernelWakeLeaseLock,
18};
19use std::collections::HashMap;
20use std::sync::Arc;
21use vfs::execution_scope::ExecutionScope;
22use zx;
23
24const KERNEL_URL: &str = "starnix_kernel#meta/starnix_kernel.cm";
26
27fn create_lease_name(kernel_name: &str) -> String {
29 format!("starnix-kernel-{}", kernel_name)
30}
31
32type InFlightWakeLease = futures::future::Shared<oneshot::Receiver<()>>;
33
34#[derive(Default)]
35struct WakeLeaseState {
36 generation: u64,
39 in_flight_generation: u64,
42 lease: Option<zx::EventPair>,
44 in_flight: Option<InFlightWakeLease>,
47}
48
49pub(crate) struct KernelWakeLease {
51 lease_name: String,
52 state: LockDepMutex<WakeLeaseState, StarnixKernelWakeLeaseLock>,
53}
54
55impl KernelWakeLease {
56 pub(crate) fn new(kernel_name: &str) -> Self {
57 Self {
58 lease_name: create_lease_name(kernel_name),
59 state: LockDepMutex::new(WakeLeaseState::default()),
60 }
61 }
62
63 fn set_initial_lease(&self, lease: Option<zx::EventPair>) {
64 let replaced = std::mem::replace(&mut self.state.lock().lease, lease);
65 drop(replaced);
66 }
67
68 fn clear_on_stop(&self) {
69 let stale_lease = {
70 let mut state = self.state.lock();
71 state.generation += 1;
72 state.in_flight_generation = state.generation;
73 state.in_flight = None;
74 state.lease.take()
75 };
76 drop(stale_lease);
77 }
78
79 fn current_generation(&self) -> u64 {
80 self.state.lock().generation
81 }
82
83 fn finish_acquire(
86 &self,
87 target_generation: u64,
88 lease: zx::EventPair,
89 ) -> Result<(), zx::EventPair> {
90 let replaced = {
91 let mut state = self.state.lock();
92 if state.generation != target_generation {
93 return Err(lease);
94 }
95 state.lease.replace(lease)
96 };
97 drop(replaced);
98 Ok(())
99 }
100
101 fn clear_in_flight_if_current(&self, target_generation: u64) {
102 let mut state = self.state.lock();
103 if state.in_flight_generation == target_generation {
104 state.in_flight = None;
105 }
106 }
107
108 #[cfg(test)]
109 pub(crate) fn has_lease(&self) -> bool {
110 self.state.lock().lease.is_some()
111 }
112}
113
114#[derive(Default)]
115struct ActivityGovernorCache {
116 proxy: LockDepMutex<Option<fpower::ActivityGovernorProxy>, ActivityGovernorProxyLock>,
117}
118
119impl ActivityGovernorCache {
120 fn get(&self) -> Result<(fpower::ActivityGovernorProxy, bool), Error> {
123 let mut guard = self.proxy.lock();
124 if let Some(proxy) = guard.as_ref() {
125 if !proxy.is_closed() {
126 return Ok((proxy.clone(), true));
127 }
128 }
129 let proxy = connect_to_protocol::<fpower::ActivityGovernorMarker>()?;
130 *guard = Some(proxy.clone());
131 Ok((proxy, false))
132 }
133
134 fn evict(&self, failed_proxy: &fpower::ActivityGovernorProxy) {
136 let mut guard = self.proxy.lock();
137 if guard.as_ref().is_some_and(|cached| {
138 cached.is_closed()
139 || cached.as_channel().as_ref().raw_handle()
140 == failed_proxy.as_channel().as_ref().raw_handle()
141 }) {
142 *guard = None;
143 }
144 }
145
146 async fn take_application_activity_lease(
149 &self,
150 activity_governor: fpower::ActivityGovernorProxy,
151 was_cached: bool,
152 lease_name: &str,
153 ) -> Result<Option<zx::EventPair>, Error> {
154 match activity_governor.take_application_activity_lease(lease_name).await {
155 Ok(lease) => Ok(Some(lease)),
156 Err(e) if was_cached && e.is_closed() => {
157 self.evict(&activity_governor);
158 let (activity_governor, _) = self.get()?;
159 match activity_governor.take_application_activity_lease(lease_name).await {
160 Ok(lease) => Ok(Some(lease)),
161 Err(e) => {
162 if e.is_closed() {
163 self.evict(&activity_governor);
164 }
165 log::warn!(
166 "Failed to acquire application activity lease for kernel: {:?}",
167 e
168 );
169 Ok(None)
170 }
171 }
172 }
173 Err(e) => {
174 if e.is_closed() {
175 self.evict(&activity_governor);
176 }
177 log::warn!("Failed to acquire application activity lease for kernel: {:?}", e);
178 Ok(None)
179 }
180 }
181 }
182}
183
184pub struct Kernels {
186 kernels: Arc<LockDepMutex<HashMap<zx::Koid, StarnixKernel>, KernelsLock>>,
187 background_tasks: ExecutionScope,
188 wake_lease_scope: fasync::Scope,
189 activity_governor: Arc<ActivityGovernorCache>,
190}
191
192impl Kernels {
193 pub fn new() -> Self {
195 Self {
196 kernels: Default::default(),
197 background_tasks: ExecutionScope::new(),
198 wake_lease_scope: fasync::Scope::new_with_name("starnix_wake_leases"),
199 activity_governor: Arc::new(ActivityGovernorCache::default()),
200 }
201 }
202
203 #[cfg(test)]
204 pub(crate) fn new_for_test(
205 kernel_job: Arc<zx::Job>,
206 kernel_name: impl Into<String>,
207 activity_governor: fpower::ActivityGovernorProxy,
208 ) -> Result<Self, Error> {
209 let kernels = Self::new();
210 *kernels.activity_governor.proxy.lock() = Some(activity_governor);
211 let (exposed_dir, _server) =
212 fidl::endpoints::create_proxy::<fidl_fuchsia_io::DirectoryMarker>();
213 let job_koid = kernel_job.koid()?;
214 let name = kernel_name.into();
215 let wake_lease = Arc::new(KernelWakeLease::new(&name));
216 kernels.kernels.lock().insert(
217 job_koid,
218 StarnixKernel {
219 name,
220 exposed_dir,
221 component_instance: zx::Event::create(),
222 job: kernel_job,
223 wake_lease,
224 },
225 );
226 Ok(kernels)
227 }
228
229 #[cfg(test)]
230 pub(crate) fn has_wake_lease(&self, container_job: &zx::Job) -> bool {
231 let Ok(job_koid) = container_job.koid() else {
232 return false;
233 };
234 self.kernels.lock().get(&job_koid).is_some_and(|kernel| kernel.wake_lease.has_lease())
235 }
236
237 #[cfg(test)]
238 pub(crate) async fn wait_for_in_flight_wake_lease(&self, container_job: &zx::Job) {
239 let Ok(job_koid) = container_job.koid() else {
240 return;
241 };
242 let in_flight = self
243 .kernels
244 .lock()
245 .get(&job_koid)
246 .and_then(|kernel| kernel.wake_lease.state.lock().in_flight.clone());
247 if let Some(in_flight) = in_flight {
248 let _ = in_flight.await;
249 }
250 }
251
252 pub async fn start(
254 &self,
255 start_info: ComponentStartInfo,
256 controller: ServerEnd<ComponentControllerMarker>,
257 ) -> Result<(), Error> {
258 let realm =
259 connect_to_protocol::<fcomponent::RealmMarker>().expect("Failed to connect to realm.");
260
261 let kernel_name = generate_kernel_name(&start_info)?;
262 let lease_name = create_lease_name(&kernel_name);
263 let wake_lease = match self.activity_governor.get() {
264 Ok((activity_governor, was_cached)) => self
265 .activity_governor
266 .take_application_activity_lease(activity_governor, was_cached, &lease_name)
267 .await
268 .unwrap_or(None),
269 Err(_) => None,
270 };
271
272 if let Ok(boot_control) = connect_to_protocol::<fpower::BootControlMarker>() {
273 log::info!(
274 "Notifying Fuchsia SystemActivityGovernor that Starnix has booted to allow suspend"
275 );
276 let _ = boot_control.set_boot_complete().await;
277 if let Err(e) = boot_control.set_boot_complete().await {
278 log::warn!(
279 "Failed to notify Fuchsia SystemActivityGovernor that Starnix has booted: {:?}",
280 e
281 );
282 }
283 }
284
285 let (kernel, on_stop) =
286 StarnixKernel::create(realm, KERNEL_URL, start_info, controller).await?;
287 let kernel_job = kernel.job.clone();
288 let kernel_koid = kernel.job.koid()?;
289
290 kernel.wake_lease.set_initial_lease(wake_lease);
291 log::info!("Acquired wake lease for {:?}", kernel_job);
292
293 self.kernels.lock().insert(kernel_koid, kernel);
294
295 let kernels = self.kernels.clone();
296 self.background_tasks.spawn(async move {
297 on_stop.await;
298 let removed = kernels.lock().remove(&kernel_koid);
299 if let Some(removed) = removed {
300 removed.wake_lease.clear_on_stop();
301 }
302 });
303
304 Ok(())
305 }
306
307 pub fn all_jobs(&self) -> Vec<Arc<zx::Job>> {
309 self.kernels.lock().iter().map(|(_, k)| Arc::clone(k.job())).collect()
310 }
311
312 pub async fn drop_wake_lease(&self, container_job: &zx::Job) -> Result<(), Error> {
316 fuchsia_trace::instant!(
318 "power",
319 "starnix-runner:drop-application-activity-lease",
320 fuchsia_trace::Scope::Process
321 );
322 let job_koid = container_job.koid()?;
324 let wake_lease_tracker =
325 self.kernels.lock().get(&job_koid).map(|kernel| Arc::clone(&kernel.wake_lease));
326 if let Some(wake_lease_tracker) = wake_lease_tracker {
327 let (active_lease, in_flight) = {
328 let mut state = wake_lease_tracker.state.lock();
329 state.generation += 1;
330 (state.lease.take(), state.in_flight.clone())
331 };
332 drop(active_lease);
333 if let Some(in_flight) = in_flight {
334 let _ = in_flight.await;
335 }
336 log::info!("Dropped wake lease for {:?}", container_job);
337 }
338 Ok(())
339 }
340
341 pub fn start_acquire_wake_lease(&self, container_job: &zx::Job) -> Result<(), Error> {
345 let _ = self.start_acquire_wake_lease_inner(container_job)?;
346 Ok(())
347 }
348
349 pub async fn acquire_wake_lease(&self, container_job: &zx::Job) -> Result<(), Error> {
352 if let Some(in_flight) = self.start_acquire_wake_lease_inner(container_job)? {
353 let _ = in_flight.await;
354 }
355 Ok(())
356 }
357
358 fn start_acquire_wake_lease_inner(
359 &self,
360 container_job: &zx::Job,
361 ) -> Result<Option<InFlightWakeLease>, Error> {
362 fuchsia_trace::duration!("power", "starnix-runner:acquire-application-activity-lease");
364 let job_koid = container_job.koid()?;
366
367 let Some(wake_lease_tracker) =
368 self.kernels.lock().get(&job_koid).map(|kernel| Arc::clone(&kernel.wake_lease))
369 else {
370 return Ok(None);
371 };
372
373 let (mut activity_governor, mut was_cached) = self.activity_governor.get()?;
374 let lease_fut = match activity_governor
377 .take_application_activity_lease(&wake_lease_tracker.lease_name)
378 .check()
379 {
380 Ok(fut) => fut,
381 Err(e) if was_cached && e.is_closed() => {
382 self.activity_governor.evict(&activity_governor);
383 let (reconnected, _) = self.activity_governor.get()?;
384 activity_governor = reconnected;
385 was_cached = false;
386 match activity_governor
387 .take_application_activity_lease(&wake_lease_tracker.lease_name)
388 .check()
389 {
390 Ok(fut) => fut,
391 Err(e) => {
392 if e.is_closed() {
393 self.activity_governor.evict(&activity_governor);
394 }
395 log::warn!(
396 "Failed to acquire application activity lease for kernel: {:?}",
397 e
398 );
399 return Ok(None);
400 }
401 }
402 }
403 Err(e) => {
404 if e.is_closed() {
405 self.activity_governor.evict(&activity_governor);
406 }
407 log::warn!("Failed to acquire application activity lease for kernel: {:?}", e);
408 return Ok(None);
409 }
410 };
411
412 let (done_tx, done_rx) = oneshot::channel::<()>();
413 let in_flight = done_rx.shared();
414 let (target_generation, prev_in_flight) = {
415 let mut state = wake_lease_tracker.state.lock();
416 state.generation += 1;
417 state.in_flight_generation = state.generation;
418 let prev = state.in_flight.replace(in_flight.clone());
419 (state.generation, prev)
420 };
421
422 let activity_governor_cache = Arc::clone(&self.activity_governor);
423 let _join_handle = self.wake_lease_scope.spawn(async move {
424 let _done_guard = scopeguard::guard(done_tx, |tx| {
425 let _ = tx.send(());
426 });
427
428 let maybe_lease = match lease_fut.await {
429 Ok(lease) => Some(lease),
430 Err(e) if was_cached && e.is_closed() => {
431 activity_governor_cache.evict(&activity_governor);
432 if wake_lease_tracker.current_generation() != target_generation {
433 None
434 } else {
435 match activity_governor_cache.get() {
436 Ok((reconnected, _)) => {
437 match reconnected
438 .take_application_activity_lease(&wake_lease_tracker.lease_name)
439 .await
440 {
441 Ok(lease) => Some(lease),
442 Err(e) => {
443 if e.is_closed() {
444 activity_governor_cache.evict(&reconnected);
445 }
446 log::warn!(
447 "Failed to acquire application activity lease for kernel: {:?}",
448 e
449 );
450 None
451 }
452 }
453 }
454 Err(e) => {
455 log::warn!(
456 "Failed to reconnect to ActivityGovernor for kernel wake lease: {:?}",
457 e
458 );
459 None
460 }
461 }
462 }
463 }
464 Err(e) => {
465 if e.is_closed() {
466 activity_governor_cache.evict(&activity_governor);
467 }
468 log::warn!("Failed to acquire application activity lease for kernel: {:?}", e);
469 None
470 }
471 };
472
473 if let Some(wake_lease) = maybe_lease {
474 match wake_lease_tracker.finish_acquire(target_generation, wake_lease) {
475 Ok(()) => {
476 log::info!("Acquired wake lease for job koid {:?}", job_koid);
477 }
478 Err(stale_lease) => {
479 drop(stale_lease);
480 log::info!(
481 "Dropped superseded in-flight wake lease for job koid {:?}",
482 job_koid
483 );
484 }
485 }
486 }
487
488 if let Some(prev) = prev_in_flight {
489 let _ = prev.await;
490 }
491 wake_lease_tracker.clear_in_flight_if_current(target_generation);
492 });
493
494 Ok(Some(in_flight))
495 }
496}
497
498impl Drop for Kernels {
499 fn drop(&mut self) {
500 self.background_tasks.shutdown();
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use futures::StreamExt;
508 use std::pin::pin;
509 use std::task::Poll;
510
511 #[fuchsia::test]
512 async fn acquire_wake_lease_reuses_cached_proxy_and_handles_disconnect() {
513 let child_job = Arc::new(fuchsia_runtime::job_default().create_child_job().unwrap());
514 let (proxy, mut stream) =
515 fidl::endpoints::create_proxy_and_stream::<fpower::ActivityGovernorMarker>();
516 let kernels = Kernels::new_for_test(child_job.clone(), "test-kernel", proxy).unwrap();
517
518 let served_requests = Arc::new(std::sync::atomic::AtomicUsize::new(0));
519 let served_requests_clone = served_requests.clone();
520 let server_task = fuchsia_async::Task::local(async move {
521 for _ in 0..2 {
522 let Some(Ok(fpower::ActivityGovernorRequest::TakeApplicationActivityLease {
523 name,
524 responder,
525 })) = stream.next().await
526 else {
527 panic!("expected TakeApplicationActivityLease request");
528 };
529 assert_eq!(name, "starnix-kernel-test-kernel");
530 served_requests_clone.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
531 let (lease_client, _lease_server) = zx::EventPair::create();
532 responder.send(lease_client).unwrap();
533 }
534 });
536
537 kernels.acquire_wake_lease(&child_job).await.unwrap();
538 assert!(kernels.has_wake_lease(&child_job));
539 kernels.drop_wake_lease(&child_job).await.unwrap();
540 assert!(!kernels.has_wake_lease(&child_job));
541
542 kernels.acquire_wake_lease(&child_job).await.unwrap();
544 assert!(kernels.has_wake_lease(&child_job));
545 assert_eq!(served_requests.load(std::sync::atomic::Ordering::Relaxed), 2);
546 kernels.drop_wake_lease(&child_job).await.unwrap();
547
548 server_task.await;
549
550 kernels.acquire_wake_lease(&child_job).await.unwrap();
553 assert!(!kernels.has_wake_lease(&child_job));
554 }
555
556 #[fuchsia::test]
557 async fn start_acquire_wake_lease_writes_request_immediately_and_stores_on_reply() {
558 let child_job = Arc::new(fuchsia_runtime::job_default().create_child_job().unwrap());
559 let (proxy, mut stream) =
560 fidl::endpoints::create_proxy_and_stream::<fpower::ActivityGovernorMarker>();
561 let kernels = Kernels::new_for_test(child_job.clone(), "test-kernel", proxy).unwrap();
562
563 kernels.start_acquire_wake_lease(&child_job).unwrap();
566 assert!(!kernels.has_wake_lease(&child_job));
567
568 let responder = {
569 let mut next_request = pin!(stream.next());
570 let Poll::Ready(Some(Ok(
571 fpower::ActivityGovernorRequest::TakeApplicationActivityLease { name, responder },
572 ))) = futures::poll!(&mut next_request)
573 else {
574 panic!("expected TakeApplicationActivityLease already queued in channel");
575 };
576 assert_eq!(name, "starnix-kernel-test-kernel");
577 responder
578 };
579 assert!(!kernels.has_wake_lease(&child_job));
580
581 let (lease_client, lease_server) = zx::EventPair::create();
584 responder.send(lease_client).unwrap();
585 kernels.wait_for_in_flight_wake_lease(&child_job).await;
586 assert!(kernels.has_wake_lease(&child_job));
587 assert_eq!(
588 lease_server
589 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
590 .to_result(),
591 Err(zx::Status::TIMED_OUT)
592 );
593
594 kernels.drop_wake_lease(&child_job).await.unwrap();
595 assert!(!kernels.has_wake_lease(&child_job));
596 assert!(
597 lease_server
598 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
599 .to_result()
600 .is_ok()
601 );
602 }
603
604 #[fuchsia::test]
605 async fn drop_wake_lease_waits_for_in_flight_acquire_and_drops_lease() {
606 let child_job = Arc::new(fuchsia_runtime::job_default().create_child_job().unwrap());
607 let (proxy, mut stream) =
608 fidl::endpoints::create_proxy_and_stream::<fpower::ActivityGovernorMarker>();
609 let kernels = Kernels::new_for_test(child_job.clone(), "test-kernel", proxy).unwrap();
610
611 kernels.start_acquire_wake_lease(&child_job).unwrap();
612 let Some(Ok(fpower::ActivityGovernorRequest::TakeApplicationActivityLease {
613 name,
614 responder,
615 })) = stream.next().await
616 else {
617 panic!("expected TakeApplicationActivityLease request");
618 };
619 assert_eq!(name, "starnix-kernel-test-kernel");
620
621 let mut drop_fut = pin!(kernels.drop_wake_lease(&child_job));
625 assert!(matches!(futures::poll!(&mut drop_fut), Poll::Pending));
626
627 let (lease_client, lease_server) = zx::EventPair::create();
628 responder.send(lease_client).unwrap();
629 drop_fut.await.unwrap();
630
631 assert!(!kernels.has_wake_lease(&child_job));
632 assert!(
633 lease_server
634 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
635 .to_result()
636 .is_ok()
637 );
638 }
639
640 #[fuchsia::test]
641 async fn newer_acquire_supersedes_out_of_order_earlier_in_flight_lease() {
642 let child_job = Arc::new(fuchsia_runtime::job_default().create_child_job().unwrap());
643 let (proxy, mut stream) =
644 fidl::endpoints::create_proxy_and_stream::<fpower::ActivityGovernorMarker>();
645 let kernels = Kernels::new_for_test(child_job.clone(), "test-kernel", proxy).unwrap();
646
647 kernels.start_acquire_wake_lease(&child_job).unwrap();
648 kernels.start_acquire_wake_lease(&child_job).unwrap();
649
650 let Some(Ok(fpower::ActivityGovernorRequest::TakeApplicationActivityLease {
651 responder: responder1,
652 ..
653 })) = stream.next().await
654 else {
655 panic!("expected first TakeApplicationActivityLease request");
656 };
657 let Some(Ok(fpower::ActivityGovernorRequest::TakeApplicationActivityLease {
658 responder: responder2,
659 ..
660 })) = stream.next().await
661 else {
662 panic!("expected second TakeApplicationActivityLease request");
663 };
664
665 let (lease2_client, lease2_server) = zx::EventPair::create();
666 responder2.send(lease2_client).unwrap();
667 let (lease1_client, lease1_server) = zx::EventPair::create();
668 responder1.send(lease1_client).unwrap();
669
670 kernels.wait_for_in_flight_wake_lease(&child_job).await;
671 assert!(kernels.has_wake_lease(&child_job));
672 assert!(
674 lease1_server
675 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
676 .to_result()
677 .is_ok()
678 );
679 assert_eq!(
680 lease2_server
681 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
682 .to_result(),
683 Err(zx::Status::TIMED_OUT)
684 );
685 }
686
687 #[fuchsia::test]
688 async fn cancelled_drop_wake_lease_preserves_in_flight_tracking_for_retry() {
689 let child_job = Arc::new(fuchsia_runtime::job_default().create_child_job().unwrap());
690 let (proxy, mut stream) =
691 fidl::endpoints::create_proxy_and_stream::<fpower::ActivityGovernorMarker>();
692 let kernels = Kernels::new_for_test(child_job.clone(), "test-kernel", proxy).unwrap();
693
694 kernels.start_acquire_wake_lease(&child_job).unwrap();
695 let Some(Ok(fpower::ActivityGovernorRequest::TakeApplicationActivityLease {
696 responder,
697 ..
698 })) = stream.next().await
699 else {
700 panic!("expected TakeApplicationActivityLease request");
701 };
702
703 {
705 let mut cancelled_drop = pin!(kernels.drop_wake_lease(&child_job));
706 assert!(matches!(futures::poll!(&mut cancelled_drop), Poll::Pending));
707 }
708
709 let mut retry_drop = pin!(kernels.drop_wake_lease(&child_job));
712 assert!(matches!(futures::poll!(&mut retry_drop), Poll::Pending));
713
714 let (lease_client, lease_server) = zx::EventPair::create();
715 responder.send(lease_client).unwrap();
716 retry_drop.await.unwrap();
717
718 assert!(!kernels.has_wake_lease(&child_job));
719 assert!(
720 lease_server
721 .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
722 .to_result()
723 .is_ok()
724 );
725 let job_koid = child_job.koid().unwrap();
728 assert!(
729 kernels
730 .kernels
731 .lock()
732 .get(&job_koid)
733 .unwrap()
734 .wake_lease
735 .state
736 .lock()
737 .in_flight
738 .is_none()
739 );
740 }
741}