pg_helper 0.3.1 → 0.4.0

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.
@@ -1,179 +1,168 @@
1
- #Main module of PgHelper gem/plugin
1
+ # Main module of PgHelper gem/plugin
2
2
  module PgHelper
3
-
4
- #Indicates that query returned unexpected columnt count
5
- class PgHelperErrorInvalidColumnCount < PGError; end
6
-
7
- #Indicates that query returned too much rows
8
- class PgHelperErrorInvalidRowCount < PGError; end
9
-
10
- # Indicates that transaction was called while inside transaction
11
- class PgHelperErrorNestedTransactionNotAllowed < PGError; end
12
-
13
- #For use inside transaction to cause rollback.
14
- class PgHelperErrorRollback < PGError; end
15
-
16
- #Indicates that call is invalid outside transaction
17
- class PgHelperErrorInvalidOutsideTransaction < PGError; end
18
-
19
- #Invalid argument
20
- class PgHelperErrorParamsMustBeArrayOfStrings < PGError; end
21
-
22
- #Main api class
23
- class QueryHelper
24
-
25
- # @return [Hash] connection params
26
- attr_accessor :connection_params
27
-
28
- # Active database connection
29
- # @return [PGconn] connection see {http://rubygems.org/gems/pg Pg gem on rubygems} for details
30
- attr_accessor :pg_connection
31
-
32
-
33
- # Creates a new instance of the QueryHelper
34
- def initialize(params)
35
- if params.kind_of? PGconn
36
- @pg_connection = params
37
- @connection_params = nil
38
- else
39
- @connection_params = params
40
- reconnect
3
+ # Main api class
4
+ class QueryHelper
5
+ # @return [Hash] connection params
6
+ attr_accessor :connection_params
7
+
8
+ # Active database connection
9
+ # @return [PGconn] connection see {http://rubygems.org/gems/pg gem}
10
+ attr_accessor :pg_connection
11
+
12
+ # Creates a new instance of the QueryHelper
13
+ def self.using_pool(pool, &_block)
14
+ helper = nil
15
+ pool.with_connection do |conn|
16
+ helper = new(conn)
17
+ yield helper
18
+ end
19
+ ensure
20
+ helper = nil
41
21
  end
42
- end
43
22
 
44
- # @param [String] query SQL select that should return one cell, may include $1, $2 etc to be replaced by arguments
45
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
46
- # @return [String]
47
- def value(query, params = [])
48
- exec(query, params) do |pg_result|
49
- verify_single_cell!(pg_result)
50
- pg_result.getvalue(0,0)
23
+ def initialize(params)
24
+ if params.is_a? PGconn
25
+ @pg_connection = params
26
+ @connection_params = nil
27
+ else
28
+ @connection_params = params
29
+ reconnect
30
+ end
51
31
  end
52
- end
53
32
 
54
- # @param [String] query SQL select that should return one column, may include $1, $2 etc to be replaced by arguments
55
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
56
- # @return [Array<String>] Values of selected column
57
- def get_column(query, params = [])
58
- exec(query, params) do |pg_result|
59
- require_single_column!(pg_result)
60
- pg_result.column_values(0)
33
+ # @param [String] query SQL select that should return one cell,
34
+ # may include $1, $2 etc to be replaced by query arguments
35
+ # @param [Array<String>] params query arguments
36
+ # @return [String]
37
+ def value(query, params = [])
38
+ exec(query, params) do |pg_result|
39
+ ValidationHelper.verify_single_cell!(pg_result)
40
+ pg_result.getvalue(0, 0)
41
+ end
61
42
  end
62
- end
63
43
 
64
- # @param [String] query SQL select that should return one row, may include $1, $2 etc to be replaced by arguments
65
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
66
- # @return [Hash] Hash of column_name => row_value for resulting row
67
- def get_hash(query, params = [])
68
- exec(query, params) do |pg_result|
69
- require_single_row!(pg_result)
70
- pg_result.res[0]
44
+ # @param [String] query SQL select that should return one column,
45
+ # may include $1, $2 etc to be replaced by query arguments
46
+ # @param [Array<String>] params query arguments
47
+ # @return [Array<String>] Values of selected column
48
+ def get_column(query, params = [])
49
+ exec(query, params) do |pg_result|
50
+ ValidationHelper.require_single_column!(pg_result)
51
+ pg_result.column_values(0)
52
+ end
71
53
  end
72
- end
73
54
 
74
- # @param [String] query SQL select, may include $1, $2 etc to be replaced by arguments
75
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
76
- # @return [Array<Array>] Array containing Array of values for each row
77
- def get_all(query, params = [])
78
- exec(query, params) do |pg_result|
79
- pg_result.values
55
+ # @param [String] query SQL select that should return one row,
56
+ # may include $1, $2 etc to be replaced by query arguments
57
+ # @param [Array<String>] params query arguments
58
+ # @return [Hash] Hash of column_name => row_value for resulting row
59
+ def get_hash(query, params = [])
60
+ exec(query, params) do |pg_result|
61
+ ValidationHelper.require_single_row!(pg_result)
62
+ pg_result[0]
63
+ end
80
64
  end
81
- end
82
65
 
83
- # @param [String] query SQL select, may include $1, $2 etc to be replaced by arguments
84
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
85
- # @return [Array<Hash>] Array containing hash of column_name => row_value for each row
86
- def get_all_hashes(query, params = [])
87
- exec(query, params) do |pg_result|
88
- pg_result.to_a
66
+ # @param [String] query SQL select,
67
+ # may include $1, $2 etc to be replaced by query arguments
68
+ # @param [Array<String>] params query arguments
69
+ # @return [Array<Array>] Array containing Array of values for each row
70
+ def get_all(query, params = [])
71
+ exec(query, params) do |pg_result|
72
+ pg_result.values
73
+ end
89
74
  end
90
- end
91
75
 
92
- # @param [String] query SQL select, may include $1, $2 etc to be replaced by arguments
93
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
94
- # @return String csv representation of query result with csv header
95
- def csv(query)
96
- csv_query = "COPY (#{query}) TO STDOUT with CSV HEADER"
97
- exec(csv_query, params = []) do
98
- csv_data = ""
99
- csv_data += buf while buf = @pg_connection.get_copy_data(true)
100
- csv_data
76
+ # @param [String] query SQL select,
77
+ # may include $1, $2 etc to be replaced by query arguments
78
+ # @param [Array<String>] params query arguments
79
+ # @return [Array<Hash>] Array of row hashes column_name => row_value
80
+ def get_all_hashes(query, params = [])
81
+ exec(query, params) do |pg_result|
82
+ pg_result.to_a
83
+ end
101
84
  end
102
- end
103
85
 
104
- # @param [String] query SQL update, may include $1, $2 etc to be replaced by arguments
105
- # @param [Array<String>] params query arguments to be passed on to PostgreSql
106
- # @return [Integer] Number of rows changed
107
- def modify(query, params = [])
108
- exec(query, params) do |pg_result|
109
- pg_result.cmd_tuples
86
+ # @param [String] query SQL select
87
+ # may include $1, $2 etc to be replaced by query arguments
88
+ # @param [Array<String>] params query arguments
89
+ # @return String csv representation of query result with csv header
90
+ def csv(query, params = [])
91
+ csv_query = "COPY (#{query}) TO STDOUT with CSV HEADER"
92
+ exec(csv_query, params) do
93
+ csv_data = ''
94
+ buf = @pg_connection.get_copy_data(true)
95
+ while buf
96
+ csv_data += buf
97
+ buf = @pg_connection.get_copy_data(true)
98
+ end
99
+ csv_data
100
+ end
110
101
  end
111
- end
112
102
 
113
- # Executes content of given block inside database transaction
114
- #@yield [QueryHelper]
115
- def transaction(&block)
116
- verify_transaction_possible!(&block)
117
- perform_transaction(&block)
118
- end
103
+ # @param [String] query SQL update,
104
+ # may include $1, $2 etc to be replaced by query arguments
105
+ # @param [Array<String>] params query arguments
106
+ # @return [Integer] Number of rows changed
107
+ def modify(query, params = [])
108
+ exec(query, params) do |pg_result|
109
+ pg_result.cmd_tuples
110
+ end
111
+ end
119
112
 
120
- # Aborts current transaction, or raises exception if invoked outside transaction.
121
- #@return [void]
122
- def rollback!
123
- raise PgHelperErrorInvalidOutsideTransaction if connection_idle?
124
- raise PgHelperErrorRollback.new
125
- end
113
+ # Executes content of given block inside database transaction
114
+ # @yield [QueryHelper]
115
+ def transaction(&block)
116
+ verify_transaction_possible!(&block)
117
+ perform_transaction(&block)
118
+ end
126
119
 
127
- protected
120
+ # Aborts current transaction, or raises exception if invoked
121
+ # outside transaction.
122
+ # @return [void]
123
+ def rollback!
124
+ fail PgHelperErrorInvalidOutsideTransaction if connection_idle?
125
+ fail PgHelperErrorRollback
126
+ end
128
127
 
129
- def connection_idle?
130
- PGconn::PQTRANS_IDLE == @pg_connection.transaction_status
131
- end
128
+ protected
132
129
 
133
- def require_single_row!(pg_result)
134
- raise PgHelperErrorInvalidRowCount.new if pg_result.ntuples != 1
135
- end
130
+ def connection_idle?
131
+ PGconn::PQTRANS_IDLE == @pg_connection.transaction_status
132
+ end
136
133
 
137
- def require_single_column!(pg_result)
138
- raise PgHelperErrorInvalidColumnCount.new if pg_result.nfields != 1
139
- end
134
+ def exec(query, params = [], &block)
135
+ check_query_params(params)
136
+ pg_result = nil
137
+ begin
138
+ pg_result = @pg_connection.exec(query, params)
139
+ block.call(pg_result)
140
+ ensure
141
+ pg_result && pg_result.clear
142
+ end
143
+ end
140
144
 
141
- def exec(query, params=[], &block)
142
- check_query_params(params)
143
- pg_result = nil
144
- begin
145
- pg_result = @pg_connection.exec(query, params)
146
- block.call(pg_result)
147
- ensure
148
- pg_result && pg_result.clear
145
+ def check_query_params(params)
146
+ params.is_a?(Array) || fail(PgHelperErrorParamsMustBeArrayOfStrings)
149
147
  end
150
- end
151
148
 
152
- def check_query_params(params)
153
- raise PgHelperErrorParamsMustBeArrayOfStrings.new unless params.is_a?(Array)
154
- end
155
- def reconnect
156
- @pg_connection = PGconn.open(@connection_params)
157
- end
149
+ def reconnect
150
+ @pg_connection = PGconn.open(@connection_params) if @connection_params
151
+ end
158
152
 
159
- def perform_transaction(&block)
160
- @pg_connection.transaction do
161
- begin
162
- block.call(self)
163
- rescue PgHelperErrorRollback
164
- true
153
+ def perform_transaction(&block)
154
+ @pg_connection.transaction do
155
+ begin
156
+ block.call(self)
157
+ rescue PgHelperErrorRollback
158
+ true
159
+ end
165
160
  end
166
161
  end
167
- end
168
162
 
169
- def verify_transaction_possible!(&block)
170
- raise PgHelperErrorNestedTransactionNotAllowed.new unless connection_idle?
171
- raise ArgumentError.new('missing block') unless block_given?
172
- end
173
-
174
- def verify_single_cell!(pg_result)
175
- require_single_row!(pg_result)
176
- require_single_column!(pg_result)
163
+ def verify_transaction_possible!(&_block)
164
+ fail PgHelperErrorNestedTransactionNotAllowed unless connection_idle?
165
+ fail ArgumentError, 'missing block' unless block_given?
166
+ end
177
167
  end
178
168
  end
179
- end
@@ -0,0 +1,44 @@
1
+ # Main module of PgHelper gem/plugin
2
+ module PgHelper
3
+ # Indicates that query returned unexpected columnt count
4
+ class PgHelperErrorInvalidColumnCount < PGError; end
5
+
6
+ # Indicates that query returned too much rows
7
+ class PgHelperErrorInvalidRowCount < PGError; end
8
+
9
+ # Indicates that transaction was called while inside transaction
10
+ class PgHelperErrorNestedTransactionNotAllowed < PGError; end
11
+
12
+ # For use inside transaction to cause rollback.
13
+ class PgHelperErrorRollback < PGError; end
14
+
15
+ # Indicates that call is invalid outside transaction
16
+ class PgHelperErrorInvalidOutsideTransaction < PGError; end
17
+
18
+ # Invalid argument
19
+ class PgHelperErrorParamsMustBeArrayOfStrings < PGError; end
20
+
21
+ # data validation
22
+ class ValidationHelper
23
+ class << self
24
+ def require_single_row!(pg_result)
25
+ fail(
26
+ PgHelperErrorInvalidRowCount,
27
+ "expected 1 row, got #{pg_result.ntuples}"
28
+ ) if pg_result.ntuples != 1
29
+ end
30
+
31
+ def require_single_column!(pg_result)
32
+ fail(
33
+ PgHelperErrorInvalidColumnCount,
34
+ "expected 1 column, got #{pg_result.nfields}"
35
+ ) if pg_result.nfields != 1
36
+ end
37
+
38
+ def verify_single_cell!(pg_result)
39
+ require_single_row!(pg_result)
40
+ require_single_column!(pg_result)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
- #Main module of PgHelper gem/plugin
1
+ # Main module of PgHelper gem/plugin
2
2
  module PgHelper
3
3
  # @return [String] gem version
4
- VERSION = "0.3.1"
4
+ VERSION = '0.4.0'
5
5
  end
@@ -19,11 +19,13 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_runtime_dependency 'pg'
22
+ s.add_runtime_dependency 'pg', '~> 0.18.0'
23
+ s.add_runtime_dependency 'thread_safe'
23
24
 
24
25
  s.add_development_dependency 'rake'
25
26
  s.add_development_dependency 'rspec'
26
27
  s.add_development_dependency 'wirble'
27
28
  s.add_development_dependency 'yard'
28
29
  s.add_development_dependency 'bluecloth' #yard hidden dependency
29
- end
30
+ s.add_development_dependency 'rubocop' #yard hidden dependency
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'pg_helper/connection_pool'
3
+
4
+ RSpec.describe PgHelper::ConnectionPool do
5
+ describe '.new'
6
+ # OPTIONS: :pool_size, :checkout_timeout,
7
+ # # REST is passed directly to pg_connection.new
8
+ describe '#checkout'
9
+ describe '#checkin(connection)'
10
+ describe '#'
11
+
12
+ def connection_options
13
+ { dbname: 'postgres', user: 'postgres', host: 'localhost' }
14
+ end
15
+
16
+ subject { described_class.new(connection_options) }
17
+
18
+ describe '#auto_connect' do
19
+ it 'is true by default' do
20
+ expect(subject.auto_connect).to be true
21
+ end
22
+
23
+ context 'when is true' do
24
+ before { subject.auto_connect = true }
25
+
26
+ it 'provides active connection' do
27
+ expect(subject.connection).not_to be_finished
28
+ end
29
+
30
+ it 'yields active connection' do
31
+ subject.with_connection { |c| expect(c).not_to be_finished }
32
+ end
33
+ end
34
+
35
+ context 'when is false' do
36
+ before { subject.auto_connect = false }
37
+ it 'raises error instead of active connection' do
38
+ expect do
39
+ subject.connection
40
+ end.to raise_exception
41
+ end
42
+
43
+ it 'raises error instead of yielding active connection' do
44
+ expect do
45
+ subject.connection
46
+ end.to raise_exception
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,310 @@
1
+ # module ActiveRecord
2
+ # module ConnectionAdapters
3
+ # class ConnectionPoolTest < ActiveRecord::TestCase
4
+ # attr_reader :pool
5
+ #
6
+ # def setup
7
+ # super
8
+ #
9
+ # # Keep a duplicate pool so we do not bother others
10
+ # @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
11
+ #
12
+ # if in_memory_db?
13
+ # # Separate connections to an in-memory database
14
+ # # create an entirely new database,
15
+ # # with an empty schema etc, so we just stub out this
16
+ # # schema on the fly.
17
+ # @pool.with_connection do |connection|
18
+ # connection.create_table :posts do |t|
19
+ # t.integer :cololumn
20
+ # end
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # def teardown
26
+ # super
27
+ # @pool.disconnect!
28
+ # end
29
+ #
30
+ # def active_connections(pool)
31
+ # pool.connections.find_all(&:in_use?)
32
+ # end
33
+ #
34
+ # def test_checkout_after_close
35
+ # connection = pool.connection
36
+ # assert connection.in_use?
37
+ #
38
+ # connection.close
39
+ # assert !connection.in_use?
40
+ #
41
+ # assert pool.connection.in_use?
42
+ # end
43
+ #
44
+ # def test_released_connection_moves_between_threads
45
+ # thread_conn = nil
46
+ #
47
+ # Thread.new {
48
+ # pool.with_connection do |conn|
49
+ # thread_conn = conn
50
+ # end
51
+ # }.join
52
+ #
53
+ # assert thread_conn
54
+ #
55
+ # Thread.new {
56
+ # pool.with_connection do |conn|
57
+ # assert_equal thread_conn, conn
58
+ # end
59
+ # }.join
60
+ # end
61
+ #
62
+ # def test_with_connection
63
+ # assert_equal 0, active_connections(pool).size
64
+ #
65
+ # main_thread = pool.connection
66
+ # assert_equal 1, active_connections(pool).size
67
+ #
68
+ # Thread.new {
69
+ # pool.with_connection do |conn|
70
+ # assert conn
71
+ # assert_equal 2, active_connections(pool).size
72
+ # end
73
+ # assert_equal 1, active_connections(pool).size
74
+ # }.join
75
+ #
76
+ # main_thread.close
77
+ # assert_equal 0, active_connections(pool).size
78
+ # end
79
+ #
80
+ # def test_active_connection_in_use
81
+ # assert !pool.active_connection?
82
+ # main_thread = pool.connection
83
+ #
84
+ # assert pool.active_connection?
85
+ #
86
+ # main_thread.close
87
+ #
88
+ # assert !pool.active_connection?
89
+ # end
90
+ #
91
+ # def test_full_pool_exception
92
+ # assert_raises(CouldNotObtainConnection) do
93
+ # (@pool.size + 1).times do
94
+ # @pool.checkout
95
+ # end
96
+ # end
97
+ # end
98
+ #
99
+ # def test_full_pool_blocks
100
+ # cs = @pool.size.times.map { @pool.checkout }
101
+ # t = Thread.new { @pool.checkout }
102
+ #
103
+ # # make sure our thread is in the timeout section
104
+ # Thread.pass until t.status == "sleep"
105
+ #
106
+ # connection = cs.first
107
+ # connection.close
108
+ # assert_equal connection, t.join.value
109
+ # end
110
+ #
111
+ # def test_removing_releases_latch
112
+ # cs = @pool.size.times.map { @pool.checkout }
113
+ # t = Thread.new { @pool.checkout }
114
+ #
115
+ # # make sure our thread is in the timeout section
116
+ # Thread.pass until t.status == "sleep"
117
+ #
118
+ # connection = cs.first
119
+ # @pool.remove connection
120
+ # assert_respond_to t.join.value, :execute
121
+ # end
122
+ #
123
+ # def test_reap_and_active
124
+ # @pool.checkout
125
+ # @pool.checkout
126
+ # @pool.checkout
127
+ # @pool.dead_connection_timeout = 0
128
+ #
129
+ # connections = @pool.connections.dup
130
+ #
131
+ # @pool.reap
132
+ #
133
+ # assert_equal connections.length, @pool.connections.length
134
+ # end
135
+ #
136
+ # def test_reap_inactive
137
+ # @pool.checkout
138
+ # @pool.checkout
139
+ # @pool.checkout
140
+ # @pool.dead_connection_timeout = 0
141
+ #
142
+ # connections = @pool.connections.dup
143
+ # connections.each do |conn|
144
+ # conn.extend(Module.new { def active?; false; end; })
145
+ # end
146
+ #
147
+ # @pool.reap
148
+ #
149
+ # assert_equal 0, @pool.connections.length
150
+ # ensure
151
+ # connections.each(&:close)
152
+ # end
153
+ #
154
+ # def test_remove_connection
155
+ # conn = @pool.checkout
156
+ # assert conn.in_use?
157
+ #
158
+ # length = @pool.connections.length
159
+ # @pool.remove conn
160
+ # assert conn.in_use?
161
+ # assert_equal(length - 1, @pool.connections.length)
162
+ # ensure
163
+ # conn.close
164
+ # end
165
+ #
166
+ # def test_remove_connection_for_thread
167
+ # conn = @pool.connection
168
+ # @pool.remove conn
169
+ # assert_not_equal(conn, @pool.connection)
170
+ # ensure
171
+ # conn.close if conn
172
+ # end
173
+ #
174
+ # def test_active_connection?
175
+ # assert !@pool.active_connection?
176
+ # assert @pool.connection
177
+ # assert @pool.active_connection?
178
+ # @pool.release_connection
179
+ # assert !@pool.active_connection?
180
+ # end
181
+ #
182
+ # def test_checkout_behaviour
183
+ # pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
184
+ # connection = pool.connection
185
+ # assert_not_nil connection
186
+ # threads = []
187
+ # 4.times do |i|
188
+ # threads << Thread.new(i) do
189
+ # connection = pool.connection
190
+ # assert_not_nil connection
191
+ # connection.close
192
+ # end
193
+ # end
194
+ #
195
+ # threads.each(&:join)
196
+ #
197
+ # Thread.new do
198
+ # assert pool.connection
199
+ # pool.connection.close
200
+ # end.join
201
+ # end
202
+ #
203
+ # # The connection pool is "fair" if threads waiting for
204
+ # # connections receive them the order in which they began
205
+ # # waiting. This ensures that we don't timeout one HTTP request
206
+ # # even while well under capacity in a multi-threaded environment
207
+ # # such as a Java servlet container.
208
+ # #
209
+ # # We don't need strict fairness: if two connections become
210
+ # # available at the same time, it's fine of two threads that were
211
+ # # waiting acquire the connections out of order.
212
+ # #
213
+ # # Thus this test prepares waiting threads and then trickles in
214
+ # # available connections slowly, ensuring the wakeup order is
215
+ # # correct in this case.
216
+ # def test_checkout_fairness
217
+ # @pool.instance_variable_set(:@size, 10)
218
+ # expected = (1..@pool.size).to_a.freeze
219
+ # # check out all connections so our threads start out waiting
220
+ # conns = expected.map { @pool.checkout }
221
+ # mutex = Mutex.new
222
+ # order = []
223
+ # errors = []
224
+ #
225
+ # threads = expected.map do |i|
226
+ # t = Thread.new {
227
+ # begin
228
+ # @pool.checkout # never checked back in
229
+ # mutex.synchronize { order << i }
230
+ # rescue => e
231
+ # mutex.synchronize { errors << e }
232
+ # end
233
+ # }
234
+ # Thread.pass until t.status == "sleep"
235
+ # t
236
+ # end
237
+ #
238
+ # # this should wake up the waiting threads one by one in order
239
+ # conns.each { |conn| @pool.checkin(conn); sleep 0.1 }
240
+ #
241
+ # threads.each(&:join)
242
+ #
243
+ # raise errors.first if errors.any?
244
+ #
245
+ # assert_equal(expected, order)
246
+ # end
247
+ #
248
+ # # As mentioned in #test_checkout_fairness, we don't care about
249
+ # # strict fairness. This test creates two groups of threads:
250
+ # # group1 whose members all start waiting before any thread in
251
+ # # group2. Enough connections are checked in to wakeup all
252
+ # # group1 threads, and the fact that only group1 and no group2
253
+ # # threads acquired a connection is enforced.
254
+ # def test_checkout_fairness_by_group
255
+ # @pool.instance_variable_set(:@size, 10)
256
+ # # take all the connections
257
+ # conns = (1..10).map { @pool.checkout }
258
+ # mutex = Mutex.new
259
+ # successes = [] # threads that successfully got a connection
260
+ # errors = []
261
+ #
262
+ # make_thread = proc do |i|
263
+ # t = Thread.new {
264
+ # begin
265
+ # @pool.checkout # never checked back in
266
+ # mutex.synchronize { successes << i }
267
+ # rescue => e
268
+ # mutex.synchronize { errors << e }
269
+ # end
270
+ # }
271
+ # Thread.pass until t.status == "sleep"
272
+ # t
273
+ # end
274
+ #
275
+ # # all group1 threads start waiting before any in group2
276
+ # group1 = (1..5).map(&make_thread)
277
+ # group2 = (6..10).map(&make_thread)
278
+ #
279
+ # # checkin n connections back to the pool
280
+ # checkin = proc do |n|
281
+ # n.times do
282
+ # c = conns.pop
283
+ # @pool.checkin(c)
284
+ # end
285
+ # end
286
+ #
287
+ # checkin.call(group1.size) # should wake up all group1
288
+ #
289
+ # loop do
290
+ # sleep 0.1
291
+ # break if mutex.synchronize {
292
+ # (successes.size + errors.size) == group1.size }
293
+ # end
294
+ #
295
+ # winners = mutex.synchronize { successes.dup }
296
+ # checkin.call(group2.size) # should wake up everyone remaining
297
+ #
298
+ # group1.each(&:join)
299
+ # group2.each(&:join)
300
+ #
301
+ # assert_equal((1..group1.size).to_a, winners.sort)
302
+ #
303
+ # if errors.any?
304
+ # raise errors.first
305
+ # end
306
+ # end
307
+ # end
308
+ # end
309
+ # end
310
+ # =