[PR #6882] [management] fix ephemeral peers never deleted due to cleanup context is cancelled #29181

Open
opened 2026-08-05 08:07:36 -04:00 by saavagebueno · 0 comments
Owner

Original Pull Request: https://github.com/netbirdio/netbird/pull/6882

State: open
Merged: No


Describe your changes

Ephemeral peers were not being deleted after their inactivity lifetime and accumulated in the store until the management server restarted.

Root cause: the ephemeral cleanup timer is armed with time.AfterFunc(delay, func(){ e.cleanup(ctx) }) in OnPeerDisconnected and LoadInitialPeers, capturing whatever context was passed in. On the peer registration path (AddPeer -> TrackEphemeralPeer) and the Sync setup-error path (cancelPeerRoutinesWithoutLock), that context is the per-request gRPC context, which is cancelled as soon as the request returns. When the timer fires ~10 minutes later, cleanup runs DeletePeers under the dead context, every store.ExecuteInTransaction(ctx, ...) fails with context.Canceled, the per-peer error is logged and swallowed (DeletePeers returns nil), and the peers are never removed. Observed in production on v0.71.2 as repeated manager.go: DeletePeers: failed to delete peer <id>: context canceled and peers with a "last seen" far exceeding the 10 minute ephemeral lifetime.

The defect dates back to v0.28.4 (commit 765aba2c, "Add context to throughout the project", #2209), which first threaded a caller-supplied context into the timer closures and into DeletePeer. Before that the timer used e.cleanup with no context, so it could not occur.

Fix: derive the timer's context with context.WithoutCancel at both arming sites, so cleanup survives the arming request being torn down while still carrying its log/trace values.

Tests: TestCleanupDeletesPeerAfterArmingContextCancelled drives the real OnPeerDisconnected -> timer -> cleanup path with a DeletePeers fake that honors context cancellation (mirroring managerImpl.DeletePeers), and fails without the fix. The existing tests missed this because their mocked DeletePeers deletes from a map unconditionally, ignoring the context. TestCleanupDeletesPeerWithDetachedContext is the passing control that isolates the cause to the context lifetime.

N/A (happy to open an issue if preferred).

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Documentation already states ephemeral peers are removed after inactivity; this restores that documented behavior rather than changing it.

Docs PR URL (required if "docs added" is checked)

n/a


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Improved cleanup reliability for ephemeral peers when the original request context is canceled.
    • Ensured scheduled cleanup continues after a peer disconnects, allowing expired peers to be removed as expected.
  • Tests

    • Added coverage for cleanup behavior with canceled and detached contexts.
**Original Pull Request:** https://github.com/netbirdio/netbird/pull/6882 **State:** open **Merged:** No --- ## Describe your changes Ephemeral peers were not being deleted after their inactivity lifetime and accumulated in the store until the management server restarted. Root cause: the ephemeral cleanup timer is armed with `time.AfterFunc(delay, func(){ e.cleanup(ctx) })` in `OnPeerDisconnected` and `LoadInitialPeers`, capturing whatever context was passed in. On the peer registration path (`AddPeer` -> `TrackEphemeralPeer`) and the Sync setup-error path (`cancelPeerRoutinesWithoutLock`), that context is the per-request gRPC context, which is cancelled as soon as the request returns. When the timer fires ~10 minutes later, `cleanup` runs `DeletePeers` under the dead context, every `store.ExecuteInTransaction(ctx, ...)` fails with `context.Canceled`, the per-peer error is logged and swallowed (`DeletePeers` returns nil), and the peers are never removed. Observed in production on v0.71.2 as repeated `manager.go: DeletePeers: failed to delete peer <id>: context canceled` and peers with a "last seen" far exceeding the 10 minute ephemeral lifetime. The defect dates back to v0.28.4 (commit 765aba2c, "Add context to throughout the project", #2209), which first threaded a caller-supplied context into the timer closures and into `DeletePeer`. Before that the timer used `e.cleanup` with no context, so it could not occur. Fix: derive the timer's context with `context.WithoutCancel` at both arming sites, so cleanup survives the arming request being torn down while still carrying its log/trace values. Tests: `TestCleanupDeletesPeerAfterArmingContextCancelled` drives the real `OnPeerDisconnected` -> timer -> `cleanup` path with a `DeletePeers` fake that honors context cancellation (mirroring `managerImpl.DeletePeers`), and fails without the fix. The existing tests missed this because their mocked `DeletePeers` deletes from a map unconditionally, ignoring the context. `TestCleanupDeletesPeerWithDetachedContext` is the passing control that isolates the cause to the context lifetime. ## Issue ticket number and link N/A (happy to open an issue if preferred). ## Stack <!-- branch-stack --> ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [x] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) Documentation already states ephemeral peers are removed after inactivity; this restores that documented behavior rather than changing it. ### Docs PR URL (required if "docs added" is checked) n/a <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/netbirdio/codesmith/netbird/pr/6882"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787480035&installation_model_id=427504&pr_number=6882&repository=netbirdio%2Fnetbird&return_to=https%3A%2F%2Fgithub.com%2Fnetbirdio%2Fnetbird%2Fpull%2F6882&signature=e122d42c3f0d610c3d898d8ab02debaac8a8bcc188e87ca03c7a143fb6f70541"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with [code]smith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved cleanup reliability for ephemeral peers when the original request context is canceled. * Ensured scheduled cleanup continues after a peer disconnects, allowing expired peers to be removed as expected. * **Tests** * Added coverage for cleanup behavior with canceled and detached contexts. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
saavagebueno added the pull-request label 2026-08-05 08:07:36 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#29181