riak-client 1.0.0.beta → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -4
- data/Gemfile +12 -17
- data/Guardfile +1 -1
- data/LICENSE +16 -0
- data/README.markdown +178 -0
- data/RELEASE_NOTES.md +99 -0
- data/Rakefile +25 -1
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +37 -19
- data/lib/riak/client.rb +322 -272
- data/lib/riak/client/beefcake_protobuffs_backend.rb +6 -10
- data/lib/riak/client/decaying.rb +28 -0
- data/lib/riak/client/excon_backend.rb +27 -11
- data/lib/riak/client/http_backend.rb +71 -2
- data/lib/riak/client/http_backend/configuration.rb +17 -3
- data/lib/riak/client/http_backend/transport_methods.rb +3 -3
- data/lib/riak/client/net_http_backend.rb +18 -14
- data/lib/riak/client/node.rb +111 -0
- data/lib/riak/client/pool.rb +180 -0
- data/lib/riak/client/protobuffs_backend.rb +15 -5
- data/lib/riak/client/search.rb +9 -3
- data/lib/riak/link.rb +5 -7
- data/lib/riak/locale/en.yml +1 -0
- data/lib/riak/node/configuration.rb +1 -0
- data/lib/riak/node/defaults.rb +19 -6
- data/lib/riak/node/generation.rb +9 -2
- data/lib/riak/node/log.rb +2 -2
- data/lib/riak/node/version.rb +22 -16
- data/lib/riak/robject.rb +19 -3
- data/lib/riak/serializers.rb +1 -1
- data/lib/riak/test_server.rb +10 -2
- data/lib/riak/version.rb +1 -1
- data/riak-client.gemspec +3 -3
- data/spec/failover/failover.rb +59 -0
- data/spec/integration/riak/http_backends_spec.rb +2 -2
- data/spec/integration/riak/node_spec.rb +16 -24
- data/spec/integration/riak/protobuffs_backends_spec.rb +1 -1
- data/spec/integration/riak/test_server_spec.rb +4 -3
- data/spec/integration/riak/threading_spec.rb +193 -0
- data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +23 -0
- data/spec/riak/beefcake_protobuffs_backend_spec.rb +4 -2
- data/spec/riak/bucket_spec.rb +2 -1
- data/spec/riak/client_spec.rb +80 -181
- data/spec/riak/excon_backend_spec.rb +3 -2
- data/spec/riak/http_backend/configuration_spec.rb +37 -5
- data/spec/riak/http_backend/object_methods_spec.rb +1 -1
- data/spec/riak/http_backend/transport_methods_spec.rb +2 -2
- data/spec/riak/http_backend_spec.rb +53 -3
- data/spec/riak/map_reduce_spec.rb +1 -1
- data/spec/riak/net_http_backend_spec.rb +1 -2
- data/spec/riak/node_spec.rb +173 -0
- data/spec/riak/pool_spec.rb +306 -0
- data/spec/riak/robject_spec.rb +8 -4
- data/spec/riak/search_spec.rb +66 -15
- data/spec/riak/serializers_spec.rb +12 -1
- data/spec/spec_helper.rb +9 -1
- data/spec/support/http_backend_implementation_examples.rb +6 -2
- data/spec/support/sometimes.rb +46 -0
- data/spec/support/test_server.rb +50 -19
- data/spec/support/unified_backend_examples.rb +11 -10
- data/spec/support/version_filter.rb +14 -0
- metadata +40 -29
- data/lib/active_support/cache/riak_store.rb +0 -2
- data/lib/riak/cache_store.rb +0 -84
- data/lib/riak/client/pump.rb +0 -30
- data/lib/riak/util/fiber1.8.rb +0 -48
- data/spec/integration/riak/cache_store_spec.rb +0 -129
@@ -24,13 +24,14 @@ else
|
|
24
24
|
|
25
25
|
before :each do
|
26
26
|
@client = Riak::Client.new(:http_port => $mock_server.port, :http_backend => :Excon) # Point to our mock
|
27
|
-
@
|
27
|
+
@node = @client.node
|
28
|
+
@backend = @client.new_http_backend
|
28
29
|
@_mock_set = false
|
29
30
|
end
|
30
31
|
|
31
32
|
after :each do
|
32
33
|
if @_mock_set
|
33
|
-
$mock_server.satisfied.should be_true("Expected #{@_mock_set.inspect}, failed")
|
34
|
+
$mock_server.satisfied.should be_true #("Expected #{@_mock_set.inspect}, failed")
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -2,7 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Riak::Client::HTTPBackend::Configuration do
|
4
4
|
let(:client){ Riak::Client.new }
|
5
|
-
|
5
|
+
let(:node){ client.node }
|
6
|
+
subject { Riak::Client::HTTPBackend.new(client, node) }
|
6
7
|
let(:uri){ URI.parse("http://127.0.0.1:8098/") }
|
7
8
|
|
8
9
|
context "generating resource URIs" do
|
@@ -200,6 +201,37 @@ describe Riak::Client::HTTPBackend::Configuration do
|
|
200
201
|
end
|
201
202
|
end
|
202
203
|
|
204
|
+
context "when Luwak is enabled" do
|
205
|
+
before { subject.should_receive(:get).with(200, uri).and_return(:headers => {'link' => ['</riak>; rel="riak_kv_wm_raw", </ping>; rel="riak_kv_wm_ping", </stats>; rel="riak_kv_wm_stats", </mapred>; rel="riak_kv_wm_mapred", </luwak>; rel="luwak_wm_file"']}) }
|
206
|
+
|
207
|
+
it "should generate a path for a file" do
|
208
|
+
url = subject.luwak_path('foo')
|
209
|
+
url.should be_kind_of(URI)
|
210
|
+
url.path.should == '/luwak/foo'
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should generate a path for the root" do
|
214
|
+
url = subject.luwak_path(nil)
|
215
|
+
url.should be_kind_of(URI)
|
216
|
+
url.path.should == '/luwak'
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should escape a nested path" do
|
220
|
+
url = subject.luwak_path("foo/bar/baz")
|
221
|
+
url.should be_kind_of(URI)
|
222
|
+
url.path.should == '/luwak/foo%2Fbar%2Fbaz'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context "when Luwak is disabled" do
|
227
|
+
before { subject.should_receive(:get).with(200, uri).and_return(:headers => {'link' => ['</riak>; rel="riak_kv_wm_raw", </ping>; rel="riak_kv_wm_ping", </stats>; rel="riak_kv_wm_stats", </mapred>; rel="riak_kv_wm_mapred"']}) }
|
228
|
+
|
229
|
+
it "should raise an error when generating the path" do
|
230
|
+
expect { subject.luwak_path(nil) }.to raise_error
|
231
|
+
expect { subject.luwak_path('foo') }.to raise_error
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
203
235
|
{
|
204
236
|
:riak_kv_wm_raw => :prefix,
|
205
237
|
:riak_kv_wm_link_walker => :prefix,
|
@@ -210,14 +242,14 @@ describe Riak::Client::HTTPBackend::Configuration do
|
|
210
242
|
subject.send(resource).should == "/path"
|
211
243
|
end
|
212
244
|
|
213
|
-
it "should fallback to
|
245
|
+
it "should fallback to node.http_paths[:#{alternate}] if the #{resource} resource is not found" do
|
214
246
|
subject.should_receive(:get).with(200, uri).and_return(:headers => {'link' => ['</>; rel="top"']})
|
215
|
-
subject.send(resource).should ==
|
247
|
+
subject.send(resource).should == node.http_paths[alternate]
|
216
248
|
end
|
217
249
|
|
218
|
-
it "should fallback to
|
250
|
+
it "should fallback to node.http_paths[:#{alternate}] if request fails" do
|
219
251
|
subject.should_receive(:get).with(200, uri).and_raise(Riak::HTTPFailedRequest.new(:get, 200, 404, {}, ""))
|
220
|
-
subject.send(resource).should ==
|
252
|
+
subject.send(resource).should == node.http_paths[alternate]
|
221
253
|
end
|
222
254
|
end
|
223
255
|
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Riak::Client::HTTPBackend::ObjectMethods do
|
4
4
|
before :each do
|
5
5
|
@client = Riak::Client.new
|
6
|
-
@backend = Riak::Client::HTTPBackend.new(@client)
|
6
|
+
@backend = Riak::Client::HTTPBackend.new(@client, @client.node)
|
7
7
|
@bucket = Riak::Bucket.new(@client, "bucket")
|
8
8
|
@object = Riak::RObject.new(@bucket, "bar")
|
9
9
|
@backend.stub!(:new_scheme?).and_return(false)
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Riak::Client::HTTPBackend::TransportMethods do
|
4
4
|
before :each do
|
5
5
|
@client = Riak::Client.new
|
6
|
-
@backend = Riak::Client::HTTPBackend.new(@client)
|
6
|
+
@backend = Riak::Client::HTTPBackend.new(@client, @client.node)
|
7
7
|
@backend.instance_variable_set(:@server_config, {})
|
8
8
|
end
|
9
9
|
|
@@ -91,7 +91,7 @@ describe Riak::Client::HTTPBackend::TransportMethods do
|
|
91
91
|
|
92
92
|
it "should allow using the https protocol" do
|
93
93
|
@client = Riak::Client.new(:protocol => 'https')
|
94
|
-
@backend = Riak::Client::HTTPBackend.new(@client)
|
94
|
+
@backend = Riak::Client::HTTPBackend.new(@client, @client.node)
|
95
95
|
@backend.root_uri.to_s.should eq("https://127.0.0.1:8098")
|
96
96
|
end
|
97
97
|
end
|
@@ -3,19 +3,25 @@ require 'spec_helper'
|
|
3
3
|
describe Riak::Client::HTTPBackend do
|
4
4
|
before :each do
|
5
5
|
@client = Riak::Client.new
|
6
|
-
@
|
6
|
+
@node = @client.nodes.first
|
7
|
+
@backend = Riak::Client::HTTPBackend.new(@client, @node)
|
7
8
|
@backend.instance_variable_set(:@server_config, {})
|
8
9
|
end
|
9
10
|
|
10
|
-
it "should take the Riak::Client when creating" do
|
11
|
+
it "should take the Riak::Client and Riak::Node when creating" do
|
11
12
|
lambda { Riak::Client::HTTPBackend.new(nil) }.should raise_error(ArgumentError)
|
12
|
-
lambda { Riak::Client::HTTPBackend.new(@client) }.
|
13
|
+
lambda { Riak::Client::HTTPBackend.new(@client) }.should raise_error(ArgumentError)
|
14
|
+
lambda { Riak::Client::HTTPBackend.new(@client, @node) }.should_not raise_error
|
13
15
|
end
|
14
16
|
|
15
17
|
it "should make the client accessible" do
|
16
18
|
@backend.client.should == @client
|
17
19
|
end
|
18
20
|
|
21
|
+
it 'should make the node accessible' do
|
22
|
+
@backend.node.should == @node
|
23
|
+
end
|
24
|
+
|
19
25
|
context "pinging the server" do
|
20
26
|
it "should succeed on 200" do
|
21
27
|
@backend.should_receive(:get).with(200, @backend.ping_path).and_return({:code => 200, :body => "OK"})
|
@@ -285,4 +291,48 @@ describe Riak::Client::HTTPBackend do
|
|
285
291
|
@backend.update_search_index(nil, 'postbody')
|
286
292
|
end
|
287
293
|
end
|
294
|
+
context "Luwak" do
|
295
|
+
before { @backend.send(:server_config)[:luwak_wm_file] = '/luwak' }
|
296
|
+
context "fetching a file" do
|
297
|
+
before do
|
298
|
+
|
299
|
+
@backend.should_receive(:get).with(200, @backend.luwak_path("greeting.txt")).and_yield("Hello,").and_yield(" world!").and_return({:code => 200, :headers => {"content-type" => ["text/plain"]}})
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should return a tempfile when no block is given" do
|
303
|
+
file = @backend.get_file("greeting.txt")
|
304
|
+
file.open {|f| f.read.should == "Hello, world!" }
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should expose the original key and content-type on the temporary file" do
|
308
|
+
file = @backend.get_file("greeting.txt")
|
309
|
+
file.original_filename.should == 'greeting.txt'
|
310
|
+
file.content_type.should == 'text/plain'
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should yield chunks of the file to the block and return nil" do
|
314
|
+
string = ""
|
315
|
+
result = @backend.get_file("greeting.txt"){|chunk| string << chunk }
|
316
|
+
result.should be_nil
|
317
|
+
string.should == "Hello, world!"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
context "storing a file" do
|
322
|
+
it "should store a file with the given filename" do
|
323
|
+
@backend.should_receive(:put).with(204, @backend.luwak_path("greeting.txt"), anything, {"Content-Type" => "text/plain"}).and_return({:code => 204, :headers => {}})
|
324
|
+
@backend.store_file("greeting.txt", "text/plain", "Hello, world").should == 'greeting.txt'
|
325
|
+
end
|
326
|
+
|
327
|
+
it "should store a file and return the key/filename when none is given" do
|
328
|
+
@backend.should_receive(:post).with(201, @backend.luwak_path(nil), anything, {"Content-Type" => "text/plain"}).and_return({:code => 201, :headers => {'location' => ["/luwak/123456789"]}})
|
329
|
+
@backend.store_file("text/plain", "Hello, world").should == '123456789'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should detect whether a file exists" do
|
334
|
+
@backend.should_receive(:head).with([200,404], @backend.luwak_path("foo")).and_return({:code => 200})
|
335
|
+
@backend.file_exists?("foo").should be_true
|
336
|
+
end
|
337
|
+
end
|
288
338
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Riak::Client::NetHTTPBackend do
|
4
4
|
before :each do
|
5
5
|
@client = Riak::Client.new(:http_backend => :NetHTTP)
|
6
|
-
@backend = @client.
|
6
|
+
@backend = @client.new_http_backend
|
7
7
|
FakeWeb.allow_net_connect = false
|
8
8
|
end
|
9
9
|
|
@@ -12,5 +12,4 @@ describe Riak::Client::NetHTTPBackend do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it_should_behave_like "HTTP backend"
|
15
|
-
|
16
15
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Riak::Node do
|
4
|
+
before :each do
|
5
|
+
@client = Riak::Client.new
|
6
|
+
@node = Riak::Client::Node.new @client
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'when initializing' do
|
10
|
+
it 'should default to the local interface on port 8098/8087' do
|
11
|
+
node = Riak::Client::Node.new @client
|
12
|
+
node.host.should == '127.0.0.1'
|
13
|
+
node.http_port.should == 8098
|
14
|
+
node.pb_port.should == 8087
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should accept a host' do
|
18
|
+
node = Riak::Client::Node.new(@client, :host => 'riak.basho.com')
|
19
|
+
node.host.should == "riak.basho.com"
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should accept an HTTP port' do
|
23
|
+
node = Riak::Client::Node.new(@client, :http_port => 9000)
|
24
|
+
node.http_port.should == 9000
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should accept a Protobuffs port' do
|
28
|
+
node = Riak::Client::Node.new @client, :pb_port => 9000
|
29
|
+
node.pb_port.should == 9000
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should accept basic_auth' do
|
33
|
+
node = Riak::Client::Node.new @client, :basic_auth => 'user:pass'
|
34
|
+
node.basic_auth.should == "user:pass"
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should accept a path prefix' do
|
38
|
+
node = Riak::Client::Node.new @client, :prefix => "/jiak/"
|
39
|
+
node.http_paths[:prefix].should == "/jiak/"
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'default prefix should be /riak/' do
|
43
|
+
@node.http_paths[:prefix].should == "/riak/"
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should accept a mapreduce path' do
|
47
|
+
node = Riak::Client::Node.new @client, :mapred => "/mr"
|
48
|
+
node.http_paths[:mapred].should == "/mr"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "default mapreduce path should be /mapred" do
|
52
|
+
@node.http_paths[:mapred].should == "/mapred"
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should accept a solr path' do
|
56
|
+
node = Riak::Client::Node.new @client, :solr => "/mr"
|
57
|
+
node.http_paths[:solr].should == "/mr"
|
58
|
+
end
|
59
|
+
|
60
|
+
it "default solr path should be /solr" do
|
61
|
+
@node.http_paths[:solr].should == "/solr"
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should accept a luwak path' do
|
65
|
+
node = Riak::Client::Node.new @client, :luwak => "/mr"
|
66
|
+
node.http_paths[:luwak].should == "/mr"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "default luwak path should be /luwak" do
|
70
|
+
@node.http_paths[:luwak].should == "/luwak"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "setting http auth" do
|
75
|
+
it "should allow setting basic_auth" do
|
76
|
+
@node.should respond_to(:basic_auth=)
|
77
|
+
@node.basic_auth = "user:pass"
|
78
|
+
@node.basic_auth.should == "user:pass"
|
79
|
+
@node.basic_auth = nil
|
80
|
+
@node.basic_auth.should == nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should require that basic auth splits into two even parts" do
|
84
|
+
lambda { @node.basic_auth ="user" }.should raise_error(ArgumentError, "basic auth must be set using 'user:pass'")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'ssl' do
|
89
|
+
before :each do
|
90
|
+
@client = Riak::Client.new
|
91
|
+
@node = Riak::Client::Node.new @client
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should not allow reading ssl options via ssl' do
|
95
|
+
@node.should_not respond_to(:ssl)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should allow setting ssl to true' do
|
99
|
+
@node.ssl = true
|
100
|
+
@node.ssl_options[:verify_mode].should eq('none')
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should should clear ssl options when setting ssl to false" do
|
104
|
+
@node.ssl = true
|
105
|
+
@node.ssl_options.should_not be_nil
|
106
|
+
@node.ssl = false
|
107
|
+
@node.ssl_options.should be_nil
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should allow setting the verify_mode to none" do
|
111
|
+
@node.ssl = {:verify_mode => "none"}
|
112
|
+
@node.ssl_options[:verify_mode].should eq("none")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should allow setting the verify_mode to peer" do
|
116
|
+
@node.ssl = {:verify_mode => "peer"}
|
117
|
+
@node.ssl_options[:verify_mode].should eq("peer")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should not allow setting the verify_mode to anything else" do
|
121
|
+
lambda {@node.ssl = {:verify_mode => :your_mom}}.should raise_error(ArgumentError)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should default verify_mode to none" do
|
125
|
+
@node.ssl = true
|
126
|
+
@node.ssl_options[:verify_mode].should eq("none")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should let the backend know if ssl is enabled" do
|
130
|
+
@node.should_not be_ssl_enabled
|
131
|
+
@node.ssl = true
|
132
|
+
@node.should be_ssl_enabled
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should allow setting the pem" do
|
136
|
+
@node.ssl = {:pem => 'i-am-a-pem'}
|
137
|
+
@node.ssl_options[:pem].should eq('i-am-a-pem')
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should set them pem from the contents of pem_file" do
|
141
|
+
filepath = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test.pem'))
|
142
|
+
@node.ssl = {:pem_file => filepath}
|
143
|
+
@node.ssl_options[:pem].should eq("i-am-a-pem\n")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should allow setting the pem_password" do
|
147
|
+
@node.ssl = {:pem_password => 'pem-pass'}
|
148
|
+
@node.ssl_options[:pem_password].should eq('pem-pass')
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should allow setting the ca_file" do
|
152
|
+
@node.ssl = {:ca_file => '/path/to/ca.crt'}
|
153
|
+
@node.ssl_options[:ca_file].should eq('/path/to/ca.crt')
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should allow setting the ca_path" do
|
157
|
+
@node.ssl = {:ca_path => '/path/to/certs/'}
|
158
|
+
@node.ssl_options[:ca_path].should eq('/path/to/certs/')
|
159
|
+
end
|
160
|
+
|
161
|
+
%w[pem ca_file ca_path].each do |option|
|
162
|
+
it "should default the verify_mode to peer when setting the #{option}" do
|
163
|
+
@node.ssl = {option.to_sym => 'test-data'}
|
164
|
+
@node.ssl_options[:verify_mode].should eq("peer")
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should allow setting the verify mode when setting the #{option}" do
|
168
|
+
@node.ssl = {option.to_sym => 'test-data', :verify_mode => "none"}
|
169
|
+
@node.ssl_options[:verify_mode].should eq("none")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
describe Riak::Client::Pool do
|
5
|
+
describe 'basics' do
|
6
|
+
subject {
|
7
|
+
described_class.new(
|
8
|
+
lambda { [0] },
|
9
|
+
lambda { |x| }
|
10
|
+
)
|
11
|
+
}
|
12
|
+
|
13
|
+
it 'yields a new object' do
|
14
|
+
subject.take do |x|
|
15
|
+
x.should == [0]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'retains a single object for serial access' do
|
20
|
+
n = 100
|
21
|
+
n.times do |i|
|
22
|
+
subject.take do |x|
|
23
|
+
x.should == [i]
|
24
|
+
x[0] += 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
subject.size.should == 1
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should be re-entrant' do
|
31
|
+
n = 10
|
32
|
+
n.times do |i|
|
33
|
+
subject.take do |x|
|
34
|
+
x.replace [1]
|
35
|
+
subject.take do |y|
|
36
|
+
y.replace [2]
|
37
|
+
subject.take do |z|
|
38
|
+
z.replace [3]
|
39
|
+
subject.take do |t|
|
40
|
+
t.replace [4]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
subject.pool.map { |e| e.object.first }.sort.should == [1,2,3,4]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
it 'should unlock when exceptions are raised' do
|
51
|
+
begin
|
52
|
+
subject.take do |x|
|
53
|
+
x << 1
|
54
|
+
subject.take do |y|
|
55
|
+
x << 2
|
56
|
+
y << 3
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue
|
61
|
+
end
|
62
|
+
subject.pool.all? { |e| not e.owner }.should == true
|
63
|
+
subject.pool.map { |e| e.object }.to_set.should == [
|
64
|
+
[0,1,2],
|
65
|
+
[0,3]
|
66
|
+
].to_set
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should delete when BadResource is raised' do
|
70
|
+
subject.open = lambda do
|
71
|
+
m = mock('resource')
|
72
|
+
m.should_receive(:close)
|
73
|
+
m
|
74
|
+
end
|
75
|
+
subject.close = lambda do |res|
|
76
|
+
res.close
|
77
|
+
end
|
78
|
+
|
79
|
+
lambda do
|
80
|
+
subject.take do |x|
|
81
|
+
raise Riak::Client::Pool::BadResource
|
82
|
+
end
|
83
|
+
end.should raise_error(Riak::Client::Pool::BadResource)
|
84
|
+
subject.size.should == 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'threads' do
|
89
|
+
subject {
|
90
|
+
described_class.new(
|
91
|
+
lambda { [] },
|
92
|
+
lambda { |x| }
|
93
|
+
)
|
94
|
+
}
|
95
|
+
|
96
|
+
it 'should allocate n objects for n concurrent operations' do
|
97
|
+
# n threads concurrently allocate and sign objects from the pool
|
98
|
+
n = 10
|
99
|
+
readyq = Queue.new
|
100
|
+
finishq = Queue.new
|
101
|
+
threads = (0...n).map do
|
102
|
+
Thread.new do
|
103
|
+
subject.take do |x|
|
104
|
+
readyq << 1
|
105
|
+
x << Thread.current
|
106
|
+
finishq.pop
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
n.times { readyq.pop }
|
112
|
+
n.times { finishq << 1 }
|
113
|
+
|
114
|
+
# Wait for completion
|
115
|
+
threads.each do |t|
|
116
|
+
t.join
|
117
|
+
end
|
118
|
+
|
119
|
+
# Should have taken exactly n objects to do this
|
120
|
+
subject.size.should == n
|
121
|
+
# And each one should be signed exactly once
|
122
|
+
subject.pool.map do |e|
|
123
|
+
e.object.size.should == 1
|
124
|
+
e.object.first
|
125
|
+
end.to_set.should == threads.to_set
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'take with filter and default' do
|
129
|
+
n = 10
|
130
|
+
subject = described_class.new(
|
131
|
+
lambda { [] },
|
132
|
+
lambda { |x| }
|
133
|
+
)
|
134
|
+
|
135
|
+
# Allocate several elements of the pool
|
136
|
+
q = Queue.new
|
137
|
+
threads = (0...n).map do |i|
|
138
|
+
Thread.new do
|
139
|
+
subject.take do |a|
|
140
|
+
a << i
|
141
|
+
q << 1
|
142
|
+
sleep 0.02
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Wait for all threads to have acquired an element
|
148
|
+
n.times { q.pop }
|
149
|
+
|
150
|
+
threads.each do |t|
|
151
|
+
t.join
|
152
|
+
end
|
153
|
+
|
154
|
+
# Get and delete existing even elements
|
155
|
+
got = Set.new
|
156
|
+
(n / 2).times do
|
157
|
+
begin
|
158
|
+
subject.take(
|
159
|
+
:filter => lambda { |x| x.first.even? },
|
160
|
+
:default => [:default]
|
161
|
+
) do |x|
|
162
|
+
got << x.first
|
163
|
+
raise Riak::Client::Pool::BadResource
|
164
|
+
end
|
165
|
+
rescue Riak::Client::Pool::BadResource
|
166
|
+
end
|
167
|
+
end
|
168
|
+
got.should == (0...n).select(&:even?).to_set
|
169
|
+
|
170
|
+
# This time, no even elements exist, so we should get the default.
|
171
|
+
subject.take(
|
172
|
+
:filter => lambda { |x| x.first.even? },
|
173
|
+
:default => :default
|
174
|
+
) do |x|
|
175
|
+
x.should == :default
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'iterates over a snapshot of all connections, even ones in use' do
|
180
|
+
started = Queue.new
|
181
|
+
n = 30
|
182
|
+
threads = (0..n).map do
|
183
|
+
Thread.new do
|
184
|
+
psleep = 0.75 * rand # up to 50ms sleep
|
185
|
+
subject.take do |a|
|
186
|
+
started << 1
|
187
|
+
a << rand
|
188
|
+
sleep psleep
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
n.times { started.pop }
|
194
|
+
touched = []
|
195
|
+
|
196
|
+
subject.each do |e|
|
197
|
+
touched << e
|
198
|
+
end
|
199
|
+
|
200
|
+
threads.each do |t|
|
201
|
+
t.join
|
202
|
+
end
|
203
|
+
|
204
|
+
touched.should be_all {|item| subject.pool.find {|e| e.object == item } }
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should clear' do
|
208
|
+
n = 10
|
209
|
+
subject = described_class.new(
|
210
|
+
lambda { mock('connection').tap {|m| m.should_receive(:teardown) }},
|
211
|
+
lambda { |b| b.teardown }
|
212
|
+
)
|
213
|
+
|
214
|
+
# Allocate several elements of the pool
|
215
|
+
q = Queue.new
|
216
|
+
threads = (0...n).map do |i|
|
217
|
+
Thread.new do
|
218
|
+
subject.take do |a|
|
219
|
+
q << 1
|
220
|
+
sleep 0.1
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Wait for all threads to have acquired an element
|
226
|
+
n.times { q.pop }
|
227
|
+
|
228
|
+
# Clear the pool while threads still have elements checked out
|
229
|
+
subject.clear
|
230
|
+
subject.pool.should be_empty
|
231
|
+
|
232
|
+
# Wait for threads to complete
|
233
|
+
threads.each do |t|
|
234
|
+
t.join
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should delete_if' do
|
239
|
+
n = 10
|
240
|
+
subject = described_class.new(
|
241
|
+
lambda { [] },
|
242
|
+
lambda { |x| }
|
243
|
+
)
|
244
|
+
|
245
|
+
# Allocate several elements of the pool
|
246
|
+
q = Queue.new
|
247
|
+
threads = (0...n).map do |i|
|
248
|
+
Thread.new do
|
249
|
+
subject.take do |a|
|
250
|
+
a << i
|
251
|
+
q << 1
|
252
|
+
sleep 0.02
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Wait for all threads to have acquired an element
|
258
|
+
n.times { q.pop }
|
259
|
+
|
260
|
+
# Delete odd elements
|
261
|
+
subject.delete_if do |x|
|
262
|
+
x.first.odd?
|
263
|
+
end
|
264
|
+
|
265
|
+
# Verify odds are gone.
|
266
|
+
subject.pool.all? do |x|
|
267
|
+
x.object.first.even?
|
268
|
+
end.should == true
|
269
|
+
|
270
|
+
# Wait for threads
|
271
|
+
threads.each do |t|
|
272
|
+
t.join
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'stress test', :slow => true do
|
277
|
+
n = 100
|
278
|
+
psleep = 0.8
|
279
|
+
tsleep = 0.01
|
280
|
+
rounds = 100
|
281
|
+
|
282
|
+
threads = (0...n).map do
|
283
|
+
Thread.new do
|
284
|
+
rounds.times do |i|
|
285
|
+
subject.take do |a|
|
286
|
+
a.should == []
|
287
|
+
a << Thread.current
|
288
|
+
a.should == [Thread.current]
|
289
|
+
|
290
|
+
# Sleep and check
|
291
|
+
while rand < psleep
|
292
|
+
sleep tsleep
|
293
|
+
a.should == [Thread.current]
|
294
|
+
end
|
295
|
+
|
296
|
+
a.delete Thread.current
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
threads.each do |t|
|
302
|
+
t.join
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|