Permalink
Browse files

Merge pull request #11 from rares/stat_watcher

StatWatcher re-factoring that passes wrapped ev_statinfo information of previous and current path state to on_change event
  • Loading branch information...
2 parents 02d6400 + c3723c6 commit 3bafe8b6b0a7dea48818b72df7f35c5ec16c71e0 @tarcieri committed May 13, 2011
View
@@ -15,7 +15,7 @@ begin
gem.add_development_dependency "rspec", ">= 2.1.0"
gem.add_development_dependency "rake-compiler", "~> 0.7.5"
gem.extensions = FileList["ext/**/extconf.rb"].to_a
-
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
end
Jeweler::GemcutterTasks.new
@@ -65,3 +65,25 @@ task :http11_parser do
raise "Failed to build C source" unless File.exist? target
end
end
+
+# adapted from http://flavoriffic.blogspot.com/2009/06/easily-valgrind-gdb-your-ruby-c.html
+def specs_command
+ require "find"
+ files = []
+ Find.find("spec") do |f|
+ files << f if File.basename(f) =~ /.*spec.*\.rb$/
+ end
+ cmdline = "#{RUBY} -I.:lib:ext:spec \
+ -e '%w[#{files.join(' ')}].each { |f| require f }'"
+end
+
+namespace :test do
+ desc "run specs with valgrind"
+ task :valgrind => :compile do
+ system "valgrind --num-callers=15 \
+ --partial-loads-ok=yes --undef-value-errors=no \
+ --tool=memcheck --leak-check=yes --track-fds=yes \
+ --show-reachable=yes #{specs_command}"
+ end
+end
+
View
@@ -13,44 +13,62 @@
static VALUE mCoolio = Qnil;
static VALUE cCoolio_Watcher = Qnil;
static VALUE cCoolio_StatWatcher = Qnil;
+static VALUE cCoolio_StatInfo = Qnil;
static VALUE cCoolio_Loop = Qnil;
static VALUE Coolio_StatWatcher_initialize(int argc, VALUE *argv, VALUE self);
static VALUE Coolio_StatWatcher_attach(VALUE self, VALUE loop);
static VALUE Coolio_StatWatcher_detach(VALUE self);
static VALUE Coolio_StatWatcher_enable(VALUE self);
static VALUE Coolio_StatWatcher_disable(VALUE self);
-static VALUE Coolio_StatWatcher_on_change(VALUE self);
+static VALUE Coolio_StatWatcher_on_change(VALUE self, VALUE previous, VALUE current);
static VALUE Coolio_StatWatcher_path(VALUE self);
+static VALUE Coolio_StatInfo_build(ev_statdata *statdata_struct);
+
static void Coolio_StatWatcher_libev_callback(struct ev_loop *ev_loop, struct ev_stat *stat, int revents);
static void Coolio_StatWatcher_dispatch_callback(VALUE self, int revents);
/*
* Coolio::StatWatcher lets you create either one-shot or periodic stats which
* run within Coolio's event loop. It's useful for creating timeouts or
* events which fire periodically.
- */
+ **/
void Init_coolio_stat_watcher()
-{
+{
mCoolio = rb_define_module("Coolio");
cCoolio_Watcher = rb_define_class_under(mCoolio, "Watcher", rb_cObject);
cCoolio_StatWatcher = rb_define_class_under(mCoolio, "StatWatcher", cCoolio_Watcher);
+ cCoolio_StatInfo = rb_struct_define("StatInfo",
+ "mtime",
+ "ctime",
+ "atime",
+ "dev",
+ "ino",
+ "mode",
+ "nlink",
+ "uid",
+ "guid",
+ "rdev",
+ "size",
+ "blksize",
+ "blocks",
+ NULL);
cCoolio_Loop = rb_define_class_under(mCoolio, "Loop", rb_cObject);
rb_define_method(cCoolio_StatWatcher, "initialize", Coolio_StatWatcher_initialize, -1);
rb_define_method(cCoolio_StatWatcher, "attach", Coolio_StatWatcher_attach, 1);
rb_define_method(cCoolio_StatWatcher, "detach", Coolio_StatWatcher_detach, 0);
rb_define_method(cCoolio_StatWatcher, "enable", Coolio_StatWatcher_enable, 0);
rb_define_method(cCoolio_StatWatcher, "disable", Coolio_StatWatcher_disable, 0);
- rb_define_method(cCoolio_StatWatcher, "on_change", Coolio_StatWatcher_on_change, 0);
+ rb_define_method(cCoolio_StatWatcher, "on_change", Coolio_StatWatcher_on_change, 2);
rb_define_method(cCoolio_StatWatcher, "path", Coolio_StatWatcher_path, 0);
}
/**
* call-seq:
* Coolio::StatWatcher.initialize(path, interval = 0) -> Coolio::StatWatcher
- *
+ *
* Create a new Coolio::StatWatcher for the given path. This will monitor the
* given path for changes at the filesystem level. The interval argument
* specified how often in seconds the path should be polled for changes.
@@ -75,11 +93,11 @@ static VALUE Coolio_StatWatcher_initialize(int argc, VALUE *argv, VALUE self)
watcher_data->dispatch_callback = Coolio_StatWatcher_dispatch_callback;
ev_stat_init(
- &watcher_data->event_types.ev_stat,
- Coolio_StatWatcher_libev_callback,
- RSTRING_PTR(path),
+ &watcher_data->event_types.ev_stat,
+ Coolio_StatWatcher_libev_callback,
+ RSTRING_PTR(path),
interval == Qnil ? 0 : NUM2DBL(interval)
- );
+ );
watcher_data->event_types.ev_stat.data = (void *)self;
return Qnil;
@@ -88,7 +106,7 @@ static VALUE Coolio_StatWatcher_initialize(int argc, VALUE *argv, VALUE self)
/**
* call-seq:
* Coolio::StatWatcher.attach(loop) -> Coolio::StatWatcher
- *
+ *
* Attach the stat watcher to the given Coolio::Loop. If the watcher is already
* attached to a loop, detach it from the old one and attach it to the new one.
*/
@@ -97,7 +115,7 @@ static VALUE Coolio_StatWatcher_attach(VALUE self, VALUE loop)
ev_tstamp interval, timeout;
struct Coolio_Loop *loop_data;
struct Coolio_Watcher *watcher_data;
-
+
if(!rb_obj_is_kind_of(loop, cCoolio_Loop))
rb_raise(rb_eArgError, "expected loop to be an instance of Coolio::Loop");
@@ -112,13 +130,13 @@ static VALUE Coolio_StatWatcher_attach(VALUE self, VALUE loop)
ev_stat_start(loop_data->ev_loop, &watcher_data->event_types.ev_stat);
rb_call_super(1, &loop);
- return self;
+ return self;
}
/**
* call-seq:
* Coolio::StatWatcher.detach -> Coolio::StatWatcher
- *
+ *
* Detach the stat watcher from its current Coolio::Loop.
*/
static VALUE Coolio_StatWatcher_detach(VALUE self)
@@ -131,23 +149,23 @@ static VALUE Coolio_StatWatcher_detach(VALUE self)
/**
* call-seq:
* Coolio::StatWatcher.enable -> Coolio::StatWatcher
- *
+ *
* Re-enable a stat watcher which has been temporarily disabled. See the
* disable method for a more thorough explanation.
*/
static VALUE Coolio_StatWatcher_enable(VALUE self)
{
Watcher_Enable(stat, self);
- return self;
+ return self;
}
/**
* call-seq:
* Coolio::StatWatcher.disable -> Coolio::StatWatcher
- *
- * Temporarily disable a stat watcher which is attached to a loop.
- * This is useful if you wish to toggle event monitoring on and off.
+ *
+ * Temporarily disable a stat watcher which is attached to a loop.
+ * This is useful if you wish to toggle event monitoring on and off.
*/
static VALUE Coolio_StatWatcher_disable(VALUE self)
{
@@ -159,18 +177,18 @@ static VALUE Coolio_StatWatcher_disable(VALUE self)
/**
* call-seq:
* Coolio::StatWatcher#on_change -> nil
- *
+ *
* Called whenever the status of the given path changes
*/
-static VALUE Coolio_StatWatcher_on_change(VALUE self)
+static VALUE Coolio_StatWatcher_on_change(VALUE self, VALUE previous, VALUE current)
{
return Qnil;
}
/**
* call-seq:
* Coolio::StatWatcher#path -> String
- *
+ *
* Retrieve the path associated with this StatWatcher
*/
static VALUE Coolio_StatWatcher_path(VALUE self)
@@ -186,6 +204,64 @@ static void Coolio_StatWatcher_libev_callback(struct ev_loop *ev_loop, struct ev
/* Coolio::Loop dispatch callback */
static void Coolio_StatWatcher_dispatch_callback(VALUE self, int revents)
-{
- rb_funcall(self, rb_intern("on_change"), 0, 0);
+{
+ struct Coolio_Watcher *watcher_data;
+ Data_Get_Struct(self, struct Coolio_Watcher, watcher_data);
+
+ VALUE previous_statdata = Coolio_StatInfo_build(&watcher_data->event_types.ev_stat.prev);
+ VALUE current_statdata = Coolio_StatInfo_build(&watcher_data->event_types.ev_stat.attr);
+ rb_funcall(self, rb_intern("on_change"), 2, previous_statdata, current_statdata);
+}
+
+/**
+ * Convience method to build StatInfo structs given an ev_statdata
+ * */
+static VALUE Coolio_StatInfo_build(ev_statdata *statdata_struct)
+{
+ VALUE at_method = rb_intern("at");
+ VALUE cTime = rb_const_get(rb_cObject, rb_intern("Time"));
+
+ VALUE mtime = Qnil;
+ VALUE ctime = Qnil;
+ VALUE atime = Qnil;
+ VALUE dev = Qnil;
+ VALUE ino = Qnil;
+ VALUE mode = Qnil;
+ VALUE nlink = Qnil;
+ VALUE uid = Qnil;
+ VALUE gid = Qnil;
+ VALUE rdev = Qnil;
+ VALUE size = Qnil;
+ VALUE blksize = Qnil;
+ VALUE blocks = Qnil;
+
+ mtime = rb_funcall(cTime, at_method, 1, INT2NUM(statdata_struct->st_mtime));
+ ctime = rb_funcall(cTime, at_method, 1, INT2NUM(statdata_struct->st_ctime));
+ atime = rb_funcall(cTime, at_method, 1, INT2NUM(statdata_struct->st_atime));
+ dev = INT2NUM(statdata_struct->st_dev);
+ ino = INT2NUM(statdata_struct->st_ino);
+ mode = INT2NUM(statdata_struct->st_mode);
+ nlink = INT2NUM(statdata_struct->st_nlink);
+ uid = INT2NUM(statdata_struct->st_uid);
+ gid = INT2NUM(statdata_struct->st_gid);
+ rdev = INT2NUM(statdata_struct->st_rdev);
+ size = INT2NUM(statdata_struct->st_size);
+ blksize = INT2NUM(statdata_struct->st_blksize);
+ blocks = INT2NUM(statdata_struct->st_blocks);
+
+ return rb_struct_new(cCoolio_StatInfo,
+ mtime,
+ ctime,
+ atime,
+ dev,
+ ino,
+ mode,
+ nlink,
+ uid,
+ gid,
+ rdev,
+ size,
+ blksize,
+ blocks,
+ NULL);
}
@@ -1,4 +1,4 @@
-require '../rev'
+require "rev"
class Tester < Rev::TCPSocket
def on_resolve_failed
print "resolved failed! that's good!"
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/../rev'
+require "rev"
ADDR = 'wilkboardonline.com'
PORT = 80
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/../rev'
+require "rev"
ADDR = 'wilkboardonline.com'
PORT = 80
@@ -35,13 +35,13 @@ def sleep_until(seconds = 1, interval = 0.1)
stopped = false;
@server.attach(loop)
Thread.new {
- loop.run_nonblock_over_and_over_again
+ loop.run_nonblock_over_and_over_again
stopped = true
}
sleep 0
stopped.should == false
loop.stop
-
+
sleep_until { stopped == true }
stopped.should == true
end
View
@@ -0,0 +1,77 @@
+require File.expand_path('../spec_helper', __FILE__)
+
+TEMP_FILE_PATH = "./test.txt"
+
+INTERVAL = 0.010
+
+class MyStatWatcher < Cool.io::StatWatcher
+ attr_accessor :accessed, :previous, :current
+
+ def initialize(path)
+ super path, INTERVAL
+ end
+
+ def on_change(previous, current)
+ self.accessed = true
+ self.previous = previous
+ self.current = current
+ end
+end
+
+def run_with_file_change(path)
+ reactor = Cool.io::Loop.new
+
+ sw = MyStatWatcher.new(path)
+ sw.attach(reactor)
+
+ tw = Cool.io::TimerWatcher.new(INTERVAL, true)
+ tw.on_timer do
+ reactor.stop if sw.accessed
+ write_file(path)
+ end
+ tw.attach(reactor)
+
+ reactor.run
+
+ tw.detach
+ sw.detach
+
+ sw
+end
+
+def write_file(path)
+ File.open(path, "w+") { |f| f.write(rand.to_s) }
+end
+
+def delete_file(path)
+ File.delete(TEMP_FILE_PATH)
+end
+
+describe Cool.io::StatWatcher do
+
+ let :watcher do
+ run_with_file_change(TEMP_FILE_PATH)
+ end
+
+ before :each do
+ write_file(TEMP_FILE_PATH)
+ end
+
+ after :each do
+ delete_file(TEMP_FILE_PATH)
+ end
+
+ it "fire on_change when the file it is watching is modified" do
+ watcher.accessed.should eql(true)
+ end
+
+ it "should pass previous and current file stat info given a stat watcher" do
+ watcher.previous.ino.should eql(watcher.current.ino)
+ end
+
+ it "should raise when the handler does not take 2 parameters" do
+ class MyStatWatcher < Cool.io::StatWatcher; def on_change; end; end
+ lambda { watcher.accessed }.should raise_error(ArgumentError)
+ end
+
+end

0 comments on commit 3bafe8b

Please sign in to comment.