| 2428 | } |
| 2429 | |
| 2430 | bool cbm_daemon_runtime_service_free(cbm_daemon_runtime_service_t *service) { |
| 2431 | if (!service || |
| 2432 | cbm_daemon_runtime_service_state(service) != CBM_DAEMON_RUNTIME_SERVICE_EXITED) { |
| 2433 | return false; |
| 2434 | } |
| 2435 | if (service->accept_thread_started && !service->accept_thread_joined) { |
| 2436 | if (cbm_thread_join(&service->accept_thread) != 0) { |
| 2437 | return false; |
| 2438 | } |
| 2439 | service->accept_thread_joined = true; |
| 2440 | } |
| 2441 | for (size_t i = 0; i < service->worker_capacity; i++) { |
| 2442 | cbm_daemon_runtime_worker_t *worker = &service->workers[i]; |
| 2443 | if (worker->thread_started) { |
| 2444 | if (!atomic_load_explicit(&worker->done, memory_order_acquire) || |
| 2445 | cbm_thread_join(&worker->thread) != 0) { |
| 2446 | return false; |
| 2447 | } |
| 2448 | runtime_worker_reset_after_join(worker); |
| 2449 | } |
| 2450 | } |
| 2451 | cbm_daemon_ipc_listener_close(service->listener); |
| 2452 | service->listener = NULL; |
| 2453 | if (!runtime_participant_guard_release_complete(&service->owned_participant_guard, |
| 2454 | service->shutdown_timeout_ms)) { |
| 2455 | /* Preserve the service allocation so the caller can retry or force |
| 2456 | * process exit without dropping the only live cleanup handle. */ |
| 2457 | return false; |
| 2458 | } |
| 2459 | cbm_daemon_coordinator_free(service->coordinator); |
| 2460 | (void)runtime_process_image_reference_release(&service->active_image); |
| 2461 | free(service->conflict_log_path); |
| 2462 | for (size_t i = 0; i < service->worker_mutexes_initialized; i++) { |
| 2463 | cbm_mutex_destroy(&service->workers[i].send_mutex); |
| 2464 | } |
| 2465 | free(service->workers); |
| 2466 | cbm_mutex_destroy(&service->mutex); |
| 2467 | free(service); |
| 2468 | return true; |
| 2469 | } |
| 2470 | |
| 2471 | bool cbm_daemon_runtime_request_activation_shutdown( |
| 2472 | const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_build_identity_t *identity, |