caffeine 0.0.2
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 +22 -0
- data/Rakefile +66 -0
- data/ext/caffeine.c +906 -0
- data/ext/extconf.rb +24 -0
- data/lib/caffeine_fragment_store.rb +29 -0
- data/lib/rails_helpers.rb +67 -0
- data/test/test_caffeine.rb +80 -0
- metadata +58 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2006 Fredrik Roos, Adocca AB
|
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 NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rake/gempackagetask'
|
8
|
+
rescue Exception
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
NAME = 'caffeine'
|
13
|
+
VERS = '0.0.2'
|
14
|
+
|
15
|
+
FILES_COMMON = FileList['Rakefile', 'lib/**/*.rb']
|
16
|
+
FILES_EXT = FileList['ext/*.c']
|
17
|
+
CLEAN.include ['ext/tags', 'ext/*.so', 'ext/*.o', 'ext/Makefile', 'ext/mkmf.log']
|
18
|
+
LICENSE = FileList['LICENSE']
|
19
|
+
|
20
|
+
desc "Does a full compile, test run"
|
21
|
+
task :default => [:test]
|
22
|
+
|
23
|
+
desc "Packages up Caffeine"
|
24
|
+
task :package => [:clean]
|
25
|
+
|
26
|
+
desc "Run all the tests"
|
27
|
+
Rake::TestTask.new do |t|
|
28
|
+
t.libs << "test"
|
29
|
+
t.test_files = FileList['test/test_*.rb']
|
30
|
+
t.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
if defined? Gem
|
34
|
+
spec = Gem::Specification.new do |s|
|
35
|
+
s.name = NAME
|
36
|
+
s.version = VERS
|
37
|
+
s.summary = 'A Ruby binding for libmemcache + extra functionality (namespaces)'
|
38
|
+
s.requirements << 'libmemcache'
|
39
|
+
s.requirements << 'libuuid'
|
40
|
+
s.files = FILES_COMMON + FILES_EXT + LICENSE
|
41
|
+
s.extensions = ['ext/extconf.rb']
|
42
|
+
s.test_files = FileList['test/test_*.rb']
|
43
|
+
#s.has_rdoc = true
|
44
|
+
#s.extra_rdoc_files = FileList['README', 'CHANGES', 'TODO']
|
45
|
+
#s.rdoc_options = RDOC_OPTS
|
46
|
+
s.author = 'Fredrik Roos'
|
47
|
+
s.email = 'fredrik.roos@adocca.com'
|
48
|
+
s.homepage = 'http://adocca-plugins.rubyforge.org'
|
49
|
+
s.rubyforge_project = "adocca-plugins"
|
50
|
+
end
|
51
|
+
|
52
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
53
|
+
pkg.need_tar_gz = true
|
54
|
+
pkg.need_zip = true
|
55
|
+
end
|
56
|
+
|
57
|
+
end # defined? Gem
|
58
|
+
|
59
|
+
task :install do
|
60
|
+
sh %{rake package}
|
61
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
62
|
+
end
|
63
|
+
|
64
|
+
task :uninstall => [:clean] do
|
65
|
+
sh %{sudo gem uninstall #{NAME}}
|
66
|
+
end
|
data/ext/caffeine.c
ADDED
@@ -0,0 +1,906 @@
|
|
1
|
+
#define _GNU_SOURCE
|
2
|
+
#ifdef f
|
3
|
+
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <stdarg.h>
|
6
|
+
#include <string.h>
|
7
|
+
|
8
|
+
#include <uuid/uuid.h>
|
9
|
+
#include <openssl/md5.h>
|
10
|
+
#include <memcache.h>
|
11
|
+
#include <ruby.h>
|
12
|
+
|
13
|
+
// Datatypes
|
14
|
+
//
|
15
|
+
|
16
|
+
// Caffeine namespace
|
17
|
+
VALUE mCaffeine = Qnil;
|
18
|
+
|
19
|
+
// The MemCache class
|
20
|
+
VALUE cMemCache = Qnil;
|
21
|
+
|
22
|
+
// the not found symbol :MemCache_no_such_entry
|
23
|
+
VALUE sMemCache_NotFound = Qnil;
|
24
|
+
|
25
|
+
// Exception class
|
26
|
+
VALUE eMemCacheError = Qnil;
|
27
|
+
VALUE eMemCacheNotFound = Qnil;
|
28
|
+
|
29
|
+
// debug flag
|
30
|
+
|
31
|
+
int debug_output;
|
32
|
+
|
33
|
+
#define DBG(args...) if (debug_output) fprintf(stderr, args);
|
34
|
+
|
35
|
+
// Ruby methods
|
36
|
+
VALUE MemCache_alloc(VALUE class);
|
37
|
+
VALUE MemCache_initialize(VALUE self, VALUE opts);
|
38
|
+
VALUE MemCache_set_servers(VALUE self, VALUE servers);
|
39
|
+
VALUE MemCache_get(int argc, VALUE *args, VALUE self);
|
40
|
+
VALUE MemCache_set(int argc, VALUE *argv, VALUE self);
|
41
|
+
VALUE MemCache_add(int argc, VALUE *argv, VALUE self);
|
42
|
+
VALUE MemCache_replace(int argc, VALUE *argv, VALUE self);
|
43
|
+
VALUE MemCache_synchronize(int argc, VALUE *argv, VALUE self);
|
44
|
+
VALUE MemCache_lock(int argc, VALUE *argv, VALUE self);
|
45
|
+
VALUE MemCache_flush(VALUE self);
|
46
|
+
VALUE MemCache_disconnect(VALUE self);
|
47
|
+
VALUE MemCache_check(VALUE self, VALUE key);
|
48
|
+
VALUE MemCache_delete(int argc, VALUE *argv, VALUE self);
|
49
|
+
VALUE MemCache_inc(int argc, VALUE *argv, VALUE self);
|
50
|
+
VALUE MemCache_dec(int argc, VALUE *argv, VALUE self);
|
51
|
+
VALUE MemCache_invalidate_namespace(VALUE self, VALUE namespace);
|
52
|
+
|
53
|
+
// Internal datatypes
|
54
|
+
|
55
|
+
// The MemCache class
|
56
|
+
// Holds a memcache context and the namespace
|
57
|
+
// and an instance id for the locking
|
58
|
+
typedef struct _memcache
|
59
|
+
{
|
60
|
+
struct memcache *mc;
|
61
|
+
char *namespace;
|
62
|
+
char instance_id[64];
|
63
|
+
} Memcache;
|
64
|
+
|
65
|
+
// A chunk of memory for the memory pool
|
66
|
+
typedef struct _memchunk
|
67
|
+
{
|
68
|
+
void *mem;
|
69
|
+
ssize_t available;
|
70
|
+
struct _memchunk *next;
|
71
|
+
} Memchunk;
|
72
|
+
|
73
|
+
typedef struct _memcache_request
|
74
|
+
{
|
75
|
+
char *key;
|
76
|
+
ssize_t keylen;
|
77
|
+
int num_chunks;
|
78
|
+
Memcache *mc; // put in here to avoid passing it around
|
79
|
+
Memchunk * first_chunk;
|
80
|
+
Memchunk * current_chunk;
|
81
|
+
} MemcacheRequest;
|
82
|
+
|
83
|
+
// Holds a pair of strings
|
84
|
+
// Used for the namespace stuff
|
85
|
+
typedef struct _key_pair
|
86
|
+
{
|
87
|
+
char *first;
|
88
|
+
char *last;
|
89
|
+
} KeyPair;
|
90
|
+
|
91
|
+
// Represents a namespace
|
92
|
+
// (An array of strings, really)
|
93
|
+
typedef struct _namespace
|
94
|
+
{
|
95
|
+
ssize_t len;
|
96
|
+
ssize_t head;
|
97
|
+
char **segments;
|
98
|
+
} Namespace;
|
99
|
+
|
100
|
+
// Various internal functions
|
101
|
+
|
102
|
+
// MemcacheRequest instantiation
|
103
|
+
static MemcacheRequest * memcache_request_for_key(VALUE self, VALUE key);
|
104
|
+
static MemcacheRequest * memcache_request_create(VALUE self);
|
105
|
+
|
106
|
+
// Mempool implementation
|
107
|
+
static Memchunk * alloc_chunk(size_t size);
|
108
|
+
static void memcache_request_free(MemcacheRequest *);
|
109
|
+
|
110
|
+
// Key generation and namespaces
|
111
|
+
static char * key_for_value(MemcacheRequest *mr, VALUE key);
|
112
|
+
static char * key_from_namespace(MemcacheRequest *mr, Namespace *ns);
|
113
|
+
static char * namespace_pop_item(Namespace *ns);
|
114
|
+
static char * namespace_shift_item(Namespace *ns);
|
115
|
+
|
116
|
+
// Utility functions
|
117
|
+
static char * key_from_segments(MemcacheRequest *mr, ...);
|
118
|
+
static Namespace * namespace_from_array(MemcacheRequest *mr, VALUE ary);
|
119
|
+
static KeyPair * key_pair_from_namespace(MemcacheRequest *mr, Namespace * namespace, char * appendage);
|
120
|
+
static char * md5_digest(MemcacheRequest *mp, const char *input);
|
121
|
+
static void get_lock_options(VALUE options, int *expiry, int *timeout);
|
122
|
+
|
123
|
+
// Memcache utility functions
|
124
|
+
static int store_cmd(int cmd, int argc, VALUE *argv, VALUE self);
|
125
|
+
static int str_add(struct memcache *mc, const char *key, char *value, int expiry);
|
126
|
+
static int str_cmp(struct memcache *mc, const char *key, char *cmpvalue);
|
127
|
+
static int do_request(struct memcache *mc, const char *key, void *val, ssize_t size);
|
128
|
+
static int lock(Memcache *mc, const char *key, int expiry, int timeout);
|
129
|
+
|
130
|
+
// Demarshalling
|
131
|
+
static VALUE load_result(void *result, size_t result_len);
|
132
|
+
|
133
|
+
// Debug stuff
|
134
|
+
static void verify_string(char * in);
|
135
|
+
static int validate_key(char *str);
|
136
|
+
static void nospace(char *str);
|
137
|
+
static int isnum(char *str, size_t len);
|
138
|
+
|
139
|
+
// Initializion function
|
140
|
+
// Define the class and its methods
|
141
|
+
void Init_caffeine()
|
142
|
+
{
|
143
|
+
// Init namespace and classes
|
144
|
+
mCaffeine = rb_define_module("Caffeine");
|
145
|
+
|
146
|
+
cMemCache = rb_define_class_under(mCaffeine, "MemCache", rb_cObject);
|
147
|
+
eMemCacheError = rb_define_class_under(mCaffeine, "MemCacheError", rb_eRuntimeError);
|
148
|
+
eMemCacheNotFound = rb_define_class_under(mCaffeine, "MemCacheNotFound", rb_eRuntimeError);
|
149
|
+
sMemCache_NotFound = ID2SYM(rb_intern("MemCache_no_such_entry"));
|
150
|
+
|
151
|
+
rb_define_alloc_func(cMemCache, MemCache_alloc);
|
152
|
+
rb_define_method(cMemCache, "initialize", MemCache_initialize, 1);
|
153
|
+
// and so on
|
154
|
+
rb_define_method(cMemCache, "get", MemCache_get, -1);
|
155
|
+
rb_define_alias(cMemCache, "[]", "get");
|
156
|
+
rb_define_method(cMemCache, "set", MemCache_set, -1);
|
157
|
+
rb_define_alias(cMemCache, "[]=", "set");
|
158
|
+
rb_define_method(cMemCache, "add", MemCache_set, -1);
|
159
|
+
rb_define_method(cMemCache, "replace", MemCache_set, -1);
|
160
|
+
rb_define_method(cMemCache, "servers=", MemCache_set_servers, 1);
|
161
|
+
rb_define_method(cMemCache, "disconnect", MemCache_disconnect, 0);
|
162
|
+
rb_define_method(cMemCache, "invalidate_namespace", MemCache_invalidate_namespace, 1);
|
163
|
+
rb_define_method(cMemCache, "inc", MemCache_inc, -1);
|
164
|
+
rb_define_method(cMemCache, "dec", MemCache_dec, -1);
|
165
|
+
rb_define_method(cMemCache, "synchronize", MemCache_synchronize, -1);
|
166
|
+
rb_define_method(cMemCache, "lock", MemCache_lock, -1);
|
167
|
+
rb_define_method(cMemCache, "unlock", MemCache_delete, -1);
|
168
|
+
rb_define_method(cMemCache, "flush", MemCache_flush, 0);
|
169
|
+
rb_define_method(cMemCache, "check", MemCache_check, 1);
|
170
|
+
rb_define_method(cMemCache, "delete", MemCache_delete, -1);
|
171
|
+
|
172
|
+
debug_output = getenv("CAFFEINE_DEBUG") ? 1 : 0;
|
173
|
+
}
|
174
|
+
|
175
|
+
// The Ruby GC wants a function to deallocate C-defined objects
|
176
|
+
// Since rails keeps a reference around to its cache objects during
|
177
|
+
// its entire lifetime this will never be called in a Rails setting,
|
178
|
+
// it's here for completeness.
|
179
|
+
void memcache_free(void *mc)
|
180
|
+
{
|
181
|
+
DBG("Disconnecting from memcache\n");
|
182
|
+
mc_server_disconnect_all(((Memcache *)mc)->mc);
|
183
|
+
free(((Memcache *)mc)->namespace);
|
184
|
+
mc_free(((Memcache *)mc)->mc);
|
185
|
+
free(mc);
|
186
|
+
}
|
187
|
+
|
188
|
+
// This is called by the default Object#new to allocate space for the
|
189
|
+
// object
|
190
|
+
VALUE MemCache_alloc(VALUE class)
|
191
|
+
{
|
192
|
+
Memcache * mc;
|
193
|
+
return Data_Make_Struct(cMemCache, Memcache, 0, memcache_free, mc);
|
194
|
+
}
|
195
|
+
|
196
|
+
// Constructor
|
197
|
+
VALUE MemCache_initialize(VALUE self, VALUE opts)
|
198
|
+
{
|
199
|
+
Memcache * mc;
|
200
|
+
uuid_t uuid;
|
201
|
+
VALUE namespace;
|
202
|
+
// Convert the self pointer (ruby VALUE) to a C struct
|
203
|
+
Data_Get_Struct(self, Memcache, mc);
|
204
|
+
|
205
|
+
// Initialize the libmemcache context
|
206
|
+
mc->mc = mc_new();
|
207
|
+
|
208
|
+
// this is unsupported, but the only way i found to turn of verbose logging
|
209
|
+
// IOW, libmemcache sucks donkey balls
|
210
|
+
mc_global_ctxt()->MCM_ERR_MASK |= (MC_ERR_LVL_INFO | MC_ERR_LVL_NOTICE);
|
211
|
+
|
212
|
+
// Right now we don't support any options beyond :namespace
|
213
|
+
// It's required so we are using a default if none is provided
|
214
|
+
if ((namespace = rb_hash_aref(opts, ID2SYM(rb_intern("namespace")))) != Qnil)
|
215
|
+
mc->namespace = strdup(StringValuePtr(namespace));
|
216
|
+
else
|
217
|
+
mc->namespace = strdup("caffeine_default_ns");
|
218
|
+
|
219
|
+
// Generate the instance id
|
220
|
+
uuid_generate(uuid);
|
221
|
+
uuid_unparse(uuid, mc->instance_id);
|
222
|
+
|
223
|
+
return Qnil;
|
224
|
+
}
|
225
|
+
|
226
|
+
// Accessor to the MemCache#servers= method.
|
227
|
+
// So far this is a write-only attribute
|
228
|
+
// Note that assigning a new array will not remove the
|
229
|
+
// previously defined servers.
|
230
|
+
VALUE MemCache_set_servers(VALUE self, VALUE servers)
|
231
|
+
{
|
232
|
+
int i;
|
233
|
+
Memcache *mc;
|
234
|
+
Data_Get_Struct(self, Memcache, mc);
|
235
|
+
|
236
|
+
Check_Type(servers, T_ARRAY);
|
237
|
+
|
238
|
+
for (i = 0; i < RARRAY(servers)->len; i++)
|
239
|
+
mc_server_add4(mc->mc, STR2CSTR(rb_ary_entry(servers, i)));
|
240
|
+
|
241
|
+
return Qnil;
|
242
|
+
}
|
243
|
+
|
244
|
+
// Explicit disconnect
|
245
|
+
VALUE MemCache_disconnect(VALUE self)
|
246
|
+
{
|
247
|
+
Memcache *mc;
|
248
|
+
Data_Get_Struct(self, Memcache, mc);
|
249
|
+
DBG("Disconnecting from memcache\n");
|
250
|
+
mc_server_disconnect_all(mc->mc);
|
251
|
+
return Qnil;
|
252
|
+
}
|
253
|
+
|
254
|
+
// The MemCache#get method. The parameter is either an
|
255
|
+
// Array, in which case we do a namespace lookup, or
|
256
|
+
// a string for normal memcached lookup
|
257
|
+
// Symbols are converted to string
|
258
|
+
VALUE MemCache_get(int argc, VALUE *args, VALUE self)
|
259
|
+
{
|
260
|
+
// This allocates the necessary data structures for a request
|
261
|
+
// and performs all the namespace magic. All methods that take
|
262
|
+
// a key does this.
|
263
|
+
MemcacheRequest * mr;
|
264
|
+
struct memcache_req *req = mc_req_new();
|
265
|
+
struct memcache_res *res[argc];
|
266
|
+
VALUE return_obj;
|
267
|
+
int tmp;
|
268
|
+
|
269
|
+
if (argc < 1)
|
270
|
+
rb_raise(rb_eRuntimeError, "Caffeine::MemCache#get(*args) takes at least one argument");
|
271
|
+
else if (argc > 1)
|
272
|
+
return_obj = rb_ary_new();
|
273
|
+
|
274
|
+
for (tmp = 0; tmp < argc; tmp++) {
|
275
|
+
mr = memcache_request_for_key(self, args[tmp]);
|
276
|
+
res[tmp] = mc_req_add(req, mr->key, strlen(mr->key));
|
277
|
+
memcache_request_free(mr);
|
278
|
+
DBG("%d: GET %s\n", tmp, mr->key);
|
279
|
+
}
|
280
|
+
|
281
|
+
mc_get(mr->mc->mc, req);
|
282
|
+
|
283
|
+
for (tmp = 0; tmp < argc; tmp++) {
|
284
|
+
if (res[tmp]->val) {
|
285
|
+
DBG("%d: Got %d bytes\n", tmp, res[tmp]->size);
|
286
|
+
if (argc == 1)
|
287
|
+
return_obj = load_result(res[tmp]->val, res[tmp]->size);
|
288
|
+
else
|
289
|
+
rb_ary_push(return_obj, load_result(res[tmp]->val, res[tmp]->size));
|
290
|
+
} else {
|
291
|
+
DBG("%d: No value for key\n", tmp);
|
292
|
+
if (argc == 1)
|
293
|
+
return_obj = sMemCache_NotFound;
|
294
|
+
else
|
295
|
+
rb_ary_push(return_obj, sMemCache_NotFound);
|
296
|
+
}
|
297
|
+
mc_res_free(req, res[tmp]);
|
298
|
+
}
|
299
|
+
|
300
|
+
mc_req_free(req);
|
301
|
+
|
302
|
+
return return_obj;
|
303
|
+
}
|
304
|
+
|
305
|
+
// Storage methods
|
306
|
+
|
307
|
+
#define MC_CMD_SET 0
|
308
|
+
#define MC_CMD_ADD 1
|
309
|
+
#define MC_CMD_REPLACE 2
|
310
|
+
|
311
|
+
const char * store_commands[] = {"SET", "ADD", "REPLACE"};
|
312
|
+
|
313
|
+
// All the storage methods wrap the store_cmd function,
|
314
|
+
// passing the storage methods
|
315
|
+
|
316
|
+
VALUE MemCache_set(int argc, VALUE *argv, VALUE self)
|
317
|
+
{
|
318
|
+
store_cmd(MC_CMD_SET, argc, argv, self);
|
319
|
+
return Qnil;
|
320
|
+
}
|
321
|
+
|
322
|
+
VALUE MemCache_add(int argc, VALUE *argv, VALUE self)
|
323
|
+
{
|
324
|
+
store_cmd(MC_CMD_ADD, argc, argv, self);
|
325
|
+
return Qnil;
|
326
|
+
}
|
327
|
+
|
328
|
+
VALUE MemCache_replace(int argc, VALUE *argv, VALUE self)
|
329
|
+
{
|
330
|
+
store_cmd(MC_CMD_REPLACE, argc, argv, self);
|
331
|
+
return Qnil;
|
332
|
+
}
|
333
|
+
|
334
|
+
// Used by all storage methods
|
335
|
+
static int store_cmd(int cmd, int argc, VALUE *argv, VALUE self)
|
336
|
+
{
|
337
|
+
MemcacheRequest * mr;
|
338
|
+
VALUE key, value, expiry, marshaled;
|
339
|
+
char number_as_string[64];
|
340
|
+
void * data;
|
341
|
+
size_t data_len;
|
342
|
+
int retval;
|
343
|
+
rb_scan_args(argc, argv, "21", &key, &value, &expiry);
|
344
|
+
mr = memcache_request_for_key(self, key);
|
345
|
+
|
346
|
+
DBG("%s %s\n", store_commands[cmd], mr->key);
|
347
|
+
|
348
|
+
// Numbers are stored in raw form (for some reason), so there's
|
349
|
+
// no marshalling
|
350
|
+
if (TYPE(value) == T_FIXNUM)
|
351
|
+
{
|
352
|
+
sprintf(number_as_string, "%d", FIX2INT(value));
|
353
|
+
data = number_as_string;
|
354
|
+
data_len = strlen(data);
|
355
|
+
} else {
|
356
|
+
marshaled = rb_marshal_dump(value, Qnil);
|
357
|
+
data = RSTRING(marshaled)->ptr;
|
358
|
+
data_len = RSTRING(marshaled)->len;
|
359
|
+
}
|
360
|
+
|
361
|
+
switch(cmd)
|
362
|
+
{
|
363
|
+
case MC_CMD_SET:
|
364
|
+
retval = mc_set(mr->mc->mc, mr->key, mr->keylen, data, data_len, NIL_P(expiry) ? 0 : FIX2INT(expiry), 0);
|
365
|
+
break;
|
366
|
+
case MC_CMD_ADD:
|
367
|
+
retval = mc_add(mr->mc->mc, mr->key, mr->keylen, data, data_len, NIL_P(expiry) ? 0 : FIX2INT(expiry), 0);
|
368
|
+
break;
|
369
|
+
case MC_CMD_REPLACE:
|
370
|
+
retval = mc_replace(mr->mc->mc, mr->key, mr->keylen, data, data_len, NIL_P(expiry) ? 0 : FIX2INT(expiry), 0);
|
371
|
+
break;
|
372
|
+
}
|
373
|
+
|
374
|
+
memcache_request_free(mr);
|
375
|
+
return retval;
|
376
|
+
}
|
377
|
+
|
378
|
+
|
379
|
+
// MemCache#invalidate_namespace
|
380
|
+
VALUE MemCache_invalidate_namespace(VALUE self, VALUE namespace)
|
381
|
+
{
|
382
|
+
MemcacheRequest * mr = memcache_request_create(self);
|
383
|
+
char *key;
|
384
|
+
|
385
|
+
// The rest of the code assumes that namespace is an array
|
386
|
+
// If namespace is a string, create a one element array,
|
387
|
+
// and put the string in there
|
388
|
+
if (TYPE(namespace) == T_STRING)
|
389
|
+
{
|
390
|
+
VALUE ary = rb_ary_new2(1);
|
391
|
+
rb_ary_push(ary, namespace);
|
392
|
+
namespace = ary;
|
393
|
+
}
|
394
|
+
|
395
|
+
key = key_from_segments(mr, key_pair_from_namespace(mr, namespace_from_array(mr, namespace), NULL)->last, NULL);
|
396
|
+
|
397
|
+
DBG("Invalidating namespace key: %s\n", key);
|
398
|
+
|
399
|
+
mc_incr(mr->mc->mc, key, strlen(key), 1);
|
400
|
+
// XXX check retval
|
401
|
+
memcache_request_free(mr);
|
402
|
+
return Qnil;
|
403
|
+
}
|
404
|
+
|
405
|
+
// MemCache#inc
|
406
|
+
VALUE MemCache_inc(int argc, VALUE *argv, VALUE self)
|
407
|
+
{
|
408
|
+
MemcacheRequest * mr;
|
409
|
+
VALUE key, amount;
|
410
|
+
int ret;
|
411
|
+
rb_scan_args(argc, argv, "11", &key, &amount);
|
412
|
+
mr = memcache_request_for_key(self, key);
|
413
|
+
ret = rb_fix_new(mc_incr(mr->mc->mc, mr->key, mr->keylen, NIL_P(amount) ? 1 : FIX2INT(amount)));
|
414
|
+
memcache_request_free(mr);
|
415
|
+
return ret;
|
416
|
+
}
|
417
|
+
|
418
|
+
// MemCache#dec
|
419
|
+
VALUE MemCache_dec(int argc, VALUE *argv, VALUE self)
|
420
|
+
{
|
421
|
+
MemcacheRequest * mr;
|
422
|
+
VALUE key, amount;
|
423
|
+
int ret;
|
424
|
+
rb_scan_args(argc, argv, "11", &key, &amount);
|
425
|
+
mr = memcache_request_for_key(self, key);
|
426
|
+
ret = rb_fix_new(mc_decr(mr->mc->mc, mr->key, mr->keylen, NIL_P(amount) ? 1 : FIX2INT(amount)));
|
427
|
+
memcache_request_free(mr);
|
428
|
+
return ret;
|
429
|
+
}
|
430
|
+
|
431
|
+
#define TRANS_MAX_TRIES 5
|
432
|
+
|
433
|
+
// Does some magic with a key unique per thread and host
|
434
|
+
// to implement atomic operations
|
435
|
+
VALUE MemCache_lock(int argc, VALUE *argv, VALUE self)
|
436
|
+
{
|
437
|
+
MemcacheRequest * mr;
|
438
|
+
VALUE retval = Qnil;
|
439
|
+
VALUE key, options;
|
440
|
+
int expiry, timeout;
|
441
|
+
|
442
|
+
rb_scan_args(argc, argv, "11", &key, &options);
|
443
|
+
mr = memcache_request_for_key(self, key);
|
444
|
+
get_lock_options(options, &expiry, &timeout);
|
445
|
+
|
446
|
+
retval = lock(mr->mc, mr->key, expiry, timeout) ? Qtrue : Qfalse;
|
447
|
+
|
448
|
+
memcache_request_free(mr);
|
449
|
+
return retval;
|
450
|
+
}
|
451
|
+
|
452
|
+
static void get_lock_options(VALUE options, int *expiry, int *timeout)
|
453
|
+
{
|
454
|
+
if (NIL_P(options))
|
455
|
+
{
|
456
|
+
*expiry = 0;
|
457
|
+
*timeout = 0;
|
458
|
+
} else {
|
459
|
+
VALUE r_expiry = rb_hash_aref(options, ID2SYM(rb_intern("expiry")));
|
460
|
+
VALUE r_timeout = rb_hash_aref(options, ID2SYM(rb_intern("timeout")));
|
461
|
+
|
462
|
+
*expiry = NIL_P(r_expiry) ? 0 : FIX2INT(r_expiry);
|
463
|
+
*timeout = NIL_P(r_timeout) ? 0 : FIX2INT(r_timeout);
|
464
|
+
}
|
465
|
+
}
|
466
|
+
|
467
|
+
static int lock(Memcache *mc, const char *key, int expiry, int timeout)
|
468
|
+
{
|
469
|
+
int tries = 0, locked_now = 0, locked_earlier = 0;
|
470
|
+
time_t start;
|
471
|
+
|
472
|
+
start = time(NULL);
|
473
|
+
|
474
|
+
// as long as add fails (key exists - from another host/thread)
|
475
|
+
// and the existing key differs from ours keep trying TRANS_MAX_TRIES times
|
476
|
+
|
477
|
+
//fprintf(stderr, "Attempting lock - expiry: %d - timeout: %d\n", expiry, timeout);
|
478
|
+
|
479
|
+
while (!(locked_now = !str_add(mc->mc, key, mc->instance_id, expiry)) &&
|
480
|
+
!(locked_earlier = !str_cmp(mc->mc, key, mc->instance_id)) &&
|
481
|
+
(timeout == 0 || (time(NULL) - start) < timeout))
|
482
|
+
{
|
483
|
+
//fprintf(stderr, "Retrying lock - time: %d - timeout: %d\n", (time(NULL) - start), timeout);
|
484
|
+
sleep(1);
|
485
|
+
}
|
486
|
+
|
487
|
+
return locked_now || locked_earlier;
|
488
|
+
}
|
489
|
+
|
490
|
+
VALUE MemCache_synchronize(int argc, VALUE *argv, VALUE self)
|
491
|
+
{
|
492
|
+
MemcacheRequest * mr;
|
493
|
+
VALUE retval = Qnil;
|
494
|
+
VALUE key, options;
|
495
|
+
int expiry, timeout;
|
496
|
+
|
497
|
+
rb_scan_args(argc, argv, "11", &key, &options);
|
498
|
+
mr = memcache_request_for_key(self, key);
|
499
|
+
get_lock_options(options, &expiry, &timeout);
|
500
|
+
|
501
|
+
if (lock(mr->mc, mr->key, expiry, timeout))
|
502
|
+
{
|
503
|
+
retval = rb_yield(Qnil);
|
504
|
+
mc_delete(mr->mc->mc, mr->key, mr->keylen, 0);
|
505
|
+
} else {
|
506
|
+
rb_raise(eMemCacheError, "Could not obtain lock");
|
507
|
+
}
|
508
|
+
return retval;
|
509
|
+
}
|
510
|
+
|
511
|
+
// MemCache#flush
|
512
|
+
VALUE MemCache_flush(VALUE self)
|
513
|
+
{
|
514
|
+
MemcacheRequest *mr = memcache_request_create(self);
|
515
|
+
DBG("Flushing memcache\n");
|
516
|
+
mc_flush_all(mr->mc->mc); // deal with error
|
517
|
+
memcache_request_free(mr);
|
518
|
+
return Qnil;
|
519
|
+
}
|
520
|
+
|
521
|
+
// MemCache#check
|
522
|
+
// Check if a value exists on the server
|
523
|
+
VALUE MemCache_check(VALUE self, VALUE key)
|
524
|
+
{
|
525
|
+
MemcacheRequest * mr = memcache_request_for_key(self, key);
|
526
|
+
void *value;
|
527
|
+
VALUE retval;
|
528
|
+
|
529
|
+
if ((value = mc_aget(mr->mc->mc, mr->key, mr->keylen)))
|
530
|
+
retval = Qtrue;
|
531
|
+
else
|
532
|
+
retval = Qfalse;
|
533
|
+
|
534
|
+
free(value);
|
535
|
+
memcache_request_free(mr);
|
536
|
+
return retval;
|
537
|
+
}
|
538
|
+
|
539
|
+
// MemCache#delete
|
540
|
+
// Delete an object on server (yes, really)
|
541
|
+
VALUE MemCache_delete(int argc, VALUE *argv, VALUE self)
|
542
|
+
{
|
543
|
+
MemcacheRequest * mr;
|
544
|
+
VALUE key, hold;
|
545
|
+
rb_scan_args(argc, argv, "11", &key, &hold);
|
546
|
+
mr = memcache_request_for_key(self, key);
|
547
|
+
mc_delete(mr->mc->mc, mr->key, mr->keylen, NIL_P(hold) ? 0 : FIX2INT(hold));
|
548
|
+
|
549
|
+
memcache_request_free(mr);
|
550
|
+
return Qnil;
|
551
|
+
}
|
552
|
+
|
553
|
+
|
554
|
+
// ruby+C stuff
|
555
|
+
#define CHUNK_SIZE 8192 * 32
|
556
|
+
|
557
|
+
|
558
|
+
// Takes a char* and returns a ruby object
|
559
|
+
// Handles the case where the string is only numbers
|
560
|
+
// without demarshalling
|
561
|
+
static VALUE load_result(void *result, size_t result_len)
|
562
|
+
{
|
563
|
+
char * result_string = (char *)result;
|
564
|
+
|
565
|
+
if (isnum(result_string, result_len))
|
566
|
+
return rb_fix_new(atoi(result_string));
|
567
|
+
else
|
568
|
+
return rb_marshal_load(rb_str_new(result, result_len));
|
569
|
+
}
|
570
|
+
|
571
|
+
|
572
|
+
// Allocates the data structure used during a request.
|
573
|
+
// Also extracts the C struct from the self value
|
574
|
+
static MemcacheRequest * memcache_request_create(VALUE self)
|
575
|
+
{
|
576
|
+
MemcacheRequest * mr = ALLOC(MemcacheRequest);
|
577
|
+
Data_Get_Struct(self, Memcache, mr->mc);
|
578
|
+
|
579
|
+
mr->first_chunk = mr->current_chunk = alloc_chunk(0);
|
580
|
+
mr->key = NULL;
|
581
|
+
mr->keylen = 0;
|
582
|
+
mr->num_chunks = 1;
|
583
|
+
|
584
|
+
//printf("%d bytes allocated\n", CHUNK_SIZE);
|
585
|
+
|
586
|
+
return mr;
|
587
|
+
}
|
588
|
+
|
589
|
+
// Create a MemCache request and the generated key
|
590
|
+
static MemcacheRequest * memcache_request_for_key(VALUE self, VALUE key)
|
591
|
+
{
|
592
|
+
MemcacheRequest * mr = memcache_request_create(self);
|
593
|
+
mr->key = key_for_value(mr, key);
|
594
|
+
mr->keylen = strlen(mr->key);
|
595
|
+
return mr;
|
596
|
+
}
|
597
|
+
|
598
|
+
|
599
|
+
// Decides if we want to have a normal memcached key or need
|
600
|
+
// to do namespace key generation
|
601
|
+
static char * key_for_value(MemcacheRequest *mr, VALUE key)
|
602
|
+
{
|
603
|
+
if (TYPE(key) == T_ARRAY && RARRAY(key)->len == 1)
|
604
|
+
key = rb_ary_entry(key, 0);
|
605
|
+
|
606
|
+
switch (TYPE(key))
|
607
|
+
{
|
608
|
+
case T_SYMBOL:
|
609
|
+
key = rb_str_new2(rb_id2name(SYM2ID(key)));
|
610
|
+
case T_STRING:
|
611
|
+
// Just concatenate the base namespace with the key
|
612
|
+
return key_from_segments(mr, mr->mc->namespace, md5_digest(mr, StringValuePtr(key)), NULL);
|
613
|
+
case T_ARRAY:
|
614
|
+
// Arrays are namespace keys proceed with processing
|
615
|
+
return key_from_namespace(mr, namespace_from_array(mr, key));
|
616
|
+
default:
|
617
|
+
rb_raise(rb_eTypeError, "Key must be String, Symbol or Array");
|
618
|
+
return NULL;
|
619
|
+
}
|
620
|
+
}
|
621
|
+
|
622
|
+
|
623
|
+
// Chunked memory allocation implementation
|
624
|
+
// malloc() style function, allocate an arbitrary amount of memory
|
625
|
+
static void * memcache_request_alloc(MemcacheRequest *mr, ssize_t size)
|
626
|
+
{
|
627
|
+
// Allocate a new chunk if we are out of space in the current one
|
628
|
+
if (size > mr->current_chunk->available)
|
629
|
+
mr->current_chunk = mr->current_chunk->next = alloc_chunk(size);
|
630
|
+
|
631
|
+
mr->current_chunk->available -= size;
|
632
|
+
return mr->current_chunk->mem + CHUNK_SIZE - mr->current_chunk->available - size;
|
633
|
+
}
|
634
|
+
|
635
|
+
// Allocates one chunk of at least CHUNK_SIZE bytes
|
636
|
+
static Memchunk * alloc_chunk(size_t size)
|
637
|
+
{
|
638
|
+
Memchunk * mc = ALLOC(Memchunk);
|
639
|
+
size = size > CHUNK_SIZE ? size : CHUNK_SIZE;
|
640
|
+
mc->available = size;
|
641
|
+
mc->mem = ALLOC_N(char, size);
|
642
|
+
mc->next = NULL;
|
643
|
+
return mc;
|
644
|
+
}
|
645
|
+
|
646
|
+
// Takes a ruby array and creates a Namespace struct (sort of a C array wrapper)
|
647
|
+
static Namespace * namespace_from_array(MemcacheRequest *mr, VALUE ary)
|
648
|
+
{
|
649
|
+
Namespace * ns = memcache_request_alloc(mr, sizeof(Namespace));
|
650
|
+
int i;
|
651
|
+
|
652
|
+
ns->len = RARRAY(ary)->len;
|
653
|
+
ns->segments = memcache_request_alloc(mr, sizeof(char *) * ns->len);
|
654
|
+
ns->head = 0;
|
655
|
+
|
656
|
+
for (i = 0; i < ns->len; i++)
|
657
|
+
{
|
658
|
+
VALUE rb_item = rb_ary_entry(ary, i);
|
659
|
+
char * item = md5_digest(mr, StringValueCStr(rb_item));
|
660
|
+
ns->segments[i] = memcache_request_alloc(mr, strlen(item) + 1);
|
661
|
+
strcpy(ns->segments[i], item);
|
662
|
+
}
|
663
|
+
return ns;
|
664
|
+
}
|
665
|
+
|
666
|
+
// Pure C stuff
|
667
|
+
|
668
|
+
static void verify_string(char * in)
|
669
|
+
{
|
670
|
+
// debug code
|
671
|
+
char *ccp;
|
672
|
+
ccp = in;
|
673
|
+
while (*ccp)
|
674
|
+
{
|
675
|
+
if(!(isalnum(*ccp) || *ccp == ':'))
|
676
|
+
abort();
|
677
|
+
ccp++;
|
678
|
+
}
|
679
|
+
}
|
680
|
+
|
681
|
+
// Take a variable number of arguments and concatenate them,
|
682
|
+
// separated by colons.
|
683
|
+
// Like SOME_ARRAY.join(':')
|
684
|
+
static char * key_from_segments(MemcacheRequest *mr, ...)
|
685
|
+
{
|
686
|
+
va_list args;
|
687
|
+
char * current;
|
688
|
+
char * ret;
|
689
|
+
ssize_t tot_len = 0;
|
690
|
+
char *cp;
|
691
|
+
int tmp;
|
692
|
+
|
693
|
+
va_start(args, mr);
|
694
|
+
|
695
|
+
// Calculate needed storage
|
696
|
+
while ((current = va_arg(args, char*)))
|
697
|
+
{
|
698
|
+
// Current string + tailing colon or null terminator
|
699
|
+
tot_len += strlen(current) + 1;
|
700
|
+
}
|
701
|
+
|
702
|
+
if (tot_len == 0)
|
703
|
+
return NULL;
|
704
|
+
|
705
|
+
va_end(args);
|
706
|
+
ret = memcache_request_alloc(mr, tot_len);
|
707
|
+
cp = ret;
|
708
|
+
|
709
|
+
va_start(args, mr);
|
710
|
+
|
711
|
+
while ((current = va_arg(args, char*)))
|
712
|
+
{
|
713
|
+
tmp = strlen(current);
|
714
|
+
mempcpy(cp, current, tmp);
|
715
|
+
cp += tmp;
|
716
|
+
*(cp++) = ':';
|
717
|
+
}
|
718
|
+
ret[tot_len-1] = '\0';
|
719
|
+
|
720
|
+
return ret;
|
721
|
+
}
|
722
|
+
|
723
|
+
// Generates a key from a namespace array
|
724
|
+
static char * key_from_namespace(MemcacheRequest *mr, Namespace *ns)
|
725
|
+
{
|
726
|
+
// does not work with single item ns
|
727
|
+
char *key = namespace_pop_item(ns);
|
728
|
+
return key_from_segments(mr, mr->mc->namespace,
|
729
|
+
md5_digest(mr, key_pair_from_namespace(mr, ns, NULL)->first), key, NULL);
|
730
|
+
}
|
731
|
+
|
732
|
+
// Convenience function to create a pair from its args
|
733
|
+
static KeyPair * key_pair_from_strings(MemcacheRequest *mr, char *first, char *last)
|
734
|
+
{
|
735
|
+
KeyPair *nk = memcache_request_alloc(mr, sizeof(KeyPair));
|
736
|
+
nk->first = first;
|
737
|
+
nk->last = last;
|
738
|
+
return nk;
|
739
|
+
}
|
740
|
+
|
741
|
+
#define NAMESPACE_KEY_PREFIX "caffeine_namespace"
|
742
|
+
#define SALT_MAXLEN_HEX 17
|
743
|
+
#define SALT_MAXLEN_DEC 32
|
744
|
+
|
745
|
+
|
746
|
+
// Takes a key and and store a random number on the server using it.
|
747
|
+
// If it's already there, return that key instead.
|
748
|
+
static char * put_salt(MemcacheRequest *mr, const char *key)
|
749
|
+
{
|
750
|
+
char * rand_hex = memcache_request_alloc(mr, SALT_MAXLEN_HEX);
|
751
|
+
char rand_number[SALT_MAXLEN_DEC];
|
752
|
+
|
753
|
+
DBG("salt key is %s\n", key);
|
754
|
+
|
755
|
+
sprintf(rand_number, "%ld", random());
|
756
|
+
|
757
|
+
// str_add returns 0 on stored
|
758
|
+
if (str_add(mr->mc->mc, key, rand_number, 0))
|
759
|
+
{
|
760
|
+
// server already had value
|
761
|
+
// maybe check this, however unlikely it is that it'll fail ...
|
762
|
+
do_request(mr->mc->mc, key, rand_number, SALT_MAXLEN_DEC);
|
763
|
+
}
|
764
|
+
|
765
|
+
sprintf(rand_hex, "%016x", atol(rand_number));
|
766
|
+
|
767
|
+
return rand_hex;
|
768
|
+
}
|
769
|
+
|
770
|
+
// This is where all the namespace magic happens. A recursive function, lots of magic.
|
771
|
+
// need documentation
|
772
|
+
static KeyPair * key_pair_from_namespace(MemcacheRequest *mr, Namespace * namespace, char * appendage)
|
773
|
+
{
|
774
|
+
char *next_part = namespace_shift_item(namespace);
|
775
|
+
char *salt, *next_key;
|
776
|
+
|
777
|
+
if (appendage) {
|
778
|
+
next_part = key_from_segments(mr, appendage, next_part, NULL);
|
779
|
+
}
|
780
|
+
|
781
|
+
next_key = key_from_segments(mr, NAMESPACE_KEY_PREFIX, next_part, NULL);
|
782
|
+
|
783
|
+
// make sure it exists in memcache
|
784
|
+
salt = put_salt(mr, next_key);
|
785
|
+
|
786
|
+
if (namespace->head >= namespace->len)
|
787
|
+
{
|
788
|
+
// ["#{next_part}:#{salt}", next_key]
|
789
|
+
return key_pair_from_strings(mr, key_from_segments(mr, next_part, salt, NULL), next_key);
|
790
|
+
} else {
|
791
|
+
KeyPair * last_part = key_pair_from_namespace(mr, namespace, next_part);
|
792
|
+
// [ "#{next_part}:#{salt}:#{last_part.first}", last_part.last ]
|
793
|
+
return key_pair_from_strings(mr, key_from_segments(mr, next_part, salt, last_part->first, NULL), last_part->last);
|
794
|
+
}
|
795
|
+
}
|
796
|
+
|
797
|
+
// helperzzz
|
798
|
+
|
799
|
+
// Add a string to the server - 0 is stored
|
800
|
+
static int str_add(struct memcache *mc, const char *key, char *value, int expiry)
|
801
|
+
{
|
802
|
+
return mc_add(mc, (char *)key, strlen(key), value, strlen(value) + 1, expiry, 0);
|
803
|
+
}
|
804
|
+
|
805
|
+
// Compare a string with a string on the server
|
806
|
+
static int str_cmp(struct memcache *mc, const char *key, char *cmpvalue)
|
807
|
+
{
|
808
|
+
int ret;
|
809
|
+
char stored_value[512];
|
810
|
+
if (!do_request(mc, key, stored_value, 512))
|
811
|
+
ret = strcmp(stored_value, cmpvalue);
|
812
|
+
else
|
813
|
+
ret = -1;
|
814
|
+
return ret;
|
815
|
+
}
|
816
|
+
|
817
|
+
// Fetch a value from the server using a user supplied memory chunk
|
818
|
+
static int do_request(struct memcache *mc, const char *key, void *val, ssize_t size)
|
819
|
+
{
|
820
|
+
struct memcache_req *req = mc_req_new();
|
821
|
+
struct memcache_res *res = mc_req_add(req, (char *)key, strlen(key));
|
822
|
+
res->size = size;
|
823
|
+
res->val = val;
|
824
|
+
mc_res_free_on_delete(res, 0);
|
825
|
+
mc_get(mc, req);
|
826
|
+
mc_req_free(req);
|
827
|
+
if (res->size == 0)
|
828
|
+
return -1;
|
829
|
+
else
|
830
|
+
return 0;
|
831
|
+
}
|
832
|
+
|
833
|
+
// Pop the last item from the namespace array and update the pointer
|
834
|
+
static char * namespace_pop_item(Namespace *ns)
|
835
|
+
{
|
836
|
+
if (ns->head == ns->len)
|
837
|
+
return NULL;
|
838
|
+
|
839
|
+
return ns->segments[ns->len---1];
|
840
|
+
}
|
841
|
+
|
842
|
+
// Pop the first item
|
843
|
+
static char * namespace_shift_item(Namespace *ns)
|
844
|
+
{
|
845
|
+
if (ns->head == ns->len)
|
846
|
+
return NULL;
|
847
|
+
|
848
|
+
return ns->segments[ns->head++];
|
849
|
+
}
|
850
|
+
|
851
|
+
// Free all the memory chunks used for this request
|
852
|
+
static void memcache_request_free(MemcacheRequest *mr)
|
853
|
+
{
|
854
|
+
Memchunk *iter, *next;
|
855
|
+
|
856
|
+
for(iter = mr->first_chunk; iter; iter = next)
|
857
|
+
{
|
858
|
+
next = iter->next;
|
859
|
+
iter->next = NULL;
|
860
|
+
free(iter->mem);
|
861
|
+
free(iter);
|
862
|
+
mr->num_chunks--;
|
863
|
+
//printf("%d bytes deallocated, %d left\n", CHUNK_SIZE, CHUNK_SIZE * mr->num_chunks);
|
864
|
+
}
|
865
|
+
mr->current_chunk = NULL;
|
866
|
+
|
867
|
+
free(mr);
|
868
|
+
}
|
869
|
+
|
870
|
+
static char * md5_digest(MemcacheRequest *mr, const char *input)
|
871
|
+
{
|
872
|
+
unsigned char *digest = memcache_request_alloc(mr, MD5_DIGEST_LENGTH);
|
873
|
+
char *hexdigest = memcache_request_alloc(mr, MD5_DIGEST_LENGTH * 2 + 1);
|
874
|
+
MD5(input, strlen(input), digest);
|
875
|
+
snprintf(hexdigest, MD5_DIGEST_LENGTH * 2, /* Don't forget to trig all my stuff! */ "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",
|
876
|
+
digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],
|
877
|
+
digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15]);
|
878
|
+
//fprintf(stderr, "Created hash %s from key \"%s\"\n", hexdigest, input);
|
879
|
+
return hexdigest;
|
880
|
+
}
|
881
|
+
|
882
|
+
// Check if a memory area consists of digits
|
883
|
+
static int isnum(char *str, size_t len)
|
884
|
+
{
|
885
|
+
char *cp = str;
|
886
|
+
while(cp < str + len)
|
887
|
+
{
|
888
|
+
if (!isdigit(*cp))
|
889
|
+
return 0;
|
890
|
+
cp++;
|
891
|
+
}
|
892
|
+
return 1;
|
893
|
+
}
|
894
|
+
|
895
|
+
static int validate_key(char *str)
|
896
|
+
{
|
897
|
+
char *cp = str;
|
898
|
+
while(*cp)
|
899
|
+
{
|
900
|
+
if (*cp == ' ')
|
901
|
+
printf("Invalid key: %s\n", str);
|
902
|
+
cp++;
|
903
|
+
}
|
904
|
+
}
|
905
|
+
|
906
|
+
#endif
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS = '-ggdb -O0 -Df'
|
4
|
+
#$CFLAGS = '-O3'
|
5
|
+
|
6
|
+
extension_name = 'caffeine'
|
7
|
+
|
8
|
+
def die
|
9
|
+
puts "STOP, HAMMERTIME: I can't touch this!"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
# Check for libmemcache
|
14
|
+
die unless have_library('memcache', 'mc_new')
|
15
|
+
# Check for libuuid
|
16
|
+
die unless have_library('uuid', 'uuid_generate')
|
17
|
+
# Check for openssl (for MD5 generation)
|
18
|
+
die unless have_library('ssl')
|
19
|
+
|
20
|
+
dir_config extension_name
|
21
|
+
create_makefile extension_name
|
22
|
+
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActionController
|
2
|
+
module Caching
|
3
|
+
module Fragments
|
4
|
+
class CaffeineStore
|
5
|
+
def initialize(cache_instance)
|
6
|
+
@cache = cache_instance
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(name, options = {})
|
10
|
+
begin
|
11
|
+
@cache.get(name, options[:expiry] || 0)
|
12
|
+
rescue Caffeine::MemCacheNotFound
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def write(name, value, options = nil)
|
18
|
+
@cache.set(name, value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(name, options = nil)
|
22
|
+
@cache.delete(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
module CacheHelper
|
4
|
+
def cache_value_erb(name, expiry = 0, &block)
|
5
|
+
cache(name, :expiry => expiry)
|
6
|
+
end
|
7
|
+
# Override the default to allow passing of options
|
8
|
+
# WHY THE FUCK isn't this done already in rails????
|
9
|
+
def cache(name = {}, options = {}, &block)
|
10
|
+
@controller.cache_erb_fragment(block, name, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Caffeine
|
17
|
+
module CacheHelpers
|
18
|
+
def cache_defined?
|
19
|
+
(defined? $DISABLE_CACHE && $DISABLE_CACHE) || !(defined? CACHE)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_value(name, expiry = 0, &block)
|
23
|
+
return default unless cache_defined?
|
24
|
+
|
25
|
+
begin
|
26
|
+
CACHE.get(name)
|
27
|
+
rescue Caffeine::MemCacheNotFound
|
28
|
+
result = block.call
|
29
|
+
CACHE.set key, result, expiry
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
return result
|
34
|
+
end
|
35
|
+
|
36
|
+
def cache_read(name, default = nil)
|
37
|
+
return default unless cache_defined?
|
38
|
+
|
39
|
+
begin
|
40
|
+
CACHE.get(name)
|
41
|
+
rescue Caffeine::MemCacheNotFound
|
42
|
+
default
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_check(name)
|
47
|
+
return false unless cache_defined?
|
48
|
+
CACHE.check(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
alias :cache_check, :check_cached_value
|
52
|
+
|
53
|
+
def cache_delete(name, hold = 0)
|
54
|
+
return unless cache_defined?
|
55
|
+
CACHE.delete(key, hold)
|
56
|
+
end
|
57
|
+
|
58
|
+
def expire_cached_namespace(ns)
|
59
|
+
return unless cache_defined?
|
60
|
+
CACHE.invalidate_namespace(ns)
|
61
|
+
end
|
62
|
+
|
63
|
+
alias :expire_cached_value, :cache_delete
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'test/unit'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'caffeine'
|
5
|
+
#require 'adocca_memcache'
|
6
|
+
|
7
|
+
class TestCaffeine < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def get_cache_instance
|
10
|
+
cache = Caffeine::MemCache.new :namespace => 'caffeinetest'
|
11
|
+
cache.servers = ["localhost:11211"]
|
12
|
+
return cache
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@cache = get_cache_instance
|
17
|
+
@cache.flush
|
18
|
+
end
|
19
|
+
|
20
|
+
# def teardown
|
21
|
+
# end
|
22
|
+
|
23
|
+
def test_basic
|
24
|
+
# ruby value
|
25
|
+
@cache.set "foo", :somevalue
|
26
|
+
assert_equal :somevalue, @cache.get("foo")
|
27
|
+
|
28
|
+
# MemCache#check
|
29
|
+
assert_equal true, @cache.check("foo")
|
30
|
+
@cache.delete "foo"
|
31
|
+
assert_equal false, @cache.check("foo")
|
32
|
+
|
33
|
+
# integer value
|
34
|
+
@cache.set "foo2", 100
|
35
|
+
assert_equal 100, @cache.get("foo2")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_multi_key
|
39
|
+
@cache.set "burK", "bajs"
|
40
|
+
@cache.set "bork", "kiss"
|
41
|
+
@cache.set "hehu", "hoj"
|
42
|
+
assert_equal(["bajs","kiss","hoj"], @cache.get("burK", "bork", "hehu"))
|
43
|
+
@cache.set ["2","4","0","1"], "bingo"
|
44
|
+
@cache.set ["4","4","0","11"], "was a dog"
|
45
|
+
assert_equal(["bingo","was a dog"], @cache.get(["2","4","0","1"], ["4","4","0","11"]))
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_namespaces
|
49
|
+
@cache.set ["ns1", "ns2", "ns3", "item"], :somevalue
|
50
|
+
@cache.set ["ns1", "ns2", "item2"], :othervalue
|
51
|
+
@cache.set ["ns1", "item3"], :yetanothervalue
|
52
|
+
@cache.set ["ns1", "item4"], :lastvalue
|
53
|
+
assert_equal :somevalue, @cache.get(["ns1", "ns2", "ns3", "item"])
|
54
|
+
@cache.invalidate_namespace ["ns1", "ns2", "ns3"]
|
55
|
+
assert_equal(:MemCache_no_such_entry, @cache.get(["ns1", "ns2", "ns3", "item"]))
|
56
|
+
assert_equal :othervalue, @cache.get(["ns1", "ns2", "item2"])
|
57
|
+
@cache.set ["ns1", "ns2", "ns3", "item"], :somevalue
|
58
|
+
assert_equal :somevalue, @cache.get(["ns1", "ns2", "ns3", "item"])
|
59
|
+
@cache.invalidate_namespace ["ns1", "ns2"]
|
60
|
+
assert_equal(:MemCache_no_such_entry, @cache.get(["ns1", "ns2", "ns3", "item"]))
|
61
|
+
assert_equal :yetanothervalue, @cache.get(["ns1", "item3"])
|
62
|
+
assert_equal :lastvalue, @cache.get(["ns1", "item4"])
|
63
|
+
@cache.invalidate_namespace ["ns1"]
|
64
|
+
assert_equal(:MemCache_no_such_entry, @cache.get(["ns1", "item3"]))
|
65
|
+
assert_equal(:MemCache_no_such_entry, @cache.get(["ns1", "item4"]))
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_lock
|
69
|
+
assert @cache.lock("testlock", :timeout => 10)
|
70
|
+
cache2 = get_cache_instance
|
71
|
+
assert !cache2.lock("testlock", :timeout => 2) # will block
|
72
|
+
@cache.unlock "testlock"
|
73
|
+
assert_not_nil cache2.lock("testlock")
|
74
|
+
cache2.unlock "testlock"
|
75
|
+
|
76
|
+
assert @cache.lock("testlock")
|
77
|
+
assert_raises(Caffeine::MemCacheError) { cache2.synchronize("testlock", :timeout => 2) { sleep(1) } }
|
78
|
+
@cache.unlock "testlock"
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: caffeine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fredrik Roos
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-22 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: fredrik.roos@adocca.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions:
|
21
|
+
- ext/extconf.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- lib/caffeine_fragment_store.rb
|
27
|
+
- lib/rails_helpers.rb
|
28
|
+
- ext/caffeine.c
|
29
|
+
- LICENSE
|
30
|
+
has_rdoc: false
|
31
|
+
homepage: http://adocca-plugins.rubyforge.org
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "0"
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements:
|
50
|
+
- libmemcache
|
51
|
+
- libuuid
|
52
|
+
rubyforge_project: adocca-plugins
|
53
|
+
rubygems_version: 1.0.1
|
54
|
+
signing_key:
|
55
|
+
specification_version: 2
|
56
|
+
summary: A Ruby binding for libmemcache + extra functionality (namespaces)
|
57
|
+
test_files:
|
58
|
+
- test/test_caffeine.rb
|