faktory_worker_ruby 0.7.1 → 1.0.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.
@@ -11,7 +11,7 @@ module Faktory
11
11
  def initialize(options)
12
12
  merged_options = Faktory.options.merge(options)
13
13
  @manager = Faktory::Manager.new(merged_options)
14
- @done = false
14
+ @current_state = nil
15
15
  @options = merged_options
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ module Faktory
22
22
 
23
23
  # Stops this instance from processing any more jobs,
24
24
  def quiet
25
- @done = true
25
+ @current_state = 'quiet'
26
26
  @manager.quiet
27
27
  end
28
28
 
@@ -32,13 +32,17 @@ module Faktory
32
32
  def stop
33
33
  deadline = Time.now + @options[:timeout]
34
34
 
35
- @done = true
35
+ @current_state = 'terminate'
36
36
  @manager.quiet
37
37
  @manager.stop(deadline)
38
38
  end
39
39
 
40
40
  def stopping?
41
- @done
41
+ @current_state == 'terminate'
42
+ end
43
+
44
+ def quiet?
45
+ @current_state == 'quiet'
42
46
  end
43
47
 
44
48
  PROCTITLES = []
@@ -50,12 +54,23 @@ module Faktory
50
54
  PROCTITLES << proc { title }
51
55
  PROCTITLES << proc { "[#{Processor.busy_count} of #{@options[:concurrency]} busy]" }
52
56
  PROCTITLES << proc { "stopping" if stopping? }
57
+ PROCTITLES << proc { "quiet" if quiet? }
53
58
 
54
59
  loop do
55
60
  $0 = PROCTITLES.map {|p| p.call }.join(" ")
56
61
 
57
62
  begin
58
- Faktory.server {|c| c.beat }
63
+ result = Faktory.server {|c| c.beat(@current_state) }
64
+ case result
65
+ when "OK"
66
+ # all good
67
+ when "terminate"
68
+ ::Process.kill('TERM', $$)
69
+ when "quiet"
70
+ ::Process.kill('TSTP', $$)
71
+ else
72
+ Faktory.logger.warn "Got unexpected BEAT: #{result}"
73
+ end
59
74
  rescue => ex
60
75
  # best effort, try again in a few secs
61
76
  end
@@ -29,8 +29,8 @@ module Faktory
29
29
  def self.job_hash_context(job_hash)
30
30
  # If we're using a wrapper class, like ActiveJob, use the "wrapped"
31
31
  # attribute to expose the underlying thing.
32
- klass = job_hash['wrapped'.freeze] || job_hash["jobtype".freeze]
33
- "#{klass} JID-#{job_hash['jid'.freeze]}"
32
+ klass = job_hash.dig('custom', 'wrapped') || job_hash["jobtype"]
33
+ "#{klass} JID-#{job_hash['jid']}"
34
34
  end
35
35
 
36
36
  def self.with_job_hash_context(job_hash, &block)
@@ -123,6 +123,9 @@ module Faktory
123
123
  cleanup.each do |processor|
124
124
  processor.kill
125
125
  end
126
+ cleanup.each do |processor|
127
+ processor.thread.join(1)
128
+ end
126
129
  end
127
130
 
128
131
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Simple middleware to save the current batch and restore it when the job executes.
4
+ #
5
+ module Faktory::Middleware::Batch
6
+ class Client
7
+ def call(payload, pool)
8
+ b = Thread.current[:faktory_batch]
9
+ if b
10
+ payload["custom"] ||= {}
11
+ payload["custom"]["bid"] = b.bid
12
+ end
13
+ yield
14
+ end
15
+ end
16
+
17
+ class Worker
18
+ def call(jobinst, payload)
19
+ jobinst.bid = payload.dig("custom", "bid")
20
+ yield
21
+ end
22
+ end
23
+ end
24
+
25
+ Faktory.configure_client do |config|
26
+ config.client_middleware do |chain|
27
+ chain.add Faktory::Middleware::Batch::Client
28
+ end
29
+ end
30
+
31
+ Faktory.configure_worker do |config|
32
+ config.client_middleware do |chain|
33
+ chain.add Faktory::Middleware::Batch::Client
34
+ end
35
+ config.worker_middleware do |chain|
36
+ chain.add Faktory::Middleware::Batch::Worker
37
+ end
38
+ end
@@ -19,10 +19,8 @@ module Faktory::Middleware::I18n
19
19
  # Pull the msg locale out and set the current thread to use it.
20
20
  class Worker
21
21
  def call(jobinst, payload)
22
- I18n.locale = payload.dig("custom", "locale") || I18n.default_locale
23
- yield
24
- ensure
25
- I18n.locale = I18n.default_locale
22
+ locale = payload.dig("custom", "locale") || I18n.default_locale
23
+ I18n.with_locale(locale) { yield }
26
24
  end
27
25
  end
28
26
  end
@@ -0,0 +1,85 @@
1
+ require 'faktory/client'
2
+
3
+ # require 'faktory/mutate'
4
+ # cl = Faktory::Client.new
5
+ # cl.discard(Faktory::RETRIES) do |filter|
6
+ # filter.with_type("QuickBooksSyncJob")
7
+ # filter.matching("*uid:12345*"))
8
+ # end
9
+ module Faktory
10
+
11
+ # Valid targets
12
+ RETRIES = "retries"
13
+ SCHEDULED = "scheduled"
14
+ DEAD = "dead"
15
+
16
+ module Mutator
17
+ class Filter
18
+ attr_accessor :hash
19
+
20
+ def initialize
21
+ @hash = {}
22
+ end
23
+
24
+ # This must be the exact type of the job, no pattern matching
25
+ def with_type(jobtype)
26
+ @hash[:jobtype] = jobtype
27
+ end
28
+
29
+ # This is a regexp that will be passed as is to Redis's SCAN.
30
+ # Notably you should surround it with * to ensure it matches
31
+ # substrings within the job payload.
32
+ # See https://redis.io/commands/scan for details.
33
+ def matching(regexp)
34
+ @hash[:regexp] = regexp
35
+ end
36
+
37
+ # One or more JIDs to target:
38
+ # filter.jids << 'abcdefgh1234'
39
+ # filter.jids = ['abcdefgh1234', '1234567890']
40
+ def jids
41
+ @hash[:jids] ||= []
42
+ end
43
+ def jids=(ary)
44
+ @hash[:jids] = Array(ary)
45
+ end
46
+ end
47
+
48
+ def discard(target, &block)
49
+ filter = Filter.new
50
+ block.call(filter) if block
51
+ mutate('discard', target, filter)
52
+ end
53
+
54
+ def kill(target, &block)
55
+ filter = Filter.new
56
+ block.call(filter) if block
57
+ mutate('kill', target, filter)
58
+ end
59
+
60
+ def requeue(target, &block)
61
+ filter = Filter.new
62
+ block.call(filter) if block
63
+ mutate('requeue', target, filter)
64
+ end
65
+
66
+ def clear(target)
67
+ mutate('discard', target, nil)
68
+ end
69
+
70
+ private
71
+
72
+ def mutate(cmd, target, filter)
73
+ payload = {:cmd => cmd,:target => target}
74
+ payload[:filter] = filter.hash if filter && !filter.hash.empty?
75
+
76
+ transaction do
77
+ command("MUTATE", JSON.dump(payload))
78
+ ok
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ Faktory::Client.send(:include, Faktory::Mutator)
@@ -88,6 +88,7 @@ module Faktory
88
88
  @@busy_count = @@busy_count + 1
89
89
  end
90
90
  begin
91
+ @job = work.job
91
92
  process(work)
92
93
  ensure
93
94
  @@busy_lock.synchronize do
@@ -148,10 +149,11 @@ module Faktory
148
149
  end
149
150
  end
150
151
  work.acknowledge
151
- rescue Faktory::Shutdown
152
- # Had to force kill this job because it didn't finish
153
- # within the timeout. Don't acknowledge the work since
154
- # we didn't properly finish it.
152
+ rescue Faktory::Shutdown => shut
153
+ # Had to force kill this job because it didn't finish within
154
+ # the timeout. Fail it so we can release any locks server-side
155
+ # and immediately restart it.
156
+ work.fail(shut)
155
157
  rescue Exception => ex
156
158
  handle_exception(ex, { :context => "Job raised exception", :job => work.job })
157
159
  work.fail(ex)
data/lib/faktory/rails.rb CHANGED
@@ -20,8 +20,22 @@ module Faktory
20
20
  # None of this matters on the client-side, only within the Faktory executor itself.
21
21
  #
22
22
  Faktory.configure_worker do |_|
23
+ if ::Rails::VERSION::MAJOR < 5
24
+ raise "Your current version of Rails, #{::Rails::VERSION::STRING}, is not supported"
25
+ end
26
+
23
27
  Faktory.options[:reloader] = Faktory::Rails::Reloader.new
24
28
  end
29
+
30
+ begin
31
+ # https://github.com/rails/rails/pull/41248
32
+ if defined?(::Mail::SMTP)
33
+ ::Mail::SMTP::DEFAULTS[:read_timeout] ||= 5
34
+ ::Mail::SMTP::DEFAULTS[:open_timeout] ||= 5
35
+ end
36
+ rescue => ex
37
+ # ignore
38
+ end
25
39
  end
26
40
 
27
41
  class Reloader
@@ -40,4 +54,11 @@ module Faktory
40
54
  end
41
55
  end
42
56
  end if defined?(::Rails)
57
+
58
+ if defined?(::Rails) && ::Rails::VERSION::MAJOR < 5
59
+ $stderr.puts("**************************************************")
60
+ $stderr.puts("🚫 ERROR: Faktory Worker does not support Rails versions under 5.x - please ensure your workers are updated")
61
+ $stderr.puts("**************************************************")
62
+ $stderr.puts("")
63
+ end
43
64
  end
@@ -94,9 +94,9 @@ module Faktory
94
94
  end
95
95
  end
96
96
 
97
- def open
97
+ def open(*args)
98
98
  unless Faktory::Testing.enabled?
99
- real_open
99
+ real_open(*args)
100
100
  end
101
101
  end
102
102
  end
@@ -0,0 +1,41 @@
1
+ module Faktory
2
+ module Trackable
3
+
4
+ ##
5
+ # Tracking allows a long-running Faktory job to report its progress:
6
+ #
7
+ # def perform(...)
8
+ # track_progress(10, "Calculating values")
9
+ # # do some work
10
+ #
11
+ # track_progress(20, "Sending emails")
12
+ # # do some more work
13
+ #
14
+ # track_progress(20, "Sending emails", reserve_until: 10.minutes.from_now)
15
+ # # do some more work
16
+ # end
17
+ #
18
+ # Note:
19
+ # 1. jobs should be small and fine-grained (and so fast) if possible.
20
+ # 2. tracking is useful for long-running jobs, tracking a fast job will only add overhead
21
+ # 3. tracking only works with a single job, use Batches to monitor a group of jobs
22
+ # 4. reserve_until allows a job to dynamically extend its reservation so it is not garbage collected by Faktory while running
23
+ # 5. you can only reserve up to 24 hours.
24
+ #
25
+ def track_progress(percent, desc=nil, reserve_until:nil)
26
+ hash = { 'jid' => jid, 'percent' => percent.to_i, 'desc' => desc }
27
+ hash["reserve_until"] = convert(reserve_until) if reserve_until
28
+ Faktory.server {|c| c.set_track(hash) }
29
+ end
30
+
31
+ private
32
+
33
+ def convert(ts)
34
+ raise ArgumentError, "Timestamp in the past: #{ts}" if Time.now > ts
35
+ raise ArgumentError, "Timestamp too far in the future: #{ts}" if (Time.now + 86400) < ts
36
+
37
+ tsf = ts.to_f
38
+ Time.at(tsf).utc.iso8601
39
+ end
40
+ end
41
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Faktory
3
- VERSION = "0.7.1"
3
+ VERSION = "1.0.2"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faktory_worker_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-23 00:00:00.000000000 Z
11
+ date: 2021-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '2.2'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 2.2.1
22
+ version: 2.2.2
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,21 +29,21 @@ dependencies:
29
29
  version: '2.2'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 2.2.1
32
+ version: 2.2.2
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activejob
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 5.1.5
39
+ version: 5.2.0
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 5.1.5
46
+ version: 5.2.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: minitest
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -76,16 +76,16 @@ dependencies:
76
76
  name: rake
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: '12'
81
+ version: '0'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '12'
88
+ version: '0'
89
89
  description: Ruby worker for Faktory.
90
90
  email:
91
91
  - mike@contribsys.com
@@ -95,6 +95,7 @@ extensions: []
95
95
  extra_rdoc_files: []
96
96
  files:
97
97
  - ".gitignore"
98
+ - ".travis.yml"
98
99
  - Changes.md
99
100
  - Gemfile
100
101
  - Gemfile.lock
@@ -105,21 +106,26 @@ files:
105
106
  - faktory_worker_ruby.gemspec
106
107
  - lib/active_job/queue_adapters/faktory_adapter.rb
107
108
  - lib/faktory.rb
109
+ - lib/faktory/batch.rb
108
110
  - lib/faktory/cli.rb
109
111
  - lib/faktory/client.rb
110
112
  - lib/faktory/connection.rb
111
113
  - lib/faktory/exception_handler.rb
112
114
  - lib/faktory/fetch.rb
115
+ - lib/faktory/io.rb
113
116
  - lib/faktory/job.rb
114
117
  - lib/faktory/job_logger.rb
115
118
  - lib/faktory/launcher.rb
116
119
  - lib/faktory/logging.rb
117
120
  - lib/faktory/manager.rb
121
+ - lib/faktory/middleware/batch.rb
118
122
  - lib/faktory/middleware/chain.rb
119
123
  - lib/faktory/middleware/i18n.rb
124
+ - lib/faktory/mutate.rb
120
125
  - lib/faktory/processor.rb
121
126
  - lib/faktory/rails.rb
122
127
  - lib/faktory/testing.rb
128
+ - lib/faktory/tracking.rb
123
129
  - lib/faktory/util.rb
124
130
  - lib/faktory/version.rb
125
131
  - lib/faktory_worker_ruby.rb
@@ -127,7 +133,7 @@ homepage: https://github.com/contribsys/faktory_worker_ruby
127
133
  licenses:
128
134
  - LGPL-3.0
129
135
  metadata: {}
130
- post_install_message:
136
+ post_install_message:
131
137
  rdoc_options: []
132
138
  require_paths:
133
139
  - lib
@@ -135,16 +141,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
141
  requirements:
136
142
  - - ">="
137
143
  - !ruby/object:Gem::Version
138
- version: 2.2.2
144
+ version: 2.5.0
139
145
  required_rubygems_version: !ruby/object:Gem::Requirement
140
146
  requirements:
141
147
  - - ">="
142
148
  - !ruby/object:Gem::Version
143
149
  version: '0'
144
150
  requirements: []
145
- rubyforge_project:
146
- rubygems_version: 2.5.1
147
- signing_key:
151
+ rubygems_version: 3.2.8
152
+ signing_key:
148
153
  specification_version: 4
149
154
  summary: Ruby worker for Faktory
150
155
  test_files: []