em-pg-client-12 0.3.4

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