@@ -422,27 +422,16 @@ def on_domain_loaded(self, event) -> None:
422422 # pylint: disable=unused-argument
423423 assert self .template
424424
425- @qubes .events .handler ("domain-start" )
426- async def on_domain_started_dispvm (
427- self ,
428- event ,
429- ** kwargs ,
430- ):
431- # pylint: disable=unused-argument
425+ async def wait_operational_preload (
426+ self , rpc : str , service : str , timeout : int | float
427+ ) -> None :
432428 """
433- When starting a qube, await for basic services to be started on
434- preloaded disposables and interrupts the domain if the qube has not
435- been requested yet.
429+ Await for preloaded disposable to become fully operational.
436430
437- :param str event: Event which was fired.
431+ :param str rpc: Pretty RPC service name.
432+ :param str service: Full command-line.
433+ :param int|float timeout: Fail after timeout is reached.
438434 """
439- if not self .is_preload :
440- return
441- timeout = self .qrexec_timeout
442- # https://github.com/QubesOS/qubes-issues/issues/9964
443- rpc = "qubes.WaitForRunningSystem"
444- path = "/run/qubes-rpc:/usr/local/etc/qubes-rpc:/etc/qubes-rpc"
445- service = '$(PATH="' + path + '" command -v ' + rpc + ")"
446435 try :
447436 self .log .info (
448437 "Preload startup waiting '%s' with '%d' seconds timeout" ,
@@ -457,18 +446,68 @@ async def on_domain_started_dispvm(
457446 ),
458447 timeout = timeout ,
459448 )
449+ self .log .info ("Preload startup completed '%s'" , rpc )
460450 except asyncio .TimeoutError :
451+ if rpc == "qubes.WaitForSession" :
452+ debug_msg = "systemd-analyze --user blame"
453+ else :
454+ debug_msg = "systemd-analyze blame"
461455 raise qubes .exc .QubesException (
462456 "Timed out call to '%s' after '%d' seconds during preload "
463- "startup" % (rpc , timeout )
457+ "startup. To debug, run the following on a new disposable of "
458+ "'%s': %s" % (rpc , timeout , self .template , debug_msg )
464459 )
465460 except (subprocess .CalledProcessError , qubes .exc .QubesException ):
461+ if rpc == "qubes.WaitForSession" :
462+ debug_msg = "systemctl --user --failed"
463+ else :
464+ debug_msg = "systemctl --failed"
466465 raise qubes .exc .QubesException (
467- "Error on call to '%s' during preload startup. To debug, run "
468- "the following on a new disposable of '%s': systemctl "
469- "--failed" % (rpc , self .template )
466+ "Error on call to '%s' during preload startup. To debug, "
467+ "run the following on a new disposable of '%s': %s "
468+ % (rpc , self .template , debug_msg )
470469 )
471470
471+ @qubes .events .handler ("domain-start" )
472+ async def on_domain_started_dispvm (
473+ self ,
474+ event ,
475+ ** kwargs ,
476+ ):
477+ # pylint: disable=unused-argument
478+ """
479+ When starting a qube, await for basic services to be started on
480+ preloaded disposables and interrupts the domain if the qube has not
481+ been requested yet.
482+
483+ :param str event: Event which was fired.
484+ """
485+ if not self .is_preload :
486+ return
487+ timeout = self .qrexec_timeout
488+ # https://github.com/QubesOS/qubes-issues/issues/9964
489+ path = "/run/qubes-rpc:/usr/local/etc/qubes-rpc:/etc/qubes-rpc"
490+ rpcs = ["qubes.WaitForRunningSystem" ]
491+ if self .features .check_with_template (
492+ "supported-feature.late-gui-daemon" , False
493+ ):
494+ rpcs .append ("qubes.WaitForSession" )
495+ try :
496+ async with asyncio .TaskGroup () as task_group :
497+ for rpc in rpcs :
498+ service = '$(PATH="' + path + '" command -v ' + rpc + ")"
499+ task_group .create_task (
500+ self .wait_operational_preload (rpc , service , timeout )
501+ )
502+ except ExceptionGroup as e :
503+ # Show detailed exception in desktop notification.
504+ wanted_ex_group , _ = e .split (qubes .exc .QubesException )
505+ if wanted_ex_group :
506+ messages = [
507+ "\n " + str (exc ) for exc in wanted_ex_group .exceptions
508+ ]
509+ raise qubes .exc .QubesException ("\n " .join (messages ))
510+ raise
472511 if not self .preload_requested :
473512 await self .pause ()
474513 self .log .info ("Preloading finished" )
0 commit comments