Lines 89.36% 42 / 47
Methods 66.66% 4 / 6
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 createBroker 100.00% 5 / 5 100.00% 1 / 1 1
 buildSyntheticServicesFor 100.00% 17 / 17 100.00% 1 / 1 1
 buildNamedArgumentsFor 100.00% 3 / 3 100.00% 1 / 1 1
 warnAboutSlowInitializeCalls 42.85% 3 / 7 0.00% 0 / 1 4.68
 warnAboutReadyTimeoutBelowInitialize 92.85% 13 / 14 0.00% 0 / 1 4.01
26final readonly class BrokerKernel
27{
28    public function __construct(
29        private StewartVersion $stewartVersion,
30        private string $userServicesFile,
31        private ProjectRoot $projectRoot,
32        private SyntheticServices $overrides = new SyntheticServices(),
33        private ProfileCompiler $compiler = new ProfileCompiler(),
34    ) {}
35
36    /** @throws ConfigurationException */
37    public function createBroker(StewartConfig $config, AppCatalog $apps, LoggerInterface $logger, ?string $configFileOption = null): BrokerLifecycle
38    {
39        $this->warnAboutSlowInitializeCalls($config, $logger);
40        $this->warnAboutReadyTimeoutBelowInitialize($config, $logger);
41
42        return $this->compiler
43            ->compileProfileContainer(ContainerProfile::Broker, $this->buildSyntheticServicesFor($config, $apps, $logger)->withOverrides($this->overrides), $this->buildNamedArgumentsFor($configFileOption))
44            ->resolveService(BrokerLifecycle::class);
45    }
46
47    /** @throws ConfigurationException */
48    private function buildSyntheticServicesFor(StewartConfig $config, AppCatalog $apps, LoggerInterface $logger): SyntheticServices
49    {
50        return new SyntheticServices()
51            ->withService(StewartConfig::class, $config)
52            ->withService(StewartVersion::class, $this->stewartVersion)
53            ->withService(AppCatalog::class, $apps)
54            ->withService(AppDefinitionCollection::class, $apps->enabled)
55            ->withService(LoggerInterface::class, $logger)
56            ->withService(ConnectionConfig::class, $config->requireHomeAssistant())
57            ->withService(SupervisionConfig::class, $config->supervision)
58            ->withService(ServiceCallPolicy::class, $config->serviceCalls)
59            ->withService(ControlConfig::class, $config->control)
60            ->withService(HttpConfig::class, $config->http)
61            ->withService(PersistenceConfig::class, $config->persistence)
62            ->withService(ProjectRoot::class, $this->projectRoot)
63            ->withService(CodegenConfig::class, $config->codegen)
64            ->withService(GitDeployConfig::class, $config->gitDeploy)
65            ->withService(ExposeConfig::class, $config->expose)
66            ->withService(OutboxLimits::class, new OutboxLimits($config->workerEventBuffer, $config->workerStateBatch));
67    }
68
69    private function buildNamedArgumentsFor(?string $configFileOption): NamedArguments
70    {
71        return new NamedArguments()
72            ->withArgument('userServicesFile', $this->userServicesFile)
73            ->withArgument('releaseCheckConfigFile', $configFileOption);
74    }
75
76    /** @throws ConfigurationException */
77    private function warnAboutSlowInitializeCalls(StewartConfig $config, LoggerInterface $logger): void
78    {
79        $initializeTimeout = $config->supervision->initializeTimeout->findDuration();
80        $commandTimeout = $config->requireHomeAssistant()->commandTimeout;
81
82        if ($initializeTimeout !== null && $commandTimeout->isLongerThan($initializeTimeout)) {
83            $logger->warning('An app initializing with a single slow service call will be dropped before that call times out', [
84                'initialize_timeout' => (string) $initializeTimeout,
85                'command_timeout' => (string) $commandTimeout,
86            ]);
87        }
88    }
89
90    private function warnAboutReadyTimeoutBelowInitialize(StewartConfig $config, LoggerInterface $logger): void
91    {
92        $readyTimeout = $config->supervision->readyTimeout->findDuration();
93        $initializeTimeout = $config->supervision->initializeTimeout->findDuration();
94
95        if ($readyTimeout === null) {
96            return;
97        }
98
99        if ($initializeTimeout === null) {
100            $logger->warning('A hanging initialize() restarts its whole worker, because initialize_timeout is off while ready_timeout is on', [
101                'ready_timeout' => (string) $readyTimeout,
102            ]);
103
104            return;
105        }
106
107        if (!$readyTimeout->isLongerThan($initializeTimeout)) {
108            $logger->warning('A worker is restarted before an app that uses its whole initialize_timeout can report ready', [
109                'initialize_timeout' => (string) $initializeTimeout,
110                'ready_timeout' => (string) $readyTimeout,
111            ]);
112        }
113    }
114}