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 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
@@ -0,0 +1,2 @@
1
+ ## 1.0.0
2
+ - Feat: a common scheduler interface [#1](https://github.com/logstash-plugins/logstash-mixin-scheduler/pull/1)
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
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+
3
+ require "logstash-core"
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