kestrel-client 0.4.0 → 0.4.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.
- data/VERSION +1 -1
- data/kestrel-client.gemspec +2 -2
- data/lib/kestrel/client.rb +22 -0
- data/lib/kestrel/client/reliable.rb +78 -7
- data/spec/kestrel/client/reliable_spec.rb +75 -1
- metadata +4 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.1
|
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.4.
|
8
|
+
s.version = "0.4.1"
|
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-08-05}
|
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 = [
|
data/lib/kestrel/client.rb
CHANGED
@@ -34,6 +34,28 @@ module Kestrel
|
|
34
34
|
super key + commands, raw
|
35
35
|
end
|
36
36
|
|
37
|
+
# ==== Parameters
|
38
|
+
# key<String>:: Queue name
|
39
|
+
# opts<Boolean,Hash>:: True/false toggles Marshalling. A Hash
|
40
|
+
# allows collision-avoiding options support.
|
41
|
+
#
|
42
|
+
# ==== Options (opts)
|
43
|
+
# :open<Boolean>:: Begins a reliable read.
|
44
|
+
# :close<Boolean>:: Ends a reliable read.
|
45
|
+
# :abort<Boolean>:: Cancels an existing reliable read
|
46
|
+
# :peek<Boolean>:: Return the head of the queue, without removal
|
47
|
+
# :timeout<Integer>:: Milliseconds to block for a new item
|
48
|
+
# :raw<Boolean>:: Toggles Marshalling. Equivalent to the "old
|
49
|
+
# style" second argument.
|
50
|
+
#
|
51
|
+
def get_from_last(key, opts = {})
|
52
|
+
opts = extract_options(opts)
|
53
|
+
raw = opts.delete(:raw)
|
54
|
+
commands = extract_queue_commands(opts)
|
55
|
+
|
56
|
+
super key + commands, raw
|
57
|
+
end
|
58
|
+
|
37
59
|
def flush(queue)
|
38
60
|
count = 0
|
39
61
|
while sizeof(queue) > 0
|
@@ -1,6 +1,17 @@
|
|
1
1
|
module Kestrel
|
2
2
|
class Client
|
3
|
+
#--
|
4
|
+
# TODO: Pull out the sticky server logic into Client. This class
|
5
|
+
# should only be responsible for the retry semantics.
|
6
|
+
# TODO: Ensure that errors are pushed onto the error queue on the
|
7
|
+
# same server on which the error occurred.
|
8
|
+
#++
|
3
9
|
class Reliable < Proxy
|
10
|
+
# Raised when a caller attempts to use this proxy across
|
11
|
+
# multiple queues.
|
12
|
+
class MultipleQueueException < StandardError; end
|
13
|
+
|
14
|
+
class RetryableJob < Struct.new(:retries, :job); end
|
4
15
|
|
5
16
|
# Number of times to retry a job before giving up
|
6
17
|
DEFAULT_RETRIES = 100
|
@@ -8,6 +19,28 @@ module Kestrel
|
|
8
19
|
# Pct. of the time during 'normal' processing we check the error queue first
|
9
20
|
ERROR_PROCESSING_RATE = 0.1
|
10
21
|
|
22
|
+
# Maximum number of gets to execute before switching servers
|
23
|
+
MAX_PER_SERVER = 100_000
|
24
|
+
|
25
|
+
# ==== Parameters
|
26
|
+
# client<Kestrel::Client>:: Client
|
27
|
+
# retry_count<Integer>:: Number of times to retry a job before
|
28
|
+
# giving up. Defaults to DEFAULT_RETRIES
|
29
|
+
# error_rate<Float>:: Pct. of the time during 'normal'
|
30
|
+
# processing we check the error queue
|
31
|
+
# first. Defaults to ERROR_PROCESSING_RATE
|
32
|
+
# per_server<Integer>:: Number of gets to execute against a
|
33
|
+
# single server, before changing
|
34
|
+
# servers. Defaults to MAX_PER_SERVER
|
35
|
+
#
|
36
|
+
def initialize(client, retry_count = nil, error_rate = nil, per_server = nil)
|
37
|
+
@retry_count = retry_count || DEFAULT_RETRIES
|
38
|
+
@error_rate = error_rate || ERROR_PROCESSING_RATE
|
39
|
+
@per_server = per_server || MAX_PER_SERVER
|
40
|
+
@counter = 0 # Command counter
|
41
|
+
super(client)
|
42
|
+
end
|
43
|
+
|
11
44
|
# Returns job from the +key+ queue 1 - ERROR_PROCESSING_RATE
|
12
45
|
# pct. of the time. Every so often, checks the error queue for
|
13
46
|
# jobs and returns a retryable job. If either the error queue or
|
@@ -18,14 +51,13 @@ module Kestrel
|
|
18
51
|
# Job, possibly retryable, or nil
|
19
52
|
#
|
20
53
|
def get(key, opts = false)
|
21
|
-
|
22
|
-
opts.merge! :close => true, :open => true
|
54
|
+
raise MultipleQueueException if @key && key != @key
|
23
55
|
|
24
56
|
job =
|
25
|
-
if rand <
|
26
|
-
|
57
|
+
if rand < @error_rate
|
58
|
+
get_with_fallback(key + "_errors", key, opts)
|
27
59
|
else
|
28
|
-
|
60
|
+
get_with_fallback(key, key + "_errors", opts)
|
29
61
|
end
|
30
62
|
|
31
63
|
if job
|
@@ -49,8 +81,12 @@ module Kestrel
|
|
49
81
|
# Boolean:: true if the job is retryable, false otherwise
|
50
82
|
#
|
51
83
|
def retry
|
84
|
+
return unless @job
|
85
|
+
|
86
|
+
close_open_transaction!
|
52
87
|
@job.retries += 1
|
53
|
-
|
88
|
+
|
89
|
+
if @job.retries < @retry_count
|
54
90
|
client.set(@key + "_errors", @job)
|
55
91
|
true
|
56
92
|
else
|
@@ -58,7 +94,42 @@ module Kestrel
|
|
58
94
|
end
|
59
95
|
end
|
60
96
|
|
61
|
-
|
97
|
+
private
|
98
|
+
|
99
|
+
# If a get against the +primary+ queue is nil, falls back to the
|
100
|
+
# +secondary+ queue.
|
101
|
+
#
|
102
|
+
# Also, this executes a get on the first request, then a get_from_last
|
103
|
+
# on each ensuing request for @per_server requests. This keeps the
|
104
|
+
# client "attached" to a single server for a period of time.
|
105
|
+
#
|
106
|
+
def get_with_fallback(primary, secondary, opts) #:nodoc:
|
107
|
+
opts = extract_options(opts)
|
108
|
+
opts.merge! :close => true, :open => true
|
109
|
+
|
110
|
+
if @counter == 0
|
111
|
+
close_open_transaction! if @job
|
112
|
+
@counter += 1
|
113
|
+
command = :get
|
114
|
+
elsif @counter < @per_server
|
115
|
+
# Open transactions are implicitly closed, here.
|
116
|
+
@counter += 1
|
117
|
+
command = :get_from_last
|
118
|
+
else
|
119
|
+
close_open_transaction! if @job
|
120
|
+
@counter = 0
|
121
|
+
command = :get
|
122
|
+
end
|
123
|
+
|
124
|
+
client.send(command, primary, opts) || client.send(command, secondary, opts)
|
125
|
+
end
|
126
|
+
|
127
|
+
def close_open_transaction! #:nodoc:
|
128
|
+
if @job.retries == 0
|
129
|
+
client.get_from_last(@key, :close => true, :open => false)
|
130
|
+
else
|
131
|
+
client.get_from_last(@key + "_errors", :close => true, :open => false)
|
132
|
+
end
|
62
133
|
end
|
63
134
|
end
|
64
135
|
end
|
@@ -1,10 +1,37 @@
|
|
1
1
|
require 'spec/spec_helper'
|
2
2
|
|
3
3
|
describe "Kestrel::Client::Reliable" do
|
4
|
+
describe "Sticky" do
|
5
|
+
before do
|
6
|
+
@max_requests = 2
|
7
|
+
@raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
|
8
|
+
@kestrel = Kestrel::Client::Reliable.new(@raw_kestrel_client, nil, nil, @max_requests)
|
9
|
+
stub(@kestrel).rand { 1 }
|
10
|
+
@queue = "some_queue"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#get" do
|
14
|
+
|
15
|
+
it 'does a get on the first request' do
|
16
|
+
mock(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
17
|
+
@kestrel.get(@queue)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does a get_from_last a number of times, then a get' do
|
21
|
+
mock(@raw_kestrel_client).get(@queue, anything).twice { :mcguffin }
|
22
|
+
mock(@raw_kestrel_client).get_from_last(@queue, anything).twice { :mcguffin }
|
23
|
+
|
24
|
+
@kestrel.get(@queue) # Initial get
|
25
|
+
@kestrel.get(@queue) # get_from_last
|
26
|
+
@kestrel.get(@queue) # get_from_last txn close, get
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
4
31
|
describe "Instance Methods" do
|
5
32
|
before do
|
6
33
|
@raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
|
7
|
-
@kestrel = Kestrel::Client::Reliable.new(@raw_kestrel_client)
|
34
|
+
@kestrel = Kestrel::Client::Reliable.new(@raw_kestrel_client, nil, nil, 1)
|
8
35
|
stub(@kestrel).rand { 1 }
|
9
36
|
@queue = "some_queue"
|
10
37
|
end
|
@@ -61,6 +88,33 @@ describe "Kestrel::Client::Reliable" do
|
|
61
88
|
@kestrel.current_try.should == 2
|
62
89
|
end
|
63
90
|
|
91
|
+
it "closes an open transaction with no retries" do
|
92
|
+
stub(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
93
|
+
@kestrel.get(@queue)
|
94
|
+
|
95
|
+
mock(@raw_kestrel_client).get_from_last(@queue, :close => true, :open => false)
|
96
|
+
@kestrel.get(@queue)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "closes an open transaction with retries" do
|
100
|
+
stub(@kestrel).rand { 0 }
|
101
|
+
stub(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
102
|
+
Kestrel::Client::Reliable::RetryableJob.new(1, :mcmuffin)
|
103
|
+
end
|
104
|
+
@kestrel.get(@queue)
|
105
|
+
|
106
|
+
mock(@raw_kestrel_client).get_from_last(@queue + "_errors", :close => true, :open => false)
|
107
|
+
@kestrel.get(@queue)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "prevents transactional gets across multiple queues" do
|
111
|
+
stub(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
112
|
+
@kestrel.get(@queue)
|
113
|
+
|
114
|
+
lambda do
|
115
|
+
@kestrel.get("transaction_fail")
|
116
|
+
end.should raise_error(Kestrel::Client::Reliable::MultipleQueueException)
|
117
|
+
end
|
64
118
|
end
|
65
119
|
|
66
120
|
describe "#current_try" do
|
@@ -89,6 +143,7 @@ describe "Kestrel::Client::Reliable" do
|
|
89
143
|
describe "#retry" do
|
90
144
|
before do
|
91
145
|
stub(@raw_kestrel_client).get(@queue, anything) { :mcmuffin }
|
146
|
+
stub(@raw_kestrel_client).get_from_last
|
92
147
|
@kestrel.get(@queue)
|
93
148
|
end
|
94
149
|
|
@@ -125,6 +180,25 @@ describe "Kestrel::Client::Reliable" do
|
|
125
180
|
@kestrel.retry.should be_false
|
126
181
|
end
|
127
182
|
|
183
|
+
it "closes an open transaction with no retries" do
|
184
|
+
stub(@raw_kestrel_client).get(@queue, anything) { :mcguffin }
|
185
|
+
@kestrel.get(@queue)
|
186
|
+
|
187
|
+
mock(@raw_kestrel_client).get_from_last(@queue, :close => true, :open => false)
|
188
|
+
@kestrel.retry
|
189
|
+
end
|
190
|
+
|
191
|
+
it "closes an open transaction with retries" do
|
192
|
+
stub(@kestrel).rand { 0 }
|
193
|
+
stub(@raw_kestrel_client).get(@queue + "_errors", anything) do
|
194
|
+
Kestrel::Client::Reliable::RetryableJob.new(1, :mcmuffin)
|
195
|
+
end
|
196
|
+
@kestrel.get(@queue)
|
197
|
+
|
198
|
+
mock(@raw_kestrel_client).get_from_last(@queue + "_errors", :close => true, :open => false)
|
199
|
+
@kestrel.retry
|
200
|
+
end
|
201
|
+
|
128
202
|
end
|
129
203
|
end
|
130
204
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kestrel-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 4
|
9
|
-
-
|
10
|
-
version: 0.4.
|
9
|
+
- 1
|
10
|
+
version: 0.4.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Freels
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-08-05 00:00:00 -07:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|