coin 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem "micro_test"
5
+ gem "pry"
6
+ # gem "pry-stack_explorer"
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ coderay (1.0.8)
5
+ method_source (0.8.1)
6
+ micro_test (0.3.1)
7
+ os
8
+ os (0.9.6)
9
+ pry (0.9.10)
10
+ coderay (~> 1.0.5)
11
+ method_source (~> 0.8)
12
+ slop (~> 3.3.1)
13
+ pry (0.9.10-java)
14
+ coderay (~> 1.0.5)
15
+ method_source (~> 0.8)
16
+ slop (~> 3.3.1)
17
+ spoon (~> 0.0)
18
+ slop (3.3.3)
19
+ spoon (0.0.1)
20
+
21
+ PLATFORMS
22
+ java
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ micro_test
27
+ pry
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nathan Hopkins
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,125 @@
1
+ # Coin
2
+
3
+ ## Memcached? We don't need no stinking Memcached.
4
+
5
+ ... Well, you might depending upon your specific needs.
6
+ But be sure to have a look at Coin before you reach for the sledgehammer.
7
+
8
+ Coin is an absurdly simple in memory object caching system written in Ruby.
9
+
10
+ ## Why Coin?
11
+
12
+ * No configuration required
13
+ * No added complexity to your stack
14
+ * Small footprint (under 200 lines)
15
+ * Simple API
16
+
17
+ Coin uses [Distributed Ruby (DRb)](http://pragprog.com/book/sidruby/the-druby-book)
18
+ to create a simple in memory caching server that addresses many of the same needs as Memcached
19
+ and other similar solutions.
20
+
21
+ ## Quick Start
22
+
23
+ Installation
24
+
25
+ ```bash
26
+ $ gem install coin
27
+ ```
28
+
29
+ Basic Usage
30
+
31
+ ```ruby
32
+ require "coin"
33
+ Coin.write :foo, true
34
+ Coin.read :foo # => true
35
+ ```
36
+
37
+ ## Next Steps
38
+
39
+ Examples of more advanced usage.
40
+
41
+ ```ruby
42
+ require "coin"
43
+
44
+ # read and/or assign a default value in a single step
45
+ Coin.read(:bar) { true } # => true
46
+
47
+ # write data with an explicit expiration (in seconds)
48
+ # this example expires in 5 seconds (default is 300)
49
+ Coin.write :bar, true, 5
50
+ sleep 5
51
+ Coin.read :bar # => nil
52
+
53
+ # delete an entry
54
+ Coin.write :bar, true
55
+ Coin.delete :bar
56
+ Coin.read :bar # => nil
57
+
58
+ # read and delete in a single step
59
+ Coin.write :bar, true
60
+ Coin.read_and_delete :bar # => true
61
+ Coin.read :bar # => nil
62
+
63
+ # determine how many items are in the cache
64
+ 10.times do |i|
65
+ Coin.write "key#{i}", true
66
+ end
67
+ Coin.length # => 10
68
+
69
+ # clear the cache
70
+ Coin.clear
71
+ Coin.length # => 0
72
+ ```
73
+
74
+ ## Deep Cuts
75
+
76
+ Coin automatically starts a DRb server that hosts the Coin::Vault.
77
+ You can take control of this behavior if needed.
78
+
79
+ ```ruby
80
+ require "coin"
81
+
82
+ # configure the port that the DRb server runs on (default is 8955)
83
+ Coin.port = 8080
84
+
85
+ # configure the URI that the DRb server runs on (defaults to druby://localhost:PORT)
86
+ Coin.uri = "druby://10.0.0.100:8080"
87
+
88
+ # access the DRb server exposing Coin::Vault
89
+ Coin.server # => #<Coin::Vault:0x007fe182852e18>
90
+
91
+ # determine if the server is running
92
+ Coin.server_running? # => true
93
+
94
+ # determine the pid of the server process
95
+ Coin.pid # => "63299"
96
+
97
+ # stop the server
98
+ Coin.stop_server # => true
99
+
100
+ # start the server
101
+ Coin.start_server # => true
102
+
103
+ # start the server forcing a restart if the server is already running
104
+ Coin.start_server true # => true
105
+ ```
106
+
107
+ ## Run the Tests
108
+
109
+ ```bash
110
+ $ gem install coin
111
+ $ gem unpack coin
112
+ $ cd coin-VERSION
113
+ $ bundle
114
+ $ mt
115
+ ```
116
+
117
+ ## Notes
118
+
119
+ Coin's default behavior launches a single DRb server that provides
120
+ shared access across all processes on a **single machine**.
121
+
122
+ _It should be relatively simple to update Coin to work across multiple machines,
123
+ so keep an eye peeled for this feature in the future._
124
+
125
+ ![Coin Processes](https://www.lucidchart.com/publicSegments/view/50b8299a-7c5c-4175-8121-6b190a7a0a70/image.png)
data/bin/coin ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby --
2
+ require "drb/drb"
3
+ require "tempfile"
4
+ require File.join(File.dirname(__FILE__), "..", "lib", "coin")
5
+
6
+ Signal.trap("HUP") do
7
+ File.delete(Coin.pid_file) if File.exist?(Coin.pid_file)
8
+ DRb.thread.kill if DRb.thread
9
+ end
10
+
11
+ if File.exist?(Coin.pid_file)
12
+ begin
13
+ Process.kill("HUP", File.read(Coin.pid_file).to_i)
14
+ rescue Exception => ex
15
+ end
16
+ end
17
+
18
+ File.open(Coin.pid_file, "w") do |file|
19
+ file.write Process.pid
20
+ end
21
+
22
+ DRb.start_service ENV["COIN_URI"], Coin::Vault.instance
23
+ puts "Coin::Vault listening at: #{ENV["COIN_URI"]}"
24
+ DRb.thread.join
data/lib/coin.rb ADDED
@@ -0,0 +1,103 @@
1
+ require "drb/drb"
2
+ require "rbconfig"
3
+ require "forwardable"
4
+
5
+ Dir[File.join(File.dirname(__FILE__), "coin", "*.rb")].each do |file|
6
+ require file
7
+ end
8
+
9
+ module Coin
10
+ class << self
11
+ extend Forwardable
12
+ def_delegators :server, :write, :read_and_delete, :delete, :clear, :length
13
+
14
+ def read(key, lifetime=300)
15
+ value = server.read(key)
16
+ if value.nil? && block_given?
17
+ value = yield
18
+ write(key, value, lifetime)
19
+ end
20
+ value
21
+ end
22
+
23
+ attr_writer :port
24
+ def port
25
+ @port ||= 8955
26
+ end
27
+
28
+ attr_writer :uri
29
+ def uri
30
+ "druby://localhost:#{port}"
31
+ end
32
+
33
+ def server
34
+ return nil unless ENV["COIN_URI"].nil?
35
+
36
+ if server_running?
37
+ if @server
38
+ begin
39
+ @server.ok? if @server
40
+ rescue DRb::DRbConnError => ex
41
+ @server = nil
42
+ end
43
+ end
44
+
45
+ if @server.nil?
46
+ begin
47
+ @server = DRbObject.new_with_uri(uri)
48
+ @server.ok?
49
+ rescue DRb::DRbConnError => ex
50
+ @server = nil
51
+ end
52
+ end
53
+ end
54
+
55
+ return @server if @server && server_running?
56
+
57
+ start_server
58
+ DRb.start_service
59
+ @server = DRbObject.new_with_uri(uri)
60
+ end
61
+
62
+ def pid_file
63
+ "/tmp/coin-pid-63f95cb5-0bae-4f66-88ec-596dfbac9244"
64
+ end
65
+
66
+ def pid
67
+ File.read(Coin.pid_file) if File.exist?(Coin.pid_file)
68
+ end
69
+
70
+ def server_running?
71
+ @pid = pid
72
+ return false unless @pid
73
+ begin
74
+ Process.kill(0, @pid.to_i)
75
+ rescue Errno::ESRCH => ex
76
+ return false
77
+ end
78
+ true
79
+ end
80
+
81
+ def start_server(force=nil)
82
+ return if server_running? && !force
83
+ stop_server if force
84
+ ruby = "#{RbConfig::CONFIG["bindir"]}/ruby"
85
+ script = File.expand_path(File.join(File.dirname(__FILE__), "..", "bin", "coin"))
86
+ env = {
87
+ "COIN_URI" => Coin.uri
88
+ }
89
+ pid = spawn(env, "#{ruby} #{script}")
90
+ Process.detach(pid)
91
+
92
+ sleep 0.1 while !server_running?
93
+ true
94
+ end
95
+
96
+ def stop_server
97
+ Process.kill("HUP", @pid.to_i) if server_running?
98
+ sleep 0.1 while server_running?
99
+ true
100
+ end
101
+
102
+ end
103
+ end
data/lib/coin/vault.rb ADDED
@@ -0,0 +1,75 @@
1
+ require "thread"
2
+ require "singleton"
3
+ require "forwardable"
4
+
5
+ module Coin
6
+ class Vault
7
+ extend Forwardable
8
+ include Singleton
9
+ def_delegators :@dict, :length
10
+
11
+ def read(key)
12
+ value = @dict[key]
13
+ value = nil if value && value_expired?(value)
14
+ return value[:value] if value
15
+ nil
16
+ end
17
+
18
+ def read_and_delete(key)
19
+ value = nil
20
+ @mutex.synchronize do
21
+ value = read(key)
22
+ @dict.delete(key)
23
+ end
24
+ value
25
+ end
26
+
27
+ def write(key, value, lifetime=300)
28
+ @mutex.synchronize do
29
+ @dict[key] = { :value => value, :cached_at => Time.now, :lifetime => lifetime }
30
+ end
31
+ value
32
+ end
33
+
34
+ def delete(key)
35
+ @mutex.synchronize { @dict.delete(key) }
36
+ end
37
+
38
+ def clear
39
+ @mutex.synchronize { @dict = {} }
40
+ end
41
+
42
+ def ok?
43
+ true
44
+ end
45
+
46
+ protected
47
+
48
+ def initialize
49
+ @mutex = Mutex.new
50
+ @dict = {}
51
+ start_sweeper
52
+ end
53
+
54
+ def start_sweeper
55
+ Thread.new do
56
+ loop do
57
+ sleep 60
58
+ sweep
59
+ end
60
+ end
61
+ end
62
+
63
+ def sweep
64
+ now = Time.now
65
+ @dict.each do |key, value|
66
+ delete(key) if value_expired?(value)
67
+ end
68
+ end
69
+
70
+ def value_expired?(value)
71
+ Time.now - value[:cached_at] > value[:lifetime]
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module Coin
2
+ VERSION = "0.1.1"
3
+ end
data/test/coin_test.rb ADDED
@@ -0,0 +1,74 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class CoinTest < MicroTest::Test
4
+ before do
5
+ @key = "key-#{SecureRandom.uuid}"
6
+ end
7
+
8
+ after do
9
+ Coin.stop_server
10
+ end
11
+
12
+ test "stop_server" do
13
+ Coin.start_server
14
+ assert Coin.server_running?
15
+ Coin.stop_server
16
+ assert Coin.server_running? == false
17
+ end
18
+
19
+ test "server method starts the server" do
20
+ Coin.stop_server
21
+ assert !Coin.server_running?
22
+ Coin.server
23
+ assert Coin.server_running?
24
+ end
25
+
26
+ test "read access starts the server" do
27
+ Coin.stop_server
28
+ assert !Coin.server_running?
29
+ Coin.read :foo
30
+ assert Coin.server_running?
31
+ end
32
+
33
+ test "read with assignment block" do
34
+ value = rand(99999999999)
35
+ assert Coin.read(@key).nil?
36
+ Coin.read(@key) { value }
37
+ assert Coin.read(@key) == value
38
+ end
39
+
40
+ test "read and delete" do
41
+ value = rand(99999999999)
42
+ Coin.write @key, value
43
+ val = Coin.read_and_delete(@key)
44
+ assert val == value
45
+ assert Coin.read(@key).nil?
46
+ end
47
+
48
+ test "write with expiration" do
49
+ Coin.write(@key, true, 1)
50
+ assert Coin.read(@key)
51
+ sleep 1
52
+ assert Coin.read(@key).nil?
53
+ end
54
+
55
+ test "delete" do
56
+ Coin.write(@key, true)
57
+ assert Coin.read(@key)
58
+ Coin.delete(@key)
59
+ assert Coin.read(@key).nil?
60
+ end
61
+
62
+ test "length" do
63
+ 10.times { |i| Coin.write("key#{i}", rand(9999)) }
64
+ assert Coin.length >= 10
65
+ end
66
+
67
+ test "clear" do
68
+ 10.times { |i| Coin.write("key#{i}", rand(9999)) }
69
+ assert Coin.length >= 10
70
+ Coin.clear
71
+ assert Coin.length == 0
72
+ end
73
+
74
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require :default, :development
4
+ require File.join(File.dirname(__FILE__), "..", "lib", "coin")
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+ require "securerandom"
3
+
4
+ class VaultTest < MicroTest::Test
5
+
6
+ before do
7
+ @vault = Coin::Vault.instance
8
+ @key = "key-#{SecureRandom.uuid}"
9
+ end
10
+
11
+ test "read using an unstored key" do
12
+ assert @vault.read(@key).nil?
13
+ end
14
+
15
+ test "basic write & read" do
16
+ value = rand(999999999)
17
+ @vault.write(@key, value)
18
+ assert @vault.read(@key) == value
19
+ end
20
+
21
+ test "read_and_delete" do
22
+ value = rand(999999999)
23
+ @vault.write(@key, value)
24
+ val = @vault.read_and_delete(@key)
25
+ assert @vault.read(@key).nil?
26
+ assert val == value
27
+ end
28
+
29
+ test "delete" do
30
+ @vault.write(@key, true)
31
+ assert @vault.read(@key)
32
+ @vault.delete(@key)
33
+ assert @vault.read(@key).nil?
34
+ end
35
+
36
+ test "clear" do
37
+ 10.times { |i| @vault.write("key#{i}", true) }
38
+ assert @vault.length >= 10
39
+ @vault.clear
40
+ assert @vault.length == 0
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Hopkins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - natehop@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/coin/vault.rb
22
+ - lib/coin/version.rb
23
+ - lib/coin.rb
24
+ - test/coin_test.rb
25
+ - test/test_helper.rb
26
+ - test/vault_test.rb
27
+ - bin/coin
28
+ - Gemfile
29
+ - Gemfile.lock
30
+ - LICENSE.txt
31
+ - README.md
32
+ homepage: https://github.com/hopsoft/coin
33
+ licenses:
34
+ - MIT
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.23
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: An absurdly simple in memory object caching system.
57
+ test_files: []
58
+ has_rdoc: