| 1524 | } |
| 1525 | |
| 1526 | static void runtime_worker_handle_stop(cbm_daemon_runtime_worker_t *worker, const uint8_t *payload, |
| 1527 | uint32_t length) { |
| 1528 | cbm_daemon_runtime_service_t *service = worker->service; |
| 1529 | char requested_build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE] = {0}; |
| 1530 | bool peer_verified = |
| 1531 | runtime_control_request_decode(payload, length, requested_build) && |
| 1532 | runtime_activation_peer_matches_claim(service, worker->peer_process_id, requested_build); |
| 1533 | uint8_t response[CBM_DAEMON_STOP_RESPONSE_SIZE]; |
| 1534 | memset(response, 0, sizeof(response)); |
| 1535 | bool accepted = false; |
| 1536 | if (peer_verified) { |
| 1537 | uint8_t count = 0U; |
| 1538 | uint32_t pids[CBM_DAEMON_CONTROL_CLIENT_CAP] = {0}; |
| 1539 | uint64_t deadline = runtime_deadline_after(service->shutdown_timeout_ms); |
| 1540 | cbm_mutex_lock(&service->mutex); |
| 1541 | response[0] = 1U; |
| 1542 | uint16_t committed = service->committed_clients > UINT16_MAX |
| 1543 | ? UINT16_MAX |
| 1544 | : (uint16_t)service->committed_clients; |
| 1545 | runtime_control_collect_clients_locked(service, worker, &count, pids); |
| 1546 | if (service->committed_clients > 0) { |
| 1547 | /* Refuse-if-busy: the caller reports these pids to the user as |
| 1548 | * the processes that must exit before a graceful stop. */ |
| 1549 | response[1] = 0x02U; |
| 1550 | } else if (service->state == CBM_DAEMON_RUNTIME_SERVICE_RUNNING) { |
| 1551 | /* Explicit stop is the sanctioned exit for PERMANENT generations |
| 1552 | * as well — begin_stopping here overrides the permanence gate. |
| 1553 | * Mirror the activation-shutdown ACK protocol: the stopping |
| 1554 | * teardown must not consume this requester's response slot before |
| 1555 | * the acceptance byte leaves. */ |
| 1556 | runtime_service_begin_stopping_locked(service, deadline, false, |
| 1557 | "daemon_stop_requested"); |
| 1558 | /* Same exit criterion as an activation drain: a permanent |
| 1559 | * coordinator never reports client-terminal, so resource |
| 1560 | * emptiness must be allowed to complete this stop promptly. */ |
| 1561 | service->activation_shutdown_requested = true; |
| 1562 | service->activation_response_inflight = true; |
| 1563 | worker->final_response_inflight = true; |
| 1564 | response[1] = 0x01U; |
| 1565 | accepted = true; |
| 1566 | } |
| 1567 | response[2] = (uint8_t)(committed >> 8); |
| 1568 | response[3] = (uint8_t)(committed & 0xFFU); |
| 1569 | response[4] = count; |
| 1570 | for (size_t index = 0; index < CBM_DAEMON_CONTROL_CLIENT_CAP; index++) { |
| 1571 | size_t offset = 8U + index * 4U; |
| 1572 | response[offset] = (uint8_t)(pids[index] >> 24); |
| 1573 | response[offset + 1U] = (uint8_t)(pids[index] >> 16); |
| 1574 | response[offset + 2U] = (uint8_t)(pids[index] >> 8); |
| 1575 | response[offset + 3U] = (uint8_t)(pids[index] & 0xFFU); |
| 1576 | } |
| 1577 | cbm_mutex_unlock(&service->mutex); |
| 1578 | } |
| 1579 | if (accepted) { |
| 1580 | char requester_pid[32]; |
| 1581 | (void)snprintf(requester_pid, sizeof(requester_pid), "%llu", |
| 1582 | (unsigned long long)worker->peer_process_id); |
| 1583 | cbm_log_info("daemon.stop_requested", "requester_pid", requester_pid, "requester_build", |
no test coverage detected