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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c9254999a6eb11a5873947aa1fee8bc5ad326be3
4
- data.tar.gz: fc080fdb56ada8713ffff8bbe2b82a535a4125c3
2
+ SHA256:
3
+ metadata.gz: fb81d9b25548e6bf5597f1913abae8f6b23267935ed80046bac46c93b9009c9e
4
+ data.tar.gz: 0277ee3dc97174765982f652a17cd25aa0b036f76fedb9cbb2e74e68828fec23
5
5
  SHA512:
6
- metadata.gz: aecc96803e996c38b4168fa287cd3f4f39eb0d8b2d448b9a6c36d9fa63fcb2cba14e2fc06f4377238d7bf6632410a1ebff38e460d8200c975249b77b1b9f7eae
7
- data.tar.gz: ded459d09b79d4017db0e878a0cbccc44c99c47998a6224ebdb34ec8bd91dddf15763cda57d435a9f79f299fb0f316c1b2ed66de5b7223c25fb2bae1d91638b5
6
+ metadata.gz: 26b39d4fffd6d0bc73d67fb887cdff1cceb46f88b8b9a43995d88c871e43bae945f67d3c5622a0cfb046e746a479f8a82c79b8bb80eada804ce6edecf38d0815
7
+ data.tar.gz: c274760c15f6fb92b7a2f3c8e00a162ffc1dac5b2a1a940de9a6565083d34198c5d08d80cf3cfa3a530a9306f541b2b01ced8f05dacaa78378a356e9d8c189e8
@@ -1,3 +1,9 @@
1
+ ### 0.3.0 (2018-03-27)
2
+
3
+ * Support scoped checkouts.
4
+
5
+ * Use frozen string literals.
6
+
1
7
  ### 0.2.0 (2016-02-05)
2
8
 
3
9
  * Add an option for a detach_if callable, which can contain logic to
@@ -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
- def initialize(options = {}, &block)
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(options[:eager] ? maximum_size : 0, &block)
27
+ @available = Array.new(eager ? maximum_size : 0, &block)
19
28
 
20
- self.timeout = options.fetch :timeout, 1
21
- self.collection = options.fetch :collection, :queue
22
- self.detach_if = options.fetch :detach_if, lambda { |_| false }
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
- if object = current_object
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 + @allocated.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
- set_current_object(@block.call) if current_object == @block
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)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Pond
2
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
3
5
  end
@@ -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
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pond'
2
4
 
3
5
  RSpec.configure do |config|
@@ -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
@@ -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.2.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: 2016-02-05 00:00:00.000000000 Z
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.5.1
97
+ rubygems_version: 2.7.3
98
98
  signing_key:
99
99
  specification_version: 4
100
100
  summary: A simple, generic, thread-safe pool