faktory_worker_ruby 0.7.0 → 1.0.1

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
@@ -1,6 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
  module Faktory
3
3
  class Rails < ::Rails::Engine
4
+ # This hook happens after `Rails::Application` is inherited within
5
+ # config/application.rb and before config is touched, usually within the
6
+ # class block. Definitely before config/environments/*.rb and
7
+ # config/initializers/*.rb.
8
+ config.before_configuration do
9
+ if defined?(::ActiveJob)
10
+ require 'active_job/queue_adapters/faktory_adapter'
11
+ end
12
+ end
13
+
4
14
  config.after_initialize do
5
15
  # This hook happens after all initializers are run, just before returning
6
16
  # from config/environment.rb back to faktory/cli.rb.
@@ -10,8 +20,22 @@ module Faktory
10
20
  # None of this matters on the client-side, only within the Faktory executor itself.
11
21
  #
12
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
+
13
27
  Faktory.options[:reloader] = Faktory::Rails::Reloader.new
14
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
15
39
  end
16
40
 
17
41
  class Reloader
@@ -30,4 +54,11 @@ module Faktory
30
54
  end
31
55
  end
32
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
33
64
  end
@@ -31,7 +31,7 @@ module Faktory
31
31
  end
32
32
 
33
33
  def self.inline!(&block)
34
- # Only allow blockless inline via `blockless_inline_is_a_bad_idea_but_I_wanna_do_it_anyway!`
34
+ # Only allow inline testing inside of a block
35
35
  # https://github.com/mperham/sidekiq/issues/3495
36
36
  unless block_given?
37
37
  raise 'Must provide a block to Faktory::Testing.inline!'
@@ -40,10 +40,6 @@ module Faktory
40
40
  __set_test_mode(:inline, &block)
41
41
  end
42
42
 
43
- def self.blockless_inline_is_a_bad_idea_but_I_wanna_do_it_anyway!
44
- __set_test_mode(:inline)
45
- end
46
-
47
43
  def self.enabled?
48
44
  self.__test_mode != :disable
49
45
  end
@@ -98,9 +94,9 @@ module Faktory
98
94
  end
99
95
  end
100
96
 
101
- def open
97
+ def open(*args)
102
98
  unless Faktory::Testing.enabled?
103
- real_open
99
+ real_open(*args)
104
100
  end
105
101
  end
106
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.0"
3
+ VERSION = "1.0.1"
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.0
4
+ version: 1.0.1
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-17 00:00:00.000000000 Z
11
+ date: 2021-02-03 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,7 +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
+ - !ruby/object:Gem::Dependency
34
+ name: activejob
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 5.2.0
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 5.2.0
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: minitest
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -62,16 +76,16 @@ dependencies:
62
76
  name: rake
63
77
  requirement: !ruby/object:Gem::Requirement
64
78
  requirements:
65
- - - "~>"
79
+ - - ">="
66
80
  - !ruby/object:Gem::Version
67
- version: '12'
81
+ version: '0'
68
82
  type: :development
69
83
  prerelease: false
70
84
  version_requirements: !ruby/object:Gem::Requirement
71
85
  requirements:
72
- - - "~>"
86
+ - - ">="
73
87
  - !ruby/object:Gem::Version
74
- version: '12'
88
+ version: '0'
75
89
  description: Ruby worker for Faktory.
76
90
  email:
77
91
  - mike@contribsys.com
@@ -81,6 +95,7 @@ extensions: []
81
95
  extra_rdoc_files: []
82
96
  files:
83
97
  - ".gitignore"
98
+ - ".travis.yml"
84
99
  - Changes.md
85
100
  - Gemfile
86
101
  - Gemfile.lock
@@ -89,22 +104,28 @@ files:
89
104
  - Rakefile
90
105
  - bin/faktory-worker
91
106
  - faktory_worker_ruby.gemspec
107
+ - lib/active_job/queue_adapters/faktory_adapter.rb
92
108
  - lib/faktory.rb
109
+ - lib/faktory/batch.rb
93
110
  - lib/faktory/cli.rb
94
111
  - lib/faktory/client.rb
95
112
  - lib/faktory/connection.rb
96
113
  - lib/faktory/exception_handler.rb
97
114
  - lib/faktory/fetch.rb
115
+ - lib/faktory/io.rb
98
116
  - lib/faktory/job.rb
99
117
  - lib/faktory/job_logger.rb
100
118
  - lib/faktory/launcher.rb
101
119
  - lib/faktory/logging.rb
102
120
  - lib/faktory/manager.rb
121
+ - lib/faktory/middleware/batch.rb
103
122
  - lib/faktory/middleware/chain.rb
104
123
  - lib/faktory/middleware/i18n.rb
124
+ - lib/faktory/mutate.rb
105
125
  - lib/faktory/processor.rb
106
126
  - lib/faktory/rails.rb
107
127
  - lib/faktory/testing.rb
128
+ - lib/faktory/tracking.rb
108
129
  - lib/faktory/util.rb
109
130
  - lib/faktory/version.rb
110
131
  - lib/faktory_worker_ruby.rb
@@ -112,7 +133,7 @@ homepage: https://github.com/contribsys/faktory_worker_ruby
112
133
  licenses:
113
134
  - LGPL-3.0
114
135
  metadata: {}
115
- post_install_message:
136
+ post_install_message:
116
137
  rdoc_options: []
117
138
  require_paths:
118
139
  - lib
@@ -120,16 +141,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
141
  requirements:
121
142
  - - ">="
122
143
  - !ruby/object:Gem::Version
123
- version: 2.2.2
144
+ version: 2.5.0
124
145
  required_rubygems_version: !ruby/object:Gem::Requirement
125
146
  requirements:
126
147
  - - ">="
127
148
  - !ruby/object:Gem::Version
128
149
  version: '0'
129
150
  requirements: []
130
- rubyforge_project:
131
- rubygems_version: 2.6.13
132
- signing_key:
151
+ rubygems_version: 3.2.6
152
+ signing_key:
133
153
  specification_version: 4
134
154
  summary: Ruby worker for Faktory
135
155
  test_files: []