pod4 0.10.6 → 1.0.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.
- checksums.yaml +5 -5
- data/.bugs/bugs +2 -1
- data/.bugs/details/b5368c7ef19065fc597b5692314da71772660963.txt +53 -0
- data/.hgtags +1 -0
- data/Gemfile +5 -5
- data/README.md +157 -46
- data/lib/pod4/basic_model.rb +9 -22
- data/lib/pod4/connection.rb +67 -0
- data/lib/pod4/connection_pool.rb +154 -0
- data/lib/pod4/errors.rb +20 -0
- data/lib/pod4/interface.rb +34 -12
- data/lib/pod4/model.rb +32 -27
- data/lib/pod4/nebulous_interface.rb +25 -30
- data/lib/pod4/null_interface.rb +22 -16
- data/lib/pod4/pg_interface.rb +84 -104
- data/lib/pod4/sequel_interface.rb +138 -82
- data/lib/pod4/tds_interface.rb +83 -70
- data/lib/pod4/tweaking.rb +105 -0
- data/lib/pod4/version.rb +1 -1
- data/md/breaking_changes.md +80 -0
- data/spec/common/basic_model_spec.rb +67 -70
- data/spec/common/connection_pool_parallelism_spec.rb +154 -0
- data/spec/common/connection_pool_spec.rb +246 -0
- data/spec/common/connection_spec.rb +129 -0
- data/spec/common/model_ai_missing_id_spec.rb +256 -0
- data/spec/common/model_plus_encrypting_spec.rb +16 -4
- data/spec/common/model_plus_tweaking_spec.rb +128 -0
- data/spec/common/model_plus_typecasting_spec.rb +10 -4
- data/spec/common/model_spec.rb +283 -363
- data/spec/common/nebulous_interface_spec.rb +159 -108
- data/spec/common/null_interface_spec.rb +88 -65
- data/spec/common/sequel_interface_pg_spec.rb +217 -161
- data/spec/common/shared_examples_for_interface.rb +50 -50
- data/spec/jruby/sequel_encrypting_jdbc_pg_spec.rb +1 -1
- data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +3 -3
- data/spec/jruby/sequel_interface_jdbc_pg_spec.rb +3 -23
- data/spec/mri/pg_encrypting_spec.rb +1 -1
- data/spec/mri/pg_interface_spec.rb +311 -223
- data/spec/mri/sequel_encrypting_spec.rb +1 -1
- data/spec/mri/sequel_interface_spec.rb +177 -180
- data/spec/mri/tds_encrypting_spec.rb +1 -1
- data/spec/mri/tds_interface_spec.rb +296 -212
- data/tags +340 -174
- metadata +19 -11
- data/md/fixme.md +0 -3
- data/md/roadmap.md +0 -125
- data/md/typecasting.md +0 -80
- data/spec/common/model_new_validate_spec.rb +0 -204
data/lib/pod4/version.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
A list of breaking / major changes by version.
|
2
|
+
|
3
|
+
1.0
|
4
|
+
===
|
5
|
+
|
6
|
+
Interfaces Can Now Note If Their ID Autoincrements
|
7
|
+
--------------------------------------------------
|
8
|
+
|
9
|
+
Autoincrement defaults to true if missing. So any models without auto-incrementing keys will need
|
10
|
+
to change to specifically name them as such.
|
11
|
+
|
12
|
+
You can now add the id field to `attr_columns` even if the ID field autoincrements. Which means
|
13
|
+
that you can refer to the id field by name as an attribute instead of using `@model_id`, if you
|
14
|
+
want.
|
15
|
+
|
16
|
+
Some minor changes that arise from this:
|
17
|
+
|
18
|
+
* #to_ot now always includes the ID field, whether or not it is named in `attr_columns`.
|
19
|
+
|
20
|
+
* If you manually update the ID field even though autoincrement is true, that change will not be
|
21
|
+
stored in the database / whatever. We don't pass that on.
|
22
|
+
|
23
|
+
* If you change the ID field in a non-autoincrement model, `@model_id` is now updated to reflect
|
24
|
+
that when you call #update. (This was not true before 1.0.)
|
25
|
+
|
26
|
+
Connection Objects
|
27
|
+
------------------
|
28
|
+
|
29
|
+
This is technically not a breaking change. No existing code needs to be rewritten; interfaces
|
30
|
+
create connection objects for you if you don't use them. But, this is a really big change
|
31
|
+
internally, and as such I would be surprised if it didn't effect existing < 1.0 code.
|
32
|
+
|
33
|
+
This counts double if you use PgInterface and TdsInterface, since these are now being served one
|
34
|
+
connection per thread and are finally really threadsafe.
|
35
|
+
|
36
|
+
NullInterface
|
37
|
+
-------------
|
38
|
+
|
39
|
+
The behaviour of NullInterface has changed. Prior to 1.0 it did not simulate an auto-incrementing
|
40
|
+
ID field. Now it does, and that behaviour is the default.
|
41
|
+
|
42
|
+
Existing code that assumes the previous behaviour should be fixed by setting the id_ai attribute to
|
43
|
+
false:
|
44
|
+
|
45
|
+
```
|
46
|
+
ifce = NullInterface.new(:code, :name, [])
|
47
|
+
ifce.id_ai = false
|
48
|
+
set_interface ifce
|
49
|
+
```
|
50
|
+
|
51
|
+
DSL To Declare a Custom List Method
|
52
|
+
-----------------------------------
|
53
|
+
|
54
|
+
This is provided by the new Tweaking mixin, so it's not a breaking change.
|
55
|
+
|
56
|
+
Model Status :empty
|
57
|
+
-------------------
|
58
|
+
|
59
|
+
This has been renamed to :unknown to reflect that it is also the status of objects created by #list;
|
60
|
+
:unknown means that validation has not been run yet. This definitely counts as a breaking change,
|
61
|
+
although you would only be effected if you were testing for :empty in a model...
|
62
|
+
|
63
|
+
|
64
|
+
0.10.1
|
65
|
+
======
|
66
|
+
|
67
|
+
Validate Method Takes A Parameter
|
68
|
+
---------------------------------
|
69
|
+
|
70
|
+
You now need to give your #validate method a parameter, the operation that is being validated --
|
71
|
+
one of :create :read :update or :delete.
|
72
|
+
|
73
|
+
In fact you could optionally do this since 0.9. But in 0.10.1 we removed the slightly confusing
|
74
|
+
feature where if validation failed on a #delete, we deleted the record anyway. So 0.10.1 marks the
|
75
|
+
point where, for all practical purposes, you have to give your method a parameter and check it at
|
76
|
+
least for :delete.
|
77
|
+
|
78
|
+
Models that don't do this will not allow deletion of records that fail validation, which presumably
|
79
|
+
is an anti-feature for you.
|
80
|
+
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require "octothorpe"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "pod4/basic_model"
|
4
|
+
require "pod4/null_interface"
|
5
5
|
|
6
6
|
|
7
|
-
describe
|
7
|
+
describe "BasicModel" do
|
8
8
|
|
9
9
|
##
|
10
10
|
# We define a model class to test, since in normal operation we would never use Model directly,
|
@@ -15,7 +15,7 @@ describe 'WeirdModel' do
|
|
15
15
|
# unless we specifically say `.and_call_original` instead of `.and_return`.
|
16
16
|
#
|
17
17
|
# This is actually quite nice, but more than a little confusing when you see it for the first
|
18
|
-
# time. Its use isn
|
18
|
+
# time. Its use isn"t spelled out in the RSpec docs AFAICS.
|
19
19
|
#
|
20
20
|
let(:weird_model_class) do
|
21
21
|
Class.new Pod4::BasicModel do
|
@@ -32,104 +32,105 @@ describe 'WeirdModel' do
|
|
32
32
|
let(:model) { weird_model_class.new(20) }
|
33
33
|
|
34
34
|
|
35
|
-
describe
|
36
|
-
|
35
|
+
describe "Model.set_interface" do
|
36
|
+
|
37
|
+
it "requires an Interface object" do
|
37
38
|
expect( weird_model_class ).to respond_to(:set_interface).with(1).argument
|
38
39
|
end
|
39
40
|
|
40
|
-
# it
|
41
|
+
# it "sets interface" - covered by the interface test
|
41
42
|
end
|
42
|
-
##
|
43
43
|
|
44
44
|
|
45
|
-
describe
|
46
|
-
|
45
|
+
describe "Model.interface" do
|
46
|
+
|
47
|
+
it "is the interface object" do
|
47
48
|
expect( weird_model_class.interface ).to be_a_kind_of NullInterface
|
48
49
|
expect( weird_model_class.interface.id_fld ).to eq :id
|
49
50
|
end
|
51
|
+
|
50
52
|
end
|
51
|
-
##
|
52
53
|
|
53
54
|
|
54
|
-
describe
|
55
|
+
describe "#new" do
|
55
56
|
|
56
|
-
it
|
57
|
+
it "takes an optional ID" do
|
57
58
|
expect{ weird_model_class.new }.not_to raise_exception
|
58
59
|
expect{ weird_model_class.new(1) }.not_to raise_exception
|
59
60
|
end
|
60
61
|
|
61
|
-
it
|
62
|
+
it "sets the ID attribute" do
|
62
63
|
expect( weird_model_class.new(23).model_id ).to eq 23
|
63
64
|
end
|
64
65
|
|
65
|
-
it
|
66
|
-
expect( weird_model_class.new.model_status ).to eq :
|
66
|
+
it "sets the status to unknown" do
|
67
|
+
expect( weird_model_class.new.model_status ).to eq :unknown
|
67
68
|
end
|
68
69
|
|
69
|
-
it
|
70
|
+
it "initializes the alerts attribute" do
|
70
71
|
expect( weird_model_class.new.alerts ).to eq([])
|
71
72
|
end
|
72
73
|
|
73
|
-
it
|
74
|
+
it "doesn""t freak out if the ID is not an integer" do
|
74
75
|
expect{ weird_model_class.new("france") }.not_to raise_exception
|
75
76
|
expect( weird_model_class.new("france").model_id ).to eq "france"
|
76
77
|
end
|
77
78
|
|
78
|
-
end
|
79
|
-
|
79
|
+
end # of #new
|
80
|
+
|
80
81
|
|
82
|
+
describe "#interface" do
|
81
83
|
|
82
|
-
|
83
|
-
it 'returns the interface set in the class definition, again' do
|
84
|
+
it "returns the interface set in the class definition, again" do
|
84
85
|
expect( weird_model_class.new.interface ).to be_a_kind_of NullInterface
|
85
86
|
expect( weird_model_class.new.interface.id_fld ).to eq :id
|
86
87
|
end
|
87
|
-
|
88
|
-
|
88
|
+
|
89
|
+
end # of #interface
|
89
90
|
|
90
91
|
|
91
|
-
describe
|
92
|
-
|
92
|
+
describe "#alerts" do
|
93
|
+
|
94
|
+
it "returns the list of alerts against the model" do
|
93
95
|
cm = weird_model_class.new
|
94
|
-
cm.fake_an_alert(:warning, :foo,
|
95
|
-
cm.fake_an_alert(:error, :bar,
|
96
|
+
cm.fake_an_alert(:warning, :foo, "one")
|
97
|
+
cm.fake_an_alert(:error, :bar, "two")
|
96
98
|
|
97
99
|
expect( cm.alerts.size ).to eq 2
|
98
100
|
expect( cm.alerts.map{|a| a.message} ).to match_array(%w|one two|)
|
99
101
|
end
|
100
|
-
end
|
101
|
-
##
|
102
102
|
|
103
|
+
end # of #alerts
|
103
104
|
|
104
|
-
describe '#add_alert' do
|
105
|
-
# add_alert is a protected method, which is only supposed to be called
|
106
|
-
# within the validate method of a subclass of Method. So we test it by
|
107
|
-
# calling our alert faking method
|
108
105
|
|
109
|
-
|
106
|
+
describe "#add_alert" do
|
107
|
+
# add_alert is a private method, which is only supposed to be called within the a subclass of
|
108
|
+
# Method. So we test it by calling our alert faking method
|
109
|
+
|
110
|
+
it "requires type, message or type, field, message" do
|
110
111
|
expect{ model.fake_an_alert }.to raise_exception ArgumentError
|
111
112
|
expect{ model.fake_an_alert(nil) }.to raise_exception ArgumentError
|
112
|
-
expect{ model.fake_an_alert(
|
113
|
+
expect{ model.fake_an_alert("foo") }.to raise_exception ArgumentError
|
113
114
|
|
114
|
-
expect{ model.fake_an_alert(:error,
|
115
|
-
expect{ model.fake_an_alert(:warning, :name,
|
115
|
+
expect{ model.fake_an_alert(:error, "foo") }.not_to raise_exception
|
116
|
+
expect{ model.fake_an_alert(:warning, :name, "bar") }.
|
116
117
|
not_to raise_exception
|
117
118
|
|
118
119
|
end
|
119
120
|
|
120
|
-
it
|
121
|
+
it "only allows valid types" do
|
121
122
|
[:brian, :werning, nil, :alert, :danger].each do |l|
|
122
|
-
expect{ model.fake_an_alert(l,
|
123
|
+
expect{ model.fake_an_alert(l, "foo") }.to raise_exception ArgumentError
|
123
124
|
end
|
124
125
|
|
125
126
|
[:warning, :error, :success, :info].each do |l|
|
126
|
-
expect{ model.fake_an_alert(l,
|
127
|
+
expect{ model.fake_an_alert(l, "foo") }.not_to raise_exception
|
127
128
|
end
|
128
129
|
|
129
130
|
end
|
130
131
|
|
131
|
-
it
|
132
|
-
lurch =
|
132
|
+
it "creates an Alert and adds it to @alerts" do
|
133
|
+
lurch = "Dnhhhhhh"
|
133
134
|
model.fake_an_alert(:error, :price, lurch)
|
134
135
|
|
135
136
|
expect( model.alerts.size ).to eq 1
|
@@ -137,80 +138,76 @@ describe 'WeirdModel' do
|
|
137
138
|
expect( model.alerts.first.message ).to eq lurch
|
138
139
|
end
|
139
140
|
|
140
|
-
it
|
141
|
-
model.fake_an_alert(:warning, :price,
|
141
|
+
it "sets @model_status if the type is worse than @model_status" do
|
142
|
+
model.fake_an_alert(:warning, :price, "xoo")
|
142
143
|
expect( model.model_status ).to eq :warning
|
143
144
|
|
144
|
-
model.fake_an_alert(:success, :price,
|
145
|
+
model.fake_an_alert(:success, :price, "flom")
|
145
146
|
expect( model.model_status ).to eq :warning
|
146
147
|
|
147
|
-
model.fake_an_alert(:info, :price,
|
148
|
+
model.fake_an_alert(:info, :price, "flom")
|
148
149
|
expect( model.model_status ).to eq :warning
|
149
150
|
|
150
|
-
model.fake_an_alert(:error, :price,
|
151
|
+
model.fake_an_alert(:error, :price, "qar")
|
151
152
|
expect( model.model_status ).to eq :error
|
152
153
|
|
153
|
-
model.fake_an_alert(:warning, :price,
|
154
|
+
model.fake_an_alert(:warning, :price, "drazq")
|
154
155
|
expect( model.model_status ).to eq :error
|
155
156
|
end
|
156
157
|
|
157
|
-
it
|
158
|
-
lurch =
|
158
|
+
it "ignores a new alert if identical to an existing one" do
|
159
|
+
lurch = "Dnhhhhhh"
|
159
160
|
2.times { model.fake_an_alert(:error, :price, lurch) }
|
160
161
|
|
161
162
|
expect( model.alerts.size ).to eq 1
|
162
163
|
end
|
163
164
|
|
164
|
-
end
|
165
|
-
##
|
165
|
+
end # of #add_alert
|
166
166
|
|
167
167
|
|
168
|
-
describe
|
168
|
+
describe "#clear_alerts" do
|
169
169
|
before do
|
170
170
|
model.fake_an_alert(:error, "bad stuff")
|
171
171
|
model.clear_alerts
|
172
172
|
end
|
173
173
|
|
174
|
-
it
|
174
|
+
it "resets the @alerts array" do
|
175
175
|
expect( model.alerts ).to eq([])
|
176
176
|
end
|
177
177
|
|
178
|
-
it
|
178
|
+
it "sets model_status to :okay" do
|
179
179
|
expect( model.model_status ).to eq :okay
|
180
180
|
end
|
181
181
|
|
182
|
-
|
183
|
-
end
|
184
|
-
##
|
182
|
+
end # of #clear_alerts
|
185
183
|
|
186
184
|
|
187
|
-
describe
|
185
|
+
describe "#raise_exceptions" do
|
188
186
|
|
189
|
-
it
|
187
|
+
it "is also known as .or_die" do
|
190
188
|
cm = weird_model_class.new
|
191
189
|
expect( cm.method(:raise_exceptions) ).to eq( cm.method(:or_die) )
|
192
190
|
end
|
193
191
|
|
194
|
-
it
|
195
|
-
model.fake_an_alert(:error, :price,
|
192
|
+
it "raises ValidationError if model status is :error" do
|
193
|
+
model.fake_an_alert(:error, :price, "qar")
|
196
194
|
expect{ model.raise_exceptions }.to raise_exception Pod4::ValidationError
|
197
195
|
end
|
198
196
|
|
199
|
-
it
|
197
|
+
it "does nothing if model status is not :error" do
|
200
198
|
expect{ model.raise_exceptions }.not_to raise_exception
|
201
199
|
|
202
|
-
model.fake_an_alert(:info, :price,
|
200
|
+
model.fake_an_alert(:info, :price, "qar")
|
203
201
|
expect{ model.raise_exceptions }.not_to raise_exception
|
204
202
|
|
205
|
-
model.fake_an_alert(:success, :price,
|
203
|
+
model.fake_an_alert(:success, :price, "qar")
|
206
204
|
expect{ model.raise_exceptions }.not_to raise_exception
|
207
205
|
|
208
|
-
model.fake_an_alert(:warning, :price,
|
206
|
+
model.fake_an_alert(:warning, :price, "qar")
|
209
207
|
expect{ model.raise_exceptions }.not_to raise_exception
|
210
208
|
end
|
211
209
|
|
212
|
-
end
|
213
|
-
##
|
210
|
+
end # of #raise_exceptions
|
214
211
|
|
215
212
|
|
216
213
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require "pod4/connection_pool"
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# These tests cover how connection pool handles being called by simultaneous threads. Note that
|
6
|
+
# none of these tests _can ever_ fail when running under MRI, because of the GIL.
|
7
|
+
#
|
8
|
+
# Under jRuby, though, they can fail. Probably! We're relying on >1 thread making the same call
|
9
|
+
# simultaneously, with 50 threads all trying to act at the same time. That's not actually _certain_
|
10
|
+
# to happen. Without the Mutex in ConnectionPool::Pool, these seem to fail MOST of the time. For
|
11
|
+
# me.
|
12
|
+
#
|
13
|
+
# These tests are in a seperate spec file because they screw up the test suite. One of two things
|
14
|
+
# happens:
|
15
|
+
#
|
16
|
+
# * A timeout waiting for threads to be "done" or be killed
|
17
|
+
# * A Stomp timeout error(!?)
|
18
|
+
#
|
19
|
+
# You can duplicate this by running these three tests, in this order:
|
20
|
+
#
|
21
|
+
# 1. This one
|
22
|
+
# 2. NebulousInterface
|
23
|
+
# 3. SequelInterface (pg)
|
24
|
+
#
|
25
|
+
# (It passes when you run it on its own.)
|
26
|
+
#
|
27
|
+
# My working theory is that we just run out of threads, somehow? It might be something to do with
|
28
|
+
# this jRuby bug: https://github.com/jruby/jruby/issues/5476
|
29
|
+
#
|
30
|
+
# For the time being I've renamed this test file `_spoc` instead of `_spec` so that it's not part
|
31
|
+
# of the test suite.
|
32
|
+
#
|
33
|
+
describe Pod4::ConnectionPool do
|
34
|
+
|
35
|
+
def make_threads(count, connection, interface)
|
36
|
+
threads = []
|
37
|
+
|
38
|
+
1.upto(count) do |idx|
|
39
|
+
threads << Thread.new do
|
40
|
+
# Set things up and wait
|
41
|
+
Thread.current[:idx] = idx # might be useful for debugging
|
42
|
+
Thread.stop
|
43
|
+
|
44
|
+
# wait for the given sync time; call #client; signal done; then wait
|
45
|
+
sleep 0.1 until Time.now >= Thread.current[:time]
|
46
|
+
connection.client(interface)
|
47
|
+
Thread.current[:done1] = true
|
48
|
+
Thread.stop
|
49
|
+
|
50
|
+
# call #close; signal done; then wait
|
51
|
+
connection.close(interface)
|
52
|
+
Thread.current[:done2] = true
|
53
|
+
Thread.stop
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
threads
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:ifce_class) do
|
61
|
+
Class.new Pod4::Interface do
|
62
|
+
def initialize; end
|
63
|
+
def close_connection; end
|
64
|
+
def new_connection(opts); @conn; end
|
65
|
+
|
66
|
+
def set_conn(c); @conn = c; end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
describe "(Parallelism)" do
|
72
|
+
|
73
|
+
before(:each) do
|
74
|
+
# If a thread suffers an exception, that's probably because of a race condition somewhere.
|
75
|
+
# eg: without the Mutex on ConnectionPool::Pool, nils get assigned to the pool somehow.
|
76
|
+
Thread.abort_on_exception = true
|
77
|
+
|
78
|
+
@connection = ConnectionPool.new(interface: ifce_class, max_clients: 55)
|
79
|
+
@connection.data_layer_options = "meh"
|
80
|
+
@interface = ifce_class.new
|
81
|
+
@interface.set_conn "floom"
|
82
|
+
|
83
|
+
# Set up 50 threads to call things at the same time.
|
84
|
+
@threads = make_threads(50, @connection, @interface)
|
85
|
+
end
|
86
|
+
|
87
|
+
after(:each) { @threads.each{|t| t.kill} }
|
88
|
+
|
89
|
+
it "assigns new items to the pool from multiple threads successfully" do
|
90
|
+
test_start = Time.now
|
91
|
+
|
92
|
+
# Ask all the threads to restart, calling #client all at the same time
|
93
|
+
# (Unfortunately it's in the hands of Ruby's scheduler whether the thread gets restarted)
|
94
|
+
at = Time.now + 2
|
95
|
+
@threads.each{|t| t[:time] = at }
|
96
|
+
@threads.each{|t| t.run }
|
97
|
+
sleep 0.1 until (@threads.all?{|t| t[:done1] } || Time.now >= test_start + 5)
|
98
|
+
|
99
|
+
# We have no control over whether the scheduler will actually restart each thread!
|
100
|
+
# Best we can do is count the number of threads that ran
|
101
|
+
count = @threads.count{|t| t[:done1] }
|
102
|
+
|
103
|
+
expect( @connection._pool.size ).to eq count
|
104
|
+
expect( @connection._pool.select{|x| x.thread_id.nil? }.size ).to eq 0
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Note that we don't have to test the safety of the operation of retrieving the client for an
|
109
|
+
# already assigned thread, or for freeing that client for use by other threads -- since only
|
110
|
+
# one thread can ever access that pool item...
|
111
|
+
#
|
112
|
+
|
113
|
+
it "reassigns items to new threads from multiple threads successfully" do
|
114
|
+
test_start = Time.now
|
115
|
+
|
116
|
+
# ask all the threads to connect -- again, the scheduler might let us down.
|
117
|
+
at = Time.now
|
118
|
+
@threads.each{|t| t[:time] = at }
|
119
|
+
@threads.each{|t| t.run }
|
120
|
+
sleep 0.1 until (@threads.all?{|t| t[:done1] } || Time.now >= test_start + 5)
|
121
|
+
count1 = @threads.count{|t| t[:done1] }
|
122
|
+
|
123
|
+
# Release all the connections (that got run in the connect phase...)
|
124
|
+
# (Again, just because we ask a thread to run, that doesn't mean it does!)
|
125
|
+
@threads.select{|t| t[:done1] }.each{|t| t.run }
|
126
|
+
sleep 0.1 until (@threads.all?{|t| t[:done2] } || Time.now >= test_start + 10)
|
127
|
+
count2 = @threads.count{|t| t[:done2] }
|
128
|
+
|
129
|
+
# Make some new threads. These should reuse connections from the pool. Make a couple less
|
130
|
+
# than should be free.
|
131
|
+
newthreads = make_threads(count2 - 2, @connection, @interface)
|
132
|
+
|
133
|
+
at = Time.now + 2
|
134
|
+
newthreads.each{|t| t[:time] = at }
|
135
|
+
newthreads.each{|t| t.run }
|
136
|
+
sleep 0.1 until (newthreads.all?{|t| t[:done1] } || Time.now >= test_start + 15)
|
137
|
+
|
138
|
+
count3 = newthreads.count{|t| t[:done1] }
|
139
|
+
|
140
|
+
# So at this point count1 is the number of threads in @threads that were connected; count2
|
141
|
+
# the number that were then released; count3 the number of threads in newthreads that were
|
142
|
+
# (re-)connected.
|
143
|
+
expect( @connection._pool.size ).to eq count1
|
144
|
+
expect( @connection._pool.select{|x| x.thread_id.nil? }.size ).to eq(count1 - count3)
|
145
|
+
|
146
|
+
# tidy up
|
147
|
+
newthreads.each{|t| t.kill }
|
148
|
+
end
|
149
|
+
|
150
|
+
end # of (Parallelism)
|
151
|
+
|
152
|
+
|
153
|
+
end
|
154
|
+
|