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 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