tool 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/tool/thread_local.rb +47 -5
- data/lib/tool/version.rb +1 -1
- data/spec/thread_local_spec.rb +63 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ad22826371837ebdeb6a642c310bf0b02815139
|
4
|
+
data.tar.gz: b16dc92838a3bb4776664d4cce4a868d3bf36067
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e68bf215746003f18c31579c68b31993821d82aa55fd0933633da2955139e5a169ad72d16cffb2fdd1f735452956292401d598018f86d0aa90ff1db252bc720
|
7
|
+
data.tar.gz: 4aea8b0920a9e735b9fa3a2548b7562517be8841b3cd7bac2011d4e782346e4841006bf43c94a81fc382fd4b819ade7d7392589844fc84c8090ebba5b3099d86
|
data/README.md
CHANGED
@@ -7,4 +7,4 @@ Included:
|
|
7
7
|
* **Decoration** - Mixin for easy method decorations.
|
8
8
|
* **EqualityMap** - Weak reference caching based on key equality.
|
9
9
|
* **ThreadLocal** - True thread locals that are properly garbage collected.
|
10
|
-
* **WarningFilter** - Enable Ruby's warnings, but filter out caused by third party gems.
|
10
|
+
* **WarningFilter** - Enable Ruby's warnings, but filter out those caused by third party gems.
|
data/lib/tool/thread_local.rb
CHANGED
@@ -29,17 +29,59 @@ module Tool
|
|
29
29
|
# Thread.new { p local }.join # [:foo]
|
30
30
|
# p local # [:foo, :bar]
|
31
31
|
class ThreadLocal < Delegator
|
32
|
+
@mutex ||= Mutex.new
|
33
|
+
@locals ||= []
|
34
|
+
|
35
|
+
# Thread finalizer.
|
36
|
+
# @!visibility private
|
37
|
+
def self.cleanup(id)
|
38
|
+
@locals.keep_if do |local|
|
39
|
+
next false unless local.weakref_alive?
|
40
|
+
local.__cleanup__
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Generates weak reference to thread and sets up finalizer.
|
46
|
+
# @return [WeakRef]
|
47
|
+
# @!visibility private
|
48
|
+
def self.ref(thread)
|
49
|
+
thread[:weakref] ||= begin
|
50
|
+
ObjectSpace.define_finalizer(thread, method(:cleanup))
|
51
|
+
WeakRef.new(thread)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @see #initialize
|
56
|
+
# @!visibility private
|
57
|
+
def self.new(*)
|
58
|
+
result = super(default)
|
59
|
+
@mutex.synchronize { @locals << WeakRef.new(result) }
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
32
63
|
def initialize(default = {})
|
33
|
-
@mutex = Mutex.new
|
34
64
|
@default = default.dup
|
35
|
-
@map
|
65
|
+
@map = {}
|
36
66
|
end
|
37
67
|
|
68
|
+
# @see Delegator
|
38
69
|
# @!visibility private
|
39
70
|
def __getobj__
|
40
|
-
ref =
|
41
|
-
@map
|
42
|
-
|
71
|
+
ref = ::Tool::ThreadLocal.ref(Thread.current)
|
72
|
+
@map[ref] ||= @default.dup
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Integer] number of threads with specific locals
|
76
|
+
# @!visibility private
|
77
|
+
def __size__
|
78
|
+
@map.size
|
79
|
+
end
|
80
|
+
|
81
|
+
# Remove locals for dead or GC'ed threads
|
82
|
+
# @!visibility private
|
83
|
+
def __cleanup__
|
84
|
+
@map.keep_if { |key, value| key.weakref_alive? and key.alive? }
|
43
85
|
end
|
44
86
|
end
|
45
87
|
end
|
data/lib/tool/version.rb
CHANGED
data/spec/thread_local_spec.rb
CHANGED
@@ -1,26 +1,71 @@
|
|
1
1
|
require 'tool/thread_local'
|
2
2
|
|
3
3
|
describe Tool::ThreadLocal do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
describe :__getobj__ do
|
5
|
+
specify 'normal access' do
|
6
|
+
subject[:foo] = 'bar'
|
7
|
+
expect(subject[:foo]).to be == 'bar'
|
8
|
+
end
|
9
|
+
|
10
|
+
specify 'concurrent access' do
|
11
|
+
subject[:foo] = 'bar'
|
12
|
+
value = Thread.new { subject[:foo] = 'baz' }.value
|
13
|
+
expect(value).to be == 'baz'
|
14
|
+
expect(subject[:foo]).to be == 'bar'
|
15
|
+
end
|
16
|
+
|
17
|
+
specify 'with an array as value' do
|
18
|
+
list = Tool::ThreadLocal.new([])
|
19
|
+
foo = Thread.new { 10.times { list << :foo; sleep(0.01) }; list.to_a }
|
20
|
+
bar = Thread.new { 10.times { list << :bar; sleep(0.01) }; list.to_a }
|
21
|
+
expect(list).to be_empty
|
22
|
+
list << :list
|
23
|
+
expect(list) .to be == [ :list ]
|
24
|
+
expect(foo.value) .to be == [ :foo ] * 10
|
25
|
+
expect(bar.value) .to be == [ :bar ] * 10
|
26
|
+
end
|
27
|
+
|
28
|
+
specify 'deals with garbage collected threads' do
|
29
|
+
subject[:a] = 'A'
|
30
|
+
|
31
|
+
Thread.new do
|
32
|
+
subject[:b] = 'B'
|
33
|
+
Thread.new do
|
34
|
+
subject[:c] = 'C'
|
35
|
+
end.value
|
36
|
+
end.value
|
8
37
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
expect(value).to be == 'baz'
|
13
|
-
expect(subject[:foo]).to be == 'bar'
|
38
|
+
GC.start
|
39
|
+
expect(subject[:a]).to be == 'A'
|
40
|
+
end
|
14
41
|
end
|
15
42
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
43
|
+
describe :__size__ do
|
44
|
+
specify 'with one thread' do
|
45
|
+
subject[:a] = 'A'
|
46
|
+
expect(subject.__size__).to be == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
specify 'with multiple threads' do
|
50
|
+
subject[:a] = 'A'
|
51
|
+
thread = Thread.new { subject[:b] = 'B'; sleep }
|
52
|
+
sleep 0.01
|
53
|
+
expect(subject.__size__).to be == 2
|
54
|
+
thread.kill
|
55
|
+
end
|
56
|
+
|
57
|
+
specify 'with dead threads' do
|
58
|
+
subject[:a] = 'A'
|
59
|
+
|
60
|
+
Thread.new do
|
61
|
+
subject[:b] = 'B'
|
62
|
+
Thread.new do
|
63
|
+
subject[:c] = 'C'
|
64
|
+
end.value
|
65
|
+
end.value
|
66
|
+
|
67
|
+
GC.start
|
68
|
+
expect(subject.__size__).to be == 1
|
69
|
+
end
|
25
70
|
end
|
26
71
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstantin Haase
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|