mini_scheduler 0.11.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d84546928a92e57ef72f4af8a5125c6d1ac12cddd32cd3b4203f819754b8c754
4
- data.tar.gz: a5a8406b9c15ef74ea8729991137ebcc37f80ea4a367e7f9ce5da11cc6620ab9
3
+ metadata.gz: ba1f84f21c6f32b3085b9632348bd401ffbc0f5920bf1b850c645a7b1599335d
4
+ data.tar.gz: 0c79d4c284be049977dec995de59bc020c108fcb0a7e300bbb975a801787df94
5
5
  SHA512:
6
- metadata.gz: c8b0a9e24bc8b785ffb0a4a01b4cef24a403604ec92851d862533b306ceab23008dad5419a7833e7c4f625d32206604f3308360db8942f61b7c254b0c2603a1d
7
- data.tar.gz: e2f99830e4ede44cd66fba4d206cb6d2c16e712e0df30888a8677ec8b5ac6db78d3538da92769985cc84e8f211025f1682c51aa41716a034e39a5b49cdeeb56a
6
+ metadata.gz: 5fa187b74ece7225aab2ddf186699cc2c4e1ce58980660e7d1caa823580f24fb9283d79f2d58a99fc4f460d451b9b85ca66a76a617a8f84f08f51c70a00edd5a
7
+ data.tar.gz: 2a75aaae5394830ad9e613494f9edbd42bf83180b1e27e24bf1428f2f70e9b35d6bc314329f97681f230dd7e586db7e881d678d3c83d748fe7e8ab5451ce5564
@@ -1,13 +1,34 @@
1
- # 0.11.0 - 24-06-2019
1
+ # 0.13.0 - 2020-11-30
2
+
3
+ - Fix exception code so it has parity with Sidekiq 4.2.3 and up, version bump cause
4
+ minimum version of Sikekiq changed.
5
+
6
+ # 0.12.3 - 2020-10-15
7
+
8
+ - Fixes a problem where scheduler didn't recover from Redis flush
9
+
10
+ # 0.12.2 - 2019-09-11
11
+
12
+ - Allow sorting schedule history by schedule name
13
+
14
+ # 0.12.1 - 2019-08-30
15
+
16
+ - Jobs that change family from per host to non per host can cause a tight loop
17
+
18
+ # 0.12.0 - 2019-08-29
19
+
20
+ - Add support for multiple workers which allows avoiding queue starvation
21
+
22
+ # 0.11.0 - 2019-06-24
2
23
 
3
24
  - Correct situation where distributed mutex could end in a tight loop when
4
25
  redis could not be contacted
5
26
 
6
- # 0.9.2 - 26-04-2019
27
+ # 0.9.2 - 2019-04-26
7
28
 
8
29
  - Correct UI so it displays durations that are longer than a minute
9
30
 
10
- # 0.9.1 - 21-01-2019
31
+ # 0.9.1 - 2019-01-21
11
32
 
12
33
  - Remove dependency on ActiveSupport and add proper dependency for Sidekiq
13
34
  - Remove Discourse specific bits from Sidekiq web scheduler tab.
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  git_source(:github) { 'https://github.com/discourse/mini_scheduler' }
data/Guardfile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # A sample Guardfile
2
3
  # More info at https://github.com/guard/guard#readme
3
4
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/discourse/mini_scheduler.svg?branch=master)](https://travis-ci.org/discourse/mini_scheduler)
1
+ [![Build Status](https://github.com/discourse/mini_scheduler/workflows/CI/badge.svg)](https://github.com/discourse/mini_scheduler/actions)
2
2
  [![Gem Version](https://badge.fury.io/rb/mini_scheduler.svg)](https://rubygems.org/gems/mini_scheduler)
3
3
 
4
4
  # mini_scheduler
@@ -30,6 +30,20 @@ In a Rails application, create files needed in your application to configure min
30
30
 
31
31
  An initializer is created named `config/initializers/mini_scheduler.rb` which lists all the configuration options.
32
32
 
33
+ ## Configuring MiniScheduler
34
+
35
+ By default each instance of MiniScheduler will run with a single worker. To amend this behavior:
36
+
37
+ ```
38
+ if Sidekiq.server? && defined?(Rails)
39
+ Rails.application.config.after_initialize do
40
+ MiniScheduler.start(workers: 5)
41
+ end
42
+ end
43
+ ```
44
+
45
+ This is useful for cases where you have extremely long running tasks that you would prefer did not starve.
46
+
33
47
  ## Usage
34
48
 
35
49
  Create jobs with a recurring schedule like this:
data/Rakefile CHANGED
@@ -1,6 +1,19 @@
1
- require "bundler/gem_tasks"
1
+ #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
2
4
  require "rspec/core/rake_task"
5
+ require 'bundler'
6
+
7
+ begin
8
+ Bundler.setup :default, :development
9
+ Bundler::GemHelper.install_tasks
10
+ rescue Bundler::BundlerError => error
11
+ $stderr.puts error.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit error.status_code
14
+ end
3
15
 
4
16
  RSpec::Core::RakeTask.new(:spec)
5
17
 
6
- task default: :spec
18
+ desc "Default: run tests"
19
+ task default: [ :spec ]
@@ -52,11 +52,11 @@ module MiniScheduler
52
52
  @skip_schedule
53
53
  end
54
54
 
55
- def self.start
55
+ def self.start(workers: 1)
56
56
  schedules = Manager.discover_schedules
57
57
 
58
58
  Manager.discover_queues.each do |queue|
59
- manager = Manager.new(queue: queue)
59
+ manager = Manager.new(queue: queue, workers: workers)
60
60
 
61
61
  schedules.each do |schedule|
62
62
  if schedule.queue == queue
@@ -2,7 +2,7 @@
2
2
 
3
3
  module MiniScheduler
4
4
  class Manager
5
- attr_accessor :random_ratio, :redis, :enable_stats, :queue
5
+ attr_accessor :random_ratio, :redis, :enable_stats, :queue, :workers
6
6
 
7
7
  class Runner
8
8
  def initialize(manager)
@@ -12,11 +12,12 @@ module MiniScheduler
12
12
  @manager = manager
13
13
  @hostname = manager.hostname
14
14
 
15
- @reschedule_orphans_thread = Thread.new do
15
+ @recovery_thread = Thread.new do
16
16
  while !@stopped
17
17
  sleep 60
18
18
 
19
19
  @mutex.synchronize do
20
+ repair_queue
20
21
  reschedule_orphans
21
22
  end
22
23
  end
@@ -29,9 +30,12 @@ module MiniScheduler
29
30
  sleep (@manager.keep_alive_duration / 2)
30
31
  end
31
32
  end
32
- @thread = Thread.new do
33
- while !@stopped
34
- process_queue
33
+ @threads = []
34
+ manager.workers.times do
35
+ @threads << Thread.new do
36
+ while !@stopped
37
+ process_queue
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -42,6 +46,12 @@ module MiniScheduler
42
46
  MiniScheduler.handle_job_exception(ex, message: "Scheduling manager keep-alive")
43
47
  end
44
48
 
49
+ def repair_queue
50
+ @manager.repair_queue
51
+ rescue => ex
52
+ MiniScheduler.handle_job_exception(ex, message: "Scheduling manager queue repair")
53
+ end
54
+
45
55
  def reschedule_orphans
46
56
  @manager.reschedule_orphans!
47
57
  rescue => ex
@@ -82,7 +92,7 @@ module MiniScheduler
82
92
 
83
93
  klass.new.perform
84
94
  rescue => e
85
- MiniScheduler.handle_job_exception(e, message: "Running a scheduled job", job: klass)
95
+ MiniScheduler.handle_job_exception(e, message: "Running a scheduled job", job: { "class" => klass })
86
96
 
87
97
  error = "#{e.class}: #{e.message} #{e.backtrace.join("\n")}"
88
98
  failed = true
@@ -119,19 +129,19 @@ module MiniScheduler
119
129
  @stopped = true
120
130
 
121
131
  @keep_alive_thread.kill
122
- @reschedule_orphans_thread.kill
132
+ @recovery_thread.kill
123
133
 
124
134
  @keep_alive_thread.join
125
- @reschedule_orphans_thread.join
135
+ @recovery_thread.join
126
136
 
127
137
  enq(nil)
128
138
 
129
139
  kill_thread = Thread.new do
130
140
  sleep 0.5
131
- @thread.kill
141
+ @threads.each(&:kill)
132
142
  end
133
143
 
134
- @thread.join
144
+ @threads.each(&:join)
135
145
  kill_thread.kill
136
146
  kill_thread.join
137
147
  end
@@ -171,6 +181,7 @@ module MiniScheduler
171
181
 
172
182
  def initialize(options = nil)
173
183
  @queue = options && options[:queue] || "default"
184
+ @workers = options && options[:workers] || 1
174
185
  @redis = MiniScheduler.redis
175
186
  @random_ratio = 0.1
176
187
  unless options && options[:skip_runner]
@@ -248,6 +259,15 @@ module MiniScheduler
248
259
  nil
249
260
  end
250
261
 
262
+ def repair_queue
263
+ return if redis.exists?(self.class.queue_key(queue)) ||
264
+ redis.exists?(self.class.queue_key(queue, hostname))
265
+
266
+ self.class.discover_schedules
267
+ .select { |schedule| schedule.queue == queue }
268
+ .each { |schedule| ensure_schedule!(schedule) }
269
+ end
270
+
251
271
  def tick
252
272
  lock do
253
273
  schedule_next_job
@@ -261,11 +281,14 @@ module MiniScheduler
261
281
 
262
282
  if due.to_i <= Time.now.to_i
263
283
  klass = get_klass(key)
264
- unless klass
284
+ if !klass || (
285
+ (klass.is_per_host && !hostname) || (hostname && !klass.is_per_host)
286
+ )
265
287
  # corrupt key, nuke it (renamed job or something)
266
288
  redis.zrem Manager.queue_key(queue, hostname), key
267
289
  return
268
290
  end
291
+
269
292
  info = schedule_info(klass)
270
293
  info.prev_run = Time.now.to_i
271
294
  info.prev_result = "QUEUED"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniScheduler
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
@@ -2,7 +2,20 @@
2
2
  <div class="col-sm-12">
3
3
  <h3>Scheduler History</h3>
4
4
  </div>
5
+
6
+ <div class="col-sm-12">
7
+ <form>
8
+ <select name="filter">
9
+ <option value="All" <%= "selected" if !@filter %>>All</option>
10
+ <% @schedules.each do |schedule| %>
11
+ <option value="<%=schedule%>" <%= "selected" if @filter == schedule.to_s%>><%=schedule%></option>
12
+ <% end %>
13
+ </select>
14
+ <input type="submit" value="Filter">
15
+ </form>
16
+ </div>
5
17
  </header>
18
+ <br>
6
19
 
7
20
  <div class="container">
8
21
  <div class="row">
@@ -39,7 +39,7 @@
39
39
  <td>
40
40
  <%= sane_duration @info.prev_duration %>
41
41
  </td>
42
- <td>
42
+ <td style="word-wrap: break-word">
43
43
  <%= @info.current_owner %>
44
44
  </td>
45
45
  <td>
@@ -4,6 +4,20 @@ module MiniScheduler
4
4
  module Web
5
5
  VIEWS = File.expand_path('views', File.dirname(__FILE__)) unless defined? VIEWS
6
6
 
7
+ def self.find_schedules_by_time
8
+ Manager.discover_schedules.sort do |a, b|
9
+ a_next = a.schedule_info.next_run
10
+ b_next = b.schedule_info.next_run
11
+ if a_next && b_next
12
+ a_next <=> b_next
13
+ elsif a_next
14
+ -1
15
+ else
16
+ 1
17
+ end
18
+ end
19
+ end
20
+
7
21
  def self.registered(app)
8
22
 
9
23
  app.helpers do
@@ -24,23 +38,24 @@ module MiniScheduler
24
38
 
25
39
  app.get "/scheduler" do
26
40
  MiniScheduler.before_sidekiq_web_request&.call
27
- @schedules = Manager.discover_schedules.sort do |a, b|
28
- a_next = a.schedule_info.next_run
29
- b_next = b.schedule_info.next_run
30
- if a_next && b_next
31
- a_next <=> b_next
32
- elsif a_next
33
- -1
34
- else
35
- 1
36
- end
37
- end
41
+ @schedules = Web.find_schedules_by_time
38
42
  erb File.read(File.join(VIEWS, 'scheduler.erb')), locals: { view_path: VIEWS }
39
43
  end
40
44
 
41
45
  app.get "/scheduler/history" do
42
46
  MiniScheduler.before_sidekiq_web_request&.call
43
- @scheduler_stats = Stat.order('started_at desc').limit(200)
47
+ @schedules = Manager.discover_schedules
48
+ @schedules.sort_by!(&:to_s)
49
+ @scheduler_stats = Stat.order('started_at desc')
50
+
51
+ @filter = params[:filter]
52
+ names = @schedules.map(&:to_s)
53
+ @filter = nil if !names.include?(@filter)
54
+ if @filter
55
+ @scheduler_stats = @scheduler_stats.where(name: @filter)
56
+ end
57
+
58
+ @scheduler_stats = @scheduler_stats.limit(200)
44
59
  erb File.read(File.join(VIEWS, 'history.erb')), locals: { view_path: VIEWS }
45
60
  end
46
61
 
@@ -15,14 +15,10 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = "https://github.com/discourse/mini_scheduler"
16
16
  spec.license = "MIT"
17
17
 
18
- # Specify which files should be added to the gem when it is released.
19
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
- end
18
+ spec.files = `git ls-files`.split($/).reject { |s| s =~ /^(spec|\.)/ }
23
19
  spec.require_paths = ["lib"]
24
20
 
25
- spec.add_dependency "sidekiq"
21
+ spec.add_dependency "sidekiq", ">= 4.2.3"
26
22
 
27
23
  spec.add_development_dependency "pg", ">= 1.0"
28
24
  spec.add_development_dependency "activesupport", ">= 5.2"
@@ -32,4 +28,5 @@ Gem::Specification.new do |spec|
32
28
  spec.add_development_dependency "guard-rspec"
33
29
  spec.add_development_dependency "mock_redis"
34
30
  spec.add_development_dependency "rake"
31
+ spec.add_development_dependency 'rubocop-discourse'
35
32
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  - Neil Lalonde
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-06-24 00:00:00.000000000 Z
12
+ date: 2020-11-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: 4.2.3
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: 4.2.3
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: pg
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +137,20 @@ dependencies:
137
137
  - - ">="
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rubocop-discourse
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
140
154
  description: Adds recurring jobs for Sidekiq
141
155
  email:
142
156
  - neil.lalonde@discourse.org
@@ -144,10 +158,6 @@ executables: []
144
158
  extensions: []
145
159
  extra_rdoc_files: []
146
160
  files:
147
- - ".gitignore"
148
- - ".rspec"
149
- - ".rubocop.yml"
150
- - ".travis.yml"
151
161
  - CHANGELOG.md
152
162
  - Gemfile
153
163
  - Guardfile
@@ -172,7 +182,7 @@ homepage: https://github.com/discourse/mini_scheduler
172
182
  licenses:
173
183
  - MIT
174
184
  metadata: {}
175
- post_install_message:
185
+ post_install_message:
176
186
  rdoc_options: []
177
187
  require_paths:
178
188
  - lib
@@ -187,8 +197,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
197
  - !ruby/object:Gem::Version
188
198
  version: '0'
189
199
  requirements: []
190
- rubygems_version: 3.0.3
191
- signing_key:
200
+ rubygems_version: 3.1.4
201
+ signing_key:
192
202
  specification_version: 4
193
203
  summary: Adds recurring jobs for Sidekiq
194
204
  test_files: []
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- Gemfile.lock
10
- .DS_Store
11
- *.swp
12
-
13
- .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper --color
@@ -1 +0,0 @@
1
- inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml
@@ -1,18 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - ruby-head
4
- - 2.5
5
- - 2.6
6
-
7
- before_install:
8
- - gem install bundler
9
-
10
- cache: bundler
11
- sudo: false
12
-
13
- services:
14
- - redis-server
15
-
16
- matrix:
17
- allow_failures:
18
- - rvm: ruby-head