fleiss 0.1.1 → 0.1.2

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 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.