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.
- checksums.yaml +5 -5
- data/.travis.yml +11 -0
- data/Changes.md +28 -3
- data/Gemfile +2 -0
- data/Gemfile.lock +26 -17
- data/README.md +10 -5
- data/faktory_worker_ruby.gemspec +4 -4
- data/lib/active_job/queue_adapters/faktory_adapter.rb +26 -18
- data/lib/faktory.rb +3 -1
- data/lib/faktory/batch.rb +178 -0
- data/lib/faktory/cli.rb +9 -0
- data/lib/faktory/client.rb +158 -35
- data/lib/faktory/connection.rb +1 -1
- data/lib/faktory/io.rb +51 -0
- data/lib/faktory/job.rb +41 -29
- data/lib/faktory/launcher.rb +20 -5
- data/lib/faktory/logging.rb +2 -2
- data/lib/faktory/manager.rb +3 -0
- data/lib/faktory/middleware/batch.rb +38 -0
- data/lib/faktory/middleware/i18n.rb +2 -4
- data/lib/faktory/mutate.rb +85 -0
- data/lib/faktory/processor.rb +6 -4
- data/lib/faktory/rails.rb +21 -0
- data/lib/faktory/testing.rb +2 -2
- data/lib/faktory/tracking.rb +41 -0
- data/lib/faktory/version.rb +1 -1
- metadata +21 -16
data/lib/faktory/launcher.rb
CHANGED
|
@@ -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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
35
|
+
@current_state = 'terminate'
|
|
36
36
|
@manager.quiet
|
|
37
37
|
@manager.stop(deadline)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def stopping?
|
|
41
|
-
@
|
|
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
|
data/lib/faktory/logging.rb
CHANGED
|
@@ -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
|
|
33
|
-
"#{klass} JID-#{job_hash['jid'
|
|
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)
|
data/lib/faktory/manager.rb
CHANGED
|
@@ -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
|
-
|
|
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)
|
data/lib/faktory/processor.rb
CHANGED
|
@@ -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
|
-
#
|
|
154
|
-
#
|
|
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
|
data/lib/faktory/testing.rb
CHANGED
|
@@ -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
|
data/lib/faktory/version.rb
CHANGED
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
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.
|
|
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
|
-
|
|
146
|
-
|
|
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: []
|