logstash-mixin-scheduler 1.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|