faktory_worker_ruby 0.8.0 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5ffc1c6d5392fd64f4463ae4a47972f7d2c13ebc
4
- data.tar.gz: cc1e0294bc5c333f7ce684f112ee1182e20d2095
2
+ SHA256:
3
+ metadata.gz: 2a183f21103562c224add2460da73a145a0c12c32a167525fd18dd02fe334241
4
+ data.tar.gz: b7c76d7001ba133f685427ffcc00bcdd75d026747106fac705b741e57f3d7b94
5
5
  SHA512:
6
- metadata.gz: 9f2a3b5f59d0a50c77f0f22ec86f291289762ba5e27dc012fa9cce34807883aee616b7ca9b7a937139b67137725f450c0e7fc504909f0a84136ebd4b6e67586e
7
- data.tar.gz: 7ef1fd93ff1d803bb7d895d63749a3deb9cd4fe4000936df5b8e1facfca884e22e439574979b821972936b16b1ea15ef31f97c3db306b90b4d7514ea214d1437
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
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'simplecov', require: false, group: :test
4
+
3
5
  gemspec
data/Gemfile.lock CHANGED
@@ -1,43 +1,51 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- faktory_worker_ruby (0.8.0)
5
- connection_pool (~> 2.2, >= 2.2.1)
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 (5.2.1)
11
- activesupport (= 5.2.1)
10
+ activejob (6.0.3.2)
11
+ activesupport (= 6.0.3.2)
12
12
  globalid (>= 0.3.6)
13
- activesupport (5.2.1)
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
- concurrent-ruby (1.0.5)
19
- connection_pool (2.2.2)
20
- globalid (0.4.1)
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.1.0)
24
+ i18n (1.8.5)
23
25
  concurrent-ruby (~> 1.0)
24
- minitest (5.11.3)
26
+ minitest (5.14.1)
25
27
  minitest-hooks (1.5.0)
26
28
  minitest (> 5.3)
27
- rake (12.3.1)
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.5)
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.1.5)
43
+ activejob (>= 5.2.0)
37
44
  faktory_worker_ruby!
38
45
  minitest (~> 5)
39
46
  minitest-hooks
40
- rake (~> 12)
47
+ rake
48
+ simplecov
41
49
 
42
50
  BUNDLED WITH
43
- 1.16.3
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 | | pulls |
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
- ## Installation
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
- First, make sure you have the [Faktory server](https://github.com/contribsys/faktory/#installation) installed.
40
+ Optionally, Rails 5.2+ for ActiveJob.
38
41
 
39
- Next, install this gem:
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, @mperham, mike @ contribsys.com
100
+ Mike Perham, @getajobmike, mike @ contribsys.com
@@ -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.2.2"
17
+ gem.required_ruby_version = ">= 2.5.0"
18
18
 
19
- gem.add_dependency 'connection_pool', '~> 2.2', ">= 2.2.1"
20
- gem.add_development_dependency 'activejob', '>= 5.1.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', '~> 12'
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
- # Faktory::Client does not support symbols as keys
36
- Faktory::Client.new.push(hash)
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.faktory = { :size => 1, :url => 'myhost:7419' }
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
@@ -8,6 +8,11 @@ require 'optparse'
8
8
  require 'erb'
9
9
  require 'fileutils'
10
10
 
11
+ module Faktory
12
+ class CLI
13
+ end
14
+ end
15
+
11
16
  require 'faktory'
12
17
  require 'faktory/util'
13
18
 
@@ -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 CommandError < StandardError;end
9
- class ParseError < StandardError;end
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
- open
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", JSON.generate(job)
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", JSON.dump({ message: ex.message[0...1000],
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
- command("BEAT", %Q[{"wid":"#{@@random_process_wid}"}])
103
- str = result
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 = JSON.parse(str)
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
- JSON.parse(str) if str
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.ssl_version = :TLSv1_2
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", JSON.dump(payload))
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 Errno::EPIPE, Errno::ECONNRESET
298
+ rescue SystemCallError, SocketError, TimeoutError
195
299
  if retryable
196
300
  retryable = false
197
- open
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 = @sock.gets
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 = @sock.read(count) if count > 0
218
- line = @sock.gets # read extra linefeeds
324
+ data = read(count) if count > 0
325
+ line = gets # read extra linefeeds
219
326
  data
220
327
  elsif chr == '-'
221
- raise CommandError, line[1..-1]
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
- raise CommandError, resp if resp != "OK"
232
- true
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
-