astro-remcached 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 2
3
2
  :major: 0
4
- :minor: 2
3
+ :minor: 3
4
+ :patch: 0
@@ -56,31 +56,98 @@ module Memcached
56
56
  hashed
57
57
  end
58
58
 
59
- def operation(op, contents, &callback)
59
+
60
+ ##
61
+ # Memcached operations
62
+ ##
63
+
64
+ def operation(request_klass, contents, &callback)
60
65
  client = client_for_key(contents[:key])
61
66
  if client
62
- client.send(op, contents, &callback)
67
+ client.send_request request_klass.new(contents), &callback
63
68
  elsif callback
64
69
  callback.call :status => Errors::DISCONNECTED
65
70
  end
66
71
  end
67
72
 
68
-
69
- ##
70
- # Memcached operations
71
- ##
72
-
73
73
  def add(contents, &callback)
74
- operation :add, contents, &callback
74
+ operation Request::Add, contents, &callback
75
75
  end
76
76
  def get(contents, &callback)
77
- operation :get, contents, &callback
77
+ operation Request::Get, contents, &callback
78
78
  end
79
79
  def set(contents, &callback)
80
- operation :set, contents, &callback
80
+ operation Request::Set, contents, &callback
81
81
  end
82
82
  def delete(contents, &callback)
83
- operation :delete, contents, &callback
83
+ operation Request::Delete, contents, &callback
84
+ end
85
+
86
+
87
+ ##
88
+ # Multi operations
89
+ #
90
+ ##
91
+
92
+ def multi_operation(request_klass, contents_list, &callback)
93
+ results = {}
94
+
95
+ # Assemble client connections per keys
96
+ client_contents = {}
97
+ contents_list.each do |contents|
98
+ client = client_for_key(contents[:key])
99
+ if client
100
+ client_contents[client] ||= []
101
+ client_contents[client] << contents
102
+ else
103
+ puts "no client for #{contents[:key].inspect}"
104
+ results[contents[:key]] = {:status => Memcached::Errors::DISCONNECTED}
105
+ end
106
+ end
107
+
108
+ # send requests and wait for responses per client
109
+ clients_pending = client_contents.length
110
+ client_contents.each do |client,contents_list|
111
+ last_i = contents_list.length - 1
112
+ client_results = {}
113
+
114
+ contents_list.each_with_index do |contents,i|
115
+ if i < last_i
116
+ request = request_klass::Quiet.new(contents)
117
+ client.send_request(request) { |response|
118
+ results[contents[:key]] = response
119
+ }
120
+ else # last request for this client
121
+ request = request_klass.new(contents)
122
+ client.send_request(request) { |response|
123
+ results[contents[:key]] = response
124
+ clients_pending -= 1
125
+ if clients_pending < 1
126
+ callback.call results
127
+ end
128
+ }
129
+ end
130
+ end
131
+ end
132
+
133
+ self
84
134
  end
135
+
136
+ def multi_add(contents_list, &callback)
137
+ multi_operation Request::Add, contents_list, &callback
138
+ end
139
+
140
+ def multi_get(contents_list, &callback)
141
+ multi_operation Request::Get, contents_list, &callback
142
+ end
143
+
144
+ def multi_set(contents_list, &callback)
145
+ multi_operation Request::Set, contents_list, &callback
146
+ end
147
+
148
+ def multi_delete(contents_list, &callback)
149
+ multi_operation Request::Delete, contents_list, &callback
150
+ end
151
+
85
152
  end
86
153
  end
@@ -129,23 +129,6 @@ module Memcached
129
129
  end
130
130
  end
131
131
 
132
-
133
- def get(contents, &callback)
134
- send_request Request::Get.new(contents), &callback
135
- end
136
-
137
- def add(contents, &callback)
138
- send_request Request::Add.new(contents), &callback
139
- end
140
-
141
- def set(contents, &callback)
142
- send_request Request::Set.new(contents), &callback
143
- end
144
-
145
- def delete(contents, &callback)
146
- send_request Request::Delete.new(contents), &callback
147
- end
148
-
149
132
  # Callback will be called multiple times
150
133
  def stats(contents={}, &callback)
151
134
  send_request Request::Stats.new(contents) do |result|
@@ -25,6 +25,10 @@ module Memcached
25
25
  DECREMENT = 0x06
26
26
  QUIT = 0x07
27
27
  STAT = 0x10
28
+ GETQ = 0x09
29
+ SETQ = 0x11
30
+ ADDQ = 0x12
31
+ DELETEQ = 0x14
28
32
 
29
33
  =begin
30
34
  Possible values of the one-byte field:
@@ -2,6 +2,8 @@ require 'remcached/pack_array'
2
2
 
3
3
  module Memcached
4
4
  class Packet
5
+ ##
6
+ # Initialize with fields
5
7
  def initialize(contents={})
6
8
  @contents = contents
7
9
  (self.class.fields +
@@ -10,15 +12,20 @@ module Memcached
10
12
  end
11
13
  end
12
14
 
15
+ ##
16
+ # Get field
13
17
  def [](field)
14
18
  @contents[field]
15
19
  end
16
20
 
21
+ ##
22
+ # Set field
17
23
  def []=(field, value)
18
24
  @contents[field] = value
19
25
  end
20
26
 
21
- # Define fields for subclasses
27
+ ##
28
+ # Define a field for subclasses
22
29
  def self.field(name, packed, default=nil)
23
30
  instance_eval do
24
31
  @fields ||= []
@@ -26,6 +33,8 @@ module Memcached
26
33
  end
27
34
  end
28
35
 
36
+ ##
37
+ # Fields of parent and this class
29
38
  def self.fields
30
39
  parent_class = ancestors[1]
31
40
  parent_fields = parent_class.respond_to?(:fields) ? parent_class.fields : []
@@ -33,6 +42,8 @@ module Memcached
33
42
  parent_fields + class_fields
34
43
  end
35
44
 
45
+ ##
46
+ # Define an extra for subclasses
36
47
  def self.extra(name, packed, default=nil)
37
48
  instance_eval do
38
49
  @extras ||= []
@@ -40,10 +51,17 @@ module Memcached
40
51
  end
41
52
  end
42
53
 
54
+ ##
55
+ # Extras of this class
43
56
  def self.extras
44
- instance_eval { @extras || [] }
57
+ parent_class = ancestors[1]
58
+ parent_extras = parent_class.respond_to?(:extras) ? parent_class.extras : []
59
+ class_extras = instance_eval { @extras || [] }
60
+ parent_extras + class_extras
45
61
  end
46
62
 
63
+ ##
64
+ # Build a packet by parsing header fields
47
65
  def self.parse_header(buf)
48
66
  pack_fmt = fields.collect { |name,fmt,default| fmt }.join
49
67
  values = PackArray.unpack(buf, pack_fmt)
@@ -56,7 +74,11 @@ module Memcached
56
74
  new contents
57
75
  end
58
76
 
59
- # Return remaining bytes
77
+ ##
78
+ # Parse body of packet when the +:total_body_length+ field is
79
+ # known by header. Pass it at least +total_body_length+ bytes.
80
+ #
81
+ # return:: [String] remaining bytes
60
82
  def parse_body(buf)
61
83
  buf, rest = buf[0..(self[:total_body_length] - 1)], buf[self[:total_body_length]..-1]
62
84
 
@@ -75,6 +97,8 @@ module Memcached
75
97
  rest
76
98
  end
77
99
 
100
+ ##
101
+ # Serialize for wire
78
102
  def to_s
79
103
  extras_s = extras_to_s
80
104
  key_s = self[:key].to_s
@@ -154,7 +178,13 @@ module Memcached
154
178
 
155
179
  class Get < Request
156
180
  def initialize(contents)
157
- super(contents.merge :opcode=>Commands::GET)
181
+ super({:opcode=>Commands::GET}.merge(contents))
182
+ end
183
+
184
+ class Quiet < Get
185
+ def initialize(contents)
186
+ super({:opcode=>Commands::GETQ}.merge(contents))
187
+ end
158
188
  end
159
189
  end
160
190
 
@@ -163,7 +193,13 @@ module Memcached
163
193
  extra :expiration, 'N', 0
164
194
 
165
195
  def initialize(contents)
166
- super(contents.merge :opcode=>Commands::ADD)
196
+ super({:opcode=>Commands::ADD}.merge(contents))
197
+ end
198
+
199
+ class Quiet < Add
200
+ def initialize(contents)
201
+ super({:opcode=>Commands::ADDQ}.merge(contents))
202
+ end
167
203
  end
168
204
  end
169
205
 
@@ -172,19 +208,31 @@ module Memcached
172
208
  extra :expiration, 'N', 0
173
209
 
174
210
  def initialize(contents)
175
- super(contents.merge :opcode=>Commands::SET)
211
+ super({:opcode=>Commands::SET}.merge(contents))
212
+ end
213
+
214
+ class Quiet < Set
215
+ def initialize(contents)
216
+ super({:opcode=>Commands::SETQ}.merge(contents))
217
+ end
176
218
  end
177
219
  end
178
220
 
179
221
  class Delete < Request
180
222
  def initialize(contents)
181
- super(contents.merge :opcode=>Commands::DELETE)
223
+ super({:opcode=>Commands::DELETE}.merge(contents))
224
+ end
225
+
226
+ class Quiet < Delete
227
+ def initialize(contents)
228
+ super({:opcode=>Commands::DELETEQ}.merge(contents))
229
+ end
182
230
  end
183
231
  end
184
232
 
185
233
  class Stats < Request
186
234
  def initialize(contents)
187
- super(contents.merge :opcode=>Commands::STAT)
235
+ super({:opcode=>Commands::STAT}.merge(contents))
188
236
  end
189
237
  end
190
238
  end
@@ -1,12 +1,15 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{remcached}
5
- s.version = "0.2.2"
8
+ s.version = "0.3.0"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Stephan Maka"]
9
- s.date = %q{2009-09-12}
12
+ s.date = %q{2009-09-17}
10
13
  s.description = %q{Ruby EventMachine memcached client}
11
14
  s.email = %q{astro@spaceboyz.net}
12
15
  s.files = [
@@ -26,12 +29,12 @@ Gem::Specification.new do |s|
26
29
  s.homepage = %q{http://github.com/astro/remcached/}
27
30
  s.rdoc_options = ["--charset=UTF-8"]
28
31
  s.require_paths = ["lib"]
29
- s.rubygems_version = %q{1.3.5}
32
+ s.rubygems_version = %q{1.3.4}
30
33
  s.summary = %q{Ruby EventMachine memcached client}
31
34
  s.test_files = [
32
- "spec/client_spec.rb",
33
- "spec/memcached_spec.rb",
34
- "spec/packet_spec.rb"
35
+ "spec/packet_spec.rb",
36
+ "spec/client_spec.rb",
37
+ "spec/memcached_spec.rb"
35
38
  ]
36
39
 
37
40
  if s.respond_to? :specification_version then
@@ -14,122 +14,6 @@ describe Memcached::Client do
14
14
  end
15
15
 
16
16
 
17
- it "should add a value" do
18
- run do
19
- @cl.add(:key => 'Hello',
20
- :value => 'World') do |result|
21
- result.should be_kind_of(Memcached::Response)
22
- result[:status].should == Memcached::Errors::NO_ERROR
23
- result[:cas].should_not == 0
24
- stop
25
- end
26
- end
27
- end
28
-
29
- it "should get a value" do
30
- run do
31
- @cl.get(:key => 'Hello') do |result|
32
- result.should be_kind_of(Memcached::Response)
33
- result[:status].should == Memcached::Errors::NO_ERROR
34
- result[:value].should == 'World'
35
- result[:cas].should_not == 0
36
- @old_cas = result[:cas]
37
- stop
38
- end
39
- end
40
- end
41
-
42
- it "should set a value" do
43
- run do
44
- @cl.set(:key => 'Hello',
45
- :value => 'Planet') do |result|
46
- result.should be_kind_of(Memcached::Response)
47
- result[:status].should == Memcached::Errors::NO_ERROR
48
- result[:cas].should_not == 0
49
- result[:cas].should_not == @old_cas
50
- stop
51
- end
52
- end
53
- end
54
-
55
- it "should get a value" do
56
- run do
57
- @cl.get(:key => 'Hello') do |result|
58
- result.should be_kind_of(Memcached::Response)
59
- result[:status].should == Memcached::Errors::NO_ERROR
60
- result[:value].should == 'Planet'
61
- result[:cas].should_not == @old_cas
62
- stop
63
- end
64
- end
65
- end
66
-
67
- it "should delete a value" do
68
- run do
69
- @cl.delete(:key => 'Hello') do |result|
70
- result.should be_kind_of(Memcached::Response)
71
- result[:status].should == Memcached::Errors::NO_ERROR
72
- stop
73
- end
74
- end
75
- end
76
-
77
- it "should not get a value" do
78
- run do
79
- @cl.get(:key => 'Hello') do |result|
80
- result.should be_kind_of(Memcached::Response)
81
- result[:status].should == Memcached::Errors::KEY_NOT_FOUND
82
- stop
83
- end
84
- end
85
- end
86
-
87
- $n = 100
88
- context "when incrementing a counter #{$n} times" do
89
- it "should initialize the counter" do
90
- run do
91
- @cl.set(:key => 'counter',
92
- :value => '0') do |result|
93
- stop
94
- end
95
- end
96
- end
97
-
98
- it "should count #{$n} times" do
99
- $counted = 0
100
- def count
101
- @cl.get(:key => 'counter') do |result|
102
- result[:status].should == Memcached::Errors::NO_ERROR
103
- value = result[:value].to_i
104
- @cl.set(:key => 'counter',
105
- :value => (value + 1).to_s,
106
- :cas => result[:cas]) do |result|
107
- if result[:status] == Memcached::Errors::KEY_EXISTS
108
- count # again
109
- else
110
- result[:status].should == Memcached::Errors::NO_ERROR
111
- $counted += 1
112
- stop if $counted >= $n
113
- end
114
- end
115
- end
116
- end
117
- run do
118
- $n.times { count }
119
- end
120
- end
121
-
122
- it "should have counted up to #{$n}" do
123
- run do
124
- @cl.get(:key => 'counter') do |result|
125
- result[:status].should == Memcached::Errors::NO_ERROR
126
- result[:value].to_i.should == $n
127
- stop
128
- end
129
- end
130
- end
131
- end
132
-
133
17
  context "when getting stats" do
134
18
  before :all do
135
19
  @stats = {}
@@ -1,15 +1,15 @@
1
1
  $: << File.dirname(__FILE__) + '/../lib'
2
2
  require 'remcached'
3
3
 
4
- describe Memcached::Client do
4
+ describe Memcached do
5
5
  def run(&block)
6
6
  EM.run do
7
7
  Memcached.servers = %w(127.0.0.2 localhost:11212 localhost localhost)
8
8
 
9
- started = false
10
- EM::PeriodicTimer.new(0.1) do
11
- if !started && Memcached.usable?
12
- started = true
9
+ @timer = EM::PeriodicTimer.new(0.01) do
10
+ # at least localhost & localhost
11
+ if Memcached.usable_clients.length >= 2
12
+ @timer.cancel
13
13
  block.call
14
14
  end
15
15
  end
@@ -21,6 +21,125 @@ describe Memcached::Client do
21
21
  EM.stop
22
22
  end
23
23
 
24
+
25
+ context "when doing a simple operation" do
26
+ it "should add a value" do
27
+ run do
28
+ Memcached.add(:key => 'Hello',
29
+ :value => 'World') do |result|
30
+ result.should be_kind_of(Memcached::Response)
31
+ result[:status].should == Memcached::Errors::NO_ERROR
32
+ result[:cas].should_not == 0
33
+ stop
34
+ end
35
+ end
36
+ end
37
+
38
+ it "should get a value" do
39
+ run do
40
+ Memcached.get(:key => 'Hello') do |result|
41
+ result.should be_kind_of(Memcached::Response)
42
+ result[:status].should == Memcached::Errors::NO_ERROR
43
+ result[:value].should == 'World'
44
+ result[:cas].should_not == 0
45
+ @old_cas = result[:cas]
46
+ stop
47
+ end
48
+ end
49
+ end
50
+
51
+ it "should set a value" do
52
+ run do
53
+ Memcached.set(:key => 'Hello',
54
+ :value => 'Planet') do |result|
55
+ result.should be_kind_of(Memcached::Response)
56
+ result[:status].should == Memcached::Errors::NO_ERROR
57
+ result[:cas].should_not == 0
58
+ result[:cas].should_not == @old_cas
59
+ stop
60
+ end
61
+ end
62
+ end
63
+
64
+ it "should get a value" do
65
+ run do
66
+ Memcached.get(:key => 'Hello') do |result|
67
+ result.should be_kind_of(Memcached::Response)
68
+ result[:status].should == Memcached::Errors::NO_ERROR
69
+ result[:value].should == 'Planet'
70
+ result[:cas].should_not == @old_cas
71
+ stop
72
+ end
73
+ end
74
+ end
75
+
76
+ it "should delete a value" do
77
+ run do
78
+ Memcached.delete(:key => 'Hello') do |result|
79
+ result.should be_kind_of(Memcached::Response)
80
+ result[:status].should == Memcached::Errors::NO_ERROR
81
+ stop
82
+ end
83
+ end
84
+ end
85
+
86
+ it "should not get a value" do
87
+ run do
88
+ Memcached.get(:key => 'Hello') do |result|
89
+ result.should be_kind_of(Memcached::Response)
90
+ result[:status].should == Memcached::Errors::KEY_NOT_FOUND
91
+ stop
92
+ end
93
+ end
94
+ end
95
+
96
+ $n = 100
97
+ context "when incrementing a counter #{$n} times" do
98
+ it "should initialize the counter" do
99
+ run do
100
+ Memcached.set(:key => 'counter',
101
+ :value => '0') do |result|
102
+ stop
103
+ end
104
+ end
105
+ end
106
+
107
+ it "should count #{$n} times" do
108
+ @counted = 0
109
+ def count
110
+ Memcached.get(:key => 'counter') do |result|
111
+ result[:status].should == Memcached::Errors::NO_ERROR
112
+ value = result[:value].to_i
113
+ Memcached.set(:key => 'counter',
114
+ :value => (value + 1).to_s,
115
+ :cas => result[:cas]) do |result|
116
+ if result[:status] == Memcached::Errors::KEY_EXISTS
117
+ count # again
118
+ else
119
+ result[:status].should == Memcached::Errors::NO_ERROR
120
+ @counted += 1
121
+ stop if @counted >= $n
122
+ end
123
+ end
124
+ end
125
+ end
126
+ run do
127
+ $n.times { count }
128
+ end
129
+ end
130
+
131
+ it "should have counted up to #{$n}" do
132
+ run do
133
+ Memcached.get(:key => 'counter') do |result|
134
+ result[:status].should == Memcached::Errors::NO_ERROR
135
+ result[:value].to_i.should == $n
136
+ stop
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
24
143
  context "when using multiple servers" do
25
144
  it "should not return the same hash for the succeeding key" do
26
145
  run do
@@ -56,4 +175,81 @@ describe Memcached::Client do
56
175
  end
57
176
  end
58
177
 
178
+ context "when manipulating multiple records at once" do
179
+ before :all do
180
+ @n = 10
181
+ end
182
+
183
+ def key(n)
184
+ "test:item:#{n}"
185
+ end
186
+
187
+ it "should add some items" do
188
+ run do
189
+ items = []
190
+ @n.times { |i|
191
+ items << { :key => key(i),
192
+ :value => 'Foo',
193
+ :expiration => 20 } if i % 2 == 0
194
+ }
195
+ Memcached.multi_add(items) { |responses|
196
+ stop
197
+ @n.times { |i|
198
+ if i % 2 == 0 && (response_i = responses[key(i)])
199
+ response_i[:status].should == Memcached::Errors::NO_ERROR
200
+ end
201
+ }
202
+ }
203
+ end
204
+ end
205
+
206
+ it "should get all items" do
207
+ run do
208
+ items = []
209
+ @n.times { |i|
210
+ items << { :key => key(i) }
211
+ }
212
+ Memcached.multi_get(items) { |responses|
213
+ stop
214
+ @n.times { |i|
215
+ if i % 2 == 0
216
+ responses.should have_key(key(i))
217
+ responses[key(i)][:status].should == Memcached::Errors::NO_ERROR
218
+ responses[key(i)][:value].should == 'Foo'
219
+ else
220
+ # either no response because request was quiet, or not
221
+ # found in case of last response
222
+ if (response_i = responses[key(i)])
223
+ response_i[:status].should == Memcached::Errors::KEY_NOT_FOUND
224
+ end
225
+ end
226
+ }
227
+ }
228
+ end
229
+ end
230
+
231
+ it "should delete all items" do
232
+ run do
233
+ items = []
234
+ @n.times { |i|
235
+ items << { :key => key(i) }
236
+ }
237
+ Memcached.multi_delete(items) { |responses|
238
+ stop
239
+ @n.times { |i|
240
+ if i % 2 == 0
241
+ # either no response because request was quiet, or ok in
242
+ # case of last response
243
+ if (response_i = responses[key(i)])
244
+ response_i[:status].should == Memcached::Errors::NO_ERROR
245
+ end
246
+ else
247
+ responses[key(i)][:status].should == Memcached::Errors::KEY_NOT_FOUND
248
+ end
249
+ }
250
+ }
251
+ end
252
+ end
253
+ end
254
+
59
255
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astro-remcached
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephan Maka
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-12 00:00:00 -07:00
12
+ date: 2009-09-17 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -62,6 +62,6 @@ signing_key:
62
62
  specification_version: 3
63
63
  summary: Ruby EventMachine memcached client
64
64
  test_files:
65
+ - spec/packet_spec.rb
65
66
  - spec/client_spec.rb
66
67
  - spec/memcached_spec.rb
67
- - spec/packet_spec.rb