kestrel-client 0.3.1 → 0.4.0
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.
- data/VERSION +1 -1
- data/kestrel-client.gemspec +10 -4
- data/lib/kestrel/client.rb +40 -5
- data/lib/kestrel/client/blocking.rb +8 -17
- data/lib/kestrel/client/reliable.rb +65 -0
- data/lib/kestrel/client/retrying.rb +51 -0
- data/spec/kestrel/client/blocking_spec.rb +4 -45
- data/spec/kestrel/client/envelope_spec.rb +1 -1
- data/spec/kestrel/client/json_spec.rb +1 -3
- data/spec/kestrel/client/namespace_spec.rb +1 -1
- data/spec/kestrel/client/partitioning_spec.rb +1 -1
- data/spec/kestrel/client/reliable_spec.rb +130 -0
- data/spec/kestrel/client/retrying_spec.rb +46 -0
- data/spec/kestrel/client/unmarshal_spec.rb +1 -1
- data/spec/kestrel/client_spec.rb +1 -1
- data/spec/kestrel/config/kestrel.yml +2 -2
- data/spec/kestrel/config_spec.rb +1 -1
- metadata +18 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/kestrel-client.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{kestrel-client}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Matt Freels", "Rael Dornfest"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-07-22}
|
13
13
|
s.description = %q{Ruby client for the Kestrel queue server}
|
14
14
|
s.email = %q{rael@twitter.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -30,6 +30,8 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/kestrel/client/namespace.rb",
|
31
31
|
"lib/kestrel/client/partitioning.rb",
|
32
32
|
"lib/kestrel/client/proxy.rb",
|
33
|
+
"lib/kestrel/client/reliable.rb",
|
34
|
+
"lib/kestrel/client/retrying.rb",
|
33
35
|
"lib/kestrel/client/unmarshal.rb",
|
34
36
|
"lib/kestrel/config.rb",
|
35
37
|
"spec/kestrel/client/blocking_spec.rb",
|
@@ -37,6 +39,8 @@ Gem::Specification.new do |s|
|
|
37
39
|
"spec/kestrel/client/json_spec.rb",
|
38
40
|
"spec/kestrel/client/namespace_spec.rb",
|
39
41
|
"spec/kestrel/client/partitioning_spec.rb",
|
42
|
+
"spec/kestrel/client/reliable_spec.rb",
|
43
|
+
"spec/kestrel/client/retrying_spec.rb",
|
40
44
|
"spec/kestrel/client/unmarshal_spec.rb",
|
41
45
|
"spec/kestrel/client_spec.rb",
|
42
46
|
"spec/kestrel/config/kestrel.yml",
|
@@ -47,7 +51,7 @@ Gem::Specification.new do |s|
|
|
47
51
|
s.homepage = %q{http://github.com/freels/kestrel-client}
|
48
52
|
s.rdoc_options = ["--charset=UTF-8"]
|
49
53
|
s.require_paths = ["lib"]
|
50
|
-
s.rubygems_version = %q{1.3.
|
54
|
+
s.rubygems_version = %q{1.3.7}
|
51
55
|
s.summary = %q{Ruby Kestrel client}
|
52
56
|
s.test_files = [
|
53
57
|
"spec/kestrel/client/blocking_spec.rb",
|
@@ -55,6 +59,8 @@ Gem::Specification.new do |s|
|
|
55
59
|
"spec/kestrel/client/json_spec.rb",
|
56
60
|
"spec/kestrel/client/namespace_spec.rb",
|
57
61
|
"spec/kestrel/client/partitioning_spec.rb",
|
62
|
+
"spec/kestrel/client/reliable_spec.rb",
|
63
|
+
"spec/kestrel/client/retrying_spec.rb",
|
58
64
|
"spec/kestrel/client/unmarshal_spec.rb",
|
59
65
|
"spec/kestrel/client_spec.rb",
|
60
66
|
"spec/kestrel/config_spec.rb",
|
@@ -65,7 +71,7 @@ Gem::Specification.new do |s|
|
|
65
71
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
66
72
|
s.specification_version = 3
|
67
73
|
|
68
|
-
if Gem::Version.new(Gem::
|
74
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
69
75
|
s.add_runtime_dependency(%q<memcached>, [">= 0"])
|
70
76
|
else
|
71
77
|
s.add_dependency(%q<memcached>, [">= 0"])
|
data/lib/kestrel/client.rb
CHANGED
@@ -7,14 +7,37 @@ module Kestrel
|
|
7
7
|
autoload :Unmarshal, 'kestrel/client/unmarshal'
|
8
8
|
autoload :Namespace, 'kestrel/client/namespace'
|
9
9
|
autoload :Json, 'kestrel/client/json'
|
10
|
-
|
10
|
+
autoload :Reliable, "kestrel/client/reliable"
|
11
|
+
autoload :Retrying, "kestrel/client/retrying"
|
11
12
|
|
12
13
|
QUEUE_STAT_NAMES = %w{items bytes total_items logsize expired_items mem_items mem_bytes age discarded}
|
13
14
|
|
15
|
+
# ==== Parameters
|
16
|
+
# key<String>:: Queue name
|
17
|
+
# opts<Boolean,Hash>:: True/false toggles Marshalling. A Hash
|
18
|
+
# allows collision-avoiding options support.
|
19
|
+
#
|
20
|
+
# ==== Options (opts)
|
21
|
+
# :open<Boolean>:: Begins a reliable read.
|
22
|
+
# :close<Boolean>:: Ends a reliable read.
|
23
|
+
# :abort<Boolean>:: Cancels an existing reliable read
|
24
|
+
# :peek<Boolean>:: Return the head of the queue, without removal
|
25
|
+
# :timeout<Integer>:: Milliseconds to block for a new item
|
26
|
+
# :raw<Boolean>:: Toggles Marshalling. Equivalent to the "old
|
27
|
+
# style" second argument.
|
28
|
+
#
|
29
|
+
def get(key, opts = false)
|
30
|
+
opts = extract_options(opts)
|
31
|
+
raw = opts.delete(:raw)
|
32
|
+
commands = extract_queue_commands(opts)
|
33
|
+
|
34
|
+
super key + commands, raw
|
35
|
+
end
|
36
|
+
|
14
37
|
def flush(queue)
|
15
38
|
count = 0
|
16
39
|
while sizeof(queue) > 0
|
17
|
-
while get
|
40
|
+
while get queue, :raw => true
|
18
41
|
count += 1
|
19
42
|
end
|
20
43
|
end
|
@@ -22,9 +45,7 @@ module Kestrel
|
|
22
45
|
end
|
23
46
|
|
24
47
|
def peek(queue)
|
25
|
-
|
26
|
-
set(queue, val)
|
27
|
-
val
|
48
|
+
get queue, :peek => true
|
28
49
|
end
|
29
50
|
|
30
51
|
def sizeof(queue)
|
@@ -46,6 +67,20 @@ module Kestrel
|
|
46
67
|
|
47
68
|
private
|
48
69
|
|
70
|
+
def extract_options(opts)
|
71
|
+
opts.is_a?(Hash) ? opts : { :raw => !!opts }
|
72
|
+
end
|
73
|
+
|
74
|
+
def extract_queue_commands(opts)
|
75
|
+
commands = [:open, :close, :abort, :peek].select do |key|
|
76
|
+
opts[key]
|
77
|
+
end
|
78
|
+
|
79
|
+
commands << "t=#{opts[:timeout]}" if opts[:timeout]
|
80
|
+
|
81
|
+
commands.map { |c| "/#{c}" }.join('')
|
82
|
+
end
|
83
|
+
|
49
84
|
def stats_for_server(server)
|
50
85
|
server_name, port = server.split(/:/)
|
51
86
|
socket = TCPSocket.new(server_name, port)
|
@@ -1,31 +1,22 @@
|
|
1
1
|
module Kestrel
|
2
2
|
class Client
|
3
3
|
class Blocking < Proxy
|
4
|
-
|
5
|
-
WAIT_TIME_BEFORE_RETRY = 0.25
|
4
|
+
DEFAULT_TIMEOUT = 1000
|
6
5
|
|
7
|
-
def get(
|
8
|
-
|
9
|
-
|
6
|
+
def get(key, opts = false)
|
7
|
+
opts = extract_options(opts)
|
8
|
+
opts[:timeout] = DEFAULT_TIMEOUT
|
9
|
+
|
10
|
+
loop do
|
11
|
+
response = client.get(key, opts)
|
12
|
+
return response if response
|
10
13
|
end
|
11
|
-
response
|
12
14
|
end
|
13
15
|
|
14
16
|
def get_without_blocking(*args)
|
15
17
|
client.get(*args)
|
16
18
|
end
|
17
19
|
|
18
|
-
def set(key, value, expiry = DEFAULT_EXPIRY, raw = false)
|
19
|
-
@retried = false
|
20
|
-
begin
|
21
|
-
client.set(key, value, expiry, raw)
|
22
|
-
rescue Memcached::Error => e
|
23
|
-
raise if @retried
|
24
|
-
sleep(WAIT_TIME_BEFORE_RETRY)
|
25
|
-
@retried = true
|
26
|
-
retry
|
27
|
-
end
|
28
|
-
end
|
29
20
|
end
|
30
21
|
end
|
31
22
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Kestrel
|
2
|
+
class Client
|
3
|
+
class Reliable < Proxy
|
4
|
+
|
5
|
+
# Number of times to retry a job before giving up
|
6
|
+
DEFAULT_RETRIES = 100
|
7
|
+
|
8
|
+
# Pct. of the time during 'normal' processing we check the error queue first
|
9
|
+
ERROR_PROCESSING_RATE = 0.1
|
10
|
+
|
11
|
+
# Returns job from the +key+ queue 1 - ERROR_PROCESSING_RATE
|
12
|
+
# pct. of the time. Every so often, checks the error queue for
|
13
|
+
# jobs and returns a retryable job. If either the error queue or
|
14
|
+
# +key+ queue are empty, attempts to pull a job from the
|
15
|
+
# alternate queue before giving up.
|
16
|
+
#
|
17
|
+
# ==== Returns
|
18
|
+
# Job, possibly retryable, or nil
|
19
|
+
#
|
20
|
+
def get(key, opts = false)
|
21
|
+
opts = extract_options(opts)
|
22
|
+
opts.merge! :close => true, :open => true
|
23
|
+
|
24
|
+
job =
|
25
|
+
if rand < ERROR_PROCESSING_RATE
|
26
|
+
client.get(key + "_errors", opts) || client.get(key, opts)
|
27
|
+
else
|
28
|
+
client.get(key, opts) || client.get(key + "_errors", opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
if job
|
32
|
+
@key = key
|
33
|
+
@job = job.is_a?(RetryableJob) ? job : RetryableJob.new(0, job)
|
34
|
+
@job.job
|
35
|
+
else
|
36
|
+
@key = @job = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_try
|
41
|
+
@job ? @job.retries + 1 : 1
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enqueues the current job on the error queue for later
|
45
|
+
# retry. If the job has been retried DEFAULT_RETRIES times,
|
46
|
+
# gives up entirely.
|
47
|
+
#
|
48
|
+
# ==== Returns
|
49
|
+
# Boolean:: true if the job is retryable, false otherwise
|
50
|
+
#
|
51
|
+
def retry
|
52
|
+
@job.retries += 1
|
53
|
+
if @job.retries < DEFAULT_RETRIES
|
54
|
+
client.set(@key + "_errors", @job)
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class RetryableJob < Struct.new(:retries, :job)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Kestrel
|
2
|
+
class Client
|
3
|
+
class Retrying < Proxy
|
4
|
+
|
5
|
+
# Number of times to retry after connection failures
|
6
|
+
DEFAULT_RETRY_COUNT = 5
|
7
|
+
|
8
|
+
# Exceptions which are connection failures we retry after
|
9
|
+
RECOVERABLE_ERRORS = [
|
10
|
+
Memcached::ServerIsMarkedDead,
|
11
|
+
Memcached::ATimeoutOccurred,
|
12
|
+
Memcached::ConnectionBindFailure,
|
13
|
+
Memcached::ConnectionFailure,
|
14
|
+
Memcached::ConnectionSocketCreateFailure,
|
15
|
+
Memcached::Failure,
|
16
|
+
Memcached::MemoryAllocationFailure,
|
17
|
+
Memcached::ReadFailure,
|
18
|
+
Memcached::ServerError,
|
19
|
+
Memcached::SystemError,
|
20
|
+
Memcached::UnknownReadFailure,
|
21
|
+
Memcached::WriteFailure
|
22
|
+
]
|
23
|
+
|
24
|
+
def initialize(client, retry_count = nil)
|
25
|
+
@retry_count = retry_count || DEFAULT_RETRY_COUNT
|
26
|
+
super(client)
|
27
|
+
end
|
28
|
+
|
29
|
+
%w(set get delete).each do |method|
|
30
|
+
class_eval "def #{method}(*args); retry_call(#{method.inspect}, *args) end", __FILE__, __LINE__
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def retry_call(method, *args) #:nodoc:
|
36
|
+
begin
|
37
|
+
tries ||= 0
|
38
|
+
client.send(method, *args)
|
39
|
+
rescue *RECOVERABLE_ERRORS
|
40
|
+
if tries < @retry_count
|
41
|
+
tries += 1
|
42
|
+
retry
|
43
|
+
else
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec/spec_helper'
|
2
2
|
|
3
3
|
describe "Kestrel::Client::Blocking" do
|
4
4
|
describe "Instance Methods" do
|
@@ -7,62 +7,21 @@ describe "Kestrel::Client::Blocking" do
|
|
7
7
|
@kestrel = Kestrel::Client::Blocking.new(@raw_kestrel_client)
|
8
8
|
end
|
9
9
|
|
10
|
-
describe "#set" do
|
11
|
-
before do
|
12
|
-
@queue = "some_queue"
|
13
|
-
@value = "some_value"
|
14
|
-
end
|
15
|
-
|
16
|
-
it "blocks on a set until the set works" do
|
17
|
-
@queue = "some_queue"
|
18
|
-
@value = "some_value"
|
19
|
-
mock(@raw_kestrel_client)\
|
20
|
-
.set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
|
21
|
-
.set(@queue, @value, anything, anything) { :mcguffin }
|
22
|
-
mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
|
23
|
-
@kestrel.set(@queue, @value).should == :mcguffin
|
24
|
-
end
|
25
|
-
|
26
|
-
it "raises if two sets in a row fail" do
|
27
|
-
mock(@raw_kestrel_client)\
|
28
|
-
.set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
|
29
|
-
.set(@queue, @value, anything, anything) { raise Memcached::Error }
|
30
|
-
mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
|
31
|
-
lambda { @kestrel.set(@queue, @value) }.should raise_error(Memcached::Error)
|
32
|
-
end
|
33
|
-
|
34
|
-
it "passes along the default expiry time if none is given" do
|
35
|
-
@queue = "some_queue"
|
36
|
-
@value = "some_value"
|
37
|
-
mock(@raw_kestrel_client).set(@queue, @value, Kestrel::Client::Blocking::DEFAULT_EXPIRY, anything)
|
38
|
-
@kestrel.set(@queue, @value)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "passes along the given expiry time if one is passed in" do
|
42
|
-
@queue = "some_queue"
|
43
|
-
@value = "some_value"
|
44
|
-
mock(@raw_kestrel_client).set(@queue, @value, 60, anything)
|
45
|
-
@kestrel.set(@queue, @value, 60)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
10
|
describe "#get" do
|
50
11
|
before do
|
51
12
|
@queue = "some_queue"
|
52
13
|
end
|
53
14
|
|
54
15
|
it "blocks on a get until the get works" do
|
55
|
-
mock(@raw_kestrel_client)
|
56
|
-
|
57
|
-
|
58
|
-
mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
|
16
|
+
mock(@raw_kestrel_client).
|
17
|
+
get(@queue, :raw => false, :timeout => Kestrel::Client::Blocking::DEFAULT_TIMEOUT) { nil }.then.
|
18
|
+
get(@queue, :raw => false, :timeout => Kestrel::Client::Blocking::DEFAULT_TIMEOUT) { :mcguffin }
|
59
19
|
@kestrel.get(@queue).should == :mcguffin
|
60
20
|
end
|
61
21
|
|
62
22
|
describe "#get_without_blocking" do
|
63
23
|
it "does not block" do
|
64
24
|
mock(@raw_kestrel_client).get(@queue) { nil }
|
65
|
-
mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).never
|
66
25
|
@kestrel.get_without_blocking(@queue).should be_nil
|
67
26
|
end
|
68
27
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe "Kestrel::Client::Reliable" do
|
4
|
+
describe "Instance Methods" do
|
5
|
+
before do
|
6
|
+
@raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
|
7
|
+
@kestrel = Kestrel::Client::Reliable.new(@raw_kestrel_client)
|
8
|
+
stub(@kestrel).rand { 1 }
|
9
|
+
@queue = "some_queue"
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#get" do
|
13
|
+
|
14
|
+
it "asks for a transaction" do
|
15
|
+
mock(@raw_kestrel_client).get(@queue, :raw => false, :open => true, :close => true) { :mcguffin }
|
16
|
+
@kestrel.get(@queue).should == :mcguffin
|
17
|
+
end
|
18
|
+
|
19
|
+
it "gets from the error queue ERROR_PROCESSING_RATE pct. of the time" do
|
20
|
+
mock(@kestrel).rand { Kestrel::Client::Reliable::ERROR_PROCESSING_RATE - 0.05 }
|
21
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) { :mcguffin }
|
22
|
+
mock(@raw_kestrel_client).get(@queue, anything).never
|
23
|
+
@kestrel.get(@queue).should == :mcguffin
|
24
|
+
end
|
25
|
+
|
26
|
+
it "falls through to the normal queue when error queue is empty" do
|
27
|
+
mock(@kestrel).rand { Kestrel::Client::Reliable::ERROR_PROCESSING_RATE - 0.05 }
|
28
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) { nil }
|
29
|
+
mock(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
30
|
+
@kestrel.get(@queue).should == :mcguffin
|
31
|
+
end
|
32
|
+
|
33
|
+
it "gets from the normal queue most of the time" do
|
34
|
+
mock(@kestrel).rand { Kestrel::Client::Reliable::ERROR_PROCESSING_RATE + 0.05 }
|
35
|
+
mock(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
36
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything).never
|
37
|
+
@kestrel.get(@queue).should == :mcguffin
|
38
|
+
end
|
39
|
+
|
40
|
+
it "falls through to the error queue when normal queue is empty" do
|
41
|
+
mock(@kestrel).rand { Kestrel::Client::Reliable::ERROR_PROCESSING_RATE + 0.05 }
|
42
|
+
mock(@raw_kestrel_client).get(@queue, anything) { nil }
|
43
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) { :mcguffin }
|
44
|
+
@kestrel.get(@queue).should == :mcguffin
|
45
|
+
end
|
46
|
+
|
47
|
+
it "is nil when both queues are empty" do
|
48
|
+
mock(@kestrel).rand { Kestrel::Client::Reliable::ERROR_PROCESSING_RATE + 0.05 }
|
49
|
+
mock(@raw_kestrel_client).get(@queue, anything) { nil }
|
50
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) { nil }
|
51
|
+
@kestrel.get(@queue).should be_nil
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns the payload of a RetryableJob" do
|
55
|
+
stub(@kestrel).rand { 0 }
|
56
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
57
|
+
Kestrel::Client::Reliable::RetryableJob.new(1, :mcmuffin)
|
58
|
+
end
|
59
|
+
|
60
|
+
@kestrel.get(@queue).should == :mcmuffin
|
61
|
+
@kestrel.current_try.should == 2
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#current_try" do
|
67
|
+
|
68
|
+
it "returns 1 if nothing has been gotten" do
|
69
|
+
@kestrel.current_try.should == 1
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns 1 for jobs that have not been retried" do
|
73
|
+
mock(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
74
|
+
@kestrel.get(@queue)
|
75
|
+
@kestrel.current_try.should == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns 1 plus the number of tries for a RetryableJob" do
|
79
|
+
stub(@kestrel).rand { 0 }
|
80
|
+
mock(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
81
|
+
Kestrel::Client::Reliable::RetryableJob.new(1, :mcmuffin)
|
82
|
+
end
|
83
|
+
@kestrel.get(@queue)
|
84
|
+
@kestrel.current_try.should == 2
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#retry" do
|
90
|
+
before do
|
91
|
+
stub(@raw_kestrel_client).get(@queue, anything) { :mcmuffin }
|
92
|
+
@kestrel.get(@queue)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "enqueues a fresh failed job to the errors queue with a retry count" do
|
96
|
+
mock(@raw_kestrel_client).set(@queue + "_errors", anything) do |queue, job|
|
97
|
+
job.retries.should == 1
|
98
|
+
job.job.should == :mcmuffin
|
99
|
+
end
|
100
|
+
@kestrel.retry.should be_true
|
101
|
+
end
|
102
|
+
|
103
|
+
it "increments the retry count and re-enqueues the retried job" do
|
104
|
+
stub(@kestrel).rand { 0 }
|
105
|
+
stub(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
106
|
+
Kestrel::Client::Reliable::RetryableJob.new(1, :mcmuffin)
|
107
|
+
end
|
108
|
+
|
109
|
+
mock(@raw_kestrel_client).set(@queue + "_errors", anything) do |queue, job|
|
110
|
+
job.retries.should == 2
|
111
|
+
job.job.should == :mcmuffin
|
112
|
+
end
|
113
|
+
|
114
|
+
@kestrel.get(@queue)
|
115
|
+
@kestrel.retry.should be_true
|
116
|
+
end
|
117
|
+
|
118
|
+
it "does not enqueue the retried job after too many tries" do
|
119
|
+
stub(@kestrel).rand { 0 }
|
120
|
+
stub(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
121
|
+
Kestrel::Client::Reliable::RetryableJob.new(Kestrel::Client::Reliable::DEFAULT_RETRIES - 1, :mcmuffin)
|
122
|
+
end
|
123
|
+
mock(@raw_kestrel_client).set(@queue + "_errors", anything).never
|
124
|
+
@kestrel.get(@queue)
|
125
|
+
@kestrel.retry.should be_false
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Kestrel::Client::Retrying do
|
4
|
+
before do
|
5
|
+
@raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
|
6
|
+
@kestrel = Kestrel::Client::Retrying.new(@raw_kestrel_client)
|
7
|
+
@queue = "some_queue"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "does not retry if no exception is raised" do
|
11
|
+
mock(@raw_kestrel_client).get(@queue) { :mcguffin }
|
12
|
+
lambda do
|
13
|
+
@kestrel.get(@queue).should == :mcguffin
|
14
|
+
end.should_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
['get', 'set', 'delete'].each do |operation|
|
18
|
+
it "retries DEFAULT_RETRY_COUNT times then fails" do
|
19
|
+
mock(@raw_kestrel_client).send(operation, @queue) { raise Memcached::ServerIsMarkedDead }.
|
20
|
+
times(Kestrel::Client::Retrying::DEFAULT_RETRY_COUNT + 1)
|
21
|
+
|
22
|
+
lambda do
|
23
|
+
@kestrel.send(operation, @queue)
|
24
|
+
end.should raise_error(Memcached::ServerIsMarkedDead)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "does not retry on non-connection related exceptions" do
|
28
|
+
[Memcached::ABadKeyWasProvidedOrCharactersOutOfRange,
|
29
|
+
Memcached::ActionQueued,
|
30
|
+
Memcached::NoServersDefined].each do |ex|
|
31
|
+
|
32
|
+
mock(@raw_kestrel_client).send(operation, @queue) { raise ex }
|
33
|
+
lambda { @kestrel.send(operation, @queue) }.should raise_error(ex)
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not retry when retry count is 0" do
|
39
|
+
kestrel = Kestrel::Client::Retrying.new(@raw_kestrel_client, 0)
|
40
|
+
mock(@raw_kestrel_client).send(operation, @queue) { raise Memcached::ServerIsMarkedDead }
|
41
|
+
lambda { kestrel.send(operation, @queue) }.should raise_error(Memcached::ServerIsMarkedDead)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/spec/kestrel/client_spec.rb
CHANGED
data/spec/kestrel/config_spec.rb
CHANGED
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kestrel-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Matt Freels
|
@@ -15,16 +16,18 @@ autorequire:
|
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date: 2010-
|
19
|
+
date: 2010-07-22 00:00:00 -07:00
|
19
20
|
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
22
23
|
name: memcached
|
23
24
|
prerelease: false
|
24
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
25
27
|
requirements:
|
26
28
|
- - ">="
|
27
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
28
31
|
segments:
|
29
32
|
- 0
|
30
33
|
version: "0"
|
@@ -53,6 +56,8 @@ files:
|
|
53
56
|
- lib/kestrel/client/namespace.rb
|
54
57
|
- lib/kestrel/client/partitioning.rb
|
55
58
|
- lib/kestrel/client/proxy.rb
|
59
|
+
- lib/kestrel/client/reliable.rb
|
60
|
+
- lib/kestrel/client/retrying.rb
|
56
61
|
- lib/kestrel/client/unmarshal.rb
|
57
62
|
- lib/kestrel/config.rb
|
58
63
|
- spec/kestrel/client/blocking_spec.rb
|
@@ -60,6 +65,8 @@ files:
|
|
60
65
|
- spec/kestrel/client/json_spec.rb
|
61
66
|
- spec/kestrel/client/namespace_spec.rb
|
62
67
|
- spec/kestrel/client/partitioning_spec.rb
|
68
|
+
- spec/kestrel/client/reliable_spec.rb
|
69
|
+
- spec/kestrel/client/retrying_spec.rb
|
63
70
|
- spec/kestrel/client/unmarshal_spec.rb
|
64
71
|
- spec/kestrel/client_spec.rb
|
65
72
|
- spec/kestrel/config/kestrel.yml
|
@@ -76,23 +83,27 @@ rdoc_options:
|
|
76
83
|
require_paths:
|
77
84
|
- lib
|
78
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
79
87
|
requirements:
|
80
88
|
- - ">="
|
81
89
|
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
82
91
|
segments:
|
83
92
|
- 0
|
84
93
|
version: "0"
|
85
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
86
96
|
requirements:
|
87
97
|
- - ">="
|
88
98
|
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
89
100
|
segments:
|
90
101
|
- 0
|
91
102
|
version: "0"
|
92
103
|
requirements: []
|
93
104
|
|
94
105
|
rubyforge_project:
|
95
|
-
rubygems_version: 1.3.
|
106
|
+
rubygems_version: 1.3.7
|
96
107
|
signing_key:
|
97
108
|
specification_version: 3
|
98
109
|
summary: Ruby Kestrel client
|
@@ -102,6 +113,8 @@ test_files:
|
|
102
113
|
- spec/kestrel/client/json_spec.rb
|
103
114
|
- spec/kestrel/client/namespace_spec.rb
|
104
115
|
- spec/kestrel/client/partitioning_spec.rb
|
116
|
+
- spec/kestrel/client/reliable_spec.rb
|
117
|
+
- spec/kestrel/client/retrying_spec.rb
|
105
118
|
- spec/kestrel/client/unmarshal_spec.rb
|
106
119
|
- spec/kestrel/client_spec.rb
|
107
120
|
- spec/kestrel/config_spec.rb
|