about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--ext/raindrops/raindrops.c63
-rw-r--r--lib/raindrops.rb24
-rw-r--r--test/test_raindrops.rb42
3 files changed, 114 insertions, 15 deletions
diff --git a/ext/raindrops/raindrops.c b/ext/raindrops/raindrops.c
index 837084c..72a6ee7 100644
--- a/ext/raindrops/raindrops.c
+++ b/ext/raindrops/raindrops.c
@@ -4,6 +4,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <stddef.h>
+#include <string.h>
 #include "raindrops_atomic.h"
 
 #ifndef SIZET2NUM
@@ -34,10 +35,18 @@ struct raindrops {
         size_t size;
         size_t capa;
         pid_t pid;
+        VALUE io;
         struct raindrop *drops;
 };
 
 /* called by GC */
+static void rd_mark(void *ptr)
+{
+        struct raindrops *r = ptr;
+        rb_gc_mark(r->io);
+}
+
+/* called by GC */
 static void rd_free(void *ptr)
 {
         struct raindrops *r = ptr;
@@ -60,7 +69,7 @@ static size_t rd_memsize(const void *ptr)
 
 static const rb_data_type_t rd_type = {
         "raindrops",
-        { NULL, rd_free, rd_memsize, /* reserved */ },
+        { rd_mark, rd_free, rd_memsize, /* reserved */ },
         /* parent, data, [ flags ] */
 };
 
@@ -87,16 +96,10 @@ static struct raindrops *get(VALUE self)
 }
 
 /*
- * call-seq:
- *        Raindrops.new(size)        -> raindrops object
- *
- * 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+.
+ * This is the _actual_ implementation of #initialize - the Ruby wrapper
+ * handles keyword-argument handling then calls this method.
  */
-static VALUE init(VALUE self, VALUE size)
+static VALUE init_cimpl(VALUE self, VALUE size, VALUE io, VALUE zero)
 {
         struct raindrops *r = DATA_PTR(self);
         int tries = 1;
@@ -113,9 +116,19 @@ static VALUE init(VALUE self, VALUE size)
         r->capa = tmp / raindrop_size;
         assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned");
 
+        r->io = io;
+
 retry:
-        r->drops = mmap(NULL, tmp,
-                        PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
+        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;
 
@@ -127,6 +140,9 @@ retry:
         }
         r->pid = getpid();
 
+        if (RTEST(zero))
+                memset(r->drops, 0, tmp);
+
         return self;
 }
 
@@ -217,14 +233,16 @@ static VALUE capa(VALUE self)
  * call-seq:
  *        rd.dup                -> rd_copy
  *
- * Duplicates and snapshots the current state of a Raindrops object.
+ * 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(dest, SIZET2NUM(src->size));
+        init_cimpl(dest, SIZET2NUM(src->size), Qnil, Qfalse);
         memcpy(dst->drops, src->drops, raindrop_size * src->size);
 
         return dest;
@@ -375,6 +393,20 @@ static VALUE evaporate_bang(VALUE self)
         return Qnil;
 }
 
+/*
+ * call-seq:
+ *         to_io        -> IO
+ *
+ * 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;
+}
+
 void Init_raindrops_ext(void)
 {
         VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
@@ -433,7 +465,7 @@ void Init_raindrops_ext(void)
 
         rb_define_alloc_func(cRaindrops, alloc);
 
-        rb_define_method(cRaindrops, "initialize", init, 1);
+        rb_define_private_method(cRaindrops, "initialize_cimpl", init_cimpl, 3);
         rb_define_method(cRaindrops, "incr", incr, -1);
         rb_define_method(cRaindrops, "decr", decr, -1);
         rb_define_method(cRaindrops, "to_ary", to_ary, 0);
@@ -444,6 +476,7 @@ void Init_raindrops_ext(void)
         rb_define_method(cRaindrops, "capa", capa, 0);
         rb_define_method(cRaindrops, "initialize_copy", init_copy, 1);
         rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0);
+        rb_define_method(cRaindrops, "to_io", to_io, 0);
 
 #ifdef __linux__
         Init_raindrops_linux_inet_diag();
diff --git a/lib/raindrops.rb b/lib/raindrops.rb
index ba273eb..dc61952 100644
--- a/lib/raindrops.rb
+++ b/lib/raindrops.rb
@@ -36,6 +36,30 @@ class Raindrops
     def total
       active + queued
     end
+  end unless defined? ListenStats
+
+  # call-seq:
+  #        Raindrops.new(size, io: nil)        -> raindrops object
+  #
+  # 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).
+  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
 
   autoload :Linux, 'raindrops/linux'
diff --git a/test/test_raindrops.rb b/test/test_raindrops.rb
index 0749694..6351c66 100644
--- a/test/test_raindrops.rb
+++ b/test/test_raindrops.rb
@@ -1,6 +1,7 @@
 # -*- encoding: binary -*-
 require 'test/unit'
 require 'raindrops'
+require 'tempfile'
 
 class TestRaindrops < Test::Unit::TestCase
 
@@ -162,4 +163,45 @@ class TestRaindrops < Test::Unit::TestCase
     assert status.success?
     assert_equal [ 1, 2 ], tmp.to_ary
   end
+
+  def test_io_backed
+    file = Tempfile.new('test_io_backed')
+    rd = Raindrops.new(4, io: file, zero: true)
+    rd[0] = 123
+    rd[1] = 456
+
+    assert_equal 123, rd[0]
+    assert_equal 456, rd[1]
+
+    rd.evaporate!
+
+    file.rewind
+    data = file.read
+    assert_equal 123, data.unpack('L!')[0]
+    assert_equal 456, data[Raindrops::SIZE..data.size].unpack('L!')[0]
+  end
+
+  def test_io_backed_reuse
+    file = Tempfile.new('test_io_backed')
+    rd = Raindrops.new(4, io: file, zero: true)
+    rd[0] = 123
+    rd[1] = 456
+    rd.evaporate!
+
+    rd = Raindrops.new(4, io: file, zero: false)
+    assert_equal 123, rd[0]
+    assert_equal 456, rd[1]
+  end
+
+  def test_iobacked_noreuse
+    file = Tempfile.new('test_io_backed')
+    rd = Raindrops.new(4, io: file, zero: true)
+    rd[0] = 123
+    rd[1] = 456
+    rd.evaporate!
+
+    rd = Raindrops.new(4, io: file, zero: true)
+    assert_equal 0, rd[0]
+    assert_equal 0, rd[1]
+  end
 end