binary42-fastcache 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Upswing CRM, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,4 @@
1
+ FastCache
2
+ =========
3
+
4
+ FastCache is a simple client library implementing the memcache protocol. It currently aims to support all documented commands and features in version 1.2.6. Secondary goals include support for a modular serialization and node selection system as well as some convenient utility classes for wrapping memcached clusters.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake/rdoctask'
2
+ require 'spec/rake/spectask'
3
+
4
+ task :default => :spec
5
+
6
+ desc 'Run specs'
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ Rake::RDocTask.new do |task|
12
+ task.rdoc_dir = 'doc'
13
+ task.title = 'FastCache - memcache client protocol implementation'
14
+ task.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
15
+ task.rdoc_files.include 'README'
16
+ task.rdoc_files.include 'lib/**/*.rb'
17
+ end
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'benchmark'
4
+ require 'fastcache'
5
+
6
+ NODE = ENV['NODE'] || 'localhost:11211'
@@ -0,0 +1,15 @@
1
+ n = 1000
2
+ Benchmark.bm(30) do |x|
3
+ x.report 'manual connection open/close' do
4
+ n.times do
5
+ conn = FastCache.connect NODE
6
+ conn.close
7
+ end
8
+ end
9
+
10
+ x.report 'block connection open/close' do
11
+ n.times do
12
+ FastCache.connect(NODE) {}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ class FastCache::ConsistentBucket
2
+ # TODO: Implement a basic Ruby consistent hasher
3
+ end
@@ -0,0 +1,21 @@
1
+ class FastCache::ModulusBucket
2
+
3
+ def initialize(key, hash)
4
+ @digest = hash.new(key).hexdigest.to_i(16)
5
+ @key = key
6
+ end
7
+
8
+ def select(nodes)
9
+ count = nodes.size
10
+ # Note: An upper bound on tries ought to be given at some point
11
+ count.times do |i|
12
+ begin
13
+ break yield(@key, nodes[(@digest + i) % count])
14
+ rescue FastCache::Memcache::ProtocolError
15
+ next
16
+ end
17
+ raise 'Unable to find suitable node to communicate with'
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,30 @@
1
+ class FastCache::CRC32
2
+
3
+ def initialize(string = nil)
4
+ @crc = 0xFFFFFFFF
5
+ self << string if string
6
+ end
7
+
8
+ def <<(string)
9
+ string.size.times do |i|
10
+ @crc ^= string[i]
11
+ 8.times do
12
+ if (@crc & 1).zero?
13
+ @crc >>= 1
14
+ else
15
+ @crc = (@crc >> 1) ^ 0xEDB88320
16
+ end
17
+ end
18
+ end
19
+ self
20
+ end
21
+
22
+ def digest
23
+ [@crc ^ 0xFFFFFFFF].pack('N')
24
+ end
25
+
26
+ def hexdigest
27
+ (@crc ^ 0xFFFFFFFF).to_s(16)
28
+ end
29
+
30
+ end
@@ -0,0 +1,3 @@
1
+ require 'openssl'
2
+
3
+ FastCache::MD5 = OpenSSL::Digest::MD5
@@ -0,0 +1,3 @@
1
+ require 'openssl'
2
+
3
+ FastCache::SHA1 = OpenSSL::Digest::SHA1
@@ -0,0 +1,24 @@
1
+ class FastCache::Connection
2
+ include FastCache::Memcache::Protocol
3
+
4
+ attr_accessor :bucket_class, :hash_class, :marshal_module
5
+ attr_reader :nodes
6
+
7
+ def initialize(nodes, opts)
8
+ @nodes = nodes.map {|(host, port)| FastCache::Memcache::Node.new(host, port)}
9
+ @bucket_class = FastCache::ModulusBucket
10
+ @hash_class = FastCache::CRC32
11
+ @marshal_module = Marshal # Ruby's built in marshal
12
+ end
13
+
14
+ def close
15
+ @nodes.each do |node| node.close end
16
+ end
17
+
18
+ private
19
+
20
+ def bucket(key)
21
+ @bucket_class.new(key, @hash_class)
22
+ end
23
+
24
+ end
@@ -0,0 +1,3 @@
1
+ class FastCache::Dictionary < FastCache::Connection
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class FastCache::Evaluator < FastCache::Connection
2
+
3
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,70 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ class FastCache::Memcache::Node
5
+ attr_reader :host, :port, :status
6
+ attr_accessor :weight
7
+
8
+ CONNECT_TIMEOUT = 0.25
9
+ RETRY_DELAY = 10.0
10
+
11
+ def initialize(host, port)
12
+ @host = host
13
+ @port = port
14
+ @weight = 1
15
+ @status = :disconnected
16
+ @socket = nil
17
+ @retry = nil
18
+ end
19
+
20
+ def read(*a)
21
+ socket.read(*a)
22
+ end
23
+
24
+ def write(data)
25
+ socket.write(data)
26
+ end
27
+
28
+ def gets
29
+ socket.gets
30
+ end
31
+
32
+ def close
33
+ socket.close
34
+ @socket = nil
35
+ end
36
+
37
+ def connect
38
+ socket
39
+ end
40
+
41
+ private
42
+
43
+ def socket
44
+ return @socket if @socket and not @socket.closed?
45
+
46
+ @socket = nil
47
+
48
+ # If the host was dead, don't retry for a while.
49
+ return if @retry and @retry > Time.now
50
+
51
+ # Attempt to connect if not already connected.
52
+ begin
53
+ @socket = timeout CONNECT_TIMEOUT do
54
+ TCPSocket.new @host, @port
55
+ end
56
+ @retry = nil
57
+ @status = :connected
58
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => err
59
+ @status = :dead
60
+ @socket = nil
61
+ @retry = Time.now + RETRY_DELAY
62
+ end
63
+
64
+ @socket
65
+ rescue IOError
66
+ @socket = nil
67
+ retry
68
+ end
69
+
70
+ end
@@ -0,0 +1,105 @@
1
+ # Informal protocol for mixin:
2
+ # bucket will ask for a key and return a bucket
3
+ # nodes will return connection nodes
4
+ module FastCache::Memcache::Protocol
5
+
6
+ def get(key)
7
+ bucket(key).select(nodes) {|node_key, node|
8
+ node.write "get #{node_key}\r\n"
9
+ resp = node.gets
10
+ break FastCache::Maybe.nothing if resp == "END\r\n"
11
+ resp =~ /(\d+)\r/
12
+ value = node.read $1.to_i
13
+ node.read 2 # \r\n
14
+ node.gets # END\r\n
15
+ break FastCache::Maybe.just(value) unless value.nil?
16
+ FastCache::Maybe.nothing
17
+ }
18
+ rescue FastCache::Memcache::ProtocolError
19
+ FastCache::Maybe.nothing
20
+ end
21
+
22
+ def set(key, value, expiry = 0)
23
+ value = value.to_s
24
+ bucket(key).select(nodes) {|node_key, node|
25
+ node.write "set #{node_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"
26
+ result = node.gets
27
+ if result == "STORED\r\n"
28
+ FastCache::Maybe.just(value)
29
+ else
30
+ FastCache::Maybe.nothing
31
+ end
32
+ }
33
+ rescue FastCache::Memcache::ProtocolError
34
+ FastCache::Maybe.nothing
35
+ end
36
+
37
+ def add(key, value, expiry = 0)
38
+ data = value.to_s
39
+ bucket(key).select(nodes) {|node_key, node|
40
+ node.write "add #{node_key} 0 #{expiry} #{data.size}\r\n#{data}\r\n"
41
+ resp = node.gets
42
+ if resp == "STORED\r\n"
43
+ FastCache::Maybe.just(data)
44
+ else
45
+ FastCache::Maybe.nothing
46
+ end
47
+ }
48
+ rescue FastCache::Memcache::ProtocolError
49
+ FastCache::Maybe.nothing
50
+ end
51
+
52
+ def replace(key, value, expiry = 0)
53
+ data = value.to_s
54
+ bucket(key).select(nodes) {|node_key, node|
55
+ node.write "replace #{node_key} 0 #{expiry} #{data.size}\r\n#{data}\r\n"
56
+ resp = node.gets
57
+ if resp == "STORED\r\n"
58
+ FastCache::Maybe.just(data)
59
+ else
60
+ FastCache::Maybe.nothing
61
+ end
62
+ }
63
+ rescue FastCache::Memcache::ProtocolError
64
+ FastCache::Maybe.nothing
65
+ end
66
+
67
+ def delete(key, expiry = 0)
68
+ bucket(key).select(nodes) {|node_key, node|
69
+ node.write "delete #{node_key} #{expiry}\r\n"
70
+ resp = node.gets
71
+ resp == "DELETED\r\n"
72
+ }
73
+ rescue FastCache::Memcache::Protocol
74
+ FastCache::Maybe.nothing
75
+ end
76
+
77
+ def cas(*)
78
+ fail 'Not implemented'
79
+ end
80
+
81
+ def incr(*)
82
+ fail 'Not implemented'
83
+ end
84
+
85
+ def decr(*)
86
+ fail 'Not implemented'
87
+ end
88
+
89
+ def flush_all_caches
90
+ fail 'Not implemented'
91
+ end
92
+
93
+ def flush_cache(key)
94
+ fail 'Not implemented'
95
+ end
96
+
97
+ def stats(*)
98
+ fail 'Not implemented'
99
+ end
100
+
101
+ def reset_connections
102
+ fail 'Not implemented yet'
103
+ end
104
+
105
+ end
@@ -0,0 +1,8 @@
1
+ module FastCache; end
2
+ module FastCache::Memcache
3
+ class ProtocolError < RuntimeError; end
4
+ end
5
+
6
+ # Memcache components
7
+ require 'fastcache/memcache/node'
8
+ require 'fastcache/memcache/protocol'
File without changes
@@ -0,0 +1,59 @@
1
+ class FastCache::Maybe
2
+
3
+ def self.nothing
4
+ self.new
5
+ end
6
+
7
+ def self.just(x)
8
+ self.new(x)
9
+ end
10
+
11
+ def initialize(*args)
12
+ args.size < 2 or raise ArgumentError,
13
+ "#{self.class.name}#initialize only takes 0 or 1 arguments. Given #{args.size}."
14
+ if args.empty?
15
+ @value = nil
16
+ @nothing = true
17
+ else
18
+ @value = *args
19
+ @nothing = false
20
+ end
21
+ end
22
+
23
+ def anything?
24
+ !@nothing
25
+ end
26
+
27
+ def join(other)
28
+ (other.nothing? || self.nothing?) ?
29
+ Maybe.nothing :
30
+ other
31
+ end
32
+
33
+ def nothing?
34
+ @nothing
35
+ end
36
+ alias empty? nothing?
37
+ alias blank? nothing?
38
+
39
+ def value
40
+ if anything?
41
+ @value
42
+ else
43
+ raise 'No value set'
44
+ end
45
+ end
46
+
47
+ def value=(obj)
48
+ @nothing = false
49
+ @value = obj
50
+ end
51
+
52
+ def void
53
+ @nothing = true
54
+ @value = nil
55
+ self
56
+ end
57
+ alias clear! void
58
+
59
+ end
data/lib/fastcache.rb ADDED
@@ -0,0 +1,68 @@
1
+ module FastCache; end
2
+
3
+ # Utilities and support
4
+ require 'fastcache/util/maybe'
5
+
6
+ # Bucketing algorithms
7
+ require 'fastcache/bucket/consistent'
8
+ require 'fastcache/bucket/modulus'
9
+
10
+ # Key hashing algorithms
11
+ require 'fastcache/hash/crc32'
12
+ require 'fastcache/hash/md5'
13
+ require 'fastcache/hash/sha1'
14
+
15
+ # Key routers
16
+ # TODO: Check for ruby-ketama
17
+ require 'fastcache/router/basic'
18
+
19
+ # Networking and protocol support code
20
+ require 'fastcache/memcache'
21
+
22
+ # Interfaces
23
+ require 'fastcache/interface/connection'
24
+ require 'fastcache/interface/dictionary'
25
+ require 'fastcache/interface/evaluator'
26
+
27
+ class << FastCache
28
+
29
+ def connect(node_list = 'localhost:11211', opts = {})
30
+ opts = {
31
+ :name => 'default',
32
+ :type => 'raw'
33
+ }.merge(opts)
34
+
35
+ connections[opts[:name]].close if connections[opts[:name]]
36
+
37
+ nodes = node_list.split(',').map {|node|
38
+ host, port = *node.split(':')
39
+ port ||= 80
40
+ [host, port.to_i]
41
+ }
42
+
43
+ connections[opts[:name]] = case(opts[:type])
44
+ when FastCache::Connection
45
+ opts[:type].new(nodes, opts[:connection])
46
+ when 'dictionary'
47
+ FastCache::Dictionary.new(nodes, opts[:connection])
48
+ when 'evaluator'
49
+ FastCache::Evaluator.new(nodes, opts[:connection])
50
+ when 'raw'
51
+ FastCache::Connection.new(nodes, opts[:connection])
52
+ else
53
+ raise "Unknown connection type #{opts[:type]}"
54
+ end
55
+ end
56
+
57
+ def connection(name = 'default')
58
+ connections[name] or raise ArgumentError,
59
+ "Invalid connection name #{name.inspect}"
60
+ end
61
+
62
+ private
63
+
64
+ def connections
65
+ @connections ||= {}
66
+ end
67
+
68
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe FastCache::Memcache::Node, 'initialize' do
4
+
5
+ before :each do
6
+ @node = FastCache::Memcache::Node.new('localhost', 11211)
7
+ end
8
+
9
+ it 'should take host and port as arguments' do
10
+ @node.host.should == 'localhost'
11
+ @node.port.should == 11211
12
+ end
13
+
14
+ it 'should set a default weight of 1' do
15
+ @node.weight.should == 1
16
+ end
17
+
18
+ it 'should set a default status of disconnected' do
19
+ @node.status.should == :disconnected
20
+ end
21
+
22
+ end
23
+
24
+ describe FastCache::Memcache::Node, 'connect' do
25
+
26
+ before :each do
27
+ @conn = FastCache::Memcache::Node.new('localhost', 11211)
28
+ end
29
+
30
+ describe '(success)' do
31
+ it 'should set the status to :connected' do
32
+ TCPSocket.should_receive(:new).with(@conn.host, @conn.port)
33
+ @conn.connect
34
+ @conn.status.should == :connected
35
+ end
36
+ end
37
+
38
+ describe '(failure)' do
39
+ it 'should set the status to :dead' do
40
+ TCPSocket.should_receive(:new).with(@conn.host, @conn.port).and_raise(Errno::ECONNREFUSED)
41
+ @conn.connect
42
+ @conn.status.should == :dead
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'fastcache'
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ include FastCache
3
+
4
+ describe Maybe, '.nothing' do
5
+
6
+ it 'should return an object with nothing' do
7
+ Maybe.nothing.should be_nothing
8
+ end
9
+
10
+ end
11
+
12
+ describe Maybe, '.just' do
13
+
14
+ it 'should return an object with the passed value' do
15
+ Maybe.just(42).value.should == 42
16
+ end
17
+
18
+ end
19
+
20
+ describe Maybe, '#anything?' do
21
+
22
+ it 'should return true if there is a value' do
23
+ Maybe.just(42).should be_anything
24
+ Maybe.just(nil).should be_anything
25
+ end
26
+
27
+ it 'should return false if there is no value' do
28
+ Maybe.nothing.should_not be_anything
29
+ end
30
+
31
+ end
32
+
33
+ describe Maybe, '#nothing?' do
34
+
35
+ it 'should return false if there is a value' do
36
+ Maybe.just(42).should_not be_nothing
37
+ Maybe.just(nil).should_not be_nothing
38
+ end
39
+
40
+ it 'should return true if there is no value' do
41
+ Maybe.nothing.should be_nothing
42
+ end
43
+
44
+ end
45
+
46
+ describe Maybe, 'getting and setting wrapped values' do
47
+
48
+ it 'should allow setting a value on nothing' do
49
+ [:hello, nil, false, true].each do |val|
50
+ what = Maybe.nothing
51
+ what.value = val
52
+ what.should be_anything
53
+ end
54
+ end
55
+
56
+ it 'should should replace existing values' do
57
+ [:hello, nil, false, true].each do |val|
58
+ what = Maybe.just(42)
59
+ what.value = val
60
+ what.value.should == val
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ describe Maybe, '#join' do
67
+
68
+ it 'should keep nothing if nothing' do
69
+ Maybe.nothing.join(Maybe.nothing).should be_nothing
70
+ Maybe.nothing.join(Maybe.just(42)).should be_nothing
71
+ end
72
+
73
+ it 'should keep the new anything if anything' do
74
+ what = Maybe.just(42).join(Maybe.just(43))
75
+ what.should be_anything
76
+ what.value.should == 43
77
+ end
78
+
79
+ it 'should become nothing if anything is passed nothing' do
80
+ Maybe.just(42).join(Maybe.nothing).should be_nothing
81
+ end
82
+
83
+ end
84
+
85
+ describe Maybe, '#void' do
86
+
87
+ it 'should make an object with anything into one with nothing' do
88
+ Maybe.just(42).void.should be_nothing
89
+ end
90
+
91
+ it 'should not let nothing remain nothing' do
92
+ Maybe.nothing.void.should be_nothing
93
+ end
94
+
95
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binary42-fastcache
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Brian Mitchell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-27 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: FastCache implements a flexible client side implementation of the memcached protocol. The library comes with a light core as well as some components to wrap the protocol with more advanced behaviors.
17
+ email: brian.mitchell@excoventures.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/fastcache/hash/sha1.rb
26
+ - lib/fastcache/hash/md5.rb
27
+ - lib/fastcache/hash/crc32.rb
28
+ - lib/fastcache/util/maybe.rb
29
+ - lib/fastcache/marshal/yaml.rb
30
+ - lib/fastcache/marshal/duck.rb
31
+ - lib/fastcache/marshal/ruby.rb
32
+ - lib/fastcache/bucket/modulus.rb
33
+ - lib/fastcache/bucket/consistent.rb
34
+ - lib/fastcache/router/basic.rb
35
+ - lib/fastcache/interface/dictionary.rb
36
+ - lib/fastcache/interface/evaluator.rb
37
+ - lib/fastcache/interface/connection.rb
38
+ - lib/fastcache/memcache.rb
39
+ - lib/fastcache/memcache/node.rb
40
+ - lib/fastcache/memcache/protocol.rb
41
+ - lib/fastcache.rb
42
+ - LICENSE
43
+ - Rakefile
44
+ - README
45
+ has_rdoc: true
46
+ homepage: http://github.com/binary42/fastcache
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --line-numbers
50
+ - --inline-source
51
+ - --main
52
+ - README
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: FastCache - a client side implementation of the memcached protocol
74
+ test_files:
75
+ - spec/util/maybe_spec.rb
76
+ - spec/spec_helper.rb
77
+ - spec/memcache/node_spec.rb
78
+ - spec/memcache/protocol_spec.rb
79
+ - benchmark/connection.rb
80
+ - benchmark/benchmark_helper.rb
81
+ - Rakefile