faktory_worker_ruby 0.8.0 → 1.0.3
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/Changes.md +26 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -15
- data/README.md +8 -5
- data/faktory_worker_ruby.gemspec +4 -4
- data/lib/active_job/queue_adapters/faktory_adapter.rb +6 -2
- data/lib/faktory.rb +3 -1
- data/lib/faktory/batch.rb +178 -0
- data/lib/faktory/cli.rb +5 -0
- data/lib/faktory/client.rb +156 -34
- data/lib/faktory/connection.rb +1 -1
- data/lib/faktory/io.rb +51 -0
- data/lib/faktory/job.rb +47 -30
- data/lib/faktory/launcher.rb +10 -5
- data/lib/faktory/logging.rb +1 -1
- data/lib/faktory/manager.rb +3 -0
- data/lib/faktory/middleware/batch.rb +38 -0
- data/lib/faktory/mutate.rb +91 -0
- data/lib/faktory/processor.rb +6 -4
- data/lib/faktory/rails.rb +11 -1
- data/lib/faktory/testing.rb +2 -2
- data/lib/faktory/tracking.rb +41 -0
- data/lib/faktory/version.rb +1 -1
- metadata +20 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2a183f21103562c224add2460da73a145a0c12c32a167525fd18dd02fe334241
|
4
|
+
data.tar.gz: b7c76d7001ba133f685427ffcc00bcdd75d026747106fac705b741e57f3d7b94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bae416fa9d2675c586a8a40ccce0005f665eb2ab2bc19e6a5e12405cafee67aef22984d9e84f6db96012143eee87307d81e089fad5fd81d6dd278489fe62437
|
7
|
+
data.tar.gz: 42806775422a7542a1a7c7e912bc373dfe8c0d2b1d47d8cccb3ce8d91a0c1a95f8a835cb99cc1b9da2262d17ac3f50e7447ecefcf4eff6ba3ef0d9672e8b1f06
|
data/Changes.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 1.0.3
|
4
|
+
|
5
|
+
- Fix corruption in `custom` hash elements [#55]
|
6
|
+
|
7
|
+
## 1.0.2
|
8
|
+
|
9
|
+
- Fix "batch not open" errors
|
10
|
+
|
11
|
+
## 1.0.1
|
12
|
+
|
13
|
+
- Run client middleware before pushing a job to Faktory [#48]
|
14
|
+
- Implement read timeouts for Faktory::Client for faktory#297
|
15
|
+
|
16
|
+
## 1.0.0
|
17
|
+
|
18
|
+
- Ruby 2.5+ is now required
|
19
|
+
- Support for Faktory Enterprise, job batches and job tracking
|
20
|
+
- Support for the MUTATE command.
|
21
|
+
- Notify Faktory when a worker process is going quiet so that the UI shows this
|
22
|
+
- Refactor Faktory::Client error handling for faktory#208
|
23
|
+
|
24
|
+
## 0.8.1
|
25
|
+
|
26
|
+
- Fix breakage with non-ActiveJobs [#29]
|
27
|
+
- Ruby 2.3+ is now required
|
28
|
+
|
3
29
|
## 0.8.0
|
4
30
|
|
5
31
|
- Add `-l LABEL` argument for adding labels to a process [#27, jpwinans]
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,43 +1,51 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
faktory_worker_ruby (0.
|
5
|
-
connection_pool (~> 2.2, >= 2.2.
|
4
|
+
faktory_worker_ruby (1.0.2)
|
5
|
+
connection_pool (~> 2.2, >= 2.2.2)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activejob (
|
11
|
-
activesupport (=
|
10
|
+
activejob (6.0.3.2)
|
11
|
+
activesupport (= 6.0.3.2)
|
12
12
|
globalid (>= 0.3.6)
|
13
|
-
activesupport (
|
13
|
+
activesupport (6.0.3.2)
|
14
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
15
|
i18n (>= 0.7, < 2)
|
16
16
|
minitest (~> 5.1)
|
17
17
|
tzinfo (~> 1.1)
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
19
|
+
concurrent-ruby (1.1.6)
|
20
|
+
connection_pool (2.2.3)
|
21
|
+
docile (1.3.2)
|
22
|
+
globalid (0.4.2)
|
21
23
|
activesupport (>= 4.2.0)
|
22
|
-
i18n (1.
|
24
|
+
i18n (1.8.5)
|
23
25
|
concurrent-ruby (~> 1.0)
|
24
|
-
minitest (5.
|
26
|
+
minitest (5.14.1)
|
25
27
|
minitest-hooks (1.5.0)
|
26
28
|
minitest (> 5.3)
|
27
|
-
rake (
|
29
|
+
rake (13.0.1)
|
30
|
+
simplecov (0.18.5)
|
31
|
+
docile (~> 1.1)
|
32
|
+
simplecov-html (~> 0.11)
|
33
|
+
simplecov-html (0.12.2)
|
28
34
|
thread_safe (0.3.6)
|
29
|
-
tzinfo (1.2.
|
35
|
+
tzinfo (1.2.7)
|
30
36
|
thread_safe (~> 0.1)
|
37
|
+
zeitwerk (2.4.0)
|
31
38
|
|
32
39
|
PLATFORMS
|
33
40
|
ruby
|
34
41
|
|
35
42
|
DEPENDENCIES
|
36
|
-
activejob (>= 5.
|
43
|
+
activejob (>= 5.2.0)
|
37
44
|
faktory_worker_ruby!
|
38
45
|
minitest (~> 5)
|
39
46
|
minitest-hooks
|
40
|
-
rake
|
47
|
+
rake
|
48
|
+
simplecov
|
41
49
|
|
42
50
|
BUNDLED WITH
|
43
|
-
1.
|
51
|
+
2.1.4
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ Faktory background job server. It is similar to [Sidekiq](http://sidekiq.org).
|
|
17
17
|
+-----------------+ +-------------------+
|
18
18
|
| | | |
|
19
19
|
| Client | | Worker |
|
20
|
-
| pushes | |
|
20
|
+
| pushes | | fetches |
|
21
21
|
| jobs | | jobs |
|
22
22
|
| | | |
|
23
23
|
| | | |
|
@@ -32,11 +32,14 @@ Faktory background job server. It is similar to [Sidekiq](http://sidekiq.org).
|
|
32
32
|
This gem contains only the client and worker parts. The
|
33
33
|
server part is [here](https://github.com/contribsys/faktory/)
|
34
34
|
|
35
|
-
##
|
35
|
+
## Requirements
|
36
|
+
|
37
|
+
* Ruby 2.5 or higher
|
38
|
+
* Faktory 1.2 or higher [Installation](https://github.com/contribsys/faktory/wiki/Installation)
|
36
39
|
|
37
|
-
|
40
|
+
Optionally, Rails 5.2+ for ActiveJob.
|
38
41
|
|
39
|
-
|
42
|
+
## Installation
|
40
43
|
|
41
44
|
gem install faktory_worker_ruby
|
42
45
|
|
@@ -94,4 +97,4 @@ PRs to improve this are very welcome).
|
|
94
97
|
|
95
98
|
## Author
|
96
99
|
|
97
|
-
Mike Perham, @
|
100
|
+
Mike Perham, @getajobmike, mike @ contribsys.com
|
data/faktory_worker_ruby.gemspec
CHANGED
@@ -14,11 +14,11 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.files = `git ls-files | grep -Ev '^(test|myapp|examples)'`.split("\n")
|
15
15
|
gem.test_files = []
|
16
16
|
gem.version = Faktory::VERSION
|
17
|
-
gem.required_ruby_version = ">= 2.
|
17
|
+
gem.required_ruby_version = ">= 2.5.0"
|
18
18
|
|
19
|
-
gem.add_dependency 'connection_pool', '~> 2.2', ">= 2.2.
|
20
|
-
gem.add_development_dependency 'activejob', '>= 5.
|
19
|
+
gem.add_dependency 'connection_pool', '~> 2.2', ">= 2.2.2"
|
20
|
+
gem.add_development_dependency 'activejob', '>= 5.2.0'
|
21
21
|
gem.add_development_dependency 'minitest', '~> 5'
|
22
22
|
gem.add_development_dependency 'minitest-hooks'
|
23
|
-
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rake'
|
24
24
|
end
|
@@ -32,8 +32,12 @@ module ActiveJob
|
|
32
32
|
hash["retry"] = opts.delete("retry") if opts.has_key?("retry")
|
33
33
|
hash["custom"] = opts.merge(hash["custom"])
|
34
34
|
end
|
35
|
-
|
36
|
-
Faktory
|
35
|
+
pool = Thread.current[:faktory_via_pool] || Faktory.server_pool
|
36
|
+
Faktory.client_middleware.invoke(hash, pool) do
|
37
|
+
pool.with do |c|
|
38
|
+
c.push(hash)
|
39
|
+
end
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
class JobWrapper #:nodoc:
|
data/lib/faktory.rb
CHANGED
@@ -52,6 +52,7 @@ module Faktory
|
|
52
52
|
# config.worker_middleware do |chain|
|
53
53
|
# chain.add MyServerHook
|
54
54
|
# end
|
55
|
+
# config.default_job_options = { retry: 3 }
|
55
56
|
# end
|
56
57
|
def self.configure_worker
|
57
58
|
yield self if worker?
|
@@ -61,7 +62,7 @@ module Faktory
|
|
61
62
|
# Configuration for Faktory client, use like:
|
62
63
|
#
|
63
64
|
# Faktory.configure_client do |config|
|
64
|
-
# config.
|
65
|
+
# config.default_job_options = { retry: 3 }
|
65
66
|
# end
|
66
67
|
def self.configure_client
|
67
68
|
yield self unless worker?
|
@@ -164,3 +165,4 @@ module Faktory
|
|
164
165
|
end
|
165
166
|
|
166
167
|
require 'faktory/rails' if defined?(::Rails::Engine)
|
168
|
+
require 'faktory/batch'
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require "faktory/middleware/batch"
|
2
|
+
|
3
|
+
module Faktory
|
4
|
+
##
|
5
|
+
# A Batch is a set of jobs which can be tracked as a group, with
|
6
|
+
# callbacks that can fire after all the jobs are attempted or successful.
|
7
|
+
# Every batch must define at least one callback.
|
8
|
+
#
|
9
|
+
# * The "complete" callback is fired when all jobs in the batch have been attempted.
|
10
|
+
# Some might have failed.
|
11
|
+
# * The "success" callback is fired when all jobs in the batch have succeeded. This
|
12
|
+
# might never be fired if a job continues to error until it runs out of retries.
|
13
|
+
#
|
14
|
+
# **Please note that batches are only available in Faktory Enterprise.** This is
|
15
|
+
# the client-side code required to implement batches, it won't work without
|
16
|
+
# the server-side component.
|
17
|
+
#
|
18
|
+
# Simple example:
|
19
|
+
#
|
20
|
+
# b = Faktory::Batch.new
|
21
|
+
# b.description = "Process all documents for user 12345"
|
22
|
+
# # a callback can be defined as just a Ruby job class
|
23
|
+
# b.success = "MySuccessCallbackJob"
|
24
|
+
# # or the full job hash...
|
25
|
+
# b.complete = { jobtype: "MyCompleteCallbackJob", args: [12345], queue: "critical" }
|
26
|
+
# b.jobs do
|
27
|
+
# SomeJob.perform_async(xyz)
|
28
|
+
# AnotherJob.perform_async(user_id)
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# At the end of the `jobs` call, the batch is persisted to the Faktory server. It must
|
32
|
+
# not be modified further with one exception: jobs within the batch can "reopen" the batch
|
33
|
+
# in order to dynamically add more jobs or child batches.
|
34
|
+
#
|
35
|
+
# Any job within a batch may "reopen" its own batch to dynamically add more jobs.
|
36
|
+
# A job can get access to its batch by using the `bid` or `batch` accessor on
|
37
|
+
# `Faktory::Job`. You can use the `bid` accessor to test if the job is part of a batch.
|
38
|
+
#
|
39
|
+
# Reopen example:
|
40
|
+
#
|
41
|
+
# class MyJob
|
42
|
+
# include Faktory::Job
|
43
|
+
#
|
44
|
+
# def perform
|
45
|
+
# batch.jobs do
|
46
|
+
# SomeOtherJob.perform_async
|
47
|
+
# end if bid
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Batches may be nested without limit by setting `parent_bid` when creating a
|
51
|
+
# batch. Generally you create child batches if you wish that subset of jobs to have
|
52
|
+
# their own callback for your application logic purposes. Otherwise you can reopen the
|
53
|
+
# current batch and add more jobs.
|
54
|
+
#
|
55
|
+
# Batch parent/child relationship is never implicit: you must manually set
|
56
|
+
# `parent_bid` if you wish to define a child batch.
|
57
|
+
#
|
58
|
+
# Nested example:
|
59
|
+
#
|
60
|
+
# class MyJob
|
61
|
+
# include Faktory::Job
|
62
|
+
#
|
63
|
+
# def perform
|
64
|
+
# child = Faktory::Batch.new
|
65
|
+
#
|
66
|
+
# # MyJob is executing as part of a previously defined batch.
|
67
|
+
# # Add a new child batch to this batch.
|
68
|
+
# child.parent_bid = bid
|
69
|
+
# child.success = ...
|
70
|
+
# child.jobs do |cb|
|
71
|
+
# SomeJob.perform_async
|
72
|
+
#
|
73
|
+
# gchild = Faktory::Batch.new
|
74
|
+
# gchild.parent_bid = cb.bid
|
75
|
+
# gchild.success = ...
|
76
|
+
# gchild.jobs do |gcb|
|
77
|
+
# ChildJob.perform_async
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Callbacks are guaranteed to be called hierarchically: child's success callback
|
84
|
+
# will not be called until gchild's success callback has executed successfully.
|
85
|
+
#
|
86
|
+
class Batch
|
87
|
+
attr_reader :bid
|
88
|
+
attr_accessor :description, :parent_bid
|
89
|
+
|
90
|
+
def initialize(bid=nil)
|
91
|
+
@bid = bid
|
92
|
+
end
|
93
|
+
|
94
|
+
def success=(val)
|
95
|
+
raise "Batch cannot be modified once created" if bid
|
96
|
+
@success = to_callback(val)
|
97
|
+
end
|
98
|
+
|
99
|
+
def complete=(val)
|
100
|
+
raise "Batch cannot be modified once created" if bid
|
101
|
+
@success = to_callback(val)
|
102
|
+
end
|
103
|
+
|
104
|
+
def jobs(&block)
|
105
|
+
Faktory.server do |client|
|
106
|
+
if @bid.nil?
|
107
|
+
@bid = client.create_batch(self, &block)
|
108
|
+
else
|
109
|
+
client.reopen_batch(self, &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_h
|
115
|
+
raise ArgumentError, "Callback required" unless defined?(@success) || defined?(@complete)
|
116
|
+
|
117
|
+
hash = {}
|
118
|
+
hash["parent_bid"] = parent_bid if parent_bid
|
119
|
+
hash["description"] = description if description
|
120
|
+
hash["success"] = @success if defined?(@success)
|
121
|
+
hash["complete"] = @complete if defined?(@complete)
|
122
|
+
hash
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def to_callback(val)
|
128
|
+
case val
|
129
|
+
when String
|
130
|
+
basic_job.merge({ "jobtype" => val })
|
131
|
+
when Class
|
132
|
+
basic_job.merge({ "jobtype" => val })
|
133
|
+
when Hash
|
134
|
+
basic_job.merge(val)
|
135
|
+
else
|
136
|
+
raise ArgumentError, "Unknown callback #{val}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def basic_job
|
141
|
+
{
|
142
|
+
"jid" => SecureRandom.hex(12),
|
143
|
+
"args" => [],
|
144
|
+
"queue" => "default",
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class BatchStatus
|
150
|
+
def initialize(bid)
|
151
|
+
@bid = bid
|
152
|
+
end
|
153
|
+
|
154
|
+
def hash
|
155
|
+
@hash ||= Faktory.server{|c| c.batch_status(@bid) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def created_at
|
159
|
+
hash["created_at"]
|
160
|
+
end
|
161
|
+
|
162
|
+
def description
|
163
|
+
hash["description"]
|
164
|
+
end
|
165
|
+
|
166
|
+
def parent_bid
|
167
|
+
hash["parent_bid"]
|
168
|
+
end
|
169
|
+
|
170
|
+
def total
|
171
|
+
hash["total"]
|
172
|
+
end
|
173
|
+
|
174
|
+
def pending
|
175
|
+
hash["pending"]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/faktory/cli.rb
CHANGED
data/lib/faktory/client.rb
CHANGED
@@ -3,14 +3,27 @@ require 'json'
|
|
3
3
|
require 'uri'
|
4
4
|
require 'digest'
|
5
5
|
require 'securerandom'
|
6
|
+
require 'timeout'
|
7
|
+
require 'faktory/io'
|
6
8
|
|
7
9
|
module Faktory
|
8
|
-
class
|
9
|
-
class
|
10
|
-
|
10
|
+
class BaseError < StandardError; end
|
11
|
+
class CommandError < BaseError; end
|
12
|
+
class ParseError < BaseError; end
|
13
|
+
|
14
|
+
# Faktory::Client provides a low-level connection to a Faktory server
|
15
|
+
# and APIs which map to Faktory commands.
|
16
|
+
#
|
17
|
+
# Most APIs will return `true` if the operation succeeded or raise a
|
18
|
+
# Faktory::BaseError if there was an unexpected error.
|
11
19
|
class Client
|
20
|
+
# provides gets() and read() that respect a read timeout
|
21
|
+
include Faktory::ReadTimeout
|
22
|
+
|
12
23
|
@@random_process_wid = ""
|
13
24
|
|
25
|
+
DEFAULT_TIMEOUT = 5.0
|
26
|
+
|
14
27
|
HASHER = proc do |iter, pwd, salt|
|
15
28
|
sha = Digest::SHA256.new
|
16
29
|
hashing = pwd + salt
|
@@ -36,10 +49,13 @@ module Faktory
|
|
36
49
|
# MY_FAKTORY_URL=tcp://:somepass@my-server.example.com:7419
|
37
50
|
#
|
38
51
|
# Note above, the URL can contain the password for secure installations.
|
39
|
-
def initialize(url: uri_from_env || 'tcp://localhost:7419', debug: false)
|
52
|
+
def initialize(url: uri_from_env || 'tcp://localhost:7419', debug: false, timeout: DEFAULT_TIMEOUT)
|
53
|
+
super
|
40
54
|
@debug = debug
|
41
55
|
@location = URI(url)
|
42
|
-
|
56
|
+
@timeout = timeout
|
57
|
+
|
58
|
+
open(@timeout)
|
43
59
|
end
|
44
60
|
|
45
61
|
def close
|
@@ -53,23 +69,95 @@ module Faktory
|
|
53
69
|
def flush
|
54
70
|
transaction do
|
55
71
|
command "FLUSH"
|
56
|
-
ok
|
72
|
+
ok
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_batch(batch, &block)
|
77
|
+
bid = transaction do
|
78
|
+
command "BATCH NEW", Faktory.dump_json(batch.to_h)
|
79
|
+
result!
|
80
|
+
end
|
81
|
+
batch.instance_variable_set(:@bid, bid)
|
82
|
+
|
83
|
+
old = Thread.current[:faktory_batch]
|
84
|
+
begin
|
85
|
+
Thread.current[:faktory_batch] = batch
|
86
|
+
# any jobs pushed in this block will implicitly have
|
87
|
+
# their `bid` attribute set so they are associated
|
88
|
+
# with the current batch.
|
89
|
+
yield batch
|
90
|
+
ensure
|
91
|
+
Thread.current[:faktory_batch] = old
|
92
|
+
end
|
93
|
+
transaction do
|
94
|
+
command "BATCH COMMIT", bid
|
95
|
+
ok
|
57
96
|
end
|
97
|
+
bid
|
58
98
|
end
|
59
99
|
|
100
|
+
def batch_status(bid)
|
101
|
+
transaction do
|
102
|
+
command "BATCH STATUS", bid
|
103
|
+
Faktory.load_json result!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def reopen_batch(b)
|
108
|
+
transaction do
|
109
|
+
command "BATCH OPEN", b.bid
|
110
|
+
ok
|
111
|
+
end
|
112
|
+
old = Thread.current[:faktory_batch]
|
113
|
+
begin
|
114
|
+
Thread.current[:faktory_batch] = b
|
115
|
+
# any jobs pushed in this block will implicitly have
|
116
|
+
# their `bid` attribute set so they are associated
|
117
|
+
# with the current batch.
|
118
|
+
yield b
|
119
|
+
ensure
|
120
|
+
Thread.current[:faktory_batch] = old
|
121
|
+
end
|
122
|
+
transaction do
|
123
|
+
command "BATCH COMMIT", b.bid
|
124
|
+
ok
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_track(jid)
|
129
|
+
transaction do
|
130
|
+
command "TRACK GET", jid
|
131
|
+
hashstr = result!
|
132
|
+
JSON.parse(hashstr)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# hash must include a 'jid' element
|
137
|
+
def set_track(hash)
|
138
|
+
transaction do
|
139
|
+
command("TRACK SET", Faktory.dump_json(hash))
|
140
|
+
ok
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Push a hash corresponding to a job payload to Faktory.
|
145
|
+
# Hash must contain "jid", "jobtype" and "args" elements at minimum.
|
146
|
+
# Returned value will either be the JID String if successful OR
|
147
|
+
# a symbol corresponding to an error.
|
60
148
|
def push(job)
|
61
149
|
transaction do
|
62
|
-
command "PUSH",
|
63
|
-
ok
|
64
|
-
job["jid"]
|
150
|
+
command "PUSH", Faktory.dump_json(job)
|
151
|
+
ok(job["jid"])
|
65
152
|
end
|
66
153
|
end
|
67
154
|
|
155
|
+
# Returns either a job hash or falsy.
|
68
156
|
def fetch(*queues)
|
69
157
|
job = nil
|
70
158
|
transaction do
|
71
159
|
command("FETCH", *queues)
|
72
|
-
job = result
|
160
|
+
job = result!
|
73
161
|
end
|
74
162
|
JSON.parse(job) if job
|
75
163
|
end
|
@@ -77,34 +165,42 @@ module Faktory
|
|
77
165
|
def ack(jid)
|
78
166
|
transaction do
|
79
167
|
command("ACK", %Q[{"jid":"#{jid}"}])
|
80
|
-
ok
|
168
|
+
ok
|
81
169
|
end
|
82
170
|
end
|
83
171
|
|
84
172
|
def fail(jid, ex)
|
85
173
|
transaction do
|
86
|
-
command("FAIL",
|
174
|
+
command("FAIL", Faktory.dump_json({ message: ex.message[0...1000],
|
87
175
|
errtype: ex.class.name,
|
88
176
|
jid: jid,
|
89
177
|
backtrace: ex.backtrace}))
|
90
|
-
ok
|
178
|
+
ok
|
91
179
|
end
|
92
180
|
end
|
93
181
|
|
94
182
|
# Sends a heartbeat to the server, in order to prove this
|
95
183
|
# worker process is still alive.
|
96
184
|
#
|
185
|
+
# You can pass in the current_state of the process, for example during shutdown
|
186
|
+
# quiet and/or terminate can be supplied.
|
187
|
+
#
|
97
188
|
# Return a string signal to process, legal values are "quiet" or "terminate".
|
98
189
|
# The quiet signal is informative: the server won't allow this process to FETCH
|
99
190
|
# any more jobs anyways.
|
100
|
-
def beat
|
191
|
+
def beat(current_state = nil)
|
101
192
|
transaction do
|
102
|
-
|
103
|
-
|
193
|
+
if current_state.nil?
|
194
|
+
command("BEAT", %Q[{"wid":"#{@@random_process_wid}"}])
|
195
|
+
else
|
196
|
+
command("BEAT", %Q[{"wid":"#{@@random_process_wid}", "current_state":"#{current_state}"}])
|
197
|
+
end
|
198
|
+
|
199
|
+
str = result!
|
104
200
|
if str == "OK"
|
105
201
|
str
|
106
202
|
else
|
107
|
-
hash =
|
203
|
+
hash = Faktory.load_json(str)
|
108
204
|
hash["state"]
|
109
205
|
end
|
110
206
|
end
|
@@ -113,8 +209,8 @@ module Faktory
|
|
113
209
|
def info
|
114
210
|
transaction do
|
115
211
|
command("INFO")
|
116
|
-
str = result
|
117
|
-
|
212
|
+
str = result!
|
213
|
+
Faktory.load_json(str) if str
|
118
214
|
end
|
119
215
|
end
|
120
216
|
|
@@ -129,12 +225,14 @@ module Faktory
|
|
129
225
|
@location.scheme =~ /tls/
|
130
226
|
end
|
131
227
|
|
132
|
-
def open
|
228
|
+
def open(timeout = DEFAULT_TIMEOUT)
|
133
229
|
if tls?
|
134
230
|
sock = TCPSocket.new(@location.hostname, @location.port)
|
231
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
232
|
+
|
135
233
|
ctx = OpenSSL::SSL::SSLContext.new
|
136
234
|
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
137
|
-
ctx.
|
235
|
+
ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
138
236
|
|
139
237
|
@sock = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
|
140
238
|
socket.sync_close = true
|
@@ -176,11 +274,10 @@ module Faktory
|
|
176
274
|
end
|
177
275
|
end
|
178
276
|
|
179
|
-
command("HELLO",
|
180
|
-
ok
|
277
|
+
command("HELLO", Faktory.dump_json(payload))
|
278
|
+
ok
|
181
279
|
end
|
182
280
|
|
183
|
-
|
184
281
|
def command(*args)
|
185
282
|
cmd = args.join(" ")
|
186
283
|
@sock.puts(cmd)
|
@@ -189,12 +286,22 @@ module Faktory
|
|
189
286
|
|
190
287
|
def transaction
|
191
288
|
retryable = true
|
289
|
+
|
290
|
+
# When using Faktory::Testing, you can get a client which does not actually
|
291
|
+
# have an underlying socket. Now if you disable testing and try to use that
|
292
|
+
# client, it will crash without a socket. This open() handles that case to
|
293
|
+
# transparently open a socket.
|
294
|
+
open(@timeout) if !@sock
|
295
|
+
|
192
296
|
begin
|
193
297
|
yield
|
194
|
-
rescue
|
298
|
+
rescue SystemCallError, SocketError, TimeoutError
|
195
299
|
if retryable
|
196
300
|
retryable = false
|
197
|
-
|
301
|
+
|
302
|
+
@sock.close rescue nil
|
303
|
+
@sock = nil
|
304
|
+
open(@timeout)
|
198
305
|
retry
|
199
306
|
else
|
200
307
|
raise
|
@@ -205,7 +312,7 @@ module Faktory
|
|
205
312
|
# I love pragmatic, simple protocols. Thanks antirez!
|
206
313
|
# https://redis.io/topics/protocol
|
207
314
|
def result
|
208
|
-
line =
|
315
|
+
line = gets
|
209
316
|
debug "< #{line}" if @debug
|
210
317
|
raise Errno::ECONNRESET, "No response" unless line
|
211
318
|
chr = line[0]
|
@@ -214,11 +321,20 @@ module Faktory
|
|
214
321
|
elsif chr == '$'
|
215
322
|
count = line[1..-1].strip.to_i
|
216
323
|
return nil if count == -1
|
217
|
-
data =
|
218
|
-
line =
|
324
|
+
data = read(count) if count > 0
|
325
|
+
line = gets # read extra linefeeds
|
219
326
|
data
|
220
327
|
elsif chr == '-'
|
221
|
-
|
328
|
+
# Server can respond with:
|
329
|
+
#
|
330
|
+
# -ERR Something unexpected
|
331
|
+
# We raise a CommandError
|
332
|
+
#
|
333
|
+
# -NOTUNIQUE Job not unique
|
334
|
+
# We return ["NOTUNIQUE", "Job not unique"]
|
335
|
+
err = line[1..-1].split(" ", 2)
|
336
|
+
raise CommandError, err[1] if err[0] == "ERR"
|
337
|
+
err
|
222
338
|
else
|
223
339
|
# this is bad, indicates we need to reset the socket
|
224
340
|
# and start fresh
|
@@ -226,10 +342,17 @@ module Faktory
|
|
226
342
|
end
|
227
343
|
end
|
228
344
|
|
229
|
-
def ok
|
345
|
+
def ok(retval=true)
|
230
346
|
resp = result
|
231
|
-
|
232
|
-
|
347
|
+
return retval if resp == "OK"
|
348
|
+
return resp[0].to_sym
|
349
|
+
end
|
350
|
+
|
351
|
+
def result!
|
352
|
+
resp = result
|
353
|
+
return nil if resp == nil
|
354
|
+
raise CommandError, resp[0] if !resp.is_a?(String)
|
355
|
+
resp
|
233
356
|
end
|
234
357
|
|
235
358
|
# FAKTORY_PROVIDER=MY_FAKTORY_URL
|
@@ -253,4 +376,3 @@ module Faktory
|
|
253
376
|
|
254
377
|
end
|
255
378
|
end
|
256
|
-
|