em-pg-client-12 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ $:.unshift "lib"
2
+ gem 'eventmachine', '~> 1.0.0'
3
+ gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
4
+ require 'eventmachine'
5
+ require 'em-synchrony'
6
+ require 'pg/em'
7
+
8
+ shared_context 'test deferred' do
9
+ it "should not finish connection on deferred connection failure" do
10
+ EM.run do
11
+ subject.connect_defer(options) do |ex|
12
+ ex.should be_an_instance_of PG::ConnectionBad
13
+ ex.connection.should be_an_instance_of subject
14
+ ex.connection.finished?.should be_false
15
+ EM.stop
16
+ end.should be_a_kind_of ::EM::Deferrable
17
+ end
18
+ end
19
+ end
20
+
21
+ shared_context 'test blocking' do
22
+ it "should not finish connection on blocking connection failure" do
23
+ EM.synchrony do
24
+ expect do
25
+ subject.new(options)
26
+ end.to raise_error(PG::ConnectionBad)
27
+ begin
28
+ subject.new(options)
29
+ rescue => ex
30
+ ex.should be_an_instance_of PG::ConnectionBad
31
+ ex.connection.should be_an_instance_of subject
32
+ ex.connection.finished?.should be_false
33
+ end
34
+ EM.stop
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'connect failure and finished? status' do
40
+ subject { PG::EM::Client }
41
+ let(:bogus_port) { 1 }
42
+
43
+ describe 'with localhost' do
44
+ let(:options) { {host: 'localhost', port: bogus_port} }
45
+ include_context 'test deferred'
46
+ include_context 'test blocking'
47
+ end
48
+
49
+ describe 'with unix socket' do
50
+ let(:options) { {host: ENV['PGHOST_UNIX'] || '/tmp', port: bogus_port} }
51
+ include_context 'test deferred'
52
+ include_context 'test blocking'
53
+ end unless RSpec.windows_os?
54
+ end
@@ -0,0 +1,91 @@
1
+ $:.unshift "lib"
2
+ gem 'eventmachine', '~> 1.0.0'
3
+ gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
4
+ require 'eventmachine'
5
+ require 'em-synchrony'
6
+ require 'pg/em'
7
+
8
+ shared_context 'test async connect timeout' do
9
+ it "should timeout expire while connecting" do
10
+ this = :first
11
+ start = Time.now
12
+ subject.connect_defer(options) do |ex|
13
+ this = :second
14
+ ex.should be_an_instance_of PG::ConnectionBad
15
+ ex.message.should include 'timeout expired (async)'
16
+ (Time.now - start).should be > timeout
17
+ EM.stop
18
+ end.should be_a_kind_of ::EM::Deferrable
19
+ this.should be :first
20
+ end
21
+
22
+ around(:each) do |testcase|
23
+ EM.run(&testcase)
24
+ end
25
+ end
26
+
27
+ shared_context 'test synchrony connect timeout' do
28
+ it "should timeout expire while connecting" do
29
+ start = Time.now
30
+ this = nil
31
+ EM.next_tick { this = :that }
32
+ expect do
33
+ subject.new(options)
34
+ end.to raise_error(PG::ConnectionBad, 'timeout expired (async)')
35
+ this.should be :that
36
+ (Time.now - start).should be > timeout
37
+ end
38
+
39
+ around(:each) do |testcase|
40
+ EM.synchrony do
41
+ begin
42
+ testcase.call
43
+ ensure
44
+ EM.stop
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'connect timeout expire' do
51
+ subject { PG::EM::Client }
52
+ let(:black_hole) { '192.168.255.254' }
53
+ let(:timeout) { 1 }
54
+ let(:envvar) { 'PGCONNECT_TIMEOUT' }
55
+
56
+ describe 'asynchronously using connect_timeout option' do
57
+ let(:options) { {host: black_hole, connect_timeout: timeout} }
58
+
59
+ before(:each) { ENV[envvar] = nil }
60
+
61
+ include_context 'test async connect timeout'
62
+ end
63
+
64
+ describe 'asynchronously using PGCONNECT_TIMEOUT env var' do
65
+ let(:options) { {host: black_hole} }
66
+
67
+ before(:each) { ENV[envvar] = timeout.to_s }
68
+
69
+ include_context 'test async connect timeout'
70
+
71
+ after(:each) { ENV[envvar] = nil }
72
+ end
73
+
74
+ describe 'sync-to-fiber using connect_timeout option' do
75
+ let(:options) { {host: black_hole, connect_timeout: timeout} }
76
+
77
+ before(:each) { ENV[envvar] = nil }
78
+
79
+ include_context 'test synchrony connect timeout'
80
+ end
81
+
82
+ describe 'sync-to-fiber using PGCONNECT_TIMEOUT env var' do
83
+ let(:options) { {host: black_hole} }
84
+
85
+ before(:each) { ENV[envvar] = timeout.to_s }
86
+
87
+ include_context 'test synchrony connect timeout'
88
+
89
+ after(:each) { ENV[envvar] = nil }
90
+ end
91
+ end
@@ -0,0 +1,133 @@
1
+ $:.unshift "lib"
2
+ gem 'eventmachine', '~> 1.0.0'
3
+ gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
4
+ require 'date'
5
+ require 'eventmachine'
6
+ require 'pg/em'
7
+
8
+ describe 'em-pg-client options' do
9
+ subject { PG::EM::Client }
10
+
11
+ let(:callback) { proc {|c, e| false } }
12
+ let(:args) { [{
13
+ query_timeout: 1, async_autoreconnect: true, connect_timeout: 10, host: 'foo'
14
+ }]}
15
+ let(:str_key_args) { [{
16
+ 'query_timeout'=>1, 'async_autoreconnect'=>true, 'connect_timeout'=>10, 'host'=>'foo'
17
+ }]}
18
+ let(:pgconn_args) { [{connect_timeout: 10, host: 'foo'}] }
19
+ let(:str_key_pgconn_args) { [{'connect_timeout'=>10, 'host'=>'foo'}] }
20
+ let(:async_options) { {
21
+ :@async_autoreconnect => true,
22
+ :@connect_timeout => 10,
23
+ :@query_timeout=>1,
24
+ :@on_connect=>nil,
25
+ :@on_autoreconnect=>nil,
26
+ :@async_command_aborted=>false} }
27
+
28
+ it "should parse options and not modify original hash" do
29
+ orig_args = args.dup
30
+ orig_options = orig_args.first.dup
31
+ options = subject.parse_async_options orig_args
32
+ options.should eq async_options
33
+ orig_args.should eq pgconn_args
34
+ args.first.should eq orig_options
35
+ end
36
+
37
+ it "should parse options with keys as strings" do
38
+ orig_args = str_key_args.dup
39
+ orig_options = orig_args.first.dup
40
+ options = subject.parse_async_options orig_args
41
+ options.should eq async_options
42
+ orig_args.should eq str_key_pgconn_args
43
+ str_key_args.first.should eq orig_options
44
+ end
45
+
46
+ it "should set async_autoreconnect according to on_autoreconnect" do
47
+ options = subject.parse_async_options []
48
+ options.should be_an_instance_of Hash
49
+ options[:@on_autoreconnect].should be_nil
50
+ options[:@async_autoreconnect].should be_false
51
+
52
+ args = [on_autoreconnect: callback]
53
+ options = subject.parse_async_options args
54
+ args.should eq [{}]
55
+ options.should be_an_instance_of Hash
56
+ options[:@on_autoreconnect].should be callback
57
+ options[:@async_autoreconnect].should be_true
58
+
59
+ args = [async_autoreconnect: false,
60
+ on_autoreconnect: callback]
61
+ options = subject.parse_async_options args
62
+ args.should eq [{}]
63
+ options.should be_an_instance_of Hash
64
+ options[:@on_autoreconnect].should be callback
65
+ options[:@async_autoreconnect].should be_false
66
+
67
+ args = [on_autoreconnect: callback,
68
+ async_autoreconnect: false]
69
+ options = subject.parse_async_options args
70
+ args.should eq [{}]
71
+ options.should be_an_instance_of Hash
72
+ options[:@on_autoreconnect].should be callback
73
+ options[:@async_autoreconnect].should be_false
74
+ end
75
+
76
+ it "should set only callable on_autoreconnect" do
77
+ expect do
78
+ subject.parse_async_options [on_autoreconnect: true]
79
+ end.to raise_error(ArgumentError, /must respond to/)
80
+
81
+ expect do
82
+ subject.parse_async_options ['on_autoreconnect' => Object.new]
83
+ end.to raise_error(ArgumentError, /must respond to/)
84
+
85
+ options = subject.parse_async_options [on_autoreconnect: callback]
86
+ options.should be_an_instance_of Hash
87
+ options[:@on_autoreconnect].should be callback
88
+ end
89
+
90
+ it "should set only callable on_connect" do
91
+ expect do
92
+ subject.parse_async_options [on_connect: true]
93
+ end.to raise_error(ArgumentError, /must respond to/)
94
+
95
+ expect do
96
+ subject.parse_async_options ['on_connect' => Object.new]
97
+ end.to raise_error(ArgumentError, /must respond to/)
98
+
99
+ options = subject.parse_async_options [on_connect: callback]
100
+ options.should be_an_instance_of Hash
101
+ options[:@on_connect].should be callback
102
+ end
103
+
104
+ it "should raise error with obsolete argument" do
105
+ expect do
106
+ subject.parse_async_options [on_reconnect: true]
107
+ end.to raise_error ArgumentError
108
+ end
109
+
110
+ it "should set on_* options with a writer or a block" do
111
+ async_args = subject.parse_async_options([])
112
+ client = subject.allocate
113
+ client.instance_eval {
114
+ async_args.each {|k, v| instance_variable_set(k, v) }
115
+ }
116
+ client.should be_an_instance_of subject
117
+ client.on_connect.should be_nil
118
+ client.on_autoreconnect.should be_nil
119
+ client.on_connect = callback
120
+ client.on_connect.should be callback
121
+ client.on_autoreconnect = callback
122
+ client.on_autoreconnect.should be callback
123
+ client.on_connect = nil
124
+ client.on_connect.should be_nil
125
+ client.on_autoreconnect = nil
126
+ client.on_autoreconnect.should be_nil
127
+ client.on_connect(&callback).should be callback
128
+ client.on_connect.should be callback
129
+ client.on_autoreconnect(&callback).should be callback
130
+ client.on_autoreconnect.should be callback
131
+ end
132
+
133
+ end
@@ -0,0 +1,679 @@
1
+ $:.unshift "lib"
2
+ gem 'eventmachine', '~> 1.0.0'
3
+ gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
4
+ require 'eventmachine'
5
+ require 'em-synchrony'
6
+ require 'pg/em/connection_pool'
7
+ require 'connection_pool_helpers'
8
+
9
+ RSpec.configure do |c|
10
+ c.include ConnectionPoolHelpers
11
+ end
12
+
13
+ describe PG::EM::ConnectionPool do
14
+ subject { PG::EM::ConnectionPool }
15
+
16
+ let(:client) { Class.new }
17
+ let(:deferrable) { PG::EM::FeaturedDeferrable }
18
+ let(:checkpoint) { proc {} }
19
+ let(:pgerror) { PG::Error.new }
20
+ let(:fooerror) { RuntimeError.new 'foo' }
21
+ let(:timeout) { 10 }
22
+ let(:hook) { proc {|c| }}
23
+
24
+ it "should allocate one connection" do
25
+ client.should_receive(:new).with({}).once.and_return(client.allocate)
26
+ pool = subject.new connection_class: client
27
+ pool.should be_an_instance_of subject
28
+ pool.max_size.should eq subject::DEFAULT_SIZE
29
+ pool.available.length.should eq 1
30
+ pool.allocated.length.should eq 0
31
+ pool.size.should eq 1
32
+ pool.available.first.should be_an_instance_of client
33
+ pool.available.first.should_receive(:finish).once
34
+ pool.finish.should be pool
35
+ pool.max_size.should eq subject::DEFAULT_SIZE
36
+ pool.available.length.should eq 0
37
+ pool.allocated.length.should eq 0
38
+ pool.size.should eq 0
39
+ end
40
+
41
+ it "should asynchronously allocate one connection" do
42
+ checkpoint.should_receive(:call) do |pool|
43
+ pool.should be_an_instance_of subject
44
+ pool.max_size.should eq 42
45
+ pool.available.length.should eq 1
46
+ pool.allocated.length.should eq 0
47
+ pool.size.should eq 1
48
+ pool.available.first.should be_an_instance_of client
49
+ end
50
+ client.should_receive(:connect_defer).with({}).once.and_return(
51
+ deferrable.new.tap {|df| df.succeed client.allocate }
52
+ )
53
+ df = subject.connect_defer connection_class: client, size: 42
54
+ df.should be_an_instance_of deferrable
55
+ df.callback(&checkpoint)
56
+ end
57
+
58
+ it "should write to client attributes and finish" do
59
+ client.should_receive(:new) do |options|
60
+ options.should be_empty
61
+ client.allocate.tap do |conn|
62
+ conn.should_receive(:connect_timeout=).with(timeout).once
63
+ conn.should_receive(:query_timeout=).with(timeout).once
64
+ conn.should_receive(:on_autoreconnect=).with(hook).twice
65
+ conn.should_receive(:on_connect=).with(hook).twice
66
+ conn.should_receive(:finish).once
67
+ end
68
+ end.exactly(3)
69
+ pool = subject.new connection_class: client, size: 3
70
+ pool.should be_an_instance_of subject
71
+ pool.connect_timeout.should be_nil
72
+ pool.query_timeout.should be_nil
73
+ pool.max_size.should eq 3
74
+ pool.available.length.should eq 1
75
+ pool.allocated.length.should eq 0
76
+ pool.size.should eq 1
77
+ pool.hold do
78
+ pool.size.should eq 1
79
+ Fiber.new do
80
+ pool.hold do
81
+ pool.size.should eq 2
82
+ Fiber.new do
83
+ pool.hold do
84
+ pool.available.length.should eq 0
85
+ pool.allocated.length.should eq 3
86
+ pool.size.should eq 3
87
+ end
88
+ end.resume
89
+ pool.available.length.should eq 1
90
+ pool.allocated.length.should eq 2
91
+ pool.connect_timeout = timeout
92
+ pool.query_timeout = timeout
93
+ pool.on_connect = hook
94
+ pool.on_connect.should be hook
95
+ pool.on_connect(&hook)
96
+ pool.on_autoreconnect = hook
97
+ pool.on_autoreconnect.should be hook
98
+ pool.on_autoreconnect(&hook)
99
+ end
100
+ end.resume
101
+ end
102
+ pool.available.length.should eq 3
103
+ pool.allocated.length.should eq 0
104
+ pool.size.should eq 3
105
+ pool.connect_timeout.should eq timeout
106
+ pool.query_timeout.should eq timeout
107
+ pool.finish.should be pool
108
+ pool.max_size.should eq 3
109
+ pool.available.length.should eq 0
110
+ pool.allocated.length.should eq 0
111
+ pool.on_connect.should be hook
112
+ pool.on_autoreconnect.should be hook
113
+ pool.size.should eq 0
114
+ end
115
+
116
+ it "should allocate new connection with altered attributes" do
117
+ client.should_receive(:new).with(
118
+ {connect_timeout: timeout, query_timeout: timeout,
119
+ on_connect: hook, on_autoreconnect: hook}
120
+ ).once.and_return(client.allocate)
121
+ pool = subject.new connection_class: client, size: 1, lazy: true
122
+ pool.should be_an_instance_of subject
123
+ pool.connect_timeout.should be_nil
124
+ pool.query_timeout.should be_nil
125
+ pool.connect_timeout = timeout
126
+ pool.query_timeout = timeout
127
+ pool.on_connect = hook
128
+ pool.on_autoreconnect(&hook)
129
+ pool.connect_timeout.should eq timeout
130
+ pool.query_timeout.should eq timeout
131
+ pool.on_connect.should be hook
132
+ pool.on_autoreconnect.should be hook
133
+ pool.max_size.should eq 1
134
+ pool.available.length.should eq 0
135
+ pool.allocated.length.should eq 0
136
+ pool.size.should eq 0
137
+ pool.hold
138
+ pool.available.length.should eq 1
139
+ pool.allocated.length.should eq 0
140
+ pool.size.should eq 1
141
+ end
142
+
143
+ it "should lazy write attributes to connecting client" do
144
+ pool = nil
145
+ client.should_receive(:new) do |options|
146
+ options.should be_empty
147
+ pool.connect_timeout = timeout
148
+ pool.query_timeout = timeout
149
+ pool.on_connect = hook
150
+ pool.on_autoreconnect(&hook)
151
+ client.allocate.tap do |conn|
152
+ conn.should_receive(:connect_timeout=).with(timeout).once
153
+ conn.should_receive(:query_timeout=).with(timeout).once
154
+ conn.should_receive(:on_autoreconnect=).with(hook).once
155
+ conn.should_receive(:on_connect=).with(hook).once
156
+ end
157
+ end.once
158
+ client.should_receive(:connect_defer) do |options|
159
+ options.should be_empty
160
+ pool.connect_timeout = timeout
161
+ pool.query_timeout = timeout
162
+ pool.on_connect(&hook)
163
+ pool.on_autoreconnect = hook
164
+ deferrable.new.tap do |df|
165
+ df.succeed(client.allocate.tap do |conn|
166
+ conn.should_receive(:connect_timeout=).with(timeout).once
167
+ conn.should_receive(:query_timeout=).with(timeout).once
168
+ conn.should_receive(:on_autoreconnect=).with(hook).once
169
+ conn.should_receive(:on_connect=).with(hook).once
170
+ end)
171
+ end
172
+ end.once
173
+ checkpoint.should_receive(:call).with(:hurray).once
174
+
175
+ pool = subject.new connection_class: client, size: 1, lazy: true
176
+ pool.should be_an_instance_of subject
177
+ pool.connect_timeout.should be_nil
178
+ pool.query_timeout.should be_nil
179
+ pool.max_size.should eq 1
180
+ pool.size.should eq 0
181
+ pool.hold
182
+ pool.connect_timeout.should eq timeout
183
+ pool.query_timeout.should eq timeout
184
+ pool.size.should eq 1
185
+
186
+ pool = subject.new connection_class: client, size: 1, lazy: true
187
+ pool.should be_an_instance_of subject
188
+ pool.connect_timeout.should be_nil
189
+ pool.query_timeout.should be_nil
190
+ pool.__send__(:hold_deferred, checkpoint) do
191
+ deferrable.new.tap { |df| df.succeed :hurray }
192
+ end
193
+ pool.connect_timeout.should eq timeout
194
+ pool.query_timeout.should eq timeout
195
+ pool.size.should eq 1
196
+ end
197
+
198
+ it "should pass missing methods to connection" do
199
+ client.should_receive(:new) do
200
+ client.allocate.tap do |conn|
201
+ conn.should_receive(:foobar).once.and_return(:ok)
202
+ end
203
+ end.once
204
+ pool = subject.new connection_class: client
205
+ pool.should be_an_instance_of subject
206
+ pool.foobar.should be :ok
207
+ pool.respond_to?(:foobar).should be_true
208
+ pool.respond_to?(:crowbar).should be_false
209
+ end
210
+
211
+ it "should hold nested commands" do
212
+ ::EM.should_not_receive(:next_tick)
213
+ client.should_receive(:new).with({}).once.and_return(client.allocate)
214
+ pool = subject.new connection_class: client
215
+ pool.should be_an_instance_of subject
216
+ checkpoint.should_receive(:check) do |conn|
217
+ pool.max_size.should eq subject::DEFAULT_SIZE
218
+ pool.available.length.should eq 0
219
+ pool.allocated.length.should eq 1
220
+ pool.size.should eq 1
221
+ end.exactly(3)
222
+ pool.hold do |conn|
223
+ conn.should be_an_instance_of client
224
+ checkpoint.check
225
+ pool.hold do |conn2|
226
+ conn2.should be conn
227
+ checkpoint.check
228
+ end
229
+ checkpoint.check
230
+ end
231
+ pool.max_size.should eq subject::DEFAULT_SIZE
232
+ pool.available.length.should eq 1
233
+ pool.allocated.length.should eq 0
234
+ pool.size.should eq 1
235
+ pool.available.first.should be_an_instance_of client
236
+ end
237
+
238
+ it "should hold commands concurrently" do
239
+ ::EM.should_not_receive(:next_tick)
240
+ client.should_receive(:new) { client.allocate }.twice
241
+ pool = subject.new connection_class: client, size: 2
242
+ pool.should be_an_instance_of subject
243
+ pool.max_size.should eq 2
244
+ pool.available.length.should eq 1
245
+ pool.allocated.length.should eq 0
246
+ pool.size.should eq 1
247
+ pool.available.first.should be_an_instance_of client
248
+ checkpoint.should_receive(:check).exactly(8)
249
+ result = []
250
+ pool.hold do |conn1|
251
+ conn1.should be_an_instance_of client
252
+ result << conn1
253
+ pool.max_size.should eq 2
254
+ pool.available.length.should eq 0
255
+ pool.allocated.length.should eq 1
256
+ pool.size.should eq 1
257
+ checkpoint.check
258
+ Fiber.new do
259
+ pool.hold do |conn2|
260
+ conn2.should be_an_instance_of client
261
+ pool.instance_variable_get(:@pending).length.should eq 0
262
+ Fiber.new do
263
+ pool.hold do |conn3|
264
+ result << conn3
265
+ conn3.should be conn2
266
+ pool.available.length.should eq 0
267
+ pool.allocated.length.should eq 2
268
+ pool.size.should eq 2
269
+ checkpoint.check
270
+ end
271
+ end.resume
272
+ pool.instance_variable_get(:@pending).length.should eq 1
273
+ Fiber.new do
274
+ pool.hold do |conn4|
275
+ result << conn4
276
+ result.should eq [conn1, conn2, conn2, conn4]
277
+ conn4.should be conn2
278
+ pool.available.length.should eq 0
279
+ pool.allocated.length.should eq 2
280
+ pool.size.should eq 2
281
+ checkpoint.check
282
+ pool.hold do |conn5|
283
+ conn5.should be conn4
284
+ pool.max_size.should eq 2
285
+ pool.available.length.should eq 0
286
+ pool.allocated.length.should eq 2
287
+ pool.size.should eq 2
288
+ checkpoint.check
289
+ end
290
+ pool.available.length.should eq 0
291
+ pool.allocated.length.should eq 2
292
+ pool.size.should eq 2
293
+ checkpoint.check
294
+ end
295
+ end.resume
296
+ pool.instance_variable_get(:@pending).length.should eq 2
297
+ result << conn2
298
+ conn2.should_not be conn1
299
+ pool.available.length.should eq 0
300
+ pool.allocated.length.should eq 2
301
+ pool.size.should eq 2
302
+ checkpoint.check
303
+ end
304
+ pool.max_size.should eq 2
305
+ pool.available.length.should eq 1
306
+ pool.allocated.length.should eq 1
307
+ pool.size.should eq 2
308
+ checkpoint.check
309
+ end.resume
310
+ pool.instance_variable_get(:@pending).length.should eq 0
311
+ end
312
+ pool.max_size.should eq 2
313
+ pool.available.length.should eq 2
314
+ pool.allocated.length.should eq 0
315
+ pool.size.should eq 2
316
+ checkpoint.check
317
+ end
318
+
319
+ it "should hold deferred commands concurrently" do
320
+ ::EM.should_not_receive(:next_tick)
321
+ client.should_not_receive(:new)
322
+ client.should_receive(:connect_defer) do
323
+ deferrable.new.tap {|d| d.succeed client.allocate }
324
+ end.twice
325
+ checkpoint.should_receive(:call).exactly(4)
326
+ checkpoint.should_receive(:check).exactly(9)
327
+ pool = subject.new connection_class: client, size: 2, lazy: true
328
+ pool.should be_an_instance_of subject
329
+ pool.max_size.should eq 2
330
+ pool.available.length.should eq 0
331
+ pool.allocated.length.should eq 0
332
+ pool.size.should eq 0
333
+ result = []
334
+ df = pool.__send__(:hold_deferred, checkpoint) do |conn1|
335
+ conn1.should be_an_instance_of client
336
+ pool.available.length.should eq 0
337
+ pool.allocated.length.should eq 1
338
+ checkpoint.check
339
+ df2 = pool.__send__(:hold_deferred, checkpoint) do |conn2|
340
+ conn2.should be_an_instance_of client
341
+ conn2.should_not be conn1
342
+ pool.available.length.should eq 0
343
+ pool.allocated.length.should eq 2
344
+ checkpoint.check
345
+ df3 = pool.__send__(:hold_deferred, checkpoint) do |conn3|
346
+ conn3.should be_an_instance_of client
347
+ conn3.should be conn2
348
+ pool.available.length.should eq 0
349
+ pool.allocated.length.should eq 2
350
+ checkpoint.check
351
+ deferrable.new.tap {|d| d.succeed :result3 }
352
+ end
353
+ df3.should be_an_instance_of deferrable
354
+ df3.should_not be df
355
+ df3.should_not be df2
356
+ df3.callback do |result3|
357
+ result << result3
358
+ result3.should eq :result3
359
+ pool.available.length.should eq 1
360
+ pool.allocated.length.should eq 1
361
+ checkpoint.check
362
+ end
363
+ pool.instance_variable_get(:@pending).length.should eq 1
364
+ deferrable.new.tap {|d| d.succeed :result2 }
365
+ end
366
+ df2.should be_an_instance_of deferrable
367
+ df2.should_not be df
368
+ df2.callback do |result2|
369
+ result << result2
370
+ result2.should eq :result2
371
+ pool.available.length.should eq 1
372
+ pool.allocated.length.should eq 1
373
+ checkpoint.check
374
+ end
375
+ pool.instance_variable_get(:@pending).length.should eq 0
376
+ deferrable.new.tap {|d| d.succeed :result1 }
377
+ end
378
+ df.should be_an_instance_of deferrable
379
+ df.callback do |result1|
380
+ result << result1
381
+ result1.should eq :result1
382
+ pool.available.length.should eq 2
383
+ pool.allocated.length.should eq 0
384
+ checkpoint.check
385
+ df4 = pool.__send__(:hold_deferred, checkpoint) do |conn4|
386
+ conn4.should be_an_instance_of client
387
+ pool.available.length.should eq 1
388
+ pool.allocated.length.should eq 1
389
+ checkpoint.check
390
+ deferrable.new.tap {|d| d.succeed :result4 }
391
+ end
392
+ df4.should be_an_instance_of deferrable
393
+ df4.should_not be df
394
+ df4.callback do |result4|
395
+ result << result4
396
+ result4.should eq :result4
397
+ pool.available.length.should eq 2
398
+ pool.allocated.length.should eq 0
399
+ checkpoint.check
400
+ end
401
+ end
402
+ pool.available.length.should eq 2
403
+ pool.allocated.length.should eq 0
404
+ pool.size.should eq 2
405
+ result.should eq [:result3, :result2, :result1, :result4]
406
+ checkpoint.check
407
+ end
408
+
409
+ it "should drop failed connection while connecting" do
410
+ pool = nil
411
+ ::EM.should_not_receive(:next_tick)
412
+ client.should_receive(:new) do
413
+ if pool
414
+ pool.available.length.should eq 0
415
+ pool.allocated.length.should eq 1
416
+ pool.size.should eq 1
417
+ end
418
+ raise PG::Error
419
+ end.twice
420
+ expect do
421
+ subject.new connection_class: client, size: 1
422
+ end.to raise_error PG::Error
423
+
424
+ pool = subject.new connection_class: client, size: 1, lazy: true
425
+ pool.should be_an_instance_of subject
426
+ pool.max_size.should eq 1
427
+ pool.available.length.should eq 0
428
+ pool.allocated.length.should eq 0
429
+ pool.size.should eq 0
430
+ expect do
431
+ pool.hold
432
+ end.to raise_error PG::Error
433
+ pool.max_size.should eq 1
434
+ pool.available.length.should eq 0
435
+ pool.allocated.length.should eq 0
436
+ pool.size.should eq 0
437
+ end
438
+
439
+ it "should drop failed connection while connecting asynchronously" do
440
+ pool = nil
441
+ ::EM.should_not_receive(:next_tick)
442
+ client.should_not_receive(:new)
443
+ client.should_receive(:connect_defer) do
444
+ if pool
445
+ pool.available.length.should eq 0
446
+ pool.allocated.length.should eq 1
447
+ pool.size.should eq 1
448
+ end
449
+ deferrable.new.tap {|d| d.fail pgerror }
450
+ end.twice
451
+ checkpoint.should_receive(:call).with(pgerror).exactly(3)
452
+ df = subject.connect_defer connection_class: client, size: 1
453
+ df.should be_an_instance_of deferrable
454
+ df.errback(&checkpoint)
455
+
456
+ pool = subject.new connection_class: client, size: 1, lazy: true
457
+ pool.should be_an_instance_of subject
458
+ pool.max_size.should eq 1
459
+ pool.available.length.should eq 0
460
+ pool.allocated.length.should eq 0
461
+ pool.size.should eq 0
462
+ df = pool.__send__(:hold_deferred, checkpoint)
463
+ df.should be_an_instance_of deferrable
464
+ df.errback(&checkpoint)
465
+ pool.max_size.should eq 1
466
+ pool.available.length.should eq 0
467
+ pool.allocated.length.should eq 0
468
+ pool.size.should eq 0
469
+ end
470
+
471
+ it "should drop only failed connection on error" do
472
+ pool = nil
473
+ ::EM.should_not_receive(:next_tick)
474
+ client.should_receive(:new) do
475
+ if pool
476
+ pool.available.length.should eq 0
477
+ pool.allocated.length.should eq 1
478
+ pool.size.should eq 1
479
+ end
480
+ client.allocate
481
+ end.twice
482
+ checkpoint.should_receive(:check).exactly(2)
483
+ pool = subject.new connection_class: client, size: 1
484
+ pool.should be_an_instance_of subject
485
+ pool.max_size.should eq 1
486
+ pool.available.length.should eq 1
487
+ pool.allocated.length.should eq 0
488
+ pool.size.should eq 1
489
+ expect do
490
+ pool.hold do |conn|
491
+ conn.should be_an_instance_of client
492
+ pool.available.length.should eq 0
493
+ pool.allocated.length.should eq 1
494
+ pool.size.should eq 1
495
+ conn.should_receive(:status).once.and_return(PG::CONNECTION_BAD)
496
+ conn.should_receive(:finished?).once.and_return(false)
497
+ conn.should_receive(:finish).once
498
+ checkpoint.check
499
+ raise PG::Error
500
+ end
501
+ end.to raise_error PG::Error
502
+ pool.max_size.should eq 1
503
+ pool.available.length.should eq 0
504
+ pool.allocated.length.should eq 0
505
+ pool.size.should eq 0
506
+ expect do
507
+ pool.hold do |conn|
508
+ pool.available.length.should eq 0
509
+ pool.allocated.length.should eq 1
510
+ pool.size.should eq 1
511
+ conn.should be_an_instance_of client
512
+ conn.should_not_receive(:status)
513
+ conn.should_not_receive(:finished?)
514
+ conn.should_not_receive(:finish)
515
+ checkpoint.check
516
+ raise 'foo'
517
+ end
518
+ end.to raise_error RuntimeError, 'foo'
519
+ pool.available.length.should eq 1
520
+ pool.allocated.length.should eq 0
521
+ pool.size.should eq 1
522
+ end
523
+
524
+ it "should drop only failed connection on deferred error" do
525
+ pool = nil
526
+ ::EM.should_not_receive(:next_tick)
527
+ client.should_not_receive(:new)
528
+ client.should_receive(:connect_defer) do
529
+ if pool
530
+ pool.available.length.should eq 0
531
+ pool.allocated.length.should eq 1
532
+ pool.size.should eq 1
533
+ end
534
+ deferrable.new.tap {|d| d.succeed client.allocate }
535
+ end.twice
536
+ checkpoint.should_receive(:check).exactly(2)
537
+ checkpoint.should_receive(:call).with(pgerror).exactly(2)
538
+ checkpoint.should_receive(:call).with(fooerror).exactly(2)
539
+ pool = subject.new connection_class: client, size: 1, lazy: true
540
+ pool.should be_an_instance_of subject
541
+ pool.max_size.should eq 1
542
+ pool.available.length.should eq 0
543
+ pool.allocated.length.should eq 0
544
+ pool.size.should eq 0
545
+ df = pool.__send__(:hold_deferred, checkpoint) do |conn|
546
+ pool.available.length.should eq 0
547
+ pool.allocated.length.should eq 1
548
+ pool.size.should eq 1
549
+ conn.should be_an_instance_of client
550
+ conn.should_receive(:status).once.and_return(PG::CONNECTION_BAD)
551
+ conn.should_receive(:finished?).once.and_return(false)
552
+ conn.should_receive(:finish).once
553
+ checkpoint.check
554
+ deferrable.new.tap {|d| d.fail pgerror }
555
+ end
556
+ df.should be_an_instance_of deferrable
557
+ df.errback(&checkpoint)
558
+ pool.max_size.should eq 1
559
+ pool.available.length.should eq 0
560
+ pool.allocated.length.should eq 0
561
+ pool.size.should eq 0
562
+ df = pool.__send__(:hold_deferred, checkpoint) do |conn|
563
+ pool.available.length.should eq 0
564
+ pool.allocated.length.should eq 1
565
+ pool.size.should eq 1
566
+ conn.should be_an_instance_of client
567
+ conn.should_not_receive(:status)
568
+ conn.should_not_receive(:finished?)
569
+ conn.should_not_receive(:finish)
570
+ checkpoint.check
571
+ deferrable.new.tap {|d| d.fail fooerror }
572
+ end
573
+ df.should be_an_instance_of deferrable
574
+ df.errback(&checkpoint)
575
+ pool.max_size.should eq 1
576
+ pool.available.length.should eq 1
577
+ pool.allocated.length.should eq 0
578
+ pool.size.should eq 1
579
+ end
580
+
581
+ let(:pool_size) { 7 }
582
+
583
+ it "pending requests should not starve on failed connections" do
584
+ pool = nil
585
+ client.should_receive(:new) do
586
+ pool.available.length.should eq 0
587
+ pool.size.should be > 0
588
+ pool.size.should be <= pool_size
589
+ pool.size.should eq pool.allocated.length
590
+ sleep_one_tick
591
+ raise PG::ConnectionBad
592
+ end.exactly(100)
593
+ client.should_receive(:connect_defer) do
594
+ pool.available.length.should eq 0
595
+ pool.size.should be > 0
596
+ pool.size.should be <= pool_size
597
+ pool.size.should eq pool.allocated.length
598
+ deferrable.new.tap do |df|
599
+ EM.next_tick { df.fail pgerror }
600
+ end
601
+ end.exactly(100)
602
+ pool = subject.new connection_class: client, lazy: true, size: pool_size
603
+ pool.should be_an_instance_of subject
604
+ pool.max_size.should eq pool_size
605
+ pool.size.should eq 0
606
+
607
+ queries = %w[fee bzz bzz fee bzz fee fee bzz fee bzz]*2
608
+ 10.times do
609
+ test_queries pool, queries.rotate! do |progress|
610
+ pending = pool.instance_variable_get(:@pending).length
611
+ expected_pending = progress.length - pool.max_size
612
+ pending.should eq [0, expected_pending].max
613
+ pool.available.length.should eq 0
614
+ pool.size.should be >= [pool_size, expected_pending].min
615
+ end
616
+ end
617
+
618
+ pool.max_size.should eq pool_size
619
+ pool.size.should eq 0
620
+ end
621
+
622
+ it "pending requests should not starve on commands with failing connections" do
623
+ pool = nil
624
+ expected_connections = if pool_size < 20
625
+ 200 + pool_size
626
+ elsif pool_size < 40
627
+ 220
628
+ elsif pool_size < 60
629
+ 180 + pool_size
630
+ else
631
+ 240
632
+ end
633
+ checkpoint.should_receive(:check_fiber).exactly(300)
634
+ checkpoint.should_receive(:check_defer).exactly(300)
635
+ checkpoint.should_receive(:connect).exactly(expected_connections)
636
+ client.stub(:new) do
637
+ pool.available.length.should eq 0
638
+ pool.size.should be > 0
639
+ pool.size.should be <= pool_size
640
+ pool.size.should eq pool.allocated.length
641
+ sleep_one_tick
642
+ checkpoint.connect
643
+ create_connection
644
+ end
645
+ pool = subject.new connection_class: client, lazy: true, size: pool_size
646
+ client.stub(:connect_defer) do
647
+ pool.available.length.should eq 0
648
+ pool.size.should be > 0
649
+ pool.size.should be <= pool_size
650
+ pool.size.should eq pool.allocated.length
651
+ checkpoint.connect
652
+ deferrable.new.tap do |df|
653
+ EM.next_tick { df.succeed create_connection }
654
+ end
655
+ end
656
+ pool.should be_an_instance_of subject
657
+ pool.max_size.should eq pool_size
658
+ pool.size.should eq 0
659
+
660
+ good_queries = %w[foo bar bar foo bar foo foo bar foo bar]*2
661
+ disconnecting_queries = %w[fee bzz bzz fee bzz fee fee bzz fee bzz]*2
662
+
663
+ 10.times do
664
+ test_queries pool, good_queries + disconnecting_queries + good_queries do |progress|
665
+ pending = pool.instance_variable_get(:@pending).length
666
+ expected_pending = progress.length - pool.max_size
667
+ pending.should eq [0, expected_pending].max
668
+ pool.size.should be > 0
669
+ end
670
+ good_queries.rotate!
671
+ disconnecting_queries.rotate!
672
+ end
673
+
674
+ pool.max_size.should eq pool_size
675
+ pool.available.length.should eq pool.size
676
+ pool.size.should eq expected_connections - 200
677
+ end
678
+
679
+ end