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