weak_hash 1.0.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Emmanuel Oga
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,47 @@
1
+ = weak_hash
2
+
3
+ A Weak Hash is a hash that stores references to objects
4
+ while retaining the garbage collector's ability to
5
+ reclaim the object.
6
+
7
+ The API is similar to that of a Hash:
8
+
9
+ object = Object.new
10
+
11
+ wh = WeakHash.new
12
+ wh["value"] = object
13
+
14
+ wh["value"] # => object
15
+
16
+ object = nil
17
+ wh["value"] # => object (if garbage collector has not collected the object yet)
18
+
19
+ GC.start; GC.start # very probable that the object is collected now.
20
+
21
+ wh["value"] # => nil
22
+
23
+ == Credits
24
+
25
+ This gem is based on two blog posts by Mauricio Fernández, including code by
26
+ him and Robert Klemme:
27
+
28
+ http://eigenclass.org/hiki/weakhash+and+weakref
29
+ http://eigenclass.org/hiki/deferred-finalizers-in-Ruby
30
+
31
+ I wrapped this on a gem for convenience, replaced a Thread.critical call
32
+ on SimpleWeakHash with a mutex (for making it work with 1.9) and
33
+ added some very simple specs.
34
+
35
+ == Note on Patches/Pull Requests
36
+
37
+ * Fork the project.
38
+ * Make your feature addition or bug fix.
39
+ * Add tests for it. This is important so I don't break it in a
40
+ future version unintentionally.
41
+ * Commit, do not mess with rakefile, version, or history.
42
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
43
+ * Send me a pull request. Bonus points for topic branches.
44
+
45
+ == Copyright
46
+
47
+ Copyright (c) 2010 Emmanuel Oga. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "weak_hash"
8
+ gem.summary = %Q{Weak Hash implementation}
9
+ gem.description = %Q{a hash that stores objects until they are garbage collected}
10
+ gem.email = "EmmanuelOga@gmail.com"
11
+ gem.homepage = "http://github.com/EmmanuelOga/weak_hash"
12
+ gem.authors = ["Emmanuel Oga"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new
41
+ rescue LoadError
42
+ task :yardoc do
43
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
44
+ end
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,42 @@
1
+ # NOTE:
2
+ # RKWeakHash is based on two blog posts by Mauricio Fernández,
3
+ # including code by him and Robert Klemme. The RKWeakHash
4
+ # class is attributed to Robert (hence the RK prefix).
5
+
6
+ require 'delegate'
7
+ require 'weakref'
8
+
9
+ RKWeakHash = DelegateClass(Hash)
10
+
11
+ # This is a weak hash similar in semantics to SimpleWeakHash but
12
+ # implemented using the standard libraries 'delegate' and 'weakref'.
13
+ # In theory using DelegateClass and WeakRef should make this work
14
+ # slower than the alternative implementation (SimpleWeakHash).
15
+ class RKWeakHash
16
+
17
+ # note: I added this method so I could provide a default value
18
+ # (a new Hash) instead of demanding it from the user.
19
+ def initialize(cache = Hash.new)
20
+ super(cache)
21
+ end
22
+
23
+ def []=(key,val) # !> method redefined; discarding old []=
24
+ __getobj__[WeakRef.new(key)] = WeakRef.new(val)
25
+ end
26
+
27
+ def [](key) # !> method redefined; discarding old []
28
+ __getobj__[WeakRef.new(key)]
29
+ end
30
+
31
+ def each(&b) # !> method redefined; discarding old each
32
+ __getobj__.each do |k,v|
33
+ b[k.__getobj__, v.__getobj__] unless k.__getobj__.nil?
34
+ end
35
+ self
36
+ end
37
+
38
+ def cleanup
39
+ delete_if {|k,v| k.__getobj__.nil?}
40
+ end
41
+ end
42
+
@@ -0,0 +1,68 @@
1
+ # NOTE:
2
+ # SimpleWeakHash is based on two blog posts by Mauricio Fernández,
3
+ # including code by him and Robert Klemme. SimpleWeakHash was created by
4
+ # Mauricio Fernández.
5
+
6
+ # SimpleWeakHash is a weak hash with more relaxed semantics than a WeakHash:
7
+ #
8
+ # * the key->value associations can disappear at any time
9
+ # * all existent associations are wiped out when the GC kicks in
10
+ #
11
+ # In particular, it removes the tacit requirement that an association remain
12
+ # alive as long as there is an external reference (i.e. outside the hash) to
13
+ # either the key or the value. The advantage is increased speed.
14
+ #
15
+ class SimpleWeakHash
16
+
17
+ def initialize
18
+ @valid = false
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ # retrieve object with the given key
23
+ # returns the object if present and it has not been
24
+ # garbage collected. Otherwise it returns nil.
25
+ def [](key)
26
+ __get_hash__[key]
27
+ end
28
+
29
+ # "store" object into the weak hash using the given key
30
+ # a weak reference is stored so the object stays references
31
+ # as long as it isn't garbage collected.
32
+ def []=(key, value)
33
+ __get_hash__[key] = value
34
+ end
35
+
36
+ # an addition to Mauricio's implementation,
37
+ # simple method that act's like Hash#update method.
38
+ def update(hash)
39
+ hash.each { |key, val| send(:[]=, key, val) }
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ # This used to use Thread.critical, but that's no longer available on ruby 1.9
46
+ # so I replaced it with a Mutex.
47
+ def __get_hash__
48
+ @mutex.synchronize do
49
+ set_internal_hash unless @valid
50
+ recover_hash
51
+ end
52
+ end
53
+
54
+ def recover_hash
55
+ ObjectSpace._id2ref(@hash_id)
56
+ rescue RangeError
57
+ set_internal_hash
58
+ ObjectSpace._id2ref(@hash_id)
59
+ end
60
+
61
+ def set_internal_hash
62
+ hash = {}
63
+ @hash_id = hash.object_id
64
+ @valid = true
65
+ ObjectSpace.define_finalizer hash, lambda { |id| @valid = id != @hash_id }
66
+ hash = nil
67
+ end
68
+ end
data/lib/weak_hash.rb ADDED
@@ -0,0 +1,80 @@
1
+ # NOTE:
2
+ # WeakHash is based on two blog posts by Mauricio Fernández,
3
+ # including code by him and Robert Klemme. WeakHash was created by
4
+ # Mauricio Fernández.
5
+
6
+ # This is a Hash where an association is removed as soon as either the key or
7
+ # the value are GCed.
8
+ #
9
+ # When storing a value, the key is cloned and used in an internal hash
10
+ # (the cache) referetting to the object_id of the corresponding value.
11
+ #
12
+ class WeakHash
13
+
14
+ attr_reader :cache
15
+
16
+ def initialize( cache = Hash.new )
17
+ @cache = cache
18
+ @key_map = {}
19
+ @rev_cache = Hash.new{|h,k| h[k] = {}}
20
+
21
+ @reclaim_value = lambda do |value_id|
22
+ if @rev_cache.has_key? value_id
23
+ @rev_cache[value_id].each_key{|key| @cache.delete key}
24
+ @rev_cache.delete value_id
25
+ end
26
+ end
27
+
28
+ @reclaim_key = lambda do |key_id|
29
+ if @key_map.has_key? key_id
30
+ @cache.delete @key_map[key_id]
31
+ end
32
+ end
33
+ end
34
+
35
+ # retrieve object with the given key
36
+ # returns the object if present and it has not been
37
+ # garbage collected. Otherwise it returns nil.
38
+ def []( key )
39
+ value_id = @cache[key]
40
+ return ObjectSpace._id2ref(value_id) unless value_id.nil?
41
+ nil
42
+ rescue RangeError
43
+ nil
44
+ end
45
+
46
+ # "store" object into the weak hash using the given key
47
+ # a weak reference is stored so the object stays references
48
+ # as long as it isn't garbage collected.
49
+ def []=( key, value )
50
+ case key
51
+ when Fixnum, Symbol, true, false
52
+ key2 = key
53
+ else
54
+ key2 = key.dup
55
+ end
56
+
57
+ @rev_cache[value.object_id][key2] = true
58
+ @cache[key2] = value.object_id
59
+ @key_map[key.object_id] = key2
60
+
61
+ begin
62
+ ObjectSpace.define_finalizer(value, @reclaim_value)
63
+ rescue ArgumentError
64
+ # Some objects will never be reclaimed by ObjectSpace, e.g. Fixnums
65
+ end
66
+
67
+ begin
68
+ ObjectSpace.define_finalizer(key, @reclaim_key)
69
+ rescue ArgumentError
70
+ # Some objects will never be reclaimed by ObjectSpace, e.g. Fixnums
71
+ end
72
+ end
73
+
74
+ # an addition to Mauricio's implementation,
75
+ # simple method that act's like Hash#update method.
76
+ def update(hash)
77
+ hash.each { |key, val| send(:[]=, key, val) }
78
+ self
79
+ end
80
+ end
@@ -0,0 +1,61 @@
1
+ require 'benchmark'
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
4
+
5
+ require 'weak_hash'
6
+ require 'rk_weak_hash'
7
+ require 'simple_weak_hash'
8
+
9
+ TIMES = 100_000
10
+
11
+ Benchmark.bmbm(30) do |bm|
12
+
13
+ wh1 = RKWeakHash.new({})
14
+ wh2 = WeakHash.new
15
+ wh3 = SimpleWeakHash.new
16
+
17
+ bm.report("RKWeakHash init") { TIMES.times{ |i| wh1[i.to_s] = i.to_s } }
18
+ bm.report("WeakHash init") { TIMES.times{ |i| wh2[i.to_s] = i.to_s } }
19
+ bm.report("SimpleWeakHash init"){ TIMES.times{ |i| wh3[i.to_s] = i.to_s } }
20
+
21
+ vals = (0..100).inject({}) { |s,x| s[x] = x; s }
22
+
23
+ bm.report("RKWeakHash lookup") do
24
+ wh1.update(vals)
25
+ vals.each{|i| wh1[i]}
26
+ end
27
+
28
+ bm.report("WeakHash lookup") do
29
+ wh2.update(vals)
30
+ vals.each{|i| wh2[i]}
31
+ end
32
+
33
+ bm.report("SimpleWeakHash lookup") do
34
+ wh3.update(vals)
35
+ vals.each{|i| wh3[i]}
36
+ end
37
+ end
38
+
39
+ __END__
40
+ ▸ ruby -v
41
+ ruby 1.9.1p376 (2009-12-07 revision 26041) [x86_64-linux]
42
+
43
+ ▸ uname -a
44
+ Linux goingmerry 2.6.32-ARCH #1 SMP PREEMPT Thu Jan 7 22:28:29 CET 2010 x86_64 AMD Athlon(tm) 64 X2 Dual Core Processor 6000+ AuthenticAMD GNU/Linux
45
+
46
+ Rehearsal -----------------------------------------------------------------
47
+ RKWeakHash init 6.680000 0.070000 6.750000 ( 6.771348)
48
+ WeakHash init 2.170000 0.030000 2.200000 ( 2.209112)
49
+ SimpleWeakHash init 0.530000 0.000000 0.530000 ( 0.533288)
50
+ RKWeakHash lookup 0.000000 0.000000 0.000000 ( 0.000874)
51
+ WeakHash lookup 0.010000 0.000000 0.010000 ( 0.003856)
52
+ SimpleWeakHash lookup 0.000000 0.000000 0.000000 ( 0.000454)
53
+ -------------------------------------------------------- total: 9.490000sec
54
+
55
+ user system total real
56
+ RKWeakHash init 12.980000 0.050000 13.030000 ( 13.025488)
57
+ WeakHash init 2.750000 0.020000 2.770000 ( 2.771461)
58
+ SimpleWeakHash init 0.300000 0.000000 0.300000 ( 0.307679)
59
+ RKWeakHash lookup 0.000000 0.000000 0.000000 ( 0.000906)
60
+ WeakHash lookup 0.010000 0.000000 0.010000 ( 0.003932)
61
+ SimpleWeakHash lookup 0.000000 0.000000 0.000000 ( 0.000408)
@@ -0,0 +1,16 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe RKWeakHash do
4
+ before do
5
+ @key = "key"
6
+ @value = "value"
7
+ @weak_hash = RKWeakHash.new
8
+
9
+ @weak_hash[@key] = @value
10
+ end
11
+
12
+ it "stores objects until a garbage collection happens" do
13
+ GC.start; GC.start
14
+ @weak_hash[@key].should_not == @value
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SimpleWeakHash do
4
+ before do
5
+ @key = "key"
6
+ @value = "value"
7
+ @weak_hash = SimpleWeakHash.new
8
+
9
+ @weak_hash[@key] = @value
10
+ end
11
+
12
+ it "stores objects until a garbage collection happens" do
13
+ GC.start; GC.start
14
+ @weak_hash[@key].should_not == @value
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'weak_hash'
4
+ require 'rk_weak_hash'
5
+ require 'simple_weak_hash'
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+
9
+ Spec::Runner.configure do |config|
10
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe WeakHash do
4
+ before do
5
+ @key = "key"
6
+ @value = "value"
7
+ @weak_hash = WeakHash.new
8
+
9
+ @weak_hash[@key] = @value
10
+ end
11
+
12
+ it "stores objects until the key is garbage collected" do
13
+ 1.upto(10) do
14
+ @weak_hash[@key].should == @value
15
+ end
16
+
17
+ @key = nil
18
+ GC.start; GC.start
19
+ @weak_hash[@key].should_not == @value
20
+ end
21
+
22
+ it "stores objects until the value is garbage collected" do
23
+ 1.upto(10) do
24
+ @weak_hash[@key].should == @value
25
+ end
26
+
27
+ @value = nil
28
+ GC.start; GC.start
29
+ @weak_hash[@key].should_not == @value
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weak_hash
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Emmanuel Oga
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-08 00:00:00 -03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
31
+ version: 1.2.9
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: yard
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description: a hash that stores objects until they are garbage collected
47
+ email: EmmanuelOga@gmail.com
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - LICENSE
54
+ - README.rdoc
55
+ files:
56
+ - .document
57
+ - .gitignore
58
+ - LICENSE
59
+ - README.rdoc
60
+ - Rakefile
61
+ - VERSION
62
+ - lib/rk_weak_hash.rb
63
+ - lib/simple_weak_hash.rb
64
+ - lib/weak_hash.rb
65
+ - scripts/benchmark.rb
66
+ - spec/rk_weak_hash_spec.rb
67
+ - spec/simple_weak_hash_spec.rb
68
+ - spec/spec_helper.rb
69
+ - spec/weak_hash_spec.rb
70
+ has_rdoc: true
71
+ homepage: http://github.com/EmmanuelOga/weak_hash
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --charset=UTF-8
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.3.6
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Weak Hash implementation
100
+ test_files:
101
+ - spec/rk_weak_hash_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/weak_hash_spec.rb
104
+ - spec/simple_weak_hash_spec.rb