fleiss 0.1.1 → 0.1.2

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: e9eb58758de53588746446aedfbd877e9b84d361de7650185265b9973aeff0b3
4
- data.tar.gz: d5f1ac77ce8f6dbef855c6a4e0f2eecdad56a7f8a5fc30f5851d95934eb92453
3
+ metadata.gz: 92e4e656c34af27e17ac918eaa0d21f5d01f31f3d5f04f4c08290ce2ebb49035
4
+ data.tar.gz: d80b83c7dcb419ff05bcee827b8e7ba314be13c7845d658a86e5142d13c34a84
5
5
  SHA512:
6
- metadata.gz: c1b97a6658b29170a51a85df91611ff562f319d03f29626bbcf3069858e19cf7cc5aed4cfec7b0508fc90a1899cf325b84fef67e9c270842609acb62cbe0fad7
7
- data.tar.gz: 28dccc2dced0062b9355f75da37989f2c008bd3b6d3be0d8cac4c8c3ea637eb41f4b94a747e10abd19ef6f85e51d45f01db62c581f1dee0b98377f6affb6cb3f
6
+ metadata.gz: 1bd0fc7a843d98020b0491fb5a1c457315f5589823b87d43ffebcce07933d548421ee9f3c705d9090d81ccc20319d363aba789559122a5fb54e65751a773c84a
7
+ data.tar.gz: 44fea18168d2e79bf9d0eec495a010f85edf313c543a1a271b5090132806c2c03a7cc653c77949a4ffe26cf46d88383192847d8d90565e37b3294a6e751494f1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fleiss (0.1.1)
4
+ fleiss (0.1.2)
5
5
  activejob (>= 5.0)
6
6
  activerecord (>= 5.0)
7
7
  concurrent-ruby
@@ -40,7 +40,7 @@ GEM
40
40
  pg (1.1.3)
41
41
  powerpack (0.1.2)
42
42
  rainbow (3.0.0)
43
- rake (12.3.1)
43
+ rake (12.3.2)
44
44
  rspec (3.8.0)
45
45
  rspec-core (~> 3.8.0)
46
46
  rspec-expectations (~> 3.8.0)
data/fleiss.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'fleiss'
3
- s.version = '0.1.1'
3
+ s.version = '0.1.2'
4
4
  s.authors = ['Black Square Media Ltd']
5
5
  s.email = ['info@blacksquaremedia.com']
6
6
  s.summary = %(Minimialist background jobs backed by ActiveJob and ActiveRecord.)
@@ -6,6 +6,7 @@ module Fleiss
6
6
 
7
7
  included do
8
8
  scope :in_queue, ->(qs) { where(queue_name: Array.wrap(qs)) }
9
+ scope :finished, -> { where.not(finished_at: nil) }
9
10
  scope :not_finished, -> { where(finished_at: nil) }
10
11
  scope :not_expired, ->(now=Time.zone.now) { where(arel_table[:expires_at].eq(nil).or(arel_table[:expires_at].gt(now))) }
11
12
  scope :started, -> { where(arel_table[:started_at].not_eq(nil)) }
@@ -29,15 +30,6 @@ module Fleiss
29
30
  started.not_finished.where(owner: owner)
30
31
  end
31
32
 
32
- # Reschedules jobs to run again.
33
- def reschedule_all(at=Time.zone.now)
34
- update_all(
35
- started_at: nil,
36
- owner: nil,
37
- scheduled_at: at,
38
- )
39
- end
40
-
41
33
  # @param [ActiveJob::Base] job the job instance
42
34
  # @option [Time] :scheduled_at schedule job at
43
35
  def enqueue(job, scheduled_at: nil)
@@ -68,23 +60,47 @@ module Fleiss
68
60
  # @param [String] owner
69
61
  # @return [Boolean] true if job was started.
70
62
  def start(owner, now: Time.zone.now)
71
- with_lock do
63
+ with_isolation do
72
64
  self.class.pending(now)
73
65
  .where(id: id)
74
- .update_all(started_at: now, owner: owner) == 1
75
- end
66
+ .update_all(started_at: now, owner: owner)
67
+ end == 1
68
+ rescue ::ActiveRecord::SerializationFailure
69
+ false
76
70
  end
77
71
 
78
72
  # Marks a job as finished.
79
73
  # @param [String] owner
80
74
  # @return [Boolean] true if successful.
81
75
  def finish(owner, now: Time.zone.now)
82
- with_lock do
76
+ with_isolation do
83
77
  self.class
84
78
  .in_progress(owner)
85
79
  .where(id: id)
86
- .update_all(finished_at: now) == 1
87
- end
80
+ .update_all(finished_at: now)
81
+ end == 1
82
+ rescue ::ActiveRecord::SerializationFailure
83
+ false
84
+ end
85
+
86
+ # Reschedules the job to run again.
87
+ def reschedule(owner, now: Time.zone.now)
88
+ with_isolation do
89
+ self.class
90
+ .in_progress(owner)
91
+ .where(id: id)
92
+ .update_all(started_at: nil, owner: nil, scheduled_at: now)
93
+ end == 1
94
+ rescue ::ActiveRecord::SerializationFailure
95
+ false
96
+ end
97
+
98
+ private
99
+
100
+ def with_isolation(&block)
101
+ return yield unless self.class.connection.supports_transaction_isolation?
102
+
103
+ self.class.transaction(isolation: :repeatable_read, &block)
88
104
  end
89
105
  end
90
106
  end
data/lib/fleiss/cli.rb CHANGED
@@ -42,26 +42,15 @@ module Fleiss
42
42
  end
43
43
 
44
44
  def run!
45
- return if @worker
46
-
47
45
  $LOAD_PATH.concat opts[:include]
48
46
  opts[:require].each {|n| require n }
49
47
  require 'fleiss/worker'
50
48
 
51
- Signal.trap('TERM') { shutdown }
52
- Signal.trap('INT') { shutdown }
53
-
54
- @worker = Fleiss::Worker.new \
49
+ Fleiss::Worker.run \
55
50
  queues: opts[:queues],
56
51
  concurrency: opts[:concurrency],
57
52
  wait_time: opts[:wait_time],
58
53
  logger: Logger.new(opts[:logfile])
59
- @worker.run
60
- @worker.wait
61
- end
62
-
63
- def shutdown
64
- @worker.shutdown if @worker
65
54
  end
66
55
 
67
56
  def parser # rubocop:disable Metrics/MethodLength
data/lib/fleiss/worker.rb CHANGED
@@ -7,6 +7,11 @@ require 'securerandom'
7
7
  class Fleiss::Worker
8
8
  attr_reader :queues, :uuid, :wait_time, :logger
9
9
 
10
+ # Shortcut for new(*args).run
11
+ def self.run(*args)
12
+ new(*args).run
13
+ end
14
+
10
15
  # Init a new worker instance
11
16
  # @param [ConnectionPool] disque client connection pool
12
17
  # @param [Hash] options
@@ -27,36 +32,20 @@ class Fleiss::Worker
27
32
  logger.info "Worker #{uuid} starting - queues: #{queues.inspect}, concurrency: #{@pool.max_length}"
28
33
  loop do
29
34
  run_cycle
30
- break if @stopped
31
-
32
35
  sleep @wait_time
33
36
  end
34
- logger.info "Worker #{uuid} shutting down"
35
- end
36
-
37
- # Blocks until worker until it's stopped.
38
- def wait
37
+ rescue SignalException => e
38
+ logger.info "Worker #{uuid} received #{e.message}. Shutting down..."
39
+ ensure
39
40
  @pool.shutdown
40
- @pool.wait_for_termination(1)
41
-
42
- Fleiss.backend
43
- .in_queue(queues)
44
- .in_progress(uuid)
45
- .reschedule_all(10.seconds.from_now)
46
41
  @pool.wait_for_termination
47
- logger.info "Worker #{uuid} shutdown complete"
48
- rescue StandardError => e
49
- handle_exception e, 'shutdown'
50
- end
51
-
52
- # Initiates the shutdown process
53
- def shutdown
54
- @stopped = true
55
42
  end
56
43
 
57
44
  private
58
45
 
59
46
  def run_cycle
47
+ return if @pool.shuttingdown?
48
+
60
49
  capacity = @pool.max_length - @pool.scheduled_task_count + @pool.completed_task_count
61
50
  return unless capacity.positive?
62
51
 
@@ -67,8 +56,6 @@ class Fleiss::Worker
67
56
  .to_a
68
57
 
69
58
  batch.each do |job|
70
- break if @stopped
71
-
72
59
  @pool.post { perform(job) }
73
60
  end
74
61
  rescue StandardError => e
@@ -76,16 +63,23 @@ class Fleiss::Worker
76
63
  end
77
64
 
78
65
  def perform(job)
79
- return unless job.start(uuid)
80
-
81
- thread_id = Thread.current.object_id.to_s(36)
82
- logger.info { "Worker #{uuid} execute job ##{job.id} by thread #{thread_id}" }
83
-
84
- ActiveJob::Base.execute job.job_data
66
+ thread_id = Thread.current.object_id.to_s(16).reverse
67
+ owner = "#{uuid}/#{thread_id}"
68
+ return unless job.start(owner)
69
+
70
+ logger.info { "Worker #{uuid} execute job ##{job.id} (by thread #{thread_id})" }
71
+ finished = false
72
+ begin
73
+ ActiveJob::Base.execute job.job_data
74
+ finished = true
75
+ rescue StandardError
76
+ finished = true
77
+ raise
78
+ ensure
79
+ finished ? job.finish(owner) : job.reschedule(owner)
80
+ end
85
81
  rescue StandardError => e
86
82
  handle_exception e, "processing job ##{job.id} (by thread #{thread_id})"
87
- ensure
88
- job.finish(uuid)
89
83
  end
90
84
 
91
85
  def handle_exception(err, intro)
@@ -46,13 +46,6 @@ RSpec.describe Fleiss::Backend::ActiveRecord do
46
46
  expect(rec.job_id.size).to eq(36)
47
47
  end
48
48
 
49
- it 'should reschedule' do
50
- job = TestJob.perform_later
51
- described_class.reschedule_all(1.hour.from_now)
52
- rec = retrieve(job)
53
- expect(rec.scheduled_at).to be_within(2.seconds).of(1.hour.from_now)
54
- end
55
-
56
49
  it 'should scope pending' do
57
50
  j1 = TestJob.perform_later
58
51
  expect(retrieve(j1).start('owner')).to be_truthy
@@ -118,4 +111,16 @@ RSpec.describe Fleiss::Backend::ActiveRecord do
118
111
  expect(rec.started_at).to be_within(2.seconds).of(Time.zone.now)
119
112
  expect(rec.finished_at).to be_within(2.seconds).of(Time.zone.now)
120
113
  end
114
+
115
+ it 'should reschedule' do
116
+ job = TestJob.perform_later
117
+ rec = retrieve(job)
118
+ expect(rec.reschedule('owner')).to be_falsey
119
+ expect(rec.start('owner')).to be_truthy
120
+ expect(rec.reschedule('other')).to be_falsey
121
+ expect(rec.reschedule('owner')).to be_truthy
122
+ expect(rec.reload.owner).to be_nil
123
+ expect(rec.started_at).to be_nil
124
+ expect(rec.scheduled_at).to be_within(2.seconds).of(Time.zone.now)
125
+ end
121
126
  end
@@ -13,12 +13,11 @@ RSpec.describe Fleiss::Worker do
13
13
  end
14
14
 
15
15
  after do
16
- subject.shutdown
17
- subject.wait
16
+ runner.kill
18
17
  end
19
18
 
20
19
  def wait_for
21
- 200.times do
20
+ 100.times do
22
21
  break if yield
23
22
 
24
23
  sleep(0.1)
@@ -26,22 +25,23 @@ RSpec.describe Fleiss::Worker do
26
25
  expect(yield).to be_truthy
27
26
  end
28
27
 
29
- it 'should run/process/shutdown' do
30
- # seed 50 jobs
31
- 50.times {|n| TestJob.perform_later(n) }
32
- wait_for { Fleiss.backend.not_finished.count > 0 }
28
+ it 'should run' do
29
+ # seed 24 jobs
30
+ 24.times {|n| TestJob.perform_later(n) }
31
+ wait_for { Fleiss.backend.not_finished.count.positive? }
33
32
 
34
33
  # ensure runner processes them all
35
34
  wait_for { Fleiss.backend.not_finished.count.zero? }
36
35
 
37
36
  # check what's been performed
38
- expect(TestJob.performed.size).to eq(50)
39
- expect(TestJob.performed).to match_array(0..49)
37
+ expect(TestJob.performed.size).to eq(24)
38
+ expect(Fleiss.backend.finished.count).to eq(24)
39
+ expect(TestJob.performed).to match_array(0..23)
40
40
  end
41
41
 
42
42
  it 'should handle failing jobs' do
43
43
  TestJob.perform_later('raise')
44
44
  wait_for { Fleiss.backend.not_finished.count.zero? }
45
- expect(Fleiss.backend.count).to eq(1)
45
+ expect(Fleiss.backend.finished.count).to eq(1)
46
46
  end
47
47
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fleiss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Black Square Media Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-06 00:00:00.000000000 Z
11
+ date: 2018-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -201,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
201
  version: '0'
202
202
  requirements: []
203
203
  rubyforge_project:
204
- rubygems_version: 2.7.7
204
+ rubygems_version: 2.7.6
205
205
  signing_key:
206
206
  specification_version: 4
207
207
  summary: Minimialist background jobs backed by ActiveJob and ActiveRecord.