Skip to content

fix(database): keep rrdset indexes allocated while host is archived - #23074

Merged
stelfrag merged 1 commit into
netdata:masterfrom
stelfrag:fix-rrdset-index-archive-lifetime
Jul 9, 2026
Merged

fix(database): keep rrdset indexes allocated while host is archived#23074
stelfrag merged 1 commit into
netdata:masterfrom
stelfrag:fix-rrdset-index-archive-lifetime

Conversation

@stelfrag

@stelfrag stelfrag commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator
Summary

Follow-up to #23056, which guarded the lookup side of the same problem.

The orphan→archived transition destroys the host's rrdset indexes and nulls
the pointers while queries may still be in flight, leaving two windows:

  1. A query that acquired a chart before the archive crashes on release:
    dictionary_destroy() defers destruction (referenced items), but
    rrdset_index_destroy() nulls host->rrdset_root_index anyway, so
    rrdset_acquired_release() dereferences NULL. Every data query holds
    acquired charts for its full duration (query_target.c), so on parents
    archiving hosts under constant query load this eventually fires.
  2. The fix in (fix(database): guard null root index in rrdset_find_and_acquire #23056) guard itself is a TOCTOU: if the archive runs entirely between
    the pointer check and its use, and nothing holds a reference, the dict is
    freed immediately and the lookup calls into freed memory.

Fix: on archive, flush the indexes (name view first, then the id index) but
keep them allocated — lookups return NULL naturally, releases always see a
valid dictionary, and a lookup racing with the archive now races with a
normal locked flush instead of destruction. The indexes are destroyed only in
rrdhost_free_unlinked(), after the host is unlinked and unreachable.

Semantics are unchanged: flush is exactly what dictionary_destroy() already
did internally when deferring, so charts held by queries are still freed when
the last reference drains. Host reactivation (archived→live) now reuses the
surviving index instead of recreating it (rrdhost.c already guards init
with if (!host->rrdset_root_index)).


Summary by cubic

Prevent crashes during host archival by flushing RRD set indexes instead of destroying them. This removes a null-deref and TOCTOU window for in-flight queries and lets reactivated hosts reuse the same indexes.

  • Bug Fixes
    • Introduced rrdset_index_flush() and use it on archive; flushes name, then id index, but keeps them allocated.
    • Destroy indexes only in rrdhost_free_unlinked(), after the host is unreachable.
    • Added a defensive guard in rrdset_find_and_acquire() to avoid accessing a freed index.

Written for commit 7efe32c. Summary will update on new commits.

Review in cubic

…ush()` to ensure safe indexing during host archival

Ensure that chart indexes remain allocated for ongoing queries during archiving by introducing `rrdset_index_flush()` and delaying index destruction to `rrdhost_free_unlinked()`. Prevent potential null dereference by guarding index availability in `rrdset_find_and_acquire()`.
@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@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.

All reported issues were addressed across 3 files

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.
Architecture diagram
sequenceDiagram
    participant Q as Query Consumer
    participant RR as rrdset_find_and_acquire()
    participant H as RRDHOST
    participant IDX as rrdset_index (id + name)
    participant SVC as service.c (archive)
    participant FREE as rrdhost_free_unlinked()

    Note over Q,FREE: Host lifecycle: archived → queries → free

    SVC->>H: archive host (orphan→archived)
    H->>H: rrdhost_cleanup_data_collection_and_health()
    H->>IDX: rrdset_index_flush()
    Note over IDX: Flush name index first, then id index<br/>Indexes remain allocated, host pointers valid
    IDX-->>H: indexes emptied but alive

    par In-flight query before archive
        Q->>RR: find chart by id
        RR->>H: check host->rrdset_root_index
        alt Index allocated (normal)
            RR->>IDX: dictionary_get() (locked)
            IDX-->>RR: chart reference
            RR-->>Q: acquired chart
        else Index NULL (defense-in-depth)
            RR-->>Q: NULL (race with free_unlinked)
        end
    and Query continues during/after archive
        Q->>RR: Release acquired chart
        RR->>IDX: rrdset_acquired_release()
        Note over IDX: dict still valid, decrements refcount<br/>Chart freed when last ref drains
        IDX-->>RR: released OK
    and New query after archive
        Q->>RR: find chart by id
        RR->>IDX: dictionary_get() (locked)
        Note over IDX: Dictionary is empty, flush already ran
        IDX-->>RR: NULL
        RR-->>Q: NULL (chart not found)
    end

    Note over H,FREE: Host eventually unlinked (no new queries can reach it)

    FREE->>H: rrdhost_free_unlinked()
    H->>IDX: rrdset_index_destroy()
    Note over IDX: Now safe to free the dictionaries<br/>No in-flight queries hold acquired charts
    IDX-->>H: indexes freed
    H->>H: free remaining host resources
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/database/rrdhost.c
@stelfrag
stelfrag marked this pull request as ready for review July 9, 2026 20:57
@stelfrag
stelfrag requested a review from thiagoftsm as a code owner July 9, 2026 20:57
Copilot AI review requested due to automatic review settings July 9, 2026 20:57
@stelfrag
stelfrag requested a review from vkalintiris as a code owner July 9, 2026 20:57

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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 3 out of 3 changed files in this pull request and generated no new comments.

@stelfrag
stelfrag merged commit 41c0623 into netdata:master Jul 9, 2026
154 checks passed
@stelfrag
stelfrag deleted the fix-rrdset-index-archive-lifetime branch July 9, 2026 21:56
stelfrag added a commit to stelfrag/netdata that referenced this pull request Jul 12, 2026
…etdata#23074)

fix(database): replace `rrdset_index_destroy()` with `rrdset_index_flush()` to ensure safe indexing during host archival

Ensure that chart indexes remain allocated for ongoing queries during archiving by introducing `rrdset_index_flush()` and delaying index destruction to `rrdhost_free_unlinked()`. Prevent potential null dereference by guarding index availability in `rrdset_find_and_acquire()`.
@stelfrag stelfrag mentioned this pull request Jul 13, 2026
Ferroin pushed a commit that referenced this pull request Jul 15, 2026
…23074)

fix(database): replace `rrdset_index_destroy()` with `rrdset_index_flush()` to ensure safe indexing during host archival

Ensure that chart indexes remain allocated for ongoing queries during archiving by introducing `rrdset_index_flush()` and delaying index destruction to `rrdhost_free_unlinked()`. Prevent potential null dereference by guarding index availability in `rrdset_find_and_acquire()`.
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