coin 0.1.1
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/Gemfile +7 -0
- data/Gemfile.lock +27 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/bin/coin +24 -0
- data/lib/coin.rb +103 -0
- data/lib/coin/vault.rb +75 -0
- data/lib/coin/version.rb +3 -0
- data/test/coin_test.rb +74 -0
- data/test/test_helper.rb +4 -0
- data/test/vault_test.rb +43 -0
- metadata +58 -0
data/Gemfile
ADDED
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
|
+

|
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
|
data/lib/coin/version.rb
ADDED
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
|
data/test/test_helper.rb
ADDED
data/test/vault_test.rb
ADDED
@@ -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:
|