Skip to content

Ensure do_close failures don't halt pipeline shutdown#19035

Merged
andrewvc merged 2 commits into
elastic:mainfrom
andrewvc:fix-do-close-guarantees
Apr 22, 2026
Merged

Ensure do_close failures don't halt pipeline shutdown#19035
andrewvc merged 2 commits into
elastic:mainfrom
andrewvc:fix-do-close-guarantees

Conversation

@andrewvc

@andrewvc andrewvc commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Fix the shutdown code to make sure all resources are released through #do_close even if one fails. I caught this while reviewing logstash-plugins/logstash-input-beats#537

Release notes

Fix the situation where if during pipeline shutdown a filter or output plugin failed to shut down resources may not be released.

What does this PR do?

Ensures resources are released on pipeline shutdown even when exceptions are raised in #close

Why is it important/What is the impact to the user?

Without this we may leak resources

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files (and/or docker env variables)
  • I have added tests that prove my fix is effective or that my feature works

Author's Checklist

  • [ ]

How to test this PR locally

#!/usr/bin/env bash
set -euo pipefail

PLUGIN_DIR=/tmp/do-close-repro/logstash/filters
mkdir -p "$PLUGIN_DIR"

cat > "$PLUGIN_DIR/boom_close.rb" <<'RUBY'
require "logstash/filters/base"

class LogStash::Filters::BoomClose < LogStash::Filters::Base
  config_name "boom_close"
  def register; end
  def filter(event); end
  def close; raise "boom from do_close" end
end
RUBY

# Run from the logstash repo root
./bin/logstash \
  --path.plugins /tmp/do-close-repro \
  -e '
    input  { generator { count => 3 } }
    filter { boom_close {} }
    output { stdout { codec => dots } }
  '

What to look for in the output:

A WARN line: plugin raised exception while closing, ignoring with :plugin=>"boom_close" and the boom from do_close exception in the keys.
The pipeline exits cleanly — the stdout output still closes, process terminates without hanging.

Example WARN line + subsequent output:


[2026-04-21T16:24:06,956][INFO ][logstash.agent           ] Pipelines running {count: 1, running_pipelines: [:main], non_running_pipelines: []}
...[2026-04-21T16:24:07,119][WARN ][logstash.javapipeline    ][main] plugin raised exception while closing, ignoring {pipeline_id: "main", exception: "RuntimeError", error: "boom from do_close", stacktrace: "/private/tmp/do-close-repro/logstash/filters/boom_close.rb:7:in 'close'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/plugin.rb:98:in 'do_close'\norg/logstash/config/ir/compiler/AbstractFilterDelegatorExt.java:90:in 'do_close'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:617:in 'close_plugin_and_ignore'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:526:in 'block in shutdown_workers'\norg/jruby/RubyArray.java:2093:in 'each'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:526:in 'shutdown_workers'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:222:in 'run'\n/Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:159:in 'block in start'", plugin: "boom_close", thread: "#<Thread:0x7be163df /Users/andrewvc/projects/logstash/logstash-core/lib/logstash/java_pipeline.rb:149 run>"}
[2026-04-21T16:24:07,121][INFO ][logstash.javapipeline    ][main] Pipeline terminated {"pipeline.id" => "main"}
[2026-04-21T16:24:07,467][INFO ][logstash.pipelinesregistry] Removed pipeline from registry successfully {pipeline_id: :main}
[2026-04-21T16:24:07,477][INFO ][logstash.runner          ] Logstash shut down.
andrewvc@avcwork ~/p/logstash (fix-do-close-guarantees)>

Route plugin do_close calls in register_plugins rollback and
shutdown_workers through close_plugin_and_ignore so one plugin
raising doesn't leave remaining filters/outputs unclosed. Also
fix the log key to use the instance-level config_name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andrewvc andrewvc self-assigned this Apr 21, 2026
@andrewvc andrewvc added the bug label Apr 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)
  • run exhaustive tests : Run the exhaustive tests Buildkite pipeline.

@mergify

mergify Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

This pull request does not have a backport label. Could you fix it @andrewvc? 🙏
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit.
  • If no backport is necessary, please add the backport-skip label

@logger.warn(
"plugin raised exception while closing, ignoring",
exception_logging_keys(e, :plugin => plugin.class.config_name))
exception_logging_keys(e, :plugin => plugin.config_name))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Delegators prevented the previous code from working for filters / outputs. Now we prefer the instance form.

@andrewvc andrewvc marked this pull request as ready for review April 21, 2026 21:29

@yaauie yaauie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍 good find.

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

👍

Filter context: unrelated safe_pipeline_worker_count warn during
start_workers tripped the strict expect().to receive(:warn).with(...)
expectation. Add allow(logger).to receive(:warn) as a pass-through.

Output context: pipeline.outputs.first on CI resolves to dummyoutputmore,
not dummyoutput — output ordering isn't guaranteed to match config text
order. Assert against raising_output.config_name so the match tracks
the plugin actually stubbed to raise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andrewvc andrewvc changed the title WIP Ensure do_close failures don't halt pipeline shutdown Ensure do_close failures don't halt pipeline shutdown Apr 22, 2026
@elasticmachine

Copy link
Copy Markdown

💚 Build Succeeded

History

cc @andrewvc

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

LGTM!

@andrewvc andrewvc merged commit 65a7b76 into elastic:main Apr 22, 2026
11 checks passed
@andrewvc andrewvc deleted the fix-do-close-guarantees branch April 22, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants