| 1555 | } |
| 1556 | |
| 1557 | static void runtime_worker_handle_stop(cbm_daemon_runtime_worker_t *worker, const uint8_t *payload, |
| 1558 | uint32_t length) { |
| 1559 | cbm_daemon_runtime_service_t *service = worker->service; |
| 1560 | char requested_build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE] = {0}; |
| 1561 | bool peer_verified = |
| 1562 | runtime_control_request_decode(payload, length, requested_build) && |
| 1563 | runtime_activation_peer_matches_claim(service, worker->peer_process_id, requested_build); |
| 1564 | uint8_t response[CBM_DAEMON_STOP_RESPONSE_SIZE]; |
| 1565 | memset(response, 0, sizeof(response)); |
| 1566 | bool accepted = false; |
| 1567 | if (peer_verified) { |
| 1568 | uint8_t count = 0U; |
| 1569 | uint32_t pids[CBM_DAEMON_CONTROL_CLIENT_CAP] = {0}; |
| 1570 | uint64_t deadline = runtime_deadline_after(service->shutdown_timeout_ms); |
| 1571 | cbm_mutex_lock(&service->mutex); |
| 1572 | response[0] = 1U; |
| 1573 | uint16_t committed = service->committed_clients > UINT16_MAX |
| 1574 | ? UINT16_MAX |
| 1575 | : (uint16_t)service->committed_clients; |
| 1576 | runtime_control_collect_clients_locked(service, worker, &count, pids); |
| 1577 | if (service->committed_clients > 0) { |
| 1578 | /* Refuse-if-busy: the caller reports these pids to the user as |
| 1579 | * the processes that must exit before a graceful stop. */ |
| 1580 | response[1] = 0x02U; |
| 1581 | } else if (service->state == CBM_DAEMON_RUNTIME_SERVICE_RUNNING) { |
| 1582 | /* Explicit stop is the sanctioned exit for PERMANENT generations |
| 1583 | * as well — begin_stopping here overrides the permanence gate. |
| 1584 | * Mirror the activation-shutdown ACK protocol: the stopping |
| 1585 | * teardown must not consume this requester's response slot before |
| 1586 | * the acceptance byte leaves. */ |
| 1587 | runtime_service_begin_stopping_locked(service, deadline, false, |
| 1588 | "daemon_stop_requested"); |
| 1589 | /* Same exit criterion as an activation drain: a permanent |
| 1590 | * coordinator never reports client-terminal, so resource |
| 1591 | * emptiness must be allowed to complete this stop promptly. */ |
| 1592 | service->activation_shutdown_requested = true; |
| 1593 | service->activation_response_inflight = true; |
| 1594 | worker->final_response_inflight = true; |
| 1595 | response[1] = 0x01U; |
| 1596 | accepted = true; |
| 1597 | } |
| 1598 | response[2] = (uint8_t)(committed >> 8); |
| 1599 | response[3] = (uint8_t)(committed & 0xFFU); |
| 1600 | response[4] = count; |
| 1601 | for (size_t index = 0; index < CBM_DAEMON_CONTROL_CLIENT_CAP; index++) { |
| 1602 | size_t offset = 8U + index * 4U; |
| 1603 | response[offset] = (uint8_t)(pids[index] >> 24); |
| 1604 | response[offset + 1U] = (uint8_t)(pids[index] >> 16); |
| 1605 | response[offset + 2U] = (uint8_t)(pids[index] >> 8); |
| 1606 | response[offset + 3U] = (uint8_t)(pids[index] & 0xFFU); |
| 1607 | } |
| 1608 | cbm_mutex_unlock(&service->mutex); |
| 1609 | } |
| 1610 | if (accepted) { |
| 1611 | char requester_pid[32]; |
| 1612 | (void)snprintf(requester_pid, sizeof(requester_pid), "%llu", |
| 1613 | (unsigned long long)worker->peer_process_id); |
| 1614 | cbm_log_info("daemon.stop_requested", "requester_pid", requester_pid, "requester_build", |
no test coverage detected