kestrel-client 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|