binary42-fastcache 0.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/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