class Raindrops
Each Raindrops
object is a container that holds several counters. It is internally a page-aligned, shared memory area that allows atomic increments, decrements, assignments and reads without any locking.
rd = Raindrops.new 4 rd.incr(0, 1) -> 1 rd.to_ary -> [ 1, 0, 0, 0 ]
Unlike many classes in this package, the core Raindrops
class is intended to be portable to all reasonably modern *nix systems supporting mmap(). Please let us know if you have portability issues, patches or pull requests at raindrops-public@yhbt.net
Constants
- MAX
The maximum value a raindrop counter can hold
- PAGE_SIZE
The size of one page of memory for a mmap()-ed
Raindrops
region. Typically 4096 bytes underLinux
.- SIZE
The size (in bytes) of a slot in a
Raindrops
object. This is the size of a word on single CPU systems and the size of the L1 cache line size if detectable.Defaults to 128 bytes if undetectable.
- TCP
This is a frozen hash storing the numeric values maps platform-independent symbol keys to platform-dependent numeric values. These states are all valid values for the Raindrops::TCP_Info#state field.
The platform-independent names of the keys in this hash are:
- :ESTABLISHED - :SYN_SENT - :SYN_RECV - :FIN_WAIT1 - :FIN_WAIT2 - :TIME_WAIT - :CLOSE - :CLOSE_WAIT - :LAST_ACK - :LISTEN - :CLOSING
This is only supported on platforms where
TCP_Info
is supported, currently FreeBSD, OpenBSD, and Linux-based systems.
Public Class Methods
Initializes a Raindrops
object to hold size
counters. size
is only a hint and the actual number of counters the object has is dependent on the CPU model, number of cores, and page size of the machine. The actual size of the object will always be equal or greater than the specified size
. If io
is provided, then the Raindrops
memory will be backed by the specified file; otherwise, it will allocate anonymous memory. The IO object must respond to truncate
, as this is used to set the size of the file. If zero
is provided, then the memory region is zeroed prior to returning. This is only meaningful if io
is also provided; in that case it controls whether any existing counter values in io
are retained (false) or whether it is entirely zeroed (true).
# File lib/raindrops.rb, line 56 def initialize(size, io: nil, zero: false) # This ruby wrapper exists to handle the keyword-argument handling, # which is otherwise kind of awkward in C. We delegate the keyword # arguments to the _actual_ initialize implementation as positional # args. initialize_cimpl(size, io, zero) end
Public Instance Methods
Returns the value of the slot designated by index
static VALUE aref(VALUE self, VALUE index) { return ULONG2NUM(*addr_of(self, index)); }
Assigns value
to the slot designated by index
static VALUE aset(VALUE self, VALUE index, VALUE value) { unsigned long *addr = addr_of(self, index); *addr = NUM2ULONG(value); return value; }
Returns the number of slots allocated (but not necessarily used) by the Raindrops
object.
static VALUE capa(VALUE self) { return SIZET2NUM(get(self)->capa); }
Decrements the value referred to by the index
by number
. number
defaults to 1
if unspecified.
static VALUE decr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr)); }
Releases mmap()-ed memory allocated for the Raindrops
object back to the OS. The Ruby garbage collector will also release memory automatically when it is not needed, but this forces release under high memory pressure.
static VALUE evaporate_bang(VALUE self) { struct raindrops *r = get(self); void *addr = r->drops; r->drops = MAP_FAILED; if (munmap(addr, raindrop_size * r->capa) != 0) rb_sys_fail("munmap"); return Qnil; }
Increments the value referred to by the index
by number
. number
defaults to 1
if unspecified.
static VALUE incr(int argc, VALUE *argv, VALUE self) { unsigned long nr = incr_decr_arg(argc, argv); return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr)); }
Duplicates and snapshots the current state of a Raindrops
object. Even if the given Raindrops
object is backed by a file, the copy will be backed by independent, anonymously mapped memory.
static VALUE init_copy(VALUE dest, VALUE source) { struct raindrops *dst = DATA_PTR(dest); struct raindrops *src = get(source); init_cimpl(dest, SIZET2NUM(src->size), Qnil, Qfalse); memcpy(dst->drops, src->drops, raindrop_size * src->size); return dest; }
Returns the number of counters a Raindrops
object can hold. Due to page alignment, this is always equal or greater than the number of requested slots passed to Raindrops.new
static VALUE size(VALUE self) { return SIZET2NUM(get(self)->size); }
Increases or decreases the current capacity of our Raindrop. Raises RangeError if new_size
is too big or small for the current backing store
static VALUE setsize(VALUE self, VALUE new_size) { size_t new_rd_size = NUM2SIZET(new_size); struct raindrops *r = get(self); if (new_rd_size <= r->capa) r->size = new_rd_size; else resize(r, new_rd_size); return new_size; }
converts the Raindrops
structure to an Array
static VALUE to_ary(VALUE self) { struct raindrops *r = get(self); VALUE rv = rb_ary_new2(r->size); size_t i; unsigned long base = (unsigned long)r->drops; for (i = 0; i < r->size; i++) { rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base))); base += raindrop_size; } return rv; }
Returns the IO object backing the memory for this raindrop, if one was specified when constructing this Raindrop. If this Raindrop is backed by anonymous memory, this method returns nil.
static VALUE to_io(VALUE self) { struct raindrops *r = get(self); return r->io; }
Private Instance Methods
This is the actual implementation of initialize - the Ruby wrapper handles keyword-argument handling then calls this method.
static VALUE init_cimpl(VALUE self, VALUE size, VALUE io, VALUE zero) { struct raindrops *r = DATA_PTR(self); int tries = 1; size_t tmp; if (r->drops != MAP_FAILED) rb_raise(rb_eRuntimeError, "already initialized"); r->size = NUM2SIZET(size); if (r->size < 1) rb_raise(rb_eArgError, "size must be >= 1"); tmp = PAGE_ALIGN(raindrop_size * r->size); r->capa = tmp / raindrop_size; assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned"); r->io = io; retry: if (RTEST(r->io)) { int fd = NUM2INT(rb_funcall(r->io, rb_intern("fileno"), 0)); rb_funcall(r->io, rb_intern("truncate"), 1, SIZET2NUM(tmp)); r->drops = mmap(NULL, tmp, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); } else { r->drops = mmap(NULL, tmp, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); } if (r->drops == MAP_FAILED) { int err = errno; if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) { rb_gc(); goto retry; } rb_sys_fail("mmap"); } r->pid = getpid(); if (RTEST(zero)) memset(r->drops, 0, tmp); return self; }