threadsafe-lru 0.0.2

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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /.rbx
data/.project ADDED
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>threadsafe-lru</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ <buildCommand>
9
+ <name>org.eclipse.dltk.core.scriptbuilder</name>
10
+ <arguments>
11
+ </arguments>
12
+ </buildCommand>
13
+ </buildSpec>
14
+ <natures>
15
+ <nature>org.eclipse.dltk.ruby.core.nature</nature>
16
+ </natures>
17
+ </projectDescription>
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dragan Milic
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # ThreadSafeLru
2
+
3
+ Thread safe LRU Cache implementation in Ruby compatible with Java's Memory Model.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'threadsafe-lru'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install threadsafe-lru
18
+
19
+ ## Usage
20
+
21
+ # initialize cache with maximum size of 200
22
+ cache_size=200
23
+ fib_cache=ThreadSafeLru::LruCache.new 200
24
+
25
+ # fetch fibonacci of 42 from cache. only first execution will perform computation (evaluate block)
26
+ fib_200 =fib_cache.get(42) {|key| fib(key)}
27
+
28
+ # drop key 42 from the cache
29
+ fib_cache.drop(42)
30
+
31
+ # clear cache by dropping all cached values
32
+ fib_cache.clear()
33
+
34
+ ## Thread safety
35
+
36
+ There are two level of locking within the cache.
37
+ * Cache lock makes sure that cache structure can be accessed only by one thread at the time.
38
+ * Value lock makes sure that only one thread produces a value for given key at the time.
39
+ All other threads interested in obtaining the value for the key are waiting for the first thread to finish
40
+
41
+ ## Testing
42
+
43
+ ThreadSafeLru has been tested with MRI 1.9.3, Jruby 1.6.2, Jruby 1.7.0.preview1 and Jruby 1.7.0.preview2
44
+
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ Autotest.add_discovery {"rspec2"}
@@ -0,0 +1,5 @@
1
+ require 'threadsafe-lru/ThreadSafeLruCache'
2
+
3
+ module ThreadSafeLru
4
+
5
+ end
@@ -0,0 +1,6 @@
1
+ module ThreadSafeLru
2
+ class DoubleLinkedList
3
+
4
+
5
+ end
6
+ end
@@ -0,0 +1,81 @@
1
+ require 'thread'
2
+
3
+ module ThreadSafeLru
4
+ class LruCache
5
+ def initialize size, &block
6
+ @size=size
7
+ @cached_values={}
8
+ @factory_block=block
9
+ @recently_used=[]
10
+ @lock=Mutex.new
11
+ end
12
+
13
+ def size
14
+ @recently_used.size
15
+ end
16
+
17
+
18
+ def drop key
19
+ @lock.synchronize do
20
+ @cached_values.delete key
21
+ @recently_used.delete key
22
+ end
23
+ end
24
+
25
+
26
+ def clear
27
+ @lock.synchronize do
28
+ @cached_values.clear
29
+ @recently_used.clear
30
+ end
31
+ end
32
+
33
+ def get key, &block
34
+ node=nil
35
+ @lock.synchronize do
36
+ node=get_node(key)
37
+ end
38
+ node.get_value(block ? block : @factory_block)
39
+ end
40
+
41
+ private
42
+
43
+ def get_node key
44
+ if @cached_values.has_key?(key)
45
+ @recently_used.delete(key)
46
+ @recently_used << key
47
+ else
48
+ while (@recently_used.size >= @size) do
49
+ dropped=@recently_used.shift
50
+ @cached_values.delete(dropped)
51
+ end
52
+ node=Node.new key
53
+ @cached_values[key]=node
54
+ @recently_used << key
55
+ end
56
+
57
+ @cached_values[key]
58
+ end
59
+
60
+ end
61
+
62
+ class Node
63
+ def initialize key
64
+ @key=key
65
+ @produced=false
66
+ @value=nil
67
+ @lock=Mutex.new
68
+ end
69
+
70
+ def get_value block
71
+ @lock.synchronize do
72
+ unless (@produced)
73
+ @value=block.call @key
74
+ @produced=true
75
+ end
76
+ @value
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,3 @@
1
+ module ThreadSafeLru
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,125 @@
1
+ require 'threadsafe-lru/ThreadSafeLruCache'
2
+ require 'thread'
3
+
4
+ module ThreadSafeLru
5
+ describe LruCache do
6
+
7
+ subject {LruCache.new(3){|key| "value: #{key}"}}
8
+
9
+ describe :get do
10
+ it "should cache single value" do
11
+ subject.get(:one){1}.should == 1
12
+ subject.get(:one){2}.should == 1
13
+ end
14
+
15
+ it "should drop least used value" do
16
+ subject.get(:one){1}.should == 1
17
+ subject.get(:two){2}.should == 2
18
+ subject.get(:three){3}.should == 3
19
+ subject.get(:four){4}.should == 4
20
+
21
+ subject.get(:one){99}.should == 99
22
+ subject.get(:three){99}.should == 3
23
+ subject.get(:four){99}.should == 4
24
+ subject.get(:two){99}.should == 99
25
+ end
26
+
27
+ it "should not corrupt state in highly concurrent situation" do
28
+
29
+ cache=subject
30
+
31
+ threads=(1..100).map do
32
+ Thread.new do
33
+ (0..2000).each do
34
+ random=rand(2000)
35
+ cache.get(random) {random}.should == random
36
+ end
37
+ end
38
+ end
39
+
40
+ threads.each {|thread| thread.join}
41
+
42
+ end
43
+
44
+ it "should make second thread wait for the first requesting thread to value" do
45
+
46
+ (1..100).each do
47
+
48
+ q1=Queue.new
49
+ q2=Queue.new
50
+
51
+ cache=subject
52
+
53
+ t1=Thread.new do
54
+ cache.get(:one) {
55
+ q1.pop
56
+ q2 << 1
57
+ "thread1"
58
+ }
59
+ end
60
+
61
+ t2=Thread.new do
62
+ q2.pop
63
+ cache.get(:one) {
64
+ "thread2"
65
+ }
66
+ end
67
+
68
+ q1<<1
69
+
70
+ t1.join
71
+ t2.join
72
+
73
+ cache.get(:one).should == "thread1"
74
+
75
+ cache.clear
76
+
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ describe :clear do
83
+ it "should drop all cached values" do
84
+ subject.get(:one){1}.should == 1
85
+ subject.get(:one){2}.should == 1
86
+ subject.clear
87
+ subject.get(:one){2}.should == 2
88
+ end
89
+ end
90
+
91
+ describe :drop do
92
+
93
+ before do
94
+ subject.get(:one){"one"}
95
+ subject.get(:two){"two"}
96
+ end
97
+
98
+ context "when there is a cached value" do
99
+
100
+ it "should drop that cached value" do
101
+ subject.get(:one).should == "one"
102
+ subject.get(:two).should == "two"
103
+
104
+ subject.drop(:one)
105
+
106
+ subject.get(:one){"newone"}.should == "newone"
107
+ subject.get(:two).should == "two"
108
+ end
109
+
110
+ end
111
+
112
+ context "when value is not cached" do
113
+ it "should not drop anything" do
114
+ subject.drop(:three)
115
+ subject.get(:one){"newone"}.should == "one"
116
+ subject.get(:two){"newtwo"}.should == "two"
117
+ end
118
+
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/threadsafe-lru/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Dragan Milic"]
6
+ gem.email = ["dragan@netice9.com"]
7
+ gem.description = %q{Thread safe implemenation of in-memory LRU cache compatible with Java Memory Model}
8
+ gem.summary = %q{Thread safe in memory LRU cache}
9
+ gem.homepage = "https://github.com/draganm/threadsafe-lru"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "threadsafe-lru"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ThreadSafeLru::VERSION
17
+ gem.add_development_dependency 'rspec'
18
+ gem.add_development_dependency 'autotest'
19
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: threadsafe-lru
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.2
6
+ platform: ruby
7
+ authors:
8
+ - Dragan Milic
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ none: false
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ none: false
28
+ prerelease: false
29
+ type: :development
30
+ - !ruby/object:Gem::Dependency
31
+ name: autotest
32
+ version_requirements: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ none: false
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ none: false
44
+ prerelease: false
45
+ type: :development
46
+ description: Thread safe implemenation of in-memory LRU cache compatible with Java Memory Model
47
+ email:
48
+ - dragan@netice9.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .project
55
+ - LICENSE
56
+ - README.md
57
+ - autotest/discover.rb
58
+ - lib/threadsafe-lru.rb
59
+ - lib/threadsafe-lru/DoubleLinkedList.rb
60
+ - lib/threadsafe-lru/ThreadSafeLruCache.rb
61
+ - lib/threadsafe-lru/version.rb
62
+ - spec/threadsafe-lru/ThreadSafeLruCache_spec.rb
63
+ - threadsafe-lru.gemspec
64
+ homepage: https://github.com/draganm/threadsafe-lru
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ none: false
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ none: false
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.24
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Thread safe in memory LRU cache
88
+ test_files:
89
+ - spec/threadsafe-lru/ThreadSafeLruCache_spec.rb
90
+ ...