| 673 | } |
| 674 | |
| 675 | void cbm_diag_stop(void) { |
| 676 | if (!g_diag_started) { |
| 677 | return; |
| 678 | } |
| 679 | atomic_store_explicit(&g_diag_stop, true, memory_order_release); |
| 680 | |
| 681 | uint64_t deadline = cbm_now_ms() + DIAG_STOP_TIMEOUT_MS; |
| 682 | while (!atomic_load_explicit(&g_diag_done, memory_order_acquire) && cbm_now_ms() < deadline) { |
| 683 | diag_sleep_ms(DIAG_WAIT_SLICE_MS); |
| 684 | } |
| 685 | bool completed = atomic_load_explicit(&g_diag_done, memory_order_acquire); |
| 686 | if (completed) { |
| 687 | /* The completion publication is the final access to diagnostics |
| 688 | * state. Detaching avoids turning an already-complete best-effort |
| 689 | * writer into an unbounded native join during daemon teardown. */ |
| 690 | (void)cbm_thread_detach(&g_diag_thread); |
| 691 | diag_cleanup_live_files(); |
| 692 | diag_directory_close(false); |
| 693 | } else { |
| 694 | /* The thread may still hold a native file or directory descriptor. |
| 695 | * Detach it and intentionally retain all static state until process |
| 696 | * exit; closing or reusing those handles would create an fd/handle |
| 697 | * reuse race if the stalled operation later resumes. */ |
| 698 | (void)cbm_thread_detach(&g_diag_thread); |
| 699 | g_diag_abandoned = true; |
| 700 | } |
| 701 | g_diag_started = false; |
| 702 | } |
| 703 | |
| 704 | #ifdef CBM_DIAGNOSTICS_ENABLE_TEST_API |
| 705 |
no test coverage detected