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 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