astro-remcached 0.2.2 → 0.3.0

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.
@@ -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