Skip to content

Fix shutdown race when restoring alert information from the database - #22448

Merged
stelfrag merged 2 commits into
netdata:masterfrom
stelfrag:fix_host_health_load
May 9, 2026
Merged

Fix shutdown race when restoring alert information from the database#22448
stelfrag merged 2 commits into
netdata:masterfrom
stelfrag:fix_host_health_load

Conversation

@stelfrag

@stelfrag stelfrag commented May 7, 2026

Copy link
Copy Markdown
Collaborator
Summary
  • Fix shutdown race: HEALTH crashed in sqlite pcache1Unpin (fault at 0x30) when sqlite_close_databases() ran while sql_health_alarm_log_load() was still inside sqlite3_step.
    • Bail the row loop on !service_running(SERVICE_HEALTH) so the early service_signal_exit(SERVICE_HEALTH) at shutdown is honored before sqlite is closed.
    • Initialize host->health_log.spinlock before sql_health_alarm_log_load() instead of after — the load already takes that lock.

Summary by cubic

Fixes a shutdown race in the HEALTH service that could crash while restoring alert logs if SQLite closes mid-iteration. Also switches health log access to a write lock during load to avoid concurrent writes.

  • Bug Fixes
    • Exit the row loop early when the service is stopping: service_running(SERVICE_HEALTH) && sqlite3_step_monitored(res) == SQLITE_ROW.
    • Initialize host->health_log.spinlock before sql_health_alarm_log_load(host) and use a write lock (rw_spinlock_write_lock/unlock) during load.

Written for commit 371b50e. Summary will update on new commits.

  Shutdown does not actively wait for HEALTH to leave sqlite3_step:
  service_wait_exit(SERVICE_HEALTH) is bounded to 3s, cancel_main_threads
  only joins threads already marked EXITED, and nd_thread_join_threads
  reaps the exited list rather than blocking on live threads. If HEALTH
  is still iterating SQL_LOAD_HEALTH_LOG when sqlite_close_databases()
  runs, sqlite3_close_v2() tears down the page cache underneath it and
  the next pcache1Unpin faults at offset 0x30.

  Bail the row loop on !service_running(SERVICE_HEALTH), matching the
  existing pattern at sqlite_health.c:567. Also move rw_spinlock_init()
  before sql_health_alarm_log_load() so the lock the load already takes
  is initialized first.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant HEALTH as HEALTH Service
    participant INIT as health_initialize_rrdhost()
    participant LOAD as sql_health_alarm_log_load()
    participant DB as SQLite Database
    participant SPIN as health_log.spinlock

    Note over HEALTH,DB: HEALTH startup flow (current)

    INIT->>SPIN: rw_spinlock_init(&spinlock)
    Note over INIT,SPIN: (CHANGED order: now before load)
    INIT->>LOAD: sql_health_alarm_log_load(host)

    LOAD->>SPIN: rw_spinlock_read_lock()
    LOAD->>DB: sqlite3_step_monitored(res)
    DB-->>LOAD: SQLITE_ROW (row data)

    loop For each row
        LOAD->>HEALTH: service_running(SERVICE_HEALTH)?
        alt HEALTH is still running
            LOAD->>LOAD: Process row, create ALARM_ENTRY
            LOAD->>DB: sqlite3_step_monitored(res) for next row
            DB-->>LOAD: SQLITE_ROW or SQLITE_DONE
        else HEALTH signaled to stop
            LOAD->>LOAD: Exit loop early
            Note over LOAD: (prevents access after DB close)
        end
    end

    LOAD->>SPIN: rw_spinlock_read_unlock()
    LOAD-->>INIT: Return

    Note over HEALTH,DB: HEALTH shutdown sequence (race fixed)

    HEALTH->>HEALTH: service_signal_exit(SERVICE_HEALTH)
    HEALTH->>DB: sqlite_close_databases()
    Note over HEALTH,DB: LOAD loop already exited, no dangling step()
Loading

@stelfrag
stelfrag marked this pull request as ready for review May 7, 2026 19:32
Copilot AI review requested due to automatic review settings May 7, 2026 19:32
@stelfrag
stelfrag marked this pull request as draft May 7, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a shutdown-time race in the HEALTH service where SQLite could be closed while alert log restoration is still iterating rows, and ensures the health log spinlock is initialized before it’s used during load.

Changes:

  • Initialize host->health_log.spinlock before calling sql_health_alarm_log_load() during host health initialization.
  • Stop iterating SQLite result rows in sql_health_alarm_log_load() when SERVICE_HEALTH is no longer running.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/health/health_event_loop.c Moves health log spinlock initialization earlier so the DB restore path can safely take the lock.
src/database/sqlite/sqlite_health.c Adds service_running(SERVICE_HEALTH) to the row-iteration loop condition to exit early during shutdown.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/database/sqlite/sqlite_health.c
@sonarqubecloud

sonarqubecloud Bot commented May 8, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/database/sqlite/sqlite_health.c
@stelfrag
stelfrag marked this pull request as ready for review May 8, 2026 10:43

@thiagoftsm thiagoftsm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues happen during shutdown and runtime. LGTM!

@stelfrag
stelfrag merged commit 5c96c3e into netdata:master May 9, 2026
162 checks passed
@stelfrag
stelfrag deleted the fix_host_health_load branch May 9, 2026 13:46
@stelfrag stelfrag mentioned this pull request Jun 22, 2026
Ferroin pushed a commit that referenced this pull request Jul 15, 2026
…22448)

* fix(health): close shutdown race in sql_health_alarm_log_load

  Shutdown does not actively wait for HEALTH to leave sqlite3_step:
  service_wait_exit(SERVICE_HEALTH) is bounded to 3s, cancel_main_threads
  only joins threads already marked EXITED, and nd_thread_join_threads
  reaps the exited list rather than blocking on live threads. If HEALTH
  is still iterating SQL_LOAD_HEALTH_LOG when sqlite_close_databases()
  runs, sqlite3_close_v2() tears down the page cache underneath it and
  the next pcache1Unpin faults at offset 0x30.

  Bail the row loop on !service_running(SERVICE_HEALTH), matching the
  existing pattern at sqlite_health.c:567. Also move rw_spinlock_init()
  before sql_health_alarm_log_load() so the lock the load already takes
  is initialized first.

* fix(health): change spinlock to write lock for health log access

(cherry picked from commit 5c96c3e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants