| 834 | } |
| 835 | |
| 836 | static bool host_wait_for_lifetime(cbm_daemon_runtime_service_t *service, |
| 837 | atomic_int *stop_requested, host_state_t *host, bool permanent) { |
| 838 | uint64_t initial_deadline = cbm_now_ms() + HOST_INITIAL_CLIENT_TIMEOUT_MS; |
| 839 | uint64_t stopping_deadline = 0; |
| 840 | for (;;) { |
| 841 | cbm_daemon_runtime_service_state_t state = cbm_daemon_runtime_service_state(service); |
| 842 | if (state == CBM_DAEMON_RUNTIME_SERVICE_EXITED) { |
| 843 | cbm_log_info("daemon.lifetime_end", "reason", "runtime_exited"); |
| 844 | return true; |
| 845 | } |
| 846 | if (state == CBM_DAEMON_RUNTIME_SERVICE_STOPPING) { |
| 847 | if (stopping_deadline == 0) { |
| 848 | stopping_deadline = cbm_now_ms() + HOST_RUNTIME_SHUTDOWN_MS; |
| 849 | } |
| 850 | if (cbm_now_ms() >= stopping_deadline) { |
| 851 | return false; |
| 852 | } |
| 853 | (void)cbm_daemon_runtime_service_wait_exited(service, HOST_WAIT_TICK_MS); |
| 854 | continue; |
| 855 | } |
| 856 | /* The nobody-ever-connected window latches on the monotonic admission |
| 857 | * total, not the live client count: a short first session can begin |
| 858 | * and end entirely between two polls of this loop, and a sampled |
| 859 | * count then reads zero forever — stopping a daemon whose client is |
| 860 | * mid-conversation between per-request connections. */ |
| 861 | bool admitted = cbm_daemon_runtime_service_clients_admitted_total(service) > 0; |
| 862 | bool stop = stop_requested && atomic_load(stop_requested); |
| 863 | /* A permanent generation (`daemon start`) skips the nobody-ever- |
| 864 | * connected window — it exists precisely to idle ahead of clients. |
| 865 | * Explicit stop requests (signals) are still honored. */ |
| 866 | if (stop || (!permanent && !admitted && cbm_now_ms() >= initial_deadline)) { |
| 867 | cbm_log_info("daemon.lifetime_end", "reason", |
| 868 | stop ? "stop_requested" : "initial_window_expired"); |
| 869 | return cbm_daemon_runtime_service_stop(service, HOST_RUNTIME_SHUTDOWN_MS); |
| 870 | } |
| 871 | host_http_reconcile_at(host, cbm_now_ms(), false); |
| 872 | (void)cbm_daemon_runtime_service_wait_exited(service, HOST_WAIT_TICK_MS); |
| 873 | } |
| 874 | } |
| 875 | |
| 876 | static bool host_application_shutdown(host_state_t *host) { |
| 877 | if (cbm_daemon_application_shutdown(host->application, HOST_APPLICATION_SHUTDOWN_MS)) { |
no test coverage detected