README: update with extra disclaimer
[raindrops.git] / ext / raindrops / raindrops.c
blob72a6ee7f8148374cff99981d5be74d644313b05c
1 #include <ruby.h>
2 #include <unistd.h>
3 #include <sys/mman.h>
4 #include <assert.h>
5 #include <errno.h>
6 #include <stddef.h>
7 #include <string.h>
8 #include "raindrops_atomic.h"
10 #ifndef SIZET2NUM
11 # define SIZET2NUM(x) ULONG2NUM(x)
12 #endif
13 #ifndef NUM2SIZET
14 # define NUM2SIZET(x) NUM2ULONG(x)
15 #endif
18 * most modern CPUs have a cache-line size of 64 or 128.
19 * We choose a bigger one by default since our structure is not
20 * heavily used
22 static size_t raindrop_size = 128;
23 static size_t rd_page_size;
25 #define PAGE_MASK (~(rd_page_size - 1))
26 #define PAGE_ALIGN(addr) (((addr) + rd_page_size - 1) & PAGE_MASK)
28 /* each raindrop is a counter */
29 struct raindrop {
30 unsigned long counter;
31 } __attribute__((packed));
33 /* allow mmap-ed regions to store more than one raindrop */
34 struct raindrops {
35 size_t size;
36 size_t capa;
37 pid_t pid;
38 VALUE io;
39 struct raindrop *drops;
42 /* called by GC */
43 static void rd_mark(void *ptr)
45 struct raindrops *r = ptr;
46 rb_gc_mark(r->io);
49 /* called by GC */
50 static void rd_free(void *ptr)
52 struct raindrops *r = ptr;
54 if (r->drops != MAP_FAILED) {
55 int rv = munmap(r->drops, raindrop_size * r->capa);
56 if (rv != 0)
57 rb_bug("munmap failed in gc: %s", strerror(errno));
60 xfree(ptr);
63 static size_t rd_memsize(const void *ptr)
65 const struct raindrops *r = ptr;
67 return r->drops == MAP_FAILED ? 0 : raindrop_size * r->capa;
70 static const rb_data_type_t rd_type = {
71 "raindrops",
72 { rd_mark, rd_free, rd_memsize, /* reserved */ },
73 /* parent, data, [ flags ] */
76 /* automatically called at creation (before initialize) */
77 static VALUE alloc(VALUE klass)
79 struct raindrops *r;
80 VALUE rv = TypedData_Make_Struct(klass, struct raindrops, &rd_type, r);
82 r->drops = MAP_FAILED;
83 return rv;
86 static struct raindrops *get(VALUE self)
88 struct raindrops *r;
90 TypedData_Get_Struct(self, struct raindrops, &rd_type, r);
92 if (r->drops == MAP_FAILED)
93 rb_raise(rb_eStandardError, "invalid or freed Raindrops");
95 return r;
99 * This is the _actual_ implementation of #initialize - the Ruby wrapper
100 * handles keyword-argument handling then calls this method.
102 static VALUE init_cimpl(VALUE self, VALUE size, VALUE io, VALUE zero)
104 struct raindrops *r = DATA_PTR(self);
105 int tries = 1;
106 size_t tmp;
108 if (r->drops != MAP_FAILED)
109 rb_raise(rb_eRuntimeError, "already initialized");
111 r->size = NUM2SIZET(size);
112 if (r->size < 1)
113 rb_raise(rb_eArgError, "size must be >= 1");
115 tmp = PAGE_ALIGN(raindrop_size * r->size);
116 r->capa = tmp / raindrop_size;
117 assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned");
119 r->io = io;
121 retry:
122 if (RTEST(r->io)) {
123 int fd = NUM2INT(rb_funcall(r->io, rb_intern("fileno"), 0));
124 rb_funcall(r->io, rb_intern("truncate"), 1, SIZET2NUM(tmp));
125 r->drops = mmap(NULL, tmp,
126 PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
127 } else {
128 r->drops = mmap(NULL, tmp,
129 PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED,
130 -1, 0);
132 if (r->drops == MAP_FAILED) {
133 int err = errno;
135 if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) {
136 rb_gc();
137 goto retry;
139 rb_sys_fail("mmap");
141 r->pid = getpid();
143 if (RTEST(zero))
144 memset(r->drops, 0, tmp);
146 return self;
150 * mremap() is currently broken with MAP_SHARED
151 * https://bugzilla.kernel.org/show_bug.cgi?id=8691
153 #if defined(HAVE_MREMAP) && !defined(MREMAP_WORKS_WITH_MAP_SHARED)
154 # undef HAVE_MREMAP
155 #endif
157 #ifdef HAVE_MREMAP
158 #ifndef MREMAP_MAYMOVE
159 # warn MREMAP_MAYMOVE undefined
160 # define MREMAP_MAYMOVE 0
161 #endif
162 static void resize(struct raindrops *r, size_t new_rd_size)
164 size_t old_size = raindrop_size * r->capa;
165 size_t new_size = PAGE_ALIGN(raindrop_size * new_rd_size);
166 void *old_address = r->drops;
167 void *rv;
169 if (r->pid != getpid())
170 rb_raise(rb_eRuntimeError, "cannot mremap() from child");
172 rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE);
173 if (rv == MAP_FAILED) {
174 int err = errno;
176 if (err == EAGAIN || err == ENOMEM) {
177 rb_gc();
178 rv = mremap(old_address, old_size, new_size, 0);
180 if (rv == MAP_FAILED)
181 rb_sys_fail("mremap");
183 r->drops = rv;
184 r->size = new_rd_size;
185 r->capa = new_size / raindrop_size;
186 assert(r->capa >= r->size && "bad sizing");
188 #else /* ! HAVE_MREMAP */
190 * we cannot use munmap + mmap to reallocate the buffer since it may
191 * already be shared by other processes, so we just fail
193 static void resize(struct raindrops *r, size_t new_rd_size)
195 rb_raise(rb_eRangeError, "mremap(2) is not available");
197 #endif /* ! HAVE_MREMAP */
200 * call-seq:
201 * rd.size = new_size
203 * Increases or decreases the current capacity of our Raindrop.
204 * Raises RangeError if +new_size+ is too big or small for the
205 * current backing store
207 static VALUE setsize(VALUE self, VALUE new_size)
209 size_t new_rd_size = NUM2SIZET(new_size);
210 struct raindrops *r = get(self);
212 if (new_rd_size <= r->capa)
213 r->size = new_rd_size;
214 else
215 resize(r, new_rd_size);
217 return new_size;
221 * call-seq:
222 * rd.capa -> Integer
224 * Returns the number of slots allocated (but not necessarily used) by
225 * the Raindrops object.
227 static VALUE capa(VALUE self)
229 return SIZET2NUM(get(self)->capa);
233 * call-seq:
234 * rd.dup -> rd_copy
236 * Duplicates and snapshots the current state of a Raindrops object. Even
237 * if the given Raindrops object is backed by a file, the copy will be backed
238 * by independent, anonymously mapped memory.
240 static VALUE init_copy(VALUE dest, VALUE source)
242 struct raindrops *dst = DATA_PTR(dest);
243 struct raindrops *src = get(source);
245 init_cimpl(dest, SIZET2NUM(src->size), Qnil, Qfalse);
246 memcpy(dst->drops, src->drops, raindrop_size * src->size);
248 return dest;
251 static unsigned long *addr_of(VALUE self, VALUE index)
253 struct raindrops *r = get(self);
254 unsigned long off = FIX2ULONG(index) * raindrop_size;
256 if (off >= raindrop_size * r->size)
257 rb_raise(rb_eArgError, "offset overrun");
259 return (unsigned long *)((unsigned long)r->drops + off);
262 static unsigned long incr_decr_arg(int argc, const VALUE *argv)
264 if (argc > 2 || argc < 1)
265 rb_raise(rb_eArgError,
266 "wrong number of arguments (%d for 1+)", argc);
268 return argc == 2 ? NUM2ULONG(argv[1]) : 1;
272 * call-seq:
273 * rd.incr(index[, number]) -> result
275 * Increments the value referred to by the +index+ by +number+.
276 * +number+ defaults to +1+ if unspecified.
278 static VALUE incr(int argc, VALUE *argv, VALUE self)
280 unsigned long nr = incr_decr_arg(argc, argv);
282 return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr));
286 * call-seq:
287 * rd.decr(index[, number]) -> result
289 * Decrements the value referred to by the +index+ by +number+.
290 * +number+ defaults to +1+ if unspecified.
292 static VALUE decr(int argc, VALUE *argv, VALUE self)
294 unsigned long nr = incr_decr_arg(argc, argv);
296 return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr));
300 * call-seq:
301 * rd.to_ary -> Array
303 * converts the Raindrops structure to an Array
305 static VALUE to_ary(VALUE self)
307 struct raindrops *r = get(self);
308 VALUE rv = rb_ary_new2(r->size);
309 size_t i;
310 unsigned long base = (unsigned long)r->drops;
312 for (i = 0; i < r->size; i++) {
313 rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base)));
314 base += raindrop_size;
317 return rv;
321 * call-seq:
322 * rd.size -> Integer
324 * Returns the number of counters a Raindrops object can hold. Due to
325 * page alignment, this is always equal or greater than the number of
326 * requested slots passed to Raindrops.new
328 static VALUE size(VALUE self)
330 return SIZET2NUM(get(self)->size);
334 * call-seq:
335 * rd[index] = value
337 * Assigns +value+ to the slot designated by +index+
339 static VALUE aset(VALUE self, VALUE index, VALUE value)
341 unsigned long *addr = addr_of(self, index);
343 *addr = NUM2ULONG(value);
345 return value;
349 * call-seq:
350 * rd[index] -> value
352 * Returns the value of the slot designated by +index+
354 static VALUE aref(VALUE self, VALUE index)
356 return ULONG2NUM(*addr_of(self, index));
359 #ifdef __linux__
360 void Init_raindrops_linux_inet_diag(void);
361 #endif
362 #ifdef HAVE_TYPE_STRUCT_TCP_INFO
363 void Init_raindrops_tcp_info(void);
364 #endif
366 #ifndef _SC_NPROCESSORS_CONF
367 # if defined _SC_NPROCESSORS_ONLN
368 # define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_ONLN
369 # elif defined _SC_NPROC_ONLN
370 # define _SC_NPROCESSORS_CONF _SC_NPROC_ONLN
371 # elif defined _SC_CRAY_NCPU
372 # define _SC_NPROCESSORS_CONF _SC_CRAY_NCPU
373 # endif
374 #endif
377 * call-seq:
378 * rd.evaporate! -> nil
380 * Releases mmap()-ed memory allocated for the Raindrops object back
381 * to the OS. The Ruby garbage collector will also release memory
382 * automatically when it is not needed, but this forces release
383 * under high memory pressure.
385 static VALUE evaporate_bang(VALUE self)
387 struct raindrops *r = get(self);
388 void *addr = r->drops;
390 r->drops = MAP_FAILED;
391 if (munmap(addr, raindrop_size * r->capa) != 0)
392 rb_sys_fail("munmap");
393 return Qnil;
397 * call-seq:
398 * to_io -> IO
400 * Returns the IO object backing the memory for this raindrop, if
401 * one was specified when constructing this Raindrop. If this
402 * Raindrop is backed by anonymous memory, this method returns nil.
404 static VALUE to_io(VALUE self)
406 struct raindrops *r = get(self);
407 return r->io;
410 void Init_raindrops_ext(void)
412 VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
413 long tmp = 2;
415 #ifdef _SC_NPROCESSORS_CONF
416 tmp = sysconf(_SC_NPROCESSORS_CONF);
417 #endif
418 /* no point in padding on single CPU machines */
419 if (tmp == 1)
420 raindrop_size = sizeof(unsigned long);
421 #ifdef _SC_LEVEL1_DCACHE_LINESIZE
422 if (tmp != 1) {
423 tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
424 if (tmp > 0)
425 raindrop_size = (size_t)tmp;
427 #endif
428 #if defined(_SC_PAGE_SIZE)
429 rd_page_size = (size_t)sysconf(_SC_PAGE_SIZE);
430 #elif defined(_SC_PAGESIZE)
431 rd_page_size = (size_t)sysconf(_SC_PAGESIZE);
432 #elif defined(HAVE_GETPAGESIZE)
433 rd_page_size = (size_t)getpagesize();
434 #elif defined(PAGE_SIZE)
435 rd_page_size = (size_t)PAGE_SIZE;
436 #elif defined(PAGESIZE)
437 rd_page_size = (size_t)PAGESIZE;
438 #else
439 # error unable to detect page size for mmap()
440 #endif
441 if ((rd_page_size == (size_t)-1) || (rd_page_size < raindrop_size))
442 rb_raise(rb_eRuntimeError,
443 "system page size invalid: %llu",
444 (unsigned long long)rd_page_size);
447 * The size of one page of memory for a mmap()-ed Raindrops region.
448 * Typically 4096 bytes under Linux.
450 rb_define_const(cRaindrops, "PAGE_SIZE", SIZET2NUM(rd_page_size));
453 * The size (in bytes) of a slot in a Raindrops object.
454 * This is the size of a word on single CPU systems and
455 * the size of the L1 cache line size if detectable.
457 * Defaults to 128 bytes if undetectable.
459 rb_define_const(cRaindrops, "SIZE", SIZET2NUM(raindrop_size));
462 * The maximum value a raindrop counter can hold
464 rb_define_const(cRaindrops, "MAX", ULONG2NUM((unsigned long)-1));
466 rb_define_alloc_func(cRaindrops, alloc);
468 rb_define_private_method(cRaindrops, "initialize_cimpl", init_cimpl, 3);
469 rb_define_method(cRaindrops, "incr", incr, -1);
470 rb_define_method(cRaindrops, "decr", decr, -1);
471 rb_define_method(cRaindrops, "to_ary", to_ary, 0);
472 rb_define_method(cRaindrops, "[]", aref, 1);
473 rb_define_method(cRaindrops, "[]=", aset, 2);
474 rb_define_method(cRaindrops, "size", size, 0);
475 rb_define_method(cRaindrops, "size=", setsize, 1);
476 rb_define_method(cRaindrops, "capa", capa, 0);
477 rb_define_method(cRaindrops, "initialize_copy", init_copy, 1);
478 rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0);
479 rb_define_method(cRaindrops, "to_io", to_io, 0);
481 #ifdef __linux__
482 Init_raindrops_linux_inet_diag();
483 #endif
484 #ifdef HAVE_TYPE_STRUCT_TCP_INFO
485 Init_raindrops_tcp_info();
486 #endif