caffeine 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (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.
@@ -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
@@ -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
@@ -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