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 CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.4.1
@@ -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.0"
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-07-22}
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 = [
@@ -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
- opts = extract_options(opts)
22
- opts.merge! :close => true, :open => true
54
+ raise MultipleQueueException if @key && key != @key
23
55
 
24
56
  job =
25
- if rand < ERROR_PROCESSING_RATE
26
- client.get(key + "_errors", opts) || client.get(key, opts)
57
+ if rand < @error_rate
58
+ get_with_fallback(key + "_errors", key, opts)
27
59
  else
28
- client.get(key, opts) || client.get(key + "_errors", opts)
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
- if @job.retries < DEFAULT_RETRIES
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
- class RetryableJob < Struct.new(:retries, :job)
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: 15
4
+ hash: 13
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 0
10
- version: 0.4.0
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-07-22 00:00:00 -07:00
19
+ date: 2010-08-05 00:00:00 -07:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency