pond 0.2.0 → 0.3.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/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
|