logstash-mixin-scheduler 1.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/LICENSE +13 -0
- data/README.md +50 -0
- data/lib/logstash/plugin_mixins/scheduler/rufus_impl.rb +243 -0
- data/lib/logstash/plugin_mixins/scheduler.rb +139 -0
- data/spec/logstash/plugin_mixins/scheduler/rufus_impl_spec.rb +105 -0
- data/spec/logstash/plugin_mixins/scheduler_spec.rb +68 -0
- data/spec/logstash/spec_helper.rb +3 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 67978c6a1909fafc833c6b6ad0ea257274a7c6e9ae05dfd7c06e1a2b3d6820d0
|
4
|
+
data.tar.gz: cf0dada853778214e4bef783d356f921e0d153557912a6a00c787270decfaefe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: db8106c162a7268c40c3bfde3f7ee888d002238a9081f94eb294fa23af97eb687502a7243fd79b4e12c006574324bc1815b114966af43e42d73a837c7a4e3816
|
7
|
+
data.tar.gz: 0caadfd21d2b8e49f51b2b77cc3858423b0ec7fb21ba5aeab0839e2c17d308de34ecdcdea93e26e15cbc09b4bfccc435f5a05182e93a34eac5f69c3b019247a2
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2022 Elastic N.V. <http://www.elastic.co>
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Scheduler Mixin
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/logstash-plugins/logstash-mixin-scheduler.svg?branch=main)](https://travis-ci.com/logstash-plugins/logstash-mixin-scheduler)
|
4
|
+
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
1. Add this gem as a runtime dependency of your Logstash plugin's `gemspec`:
|
9
|
+
|
10
|
+
~~~ ruby
|
11
|
+
Gem::Specification.new do |s|
|
12
|
+
# ...
|
13
|
+
|
14
|
+
s.add_runtime_dependency 'logstash-mixin-scheduler', '~> 1.0'
|
15
|
+
end
|
16
|
+
~~~
|
17
|
+
|
18
|
+
2. In your plugin code, require this library and include it into your plugin class
|
19
|
+
that already inherits `LogStash::Plugin`:
|
20
|
+
|
21
|
+
~~~ ruby
|
22
|
+
require 'logstash/plugin_mixins/scheduler'
|
23
|
+
|
24
|
+
class LogStash::Inputs::Bar < Logstash::Inputs::Base
|
25
|
+
|
26
|
+
include LogStash::PluginMixins::Scheduler
|
27
|
+
|
28
|
+
# Schedule of when to periodically run statement, in Cron format
|
29
|
+
# for example: "* * * * *" (execute query every minute, on the minute)
|
30
|
+
config :schedule, :validate => :string
|
31
|
+
|
32
|
+
def run(queue)
|
33
|
+
if @schedule
|
34
|
+
scheduler.cron(@schedule) { serve_drinks(queue) }
|
35
|
+
scheduler.join
|
36
|
+
else
|
37
|
+
serve_drinks(queue)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# def server_drinks ...
|
42
|
+
|
43
|
+
end
|
44
|
+
~~~
|
45
|
+
|
46
|
+
## Development
|
47
|
+
|
48
|
+
This gem:
|
49
|
+
- *MUST* remain API-stable at 1.x
|
50
|
+
- *MUST NOT* introduce additional runtime dependencies
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'rufus/scheduler'
|
2
|
+
|
3
|
+
require 'logstash/util/loggable'
|
4
|
+
|
5
|
+
module LogStash module PluginMixins module Scheduler module RufusImpl
|
6
|
+
# @private
|
7
|
+
class SchedulerAdapter
|
8
|
+
include SchedulerInterface
|
9
|
+
|
10
|
+
include LogStash::Util::Loggable
|
11
|
+
|
12
|
+
# @private
|
13
|
+
attr_reader :impl
|
14
|
+
|
15
|
+
def initialize(name, opts)
|
16
|
+
if name && !opts.key?(:thread_name)
|
17
|
+
opts[:thread_name] = name
|
18
|
+
end
|
19
|
+
opts[:max_work_threads] ||= 1
|
20
|
+
# amount the scheduler thread sleeps between checking whether jobs
|
21
|
+
# should trigger, default is 0.3 which is a bit too often ...
|
22
|
+
# in theory the cron expression '* * * * * *' supports running jobs
|
23
|
+
# every second but this is very rare, we could potentially go higher
|
24
|
+
opts[:frequency] ||= 1.0
|
25
|
+
|
26
|
+
logger = opts.delete(:logger) || self.logger
|
27
|
+
@impl = SchedulerImpl.new(opts, logger)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @overload
|
31
|
+
def cron(schedule, opts = {}, &task); __schedule(:cron, schedule, opts, &task); end
|
32
|
+
# @overload
|
33
|
+
def every(period, opts = {}, &task); __schedule(:every, period, opts, &task); end
|
34
|
+
# @overload
|
35
|
+
def at(timestamp, opts = {}, &task); __schedule(:at, timestamp, opts, &task); end
|
36
|
+
# @overload
|
37
|
+
def in(delay, opts = {}, &task); __schedule(:in, delay, opts, &task); end
|
38
|
+
# @overload
|
39
|
+
def interval(interval, opts = {}, &task); __schedule(:interval, interval, opts, &task); end
|
40
|
+
|
41
|
+
# @overload
|
42
|
+
def release; @impl.shutdown end
|
43
|
+
# @overload
|
44
|
+
def release!; @impl.shutdown(:wait) end
|
45
|
+
# @overload
|
46
|
+
def running?; !@impl.down? end
|
47
|
+
|
48
|
+
# @overload
|
49
|
+
def join; @impl.join end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def __schedule(type, arg, opts, &task)
|
54
|
+
unless block_given?
|
55
|
+
raise ArgumentError, 'missing task - worker task to execute'
|
56
|
+
end
|
57
|
+
JobAdapter.new @impl.send(:"schedule_#{type}", arg, opts, &task)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# @private
|
63
|
+
class SchedulerImpl < ::Rufus::Scheduler
|
64
|
+
|
65
|
+
# Rufus::Scheduler >= 3.4 moved the Time impl into a gem EoTime = ::EtOrbi::EoTime`
|
66
|
+
# Rufus::Scheduler 3.1 - 3.3 using it's own Time impl `Rufus::Scheduler::ZoTime`
|
67
|
+
TimeImpl = defined?(::Rufus::Scheduler::EoTime) ? ::Rufus::Scheduler::EoTime :
|
68
|
+
(defined?(::Rufus::Scheduler::ZoTime) ? ::Rufus::Scheduler::ZoTime : ::Time)
|
69
|
+
|
70
|
+
def initialize(opts, logger)
|
71
|
+
super(opts)
|
72
|
+
@_logger = logger
|
73
|
+
end
|
74
|
+
|
75
|
+
# @overload
|
76
|
+
def join
|
77
|
+
fail NotRunningError.new('cannot join scheduler that is not running') unless @thread
|
78
|
+
fail ThreadError.new('scheduler thread cannot join itself') if @thread == Thread.current
|
79
|
+
@thread.join # makes scheduler.join behavior consistent across 3.x versions
|
80
|
+
end
|
81
|
+
|
82
|
+
# @overload
|
83
|
+
def shutdown(opt=nil)
|
84
|
+
if @thread # do not fail when scheduler thread failed to start
|
85
|
+
super(opt)
|
86
|
+
else
|
87
|
+
@started_at = nil # bare minimum to look like the scheduler is down
|
88
|
+
# when the scheduler thread fails `@started_at = ...` might still be set
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @overload
|
93
|
+
def timeout_jobs
|
94
|
+
# Rufus relies on `Thread.list` which is a blocking operation and with many schedulers
|
95
|
+
# (and threads) within LS will have a negative impact on performance as scheduler
|
96
|
+
# threads will end up waiting to obtain the `Thread.list` lock.
|
97
|
+
#
|
98
|
+
# However, this isn't necessary we can easily detect whether there are any jobs
|
99
|
+
# that might need to timeout: only when `@opts[:timeout]` is set causes worker thread(s)
|
100
|
+
# to have a `Thread.current[:rufus_scheduler_timeout]` that is not nil
|
101
|
+
return unless @opts[:timeout]
|
102
|
+
super
|
103
|
+
end
|
104
|
+
|
105
|
+
# @overload
|
106
|
+
def work_threads(query = :all)
|
107
|
+
if query == :__all_no_cache__ # special case from JobDecorator#start_work_thread
|
108
|
+
@_work_threads = nil # when a new worker thread is being added reset
|
109
|
+
return super(:all)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Gets executed every time a job is triggered, we're going to cache the
|
113
|
+
# worker threads for this scheduler (to avoid `Thread.list`) - they only
|
114
|
+
# change when a new thread is being started from #start_work_thread ...
|
115
|
+
work_threads = @_work_threads
|
116
|
+
if work_threads.nil?
|
117
|
+
work_threads = threads.select { |t| t[:rufus_scheduler_work_thread] }
|
118
|
+
@_work_threads = work_threads
|
119
|
+
end
|
120
|
+
|
121
|
+
case query
|
122
|
+
when :active then work_threads.select { |t| t[:rufus_scheduler_job] }
|
123
|
+
when :vacant then work_threads.reject { |t| t[:rufus_scheduler_job] }
|
124
|
+
else work_threads
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# @overload
|
129
|
+
def on_error(job, err)
|
130
|
+
details = { exception: err.class, message: err.message, backtrace: err.backtrace }
|
131
|
+
details[:cause] = err.cause if err.cause
|
132
|
+
|
133
|
+
details[:now] = debug_format_time(TimeImpl.now)
|
134
|
+
details[:last_time] = (debug_format_time(job.last_time) rescue nil)
|
135
|
+
details[:next_time] = (debug_format_time(job.next_time) rescue nil)
|
136
|
+
details[:job] = job
|
137
|
+
|
138
|
+
details[:opts] = @opts
|
139
|
+
details[:started_at] = started_at
|
140
|
+
details[:thread] = thread.inspect
|
141
|
+
details[:jobs_size] = @jobs.size
|
142
|
+
details[:work_threads_size] = work_threads.size
|
143
|
+
details[:work_queue_size] = work_queue.size
|
144
|
+
|
145
|
+
logger.error("Scheduler intercepted an error:", details)
|
146
|
+
|
147
|
+
rescue => e
|
148
|
+
logger.error("Scheduler failed in #on_error #{e.inspect}")
|
149
|
+
end
|
150
|
+
|
151
|
+
def debug_format_time(time)
|
152
|
+
# EtOrbi::EoTime used by (newer) Rufus::Scheduler has to_debug_s https://git.io/JyiPj
|
153
|
+
time.respond_to?(:to_debug_s) ? time.to_debug_s : time.strftime("%Y-%m-%dT%H:%M:%S.%L")
|
154
|
+
end
|
155
|
+
private :debug_format_time
|
156
|
+
|
157
|
+
# @private helper used by JobDecorator
|
158
|
+
def work_thread_name_prefix
|
159
|
+
( @opts[:thread_name] || "#{@thread_key}_scheduler" ) + '_worker-'
|
160
|
+
end
|
161
|
+
|
162
|
+
def logger; @_logger end
|
163
|
+
private :logger
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
# @overload
|
168
|
+
def start
|
169
|
+
ret = super() # @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
|
170
|
+
|
171
|
+
# at least set thread.name for easier thread dump analysis
|
172
|
+
if @thread.is_a?(Thread) && @thread.respond_to?(:name=)
|
173
|
+
@thread.name = @thread[:name] if @thread[:name]
|
174
|
+
end
|
175
|
+
|
176
|
+
ret
|
177
|
+
end
|
178
|
+
|
179
|
+
# @overload
|
180
|
+
def do_schedule(job_type, t, callable, opts, return_job_instance, block)
|
181
|
+
job_or_id = super
|
182
|
+
|
183
|
+
job_or_id.extend JobDecorator if return_job_instance
|
184
|
+
|
185
|
+
job_or_id
|
186
|
+
end
|
187
|
+
|
188
|
+
module JobDecorator
|
189
|
+
|
190
|
+
def start_work_thread
|
191
|
+
prev_thread_count = @scheduler.work_threads.size
|
192
|
+
|
193
|
+
ret = super() # does not return Thread instance in 3.0
|
194
|
+
|
195
|
+
work_threads = @scheduler.work_threads(:__all_no_cache__)
|
196
|
+
while prev_thread_count == work_threads.size # very unlikely
|
197
|
+
Thread.pass
|
198
|
+
work_threads = @scheduler.work_threads(:__all_no_cache__)
|
199
|
+
end
|
200
|
+
|
201
|
+
work_thread_name_prefix = @scheduler.work_thread_name_prefix
|
202
|
+
|
203
|
+
work_threads.sort! do |t1, t2|
|
204
|
+
if t1[:name].nil?
|
205
|
+
t2[:name].nil? ? 0 : +1 # nils at the end
|
206
|
+
elsif t2[:name].nil?
|
207
|
+
t1[:name].nil? ? 0 : -1
|
208
|
+
else
|
209
|
+
t1[:name] <=> t2[:name]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
work_threads.each_with_index do |thread, i|
|
214
|
+
unless thread[:name]
|
215
|
+
thread[:name] = "#{work_thread_name_prefix}#{sprintf('%02i', i)}"
|
216
|
+
thread.name = thread[:name] if thread.respond_to?(:name=)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
ret
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
private_constant :JobDecorator
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
# @private
|
229
|
+
class JobAdapter
|
230
|
+
include JobInterface
|
231
|
+
|
232
|
+
# @private
|
233
|
+
attr_reader :impl
|
234
|
+
|
235
|
+
def initialize(job)
|
236
|
+
@impl = job
|
237
|
+
end
|
238
|
+
|
239
|
+
# @overload
|
240
|
+
def job_id; @impl.job_id end
|
241
|
+
end
|
242
|
+
|
243
|
+
end end end end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'logstash/namespace'
|
4
|
+
require 'logstash/plugin'
|
5
|
+
require 'logstash/util/thread_safe_attributes'
|
6
|
+
|
7
|
+
module LogStash
|
8
|
+
module PluginMixins
|
9
|
+
module Scheduler
|
10
|
+
|
11
|
+
extend LogStash::Util::ThreadSafeAttributes
|
12
|
+
|
13
|
+
##
|
14
|
+
# @private
|
15
|
+
# @param base [Class]: a class that inherits `LogStash::Plugin`, typically one
|
16
|
+
# descending from one of the four plugin base classes
|
17
|
+
# (e.g., `LogStash::Inputs::Base`)
|
18
|
+
# @raise [ArgumentError]
|
19
|
+
# @return [void]
|
20
|
+
def self.included(base)
|
21
|
+
fail(ArgumentError, "`#{base}` must inherit LogStash::Plugin") unless base < LogStash::Plugin
|
22
|
+
instance_methods = base.instance_methods
|
23
|
+
base.send(:prepend, StopHook) if instance_methods.include?(:stop)
|
24
|
+
base.send(:prepend, CloseHook) if instance_methods.include?(:close)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
module StopHook
|
29
|
+
def stop
|
30
|
+
release_scheduler! # wait till scheduler halts
|
31
|
+
super # plugin.stop
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private_constant :StopHook
|
35
|
+
|
36
|
+
# @private
|
37
|
+
module CloseHook
|
38
|
+
def close
|
39
|
+
super # plugin.close
|
40
|
+
release_scheduler
|
41
|
+
end
|
42
|
+
end
|
43
|
+
private_constant :CloseHook
|
44
|
+
|
45
|
+
# def scheduler(); @_scheduler ||= new_scheduler({}) end
|
46
|
+
lazy_init_attr(:scheduler, variable: :@_scheduler) { start_scheduler({}) }
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Release jobs registered by the plugin from executing.
|
51
|
+
# This method executes from the plugin's #close method.
|
52
|
+
def release_scheduler
|
53
|
+
@_scheduler.release if @_scheduler
|
54
|
+
end
|
55
|
+
|
56
|
+
# Release jobs registered by the plugin from executing.
|
57
|
+
# This method executes from the plugin's #stop method.
|
58
|
+
# @note Might block until the scheduler operation completes!
|
59
|
+
def release_scheduler!
|
60
|
+
@_scheduler.release! if @_scheduler
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param opts [Hash] scheduler options
|
64
|
+
# @return [SchedulerInterface] scheduler instance
|
65
|
+
def start_scheduler(opts, name: nil)
|
66
|
+
if name.nil?
|
67
|
+
unless self.class.name
|
68
|
+
raise ArgumentError, "can not generate a scheduler name for anonymous class: #{inspect}"
|
69
|
+
end
|
70
|
+
pipeline_id = (respond_to?(:execution_context) && execution_context&.pipeline_id) || 'main'
|
71
|
+
plugin_name = self.class.name.split('::').last # e.g. "jdbc"
|
72
|
+
name = "[#{pipeline_id}]|#{self.class.plugin_type}|#{plugin_name}|scheduler"
|
73
|
+
# thread naming convention: [psql1]|input|jdbc|scheduler
|
74
|
+
end
|
75
|
+
RufusImpl::SchedulerAdapter.new name, { logger: logger }.merge(opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Scheduler interface usable by plugins.
|
79
|
+
module SchedulerInterface
|
80
|
+
|
81
|
+
# All scheduling methods return a `Scheduler::JobInterface`
|
82
|
+
|
83
|
+
# @return a job object which responds to a #job_id method
|
84
|
+
# @abstract
|
85
|
+
def cron(schedule, opts = {}, &task); fail NotImplementedError end
|
86
|
+
# @return a job object which responds to a #job_id method
|
87
|
+
# @abstract
|
88
|
+
def every(period, opts = {}, &task); fail NotImplementedError end
|
89
|
+
# @return a job object which responds to a #job_id method
|
90
|
+
# @abstract
|
91
|
+
def at(timestamp, opts = {}, &task); fail NotImplementedError end
|
92
|
+
# @return a job object which responds to a #job_id method
|
93
|
+
# @abstract
|
94
|
+
def in(delay, opts = {}, &task); fail NotImplementedError end
|
95
|
+
# @return a job object which responds to a #job_id method
|
96
|
+
# @abstract
|
97
|
+
def interval(interval, opts = {}, &task); fail NotImplementedError; end
|
98
|
+
|
99
|
+
# Blocks until _all_ jobs are joined, including jobs
|
100
|
+
# that are scheduled after this join has begun blocking.
|
101
|
+
# @abstract
|
102
|
+
def join; fail NotImplementedError end
|
103
|
+
|
104
|
+
# Release the scheduler:
|
105
|
+
# - prevents additional jobs from being registered,
|
106
|
+
# - and unschedules all future invocations of jobs previously registered
|
107
|
+
#
|
108
|
+
# This operation does not block until the scheduler stops executing jobs.
|
109
|
+
# @abstract
|
110
|
+
def release; fail NotImplementedError end
|
111
|
+
|
112
|
+
# Release the scheduler:
|
113
|
+
# - prevents additional jobs from being registered,
|
114
|
+
# - and unschedules all future invocations of jobs previously registered
|
115
|
+
#
|
116
|
+
# This operation attempts to WAIT until the scheduler operation completes (if supported).
|
117
|
+
# @abstract
|
118
|
+
def release!; release end
|
119
|
+
|
120
|
+
# Is this scheduler potentially executing our jobs.
|
121
|
+
#
|
122
|
+
# @abstract
|
123
|
+
# @see #release
|
124
|
+
def running?; fail NotImplementedError end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
# Interface provided by a scheduled job.
|
129
|
+
module JobInterface
|
130
|
+
|
131
|
+
def job_id; fail NotImplementedError end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
require 'logstash/plugin_mixins/scheduler/rufus_impl'
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin_mixins/scheduler"
|
4
|
+
|
5
|
+
describe LogStash::PluginMixins::Scheduler::RufusImpl do
|
6
|
+
|
7
|
+
let(:name) { '[test]<jdbc_scheduler' }
|
8
|
+
|
9
|
+
let(:opts) do
|
10
|
+
{ :max_work_threads => 2 }
|
11
|
+
end
|
12
|
+
|
13
|
+
subject(:scheduler) do
|
14
|
+
LogStash::PluginMixins::Scheduler::RufusImpl::SchedulerAdapter.new(name, opts)
|
15
|
+
end
|
16
|
+
|
17
|
+
after { scheduler.impl.shutdown }
|
18
|
+
|
19
|
+
it "sets scheduler thread name" do
|
20
|
+
expect( scheduler.impl.thread.name ).to include name
|
21
|
+
end
|
22
|
+
|
23
|
+
it "gets interrupted from join" do
|
24
|
+
scheduler.every('1s') { 42**1000 }
|
25
|
+
join_thread = Thread.start { scheduler.join }
|
26
|
+
sleep 1.1
|
27
|
+
expect(join_thread).to be_alive
|
28
|
+
expect(scheduler.impl.down?).to be false
|
29
|
+
scheduler.release
|
30
|
+
Thread.pass
|
31
|
+
expect(scheduler.impl.down?).to be true
|
32
|
+
sleep 0.1
|
33
|
+
try(10) { expect(join_thread).to_not be_alive }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "gets interrupted from join (wait shutdown)" do
|
37
|
+
scheduler.cron('* * * * * *') { 42**1000 }
|
38
|
+
expect(scheduler.impl.down?).to be false
|
39
|
+
join_thread = Thread.start { scheduler.join }
|
40
|
+
sleep 1.1
|
41
|
+
expect(join_thread).to be_alive
|
42
|
+
scheduler.release!
|
43
|
+
expect(scheduler.impl.down?).to be true
|
44
|
+
sleep 0.1
|
45
|
+
try(10) { expect(join_thread).to_not be_alive }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'cron schedule' do
|
49
|
+
|
50
|
+
before do
|
51
|
+
scheduler.cron('* * * * * *') { sleep 1.25 } # every second
|
52
|
+
end
|
53
|
+
|
54
|
+
it "sets worker thread names" do
|
55
|
+
sleep 3.0
|
56
|
+
threads = scheduler.impl.work_threads
|
57
|
+
threads.sort! { |t1, t2| (t1.name || '') <=> (t2.name || '') }
|
58
|
+
|
59
|
+
expect( threads.size ).to eql 2
|
60
|
+
expect( threads.first.name ).to eql "#{name}_worker-00"
|
61
|
+
expect( threads.last.name ).to eql "#{name}_worker-01"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'every 1s' do
|
67
|
+
|
68
|
+
before do
|
69
|
+
scheduler.in('1s') { raise 'TEST' } # every second
|
70
|
+
end
|
71
|
+
|
72
|
+
it "logs errors handled" do
|
73
|
+
expect( scheduler.impl.send(:logger) ).to receive(:error).with /Scheduler intercepted an error/, hash_including(:message => 'TEST')
|
74
|
+
sleep 2.25
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'work threads' do
|
80
|
+
|
81
|
+
let(:opts) { super().merge :max_work_threads => 3 }
|
82
|
+
|
83
|
+
let(:counter) { java.util.concurrent.atomic.AtomicLong.new(0) }
|
84
|
+
|
85
|
+
before do
|
86
|
+
scheduler.cron('* * * * * *') { counter.increment_and_get; sleep 3.25 } # every second
|
87
|
+
end
|
88
|
+
|
89
|
+
it "are working" do
|
90
|
+
sleep(0.05) while counter.get == 0
|
91
|
+
expect( scheduler.impl.work_threads.size ).to eql 1
|
92
|
+
sleep(0.05) while counter.get == 1
|
93
|
+
expect( scheduler.impl.work_threads.size ).to eql 2
|
94
|
+
sleep(0.05) while counter.get == 2
|
95
|
+
expect( scheduler.impl.work_threads.size ).to eql 3
|
96
|
+
|
97
|
+
sleep 1.25
|
98
|
+
expect( scheduler.impl.work_threads.size ).to eql 3
|
99
|
+
sleep 1.25
|
100
|
+
expect( scheduler.impl.work_threads.size ).to eql 3
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin_mixins/scheduler"
|
4
|
+
|
5
|
+
module LogStash
|
6
|
+
module Inputs
|
7
|
+
class Foo < LogStash::Inputs::Base
|
8
|
+
include LogStash::PluginMixins::Scheduler
|
9
|
+
config_name 'foo'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module LogStash
|
15
|
+
module Filters
|
16
|
+
class Bar < LogStash::Inputs::Base
|
17
|
+
include LogStash::PluginMixins::Scheduler
|
18
|
+
config_name 'bar'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe LogStash::PluginMixins::Scheduler do
|
24
|
+
|
25
|
+
subject(:mixin) { described_class }
|
26
|
+
|
27
|
+
context 'included into a class' do
|
28
|
+
context 'that does not inherit from `LogStash::Plugin`' do
|
29
|
+
let(:plugin_class) { Class.new }
|
30
|
+
it 'fails with an ArgumentError' do
|
31
|
+
expect do
|
32
|
+
plugin_class.send(:include, mixin)
|
33
|
+
end.to raise_error(ArgumentError, /LogStash::Plugin/)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
[ LogStash::Inputs::Foo, LogStash::Filters::Bar ].each do |base_class|
|
38
|
+
context "#{base_class} plugin" do
|
39
|
+
|
40
|
+
let(:plugin_class) { base_class }
|
41
|
+
|
42
|
+
it 'works when mixin is includes and provides a scheduler method' do
|
43
|
+
plugin = plugin_class.new Hash.new
|
44
|
+
expect( plugin.scheduler ).to be_a LogStash::PluginMixins::Scheduler::SchedulerInterface
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'hooks' do
|
48
|
+
|
49
|
+
let(:plugin) { plugin_class.new Hash.new }
|
50
|
+
|
51
|
+
it 'shuts-down the scheduler on close' do
|
52
|
+
scheduler = plugin.scheduler
|
53
|
+
expect( scheduler.running? ).to be true
|
54
|
+
plugin.do_close
|
55
|
+
expect( scheduler.running? ).to be false
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'shuts-down the scheduler on stop' do
|
59
|
+
scheduler = plugin.scheduler
|
60
|
+
plugin.stop
|
61
|
+
expect( scheduler.running? ).to be false
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-mixin-scheduler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Elastic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-06-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '7.16'
|
19
|
+
name: logstash-core
|
20
|
+
prerelease: false
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.9
|
33
|
+
name: rufus-scheduler
|
34
|
+
prerelease: false
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.9
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
name: logstash-devutils
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
name: logstash-codec-plain
|
62
|
+
prerelease: false
|
63
|
+
type: :development
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.9'
|
75
|
+
name: rspec
|
76
|
+
prerelease: false
|
77
|
+
type: :development
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.9'
|
83
|
+
description:
|
84
|
+
email: info@elastic.co
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- CHANGELOG.md
|
90
|
+
- LICENSE
|
91
|
+
- README.md
|
92
|
+
- lib/logstash/plugin_mixins/scheduler.rb
|
93
|
+
- lib/logstash/plugin_mixins/scheduler/rufus_impl.rb
|
94
|
+
- spec/logstash/plugin_mixins/scheduler/rufus_impl_spec.rb
|
95
|
+
- spec/logstash/plugin_mixins/scheduler_spec.rb
|
96
|
+
- spec/logstash/spec_helper.rb
|
97
|
+
homepage: https://github.com/logstash-plugins/logstash-mixin-scheduler
|
98
|
+
licenses:
|
99
|
+
- Apache-2.0
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubygems_version: 3.1.6
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Scheduler for Logstash plugins
|
120
|
+
test_files:
|
121
|
+
- spec/logstash/plugin_mixins/scheduler/rufus_impl_spec.rb
|
122
|
+
- spec/logstash/plugin_mixins/scheduler_spec.rb
|
123
|
+
- spec/logstash/spec_helper.rb
|