pond 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +6 -0
- data/lib/pond.rb +39 -26
- data/lib/pond/version.rb +3 -1
- data/spec/checkout_spec.rb +45 -14
- data/spec/config_spec.rb +4 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/wrapper_spec.rb +5 -3
- data/tasks/stress.rake +3 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fb81d9b25548e6bf5597f1913abae8f6b23267935ed80046bac46c93b9009c9e
|
4
|
+
data.tar.gz: 0277ee3dc97174765982f652a17cd25aa0b036f76fedb9cbb2e74e68828fec23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26b39d4fffd6d0bc73d67fb887cdff1cceb46f88b8b9a43995d88c871e43bae945f67d3c5622a0cfb046e746a479f8a82c79b8bb80eada804ce6edecf38d0815
|
7
|
+
data.tar.gz: c274760c15f6fb92b7a2f3c8e00a162ffc1dac5b2a1a940de9a6565083d34198c5d08d80cf3cfa3a530a9306f541b2b01ced8f05dacaa78378a356e9d8c189e8
|
data/CHANGELOG.md
CHANGED
data/lib/pond.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'monitor'
|
2
4
|
|
3
5
|
require 'pond/version'
|
@@ -7,32 +9,41 @@ class Pond
|
|
7
9
|
|
8
10
|
attr_reader :allocated, :available, :timeout, :collection, :maximum_size, :detach_if
|
9
11
|
|
10
|
-
|
12
|
+
DEFAULT_DETACH_IF = lambda { |_| false }
|
13
|
+
|
14
|
+
def initialize(
|
15
|
+
maximum_size: 10,
|
16
|
+
eager: false,
|
17
|
+
timeout: 1,
|
18
|
+
collection: :queue,
|
19
|
+
detach_if: DEFAULT_DETACH_IF,
|
20
|
+
&block
|
21
|
+
)
|
11
22
|
@block = block
|
12
23
|
@monitor = Monitor.new
|
13
24
|
@cv = Monitor::ConditionVariable.new(@monitor)
|
14
25
|
|
15
|
-
maximum_size = options.fetch :maximum_size, 10
|
16
|
-
|
17
26
|
@allocated = {}
|
18
|
-
@available = Array.new(
|
27
|
+
@available = Array.new(eager ? maximum_size : 0, &block)
|
19
28
|
|
20
|
-
self.timeout =
|
21
|
-
self.collection =
|
22
|
-
self.detach_if =
|
29
|
+
self.timeout = timeout
|
30
|
+
self.collection = collection
|
31
|
+
self.detach_if = detach_if
|
23
32
|
self.maximum_size = maximum_size
|
24
33
|
end
|
25
34
|
|
26
|
-
def checkout(&block)
|
27
|
-
|
35
|
+
def checkout(scope: nil, &block)
|
36
|
+
raise "Can't checkout with a non-frozen scope" unless scope.frozen?
|
37
|
+
|
38
|
+
if object = current_object(scope: scope)
|
28
39
|
yield object
|
29
40
|
else
|
30
|
-
checkout_object(&block)
|
41
|
+
checkout_object(scope: scope, &block)
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
34
45
|
def size
|
35
|
-
sync { @available.size +
|
46
|
+
sync { @allocated.inject(@available.size){|sum, (h, k)| sum + k.length} }
|
36
47
|
end
|
37
48
|
|
38
49
|
def timeout=(timeout)
|
@@ -60,22 +71,22 @@ class Pond
|
|
60
71
|
|
61
72
|
private
|
62
73
|
|
63
|
-
def checkout_object
|
64
|
-
lock_object
|
65
|
-
yield current_object
|
74
|
+
def checkout_object(scope:)
|
75
|
+
lock_object(scope: scope)
|
76
|
+
yield current_object(scope: scope)
|
66
77
|
ensure
|
67
|
-
unlock_object
|
78
|
+
unlock_object(scope: scope)
|
68
79
|
end
|
69
80
|
|
70
|
-
def lock_object
|
81
|
+
def lock_object(scope:)
|
71
82
|
deadline = Time.now + @timeout
|
72
83
|
|
73
|
-
until current_object
|
84
|
+
until current_object(scope: scope)
|
74
85
|
raise Timeout if (time_left = deadline - Time.now) < 0
|
75
86
|
|
76
87
|
sync do
|
77
88
|
if object = get_object(time_left)
|
78
|
-
set_current_object(object)
|
89
|
+
set_current_object(object, scope: scope)
|
79
90
|
end
|
80
91
|
end
|
81
92
|
end
|
@@ -85,16 +96,18 @@ class Pond
|
|
85
96
|
# call the instantiation block while we have the lock, since it may take a
|
86
97
|
# long time to return. So, we set the checked-out object to the block as a
|
87
98
|
# signal that it needs to be called.
|
88
|
-
|
99
|
+
if current_object(scope: scope) == @block
|
100
|
+
set_current_object(@block.call, scope: scope)
|
101
|
+
end
|
89
102
|
end
|
90
103
|
|
91
|
-
def unlock_object
|
104
|
+
def unlock_object(scope:)
|
92
105
|
object = nil
|
93
106
|
detach_if = nil
|
94
107
|
should_return_object = nil
|
95
108
|
|
96
109
|
sync do
|
97
|
-
object = current_object
|
110
|
+
object = current_object(scope: scope)
|
98
111
|
detach_if = self.detach_if
|
99
112
|
should_return_object = object && object != @block && size <= maximum_size
|
100
113
|
end
|
@@ -105,7 +118,7 @@ class Pond
|
|
105
118
|
ensure
|
106
119
|
sync do
|
107
120
|
@available << object if detach_check_finished && should_return_object
|
108
|
-
@allocated.delete(Thread.current)
|
121
|
+
@allocated[scope].delete(Thread.current)
|
109
122
|
@cv.signal
|
110
123
|
end
|
111
124
|
end
|
@@ -122,12 +135,12 @@ class Pond
|
|
122
135
|
end
|
123
136
|
end
|
124
137
|
|
125
|
-
def current_object
|
126
|
-
sync { @allocated[Thread.current] }
|
138
|
+
def current_object(scope:)
|
139
|
+
sync { (@allocated[scope] ||= {})[Thread.current] }
|
127
140
|
end
|
128
141
|
|
129
|
-
def set_current_object(object)
|
130
|
-
sync { @allocated[Thread.current] = object }
|
142
|
+
def set_current_object(object, scope:)
|
143
|
+
sync { (@allocated[scope] ||= {})[Thread.current] = object }
|
131
144
|
end
|
132
145
|
|
133
146
|
def sync(&block)
|
data/lib/pond/version.rb
CHANGED
data/spec/checkout_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Pond, "#checkout" do
|
@@ -20,25 +22,53 @@ describe Pond, "#checkout" do
|
|
20
22
|
|
21
23
|
pond.checkout do |i|
|
22
24
|
pond.available.should == []
|
23
|
-
pond.allocated.should == {Thread.current => 1}
|
25
|
+
pond.allocated.should == {nil => {Thread.current => 1}}
|
24
26
|
i.should == 1
|
25
27
|
end
|
26
28
|
|
27
29
|
pond.available.should == [1]
|
28
|
-
pond.allocated.should == {}
|
30
|
+
pond.allocated.should == {nil => {}}
|
29
31
|
pond.size.should == 1
|
30
32
|
|
31
33
|
pond.checkout do |i|
|
32
34
|
pond.available.should == []
|
33
|
-
pond.allocated.should == {Thread.current => 1}
|
35
|
+
pond.allocated.should == {nil => {Thread.current => 1}}
|
34
36
|
i.should == 1
|
35
37
|
end
|
36
38
|
|
37
39
|
pond.available.should == [1]
|
38
|
-
pond.allocated.should == {}
|
40
|
+
pond.allocated.should == {nil => {}}
|
39
41
|
pond.size.should == 1
|
40
42
|
end
|
41
43
|
|
44
|
+
it "should checkout objects to the given scope, if any" do
|
45
|
+
int = 0
|
46
|
+
pond = Pond.new(eager: true) { int += 1 }
|
47
|
+
pond.size.should == 10
|
48
|
+
pond.available.should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
49
|
+
|
50
|
+
pond.checkout do |a|
|
51
|
+
pond.checkout do |b|
|
52
|
+
pond.checkout(scope: :blah) do |c|
|
53
|
+
pond.checkout(scope: :blah) do |d|
|
54
|
+
pond.checkout do |e|
|
55
|
+
[a, b, c, d, e].should == [1, 1, 2, 2, 1]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
pond.available.should == [3, 4, 5, 6, 7, 8, 9, 10, 2, 1]
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should throw an error if the passed scope is not frozen" do
|
66
|
+
int = 0
|
67
|
+
pond = Pond.new(eager: true) { int += 1 }
|
68
|
+
|
69
|
+
proc { pond.checkout(scope: String.new) }.should raise_error RuntimeError, "Can't checkout with a non-frozen scope"
|
70
|
+
end
|
71
|
+
|
42
72
|
it "should not instantiate objects in excess of the specified maximum_size" do
|
43
73
|
object = nil
|
44
74
|
pond = Pond.new(:maximum_size => 1) { object = Object.new }
|
@@ -70,19 +100,19 @@ describe Pond, "#checkout" do
|
|
70
100
|
q1.pop
|
71
101
|
|
72
102
|
pond.size.should == 1
|
73
|
-
pond.allocated.should == {t => 1}
|
103
|
+
pond.allocated.should == {nil => {t => 1}}
|
74
104
|
pond.available.should == []
|
75
105
|
|
76
106
|
pond.checkout { |i| i.should == 2 }
|
77
107
|
|
78
108
|
pond.size.should == 2
|
79
|
-
pond.allocated.should == {t => 1}
|
109
|
+
pond.allocated.should == {nil => {t => 1}}
|
80
110
|
pond.available.should == [2]
|
81
111
|
|
82
112
|
q2.push nil
|
83
113
|
t.join
|
84
114
|
|
85
|
-
pond.allocated.should == {}
|
115
|
+
pond.allocated.should == {nil => {}}
|
86
116
|
pond.available.should == [2, 1]
|
87
117
|
end
|
88
118
|
|
@@ -108,7 +138,7 @@ describe Pond, "#checkout" do
|
|
108
138
|
end
|
109
139
|
|
110
140
|
it "should yield an object to only one thread when many are waiting" do
|
111
|
-
pond = Pond.new(:maximum_size => 1) { 2 }
|
141
|
+
pond = Pond.new(:maximum_size => 1, timeout: 360000) { 2 }
|
112
142
|
|
113
143
|
q1, q2, q3 = Queue.new, Queue.new, Queue.new
|
114
144
|
|
@@ -227,7 +257,7 @@ describe Pond, "#checkout" do
|
|
227
257
|
end
|
228
258
|
end.should raise_error RuntimeError, "Blah!"
|
229
259
|
|
230
|
-
pond.allocated.should == {}
|
260
|
+
pond.allocated.should == {nil => {}}
|
231
261
|
pond.available.should == [1]
|
232
262
|
end
|
233
263
|
|
@@ -271,7 +301,7 @@ describe Pond, "#checkout" do
|
|
271
301
|
pond.checkout { |i| i.should == 1 }
|
272
302
|
|
273
303
|
pond.size.should == 1
|
274
|
-
pond.allocated.should == {}
|
304
|
+
pond.allocated.should == {nil => {}}
|
275
305
|
pond.available.should == [1]
|
276
306
|
|
277
307
|
error = true
|
@@ -280,6 +310,7 @@ describe Pond, "#checkout" do
|
|
280
310
|
i.should == 1
|
281
311
|
|
282
312
|
t = Thread.new do
|
313
|
+
Thread.current.report_on_exception = false
|
283
314
|
pond.checkout{}
|
284
315
|
end
|
285
316
|
|
@@ -287,7 +318,7 @@ describe Pond, "#checkout" do
|
|
287
318
|
end
|
288
319
|
|
289
320
|
pond.size.should == 1
|
290
|
-
pond.allocated.should == {}
|
321
|
+
pond.allocated.should == {nil => {}}
|
291
322
|
pond.available.should == [1]
|
292
323
|
|
293
324
|
error = false
|
@@ -303,7 +334,7 @@ describe Pond, "#checkout" do
|
|
303
334
|
end
|
304
335
|
|
305
336
|
pond.size.should == 2
|
306
|
-
pond.allocated.should == {}
|
337
|
+
pond.allocated.should == {nil => {}}
|
307
338
|
pond.available.should == [2, 1]
|
308
339
|
end
|
309
340
|
|
@@ -347,7 +378,7 @@ describe Pond, "#checkout" do
|
|
347
378
|
|
348
379
|
q1.pop
|
349
380
|
pond.available.should == []
|
350
|
-
pond.allocated.should == {t => 1}
|
381
|
+
pond.allocated.should == {nil => {t => 1}}
|
351
382
|
|
352
383
|
# t is in the middle of invoking detach_if, we should still be able to
|
353
384
|
# instantiate new objects and check them out.
|
@@ -392,6 +423,6 @@ describe Pond, "#checkout" do
|
|
392
423
|
checked_out.should == 2
|
393
424
|
|
394
425
|
pond.available.should == [3, 4, 5, 1]
|
395
|
-
pond.allocated.should == {}
|
426
|
+
pond.allocated.should == {nil => {}}
|
396
427
|
end
|
397
428
|
end
|
data/spec/config_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Pond, "configuration" do
|
@@ -105,13 +107,13 @@ describe Pond, "configuration" do
|
|
105
107
|
|
106
108
|
pond.size.should == 1
|
107
109
|
pond.available.should == []
|
108
|
-
pond.allocated.should == {t => 1}
|
110
|
+
pond.allocated.should == {nil => {t => 1}}
|
109
111
|
|
110
112
|
q2.push nil
|
111
113
|
t.join
|
112
114
|
|
113
115
|
pond.size.should == 0
|
114
116
|
pond.available.should == []
|
115
|
-
pond.allocated.should == {}
|
117
|
+
pond.allocated.should == {nil => {}}
|
116
118
|
end
|
117
119
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/wrapper_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Pond::Wrapper do
|
@@ -25,7 +27,7 @@ describe Pond::Wrapper do
|
|
25
27
|
id = @wrapper.id
|
26
28
|
|
27
29
|
@pond.size.should == 1
|
28
|
-
@pond.allocated.should == {}
|
30
|
+
@pond.allocated.should == {nil => {}}
|
29
31
|
@pond.available.map(&:id).should == [id]
|
30
32
|
end
|
31
33
|
|
@@ -51,7 +53,7 @@ describe Pond::Wrapper do
|
|
51
53
|
|
52
54
|
@wrapper.id.should == id1
|
53
55
|
|
54
|
-
@pond.allocated.keys.should == [Thread.current, t]
|
56
|
+
@pond.allocated[nil].keys.should == [Thread.current, t]
|
55
57
|
@pond.available.should == []
|
56
58
|
|
57
59
|
q2.push nil
|
@@ -61,7 +63,7 @@ describe Pond::Wrapper do
|
|
61
63
|
@wrapper.id.should == id1
|
62
64
|
end
|
63
65
|
|
64
|
-
@pond.allocated.should == {}
|
66
|
+
@pond.allocated.should == {nil => {}}
|
65
67
|
@pond.available.map(&:id).should == [id2, id1]
|
66
68
|
end
|
67
69
|
end
|
data/tasks/stress.rake
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pond'
|
2
4
|
|
3
5
|
desc "Stress test the Pond gem to check for concurrency issues."
|
@@ -9,7 +11,7 @@ task :stress do
|
|
9
11
|
|
10
12
|
pond = Pond.new(detach_if: detach_if) do
|
11
13
|
raise "Bad Instantiation!" if rand < 0.05
|
12
|
-
"Good!"
|
14
|
+
"Good!".dup
|
13
15
|
end
|
14
16
|
|
15
17
|
threads =
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pond
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
94
|
version: '0'
|
95
95
|
requirements: []
|
96
96
|
rubyforge_project:
|
97
|
-
rubygems_version: 2.
|
97
|
+
rubygems_version: 2.7.3
|
98
98
|
signing_key:
|
99
99
|
specification_version: 4
|
100
100
|
summary: A simple, generic, thread-safe pool
|