Skip to content

otelcol: synchronize Run and Shutdown lifecycle - #14989

Merged
mx-psi merged 4 commits into
open-telemetry:mainfrom
Rajneesh180:fix/sync-run-shutdown-lifecycle-4947
Apr 22, 2026
Merged

otelcol: synchronize Run and Shutdown lifecycle#14989
mx-psi merged 4 commits into
open-telemetry:mainfrom
Rajneesh180:fix/sync-run-shutdown-lifecycle-4947

Conversation

@Rajneesh180

@Rajneesh180 Rajneesh180 commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Right now Shutdown() returns before Run() is done cleaning up, which means callers that treat "Shutdown returned" as "everything is freed" can run into goroutine leaks and use-after-close problems. This is the root of #4947.

The fix adds a done channel that Run() closes via defer when it finishes, and an atomic.Bool so Shutdown() knows whether Run() was called. If it was, Shutdown() blocks on <-col.done until Run() is fully done. This sidesteps the WaitGroup idea from #8811 that deadlocks when Shutdown happens before Run.

I also added a CompareAndSwap guard on the Run() entry so calling it twice returns an error instead of panicking on the double-close of the done channel, and the early-return path (when Shutdown was called before Run) now properly shuts down the config provider so we don't leak confmap resources.

Tests cover shutdown-before-run, shutdown-during-run, double-run, and the blocking guarantee.

Fixes #4947

Copilot AI review requested due to automatic review settings March 23, 2026 19:32
@Rajneesh180
Rajneesh180 requested a review from a team as a code owner March 23, 2026 19:32
@Rajneesh180
Rajneesh180 requested a review from axw March 23, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the otelcol.Collector lifecycle so Shutdown() blocks until Run() has fully completed cleanup, aligning behavior with the expectations in #4947 and avoiding callers racing on resource teardown.

Changes:

  • Add done signaling and a runStarted flag to synchronize Run() completion with Shutdown().
  • Update/extend otelcol tests to account for the new blocking Shutdown() behavior and add coverage for the blocking guarantee.
  • Add a changelog entry describing the lifecycle semantics change.

Reviewed changes

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

File Description
otelcol/collector.go Adds done channel + runStarted flag; Shutdown() waits for Run() completion; Run() errors immediately if already shut down.
otelcol/collector_test.go Updates existing tests to avoid races and adds TestShutdownBlocksUntilRunCompletes.
.chloggen/sync-run-shutdown-lifecycle.yaml Documents the lifecycle synchronization change for release notes.

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

Comment thread otelcol/collector.go Outdated
// Sets up the control logic for config reloading and shutdown.
// If Shutdown was called before Run, Run returns an error without starting.
func (col *Collector) Run(ctx context.Context) error {
col.runStarted.Store(true)

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

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

Run defers close(col.done), which will panic if Run is ever called more than once (including concurrent calls). Even though the doc comment says consecutive calls are not allowed, a public API should return a deterministic error instead of panicking. Consider guarding entry with something like CompareAndSwap(false, true) (or a sync.Once/state check) and return an error when Run is invoked after it has already started/returned, and ensure done is only closed once.

Suggested change
col.runStarted.Store(true)
if !col.runStarted.CompareAndSwap(false, true) {
return errors.New("collector server Run was already called")
}

Copilot uses AI. Check for mistakes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35120ccfea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread otelcol/collector.go Outdated
Comment on lines +346 to +348
case <-col.shutdownChan:
col.setCollectorState(StateClosed)
return errors.New("collector server was already shut down")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invoke provider shutdown on early Run return

This early-return branch skips col.shutdown(...), so when Shutdown() was called before Run(), Run exits without ever calling col.configProvider.Shutdown. NewCollector has already instantiated confmap providers, and provider lifecycle requires Shutdown to release resources/goroutines; in this new pre-shutdown flow, custom providers can leak background resources for the rest of the process.

Useful? React with 👍 / 👎.

@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from 35120cc to 537059d Compare March 23, 2026 20:32
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Rebased and pushed — the implementation addresses both the CompareAndSwap guard for double-Run prevention and the config provider shutdown on early return when Shutdown() was called before Run(). Tests cover all three scenarios: shutdown-before-run, double-run-returns-error, and shutdown-blocks-until-run-completes.

CI is waiting on first-time contributor approval from a maintainer — @axw would you mind approving the workflow runs when you get a chance? Happy to iterate on anything once CI results are in.

Comment thread otelcol/collector.go Outdated
Comment thread otelcol/collector.go Outdated
@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from 537059d to 479a943 Compare March 27, 2026 12:01
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Addressed @axw's review: Shutdown-before-Run no longer returns an error — go Run(ctx) followed by Shutdown() is a valid pattern, returning an error there was non-deterministic. Now returns nil after cleaning up the config provider. Simplified the error return to just the config provider error, dropped the errors.Join wrapper. Tests updated accordingly.

Comment thread otelcol/collector.go Outdated
Comment thread otelcol/collector.go Outdated
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Switched to sync.WaitGroup as you suggested — cleaner than the done channel. Also dropped the CompareAndSwap guard per your other comment; consecutive Run calls are just documented as unsupported.

axw's point about the configProvider.Shutdown wrapping is addressed in the same commit. Pushed.

Comment thread .chloggen/sync-run-shutdown-lifecycle.yaml Outdated
Comment thread .chloggen/sync-run-shutdown-lifecycle.yaml
@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from bb20b84 to 08152b1 Compare April 1, 2026 12:37
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Addressed review — fixed component path to pkg/otelcol, added cspell ignore for "lifecycles", and corrected changelog subtext to reflect nil-return on shutdown-before-run.

@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.30%. Comparing base (307e3ab) to head (ddc1211).
⚠️ Report is 26 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #14989      +/-   ##
==========================================
- Coverage   91.24%   90.30%   -0.94%     
==========================================
  Files         699      699              
  Lines       44913    53703    +8790     
==========================================
+ Hits        40979    48497    +7518     
- Misses       2786     4060    +1274     
+ Partials     1148     1146       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from 08152b1 to 548c4dd Compare April 1, 2026 18:16
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

added a test for the configProvider.Shutdown error path, should bring patch coverage to 100%

@codspeed-hq

This comment was marked as outdated.

@axw

axw commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

@Rajneesh180 please fix the lint issues

@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Fixed the lint — assert.ErrorContains/ErrorIsrequire.ErrorContains/require.ErrorIs per testifylint require-error rule. Pushed as a regular commit to preserve existing approvals.

@dmathieu dmathieu added the ready-to-merge Code review completed; ready to merge by maintainers label Apr 2, 2026
@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from 638babd to 2fb61e8 Compare April 14, 2026 11:47
@Rajneesh180
Rajneesh180 requested a review from axw April 14, 2026 18:15
@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from 2fb61e8 to ac54c9e Compare April 15, 2026 07:31
Shutdown() currently returns before Run() finishes its cleanup, so callers
that rely on Shutdown returning to mean "all resources freed" can hit
goroutine leaks or use-after-close bugs (open-telemetry#4947).

Fix: a done channel is closed via defer at the end of Run(), and Shutdown
waits on it when Run has been called. An atomic bool tracks whether Run
was called so Shutdown knows if it needs to block. This avoids the
WaitGroup approach from open-telemetry#8811 that deadlocks when Shutdown precedes Run.

Also guards against double-Run with CompareAndSwap (returns an error
instead of panicking on double close), and shuts down the config provider
in the early-return path when Shutdown was called before Run.

Fixes open-telemetry#4947

Signed-off-by: Rajneesh Chaudhary <rajneeshrehsaan48@gmail.com>
Replace done channel + runStarted atomic.Bool with sync.WaitGroup per
review feedback. Remove CompareAndSwap double-call guard — consecutive
calls to Run are documented as unsupported behavior rather than
returning an error.

Addresses review comments from bogdandrutu and axw.

Signed-off-by: Rajneesh Chaudhary <rajneeshrehsaan48@gmail.com>
Signed-off-by: Rajneesh Chaudhary <rajneeshrehsaan48@gmail.com>
Signed-off-by: Rajneesh Chaudhary <rajneeshrehsaan48@gmail.com>
@Rajneesh180
Rajneesh180 force-pushed the fix/sync-run-shutdown-lifecycle-4947 branch from ac54c9e to ddc1211 Compare April 15, 2026 16:45
@Rajneesh180

Copy link
Copy Markdown
Contributor Author

@bogdandrutu This has been ready for a couple of weeks now with approvals from @axw and @dmathieu, and CI is green on the latest rebase. Would you be able to merge when you get a chance? Thanks!

@mx-psi
mx-psi enabled auto-merge April 22, 2026 09:23
@mx-psi
mx-psi added this pull request to the merge queue Apr 22, 2026
Merged via the queue into open-telemetry:main with commit 6caf258 Apr 22, 2026
64 of 65 checks passed
@otelbot

otelbot Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution @Rajneesh180! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this survey.

@Rajneesh180

Copy link
Copy Markdown
Contributor Author

Thank you all for your kind collaboration. I’m looking forward to working on more issues. Could you suggest any issues I can take up next, @axw @dmathieu @mx-psi ?

@axw

axw commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

@Rajneesh180 thanks! Please keep an eye out for "help wanted" issues: https://github.com/open-telemetry/opentelemetry-collector/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22

raghu999 pushed a commit to raghu999/opentelemetry-collector that referenced this pull request Aug 6, 2026
Right now `Shutdown()` returns before `Run()` is done cleaning up, which
means callers that treat "Shutdown returned" as "everything is freed"
can run into goroutine leaks and use-after-close problems. This is the
root of open-telemetry#4947.

The fix adds a `done` channel that `Run()` closes via `defer` when it
finishes, and an `atomic.Bool` so `Shutdown()` knows whether `Run()` was
called. If it was, `Shutdown()` blocks on `<-col.done` until `Run()` is
fully done. This sidesteps the WaitGroup idea from open-telemetry#8811 that deadlocks
when `Shutdown` happens before `Run`.

I also added a `CompareAndSwap` guard on the `Run()` entry so calling it
twice returns an error instead of panicking on the double-close of the
done channel, and the early-return path (when Shutdown was called before
Run) now properly shuts down the config provider so we don't leak
confmap resources.

Tests cover shutdown-before-run, shutdown-during-run, double-run, and
the blocking guarantee.

Fixes open-telemetry#4947

---------

Signed-off-by: Rajneesh Chaudhary <rajneeshrehsaan48@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Code review completed; ready to merge by maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collector Shutdown should block until Run cleans up

6 participants