refactor test tasks, test:plugins now correctly run specs of installed plugins, including local ones

robustness in shutdown handling and other thread safeties
This commit is contained in:
Colin Surprenant 2015-03-31 18:19:21 -04:00 committed by Pier-Hugues Pellerin
parent fb88eef749
commit 380ea33708
2 changed files with 73 additions and 32 deletions

View file

@ -43,14 +43,19 @@ class LogStash::Pipeline
@settings = { @settings = {
"filter-workers" => 1, "filter-workers" => 1,
} }
@run_mutex = Mutex.new
@ready = false
@started = false
@input_threads = []
end # def initialize end # def initialize
def ready? def ready?
return @ready @run_mutex.synchronize{@ready}
end end
def started? def started?
return @started @run_mutex.synchronize{@started}
end end
def configure(setting, value) def configure(setting, value)
@ -69,14 +74,14 @@ class LogStash::Pipeline
end end
def run def run
@started = true @run_mutex.synchronize{@started = true}
@input_threads = []
start_inputs # synchronize @input_threads between run and shutdown
@run_mutex.synchronize{start_inputs}
start_filters if filters? start_filters if filters?
start_outputs start_outputs
@ready = true @run_mutex.synchronize{@ready = true}
@logger.info("Pipeline started") @logger.info("Pipeline started")
@logger.terminal("Logstash startup completed") @logger.terminal("Logstash startup completed")
@ -132,7 +137,7 @@ class LogStash::Pipeline
moreinputs = [] moreinputs = []
@inputs.each do |input| @inputs.each do |input|
if input.threadable && input.threads > 1 if input.threadable && input.threads > 1
(input.threads-1).times do |i| (input.threads - 1).times do |i|
moreinputs << input.clone moreinputs << input.clone
end end
end end
@ -171,7 +176,7 @@ class LogStash::Pipeline
begin begin
plugin.run(@input_to_filter) plugin.run(@input_to_filter)
rescue LogStash::ShutdownSignal rescue LogStash::ShutdownSignal
return # ignore and quit
rescue => e rescue => e
if @logger.debug? if @logger.debug?
@logger.error(I18n.t("logstash.pipeline.worker-error-debug", @logger.error(I18n.t("logstash.pipeline.worker-error-debug",
@ -183,14 +188,23 @@ class LogStash::Pipeline
:plugin => plugin.inspect, :error => e)) :plugin => plugin.inspect, :error => e))
end end
puts e.backtrace if @logger.debug? puts e.backtrace if @logger.debug?
plugin.teardown # input teardown must be synchronized since is can be called concurrently by
# the input worker thread and from the pipeline thread shutdown method.
# this means that input teardown methods must support multiple calls.
@run_mutex.synchronize{plugin.teardown}
sleep 1 sleep 1
retry retry
end end
rescue LogStash::ShutdownSignal
# nothing
ensure ensure
plugin.teardown begin
# input teardown must be synchronized since is can be called concurrently by
# the input worker thread and from the pipeline thread shutdown method.
# this means that input teardown methods must support multiple calls.
@run_mutex.synchronize{plugin.teardown}
rescue LogStash::ShutdownSignal
# teardown could receive the ShutdownSignal, retry it
retry
end
end # def inputworker end # def inputworker
def filterworker def filterworker
@ -201,6 +215,7 @@ class LogStash::Pipeline
case event case event
when LogStash::Event when LogStash::Event
# filter_func returns all filtered events, including cancelled ones
filter_func(event).each { |e| @filter_to_output.push(e) unless e.cancelled? } filter_func(event).each { |e| @filter_to_output.push(e) unless e.cancelled? }
when LogStash::FlushEvent when LogStash::FlushEvent
# handle filter flushing here so that non threadsafe filters (thus only running one filterworker) # handle filter flushing here so that non threadsafe filters (thus only running one filterworker)
@ -240,18 +255,30 @@ class LogStash::Pipeline
def shutdown def shutdown
@input_threads.each do |thread| @input_threads.each do |thread|
# Interrupt all inputs # Interrupt all inputs
@logger.info("Sending shutdown signal to input thread", @logger.info("Sending shutdown signal to input thread", :thread => thread)
:thread => thread)
thread.raise(LogStash::ShutdownSignal)
begin
thread.wakeup # in case it's in blocked IO or sleeping
rescue ThreadError
end
# Sometimes an input is stuck in a blocking I/O # synchronize both ShutdownSignal and teardown below. by synchronizing both
# so we need to tell it to teardown directly # we will avoid potentially sending a shutdown signal when the inputworker is
@inputs.each do |input| # executing the teardown method.
input.teardown @run_mutex.synchronize do
thread.raise(LogStash::ShutdownSignal)
begin
thread.wakeup # in case it's in blocked IO or sleeping
rescue ThreadError
end
end
end
# sometimes an input is stuck in a blocking I/O so we need to tell it to teardown directly
@inputs.each do |input|
begin
# input teardown must be synchronized since is can be called concurrently by
# the input worker thread and from the pipeline thread shutdown method.
# this means that input teardown methods must support multiple calls.
@run_mutex.synchronize{input.teardown}
rescue LogStash::ShutdownSignal
# teardown could receive the ShutdownSignal, retry it
retry
end end
end end
@ -265,8 +292,10 @@ class LogStash::Pipeline
return klass.new(*args) return klass.new(*args)
end end
# for backward compatibility in devutils for the rspec helpers # for backward compatibility in devutils for the rspec helpers, this method is not used
# in the pipeline anymore.
def filter(event, &block) def filter(event, &block)
# filter_func returns all filtered events, including cancelled ones
filter_func(event).each { |e| block.call(e) } filter_func(event).each { |e| block.call(e) }
end end

View file

@ -5,25 +5,37 @@
# In general this is not a problem, because the most common rspec usage # In general this is not a problem, because the most common rspec usage
# is throw the rake task, where rspec sets this himself internally. # is throw the rake task, where rspec sets this himself internally.
## ##
require "logstash/pluginmanager/util"
namespace "test" do namespace "test" do
def run_rspec(*args) task "setup" do
require "logstash/environment" require "logstash/environment"
LogStash::Environment.bundler_setup!({:without => []}) LogStash::Environment.bundler_setup!({:without => []})
require "rspec/core/runner" require "rspec/core/runner"
require "rspec" require "rspec"
RSpec::Core::Runner.run([*args])
end end
task "core" do desc "run core specs"
exit run_rspec(Rake::FileList["spec/**/*_spec.rb"]) task "core" => ["setup"] do
exit(RSpec::Core::Runner.run([Rake::FileList["spec/**/*_spec.rb"]]))
end end
task "core-fail-fast" do desc "run core specs in fail-fast mode"
exit run_rspec("--fail-fast", Rake::FileList["spec/**/*_spec.rb"]) task "core-fail-fast" => ["setup"] do
exit(Spec::Core::Runner.run(["--fail-fast", Rake::FileList["spec/**/*_spec.rb"]]))
end end
task "plugins" do desc "run all installed plugins specs"
exit run_rspec("--order", "rand", Rake::FileList[File.join(ENV["GEM_HOME"], "gems/logstash-*/spec/{input,filter,codec,output}s/*_spec.rb")]) task "plugins" => ["setup"] do
# grab all spec files using the live plugins gem specs. this allows correclty also running the specs
# of a local plugin dir added using the Gemfile :path option. before this, any local plugin spec would
# not be run because they were not under the vendor/bundle/jruby/1.9/gems path
test_files = LogStash::PluginManager.find_plugins_gem_specs.map do |spec|
Rake::FileList[File.join(spec.gem_dir, "spec/{input,filter,codec,output}s/*_spec.rb")]
end.flatten
# "--format=documentation"
exit(RSpec::Core::Runner.run(["--order", "rand", test_files]))
end end
task "install-core" => ["bootstrap", "plugin:install-core", "plugin:install-development-dependencies"] task "install-core" => ["bootstrap", "plugin:install-core", "plugin:install-development-dependencies"]