channel 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,52 @@
1
+ Simple native Channel object for Ruby MRI
2
+ (c) 2009 Lourens Naudé (methodmissing), James Tucker (raggi)
3
+
4
+ http://github.com/methodmissing/channel
5
+
6
+ This library works with Ruby 1.8 and 1.9 and is a more efficient
7
+ implementation of the following Ruby code :
8
+
9
+ class RubyChannel
10
+ def initialize
11
+ @subscribers = []
12
+ end
13
+ def subscribe(&b)
14
+ @subscribers << b
15
+ self
16
+ end
17
+ def <<(o)
18
+ @subscribers.each { |s| s.call(o) }
19
+ end
20
+ end
21
+
22
+ Use cases :
23
+
24
+ * In process message dispatch
25
+ * Parsers and protocols with a message, error and warning channel
26
+ * Facilitates decoupled notification and callback patterns
27
+
28
+ Caveats :
29
+
30
+ * Fixed width channel size for very thin, and fast, subscriber management
31
+
32
+ Examples :
33
+
34
+ counter = 0
35
+ ch = Channel.new.subscribe{|obj| counter += obj }
36
+ ch << 3
37
+ ch << -2
38
+ counter #=> 1
39
+
40
+ Todo :
41
+
42
+ * A deferrable mode that notifies on a background thread (pending 1.9)
43
+ * More flexible notification mechanism through integration with http://github.com/methodmissing/callback/tree/master
44
+ * Filters
45
+
46
+ To run the test suite:
47
+
48
+ rake
49
+
50
+ To run the benchmarks:
51
+
52
+ rake bench
@@ -0,0 +1,69 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ CH_ROOT = 'ext/channel'
6
+
7
+ desc 'Default: test'
8
+ task :default => :test
9
+
10
+ desc 'Run channel tests.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs = [CH_ROOT]
13
+ t.pattern = 'test/test_*.rb'
14
+ t.verbose = true
15
+ end
16
+ task :test => :build
17
+
18
+ namespace :build do
19
+ file "#{CH_ROOT}/channel.c"
20
+ file "#{CH_ROOT}/extconf.rb"
21
+ file "#{CH_ROOT}/Makefile" => %W(#{CH_ROOT}/channel.c #{CH_ROOT}/extconf.rb) do
22
+ Dir.chdir(CH_ROOT) do
23
+ ruby 'extconf.rb'
24
+ end
25
+ end
26
+
27
+ desc "generate makefile"
28
+ task :makefile => %W(#{CH_ROOT}/Makefile #{CH_ROOT}/channel.c)
29
+
30
+ dlext = Config::CONFIG['DLEXT']
31
+ file "#{CH_ROOT}/channel.#{dlext}" => %W(#{CH_ROOT}/Makefile #{CH_ROOT}/channel.c) do
32
+ Dir.chdir(CH_ROOT) do
33
+ sh 'make' # TODO - is there a config for which make somewhere?
34
+ end
35
+ end
36
+
37
+ desc "compile channel extension"
38
+ task :compile => "#{CH_ROOT}/channel.#{dlext}"
39
+
40
+ task :clean do
41
+ Dir.chdir(CH_ROOT) do
42
+ sh 'make clean'
43
+ end if File.exists?("#{CH_ROOT}/Makefile")
44
+ end
45
+
46
+ CLEAN.include("#{CH_ROOT}/Makefile")
47
+ CLEAN.include("#{CH_ROOT}/channel.#{dlext}")
48
+ end
49
+
50
+ task :clean => %w(build:clean)
51
+
52
+ desc "compile"
53
+ task :build => %w(build:compile)
54
+
55
+ task :install do |t|
56
+ Dir.chdir(CH_ROOT) do
57
+ sh 'sudo make install'
58
+ end
59
+ end
60
+
61
+ desc "clean build install"
62
+ task :setup => %w(clean build install)
63
+
64
+ desc "run benchmarks"
65
+ task :bench do |t|
66
+ ruby "bench/bench.rb"
67
+ ruby "bench/bench_deferred.rb"
68
+ end
69
+ task :bench => :build
@@ -0,0 +1,15 @@
1
+ require "benchmark"
2
+ $:.unshift "."
3
+ require File.dirname(__FILE__) + "/../ext/channel/channel"
4
+ require File.dirname(__FILE__) + "/ruby_channel"
5
+
6
+ channel = Channel.new(16)
7
+ 16.times{ channel.subscribe{|o| o } }
8
+ rb_channel = RubyChannel.new(16)
9
+ 16.times{ rb_channel.subscribe{|o| o } }
10
+
11
+ TESTS = 10_000
12
+ Benchmark.bmbm do |results|
13
+ results.report("Channel#<<") { TESTS.times { channel << :obj } }
14
+ results.report("RubyChannel#<<") { TESTS.times { rb_channel << :obj } }
15
+ end
@@ -0,0 +1,19 @@
1
+ class RubyChannel
2
+ def initialize(size = 1,defer = false)
3
+ @size = size
4
+ @subscribers = []
5
+ @defer = defer
6
+ end
7
+ def subscribe(&b)
8
+ raise ArgumentError.new("Maximum number of subscribers exceeded!") unless @subscribers.size < @size
9
+ @subscribers << b
10
+ self
11
+ end
12
+ def <<(o)
13
+ unless @defer
14
+ @subscribers.each { |s| s.call(o) }
15
+ else
16
+ Thread.new{ @subscribers.each { |s| s.call(o) } }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "channel"
3
+ s.version = "1.0.1"
4
+ s.date = "2009-08-28"
5
+ s.summary = "Native MRI channel"
6
+ s.email = "lourens@methodmissing.com"
7
+ s.homepage = "http://github.com/methodmissing/channel"
8
+ s.description = "Simple native Channel object for Ruby MRI (1.8.{6,7} and 1.9.2)"
9
+ s.has_rdoc = true
10
+ s.authors = ["Lourens Naudé (methodmissing)","James Tucker (raggi)"]
11
+ s.platform = Gem::Platform::RUBY
12
+ s.files = %w[
13
+ README
14
+ Rakefile
15
+ bench/bench.rb
16
+ bench/ruby_channel.rb
17
+ ext/channel/extconf.rb
18
+ ext/channel/channel.c
19
+ channel.gemspec
20
+ ] + Dir.glob('test/*')
21
+ s.rdoc_options = ["--main", "README"]
22
+ s.extra_rdoc_files = ["README"]
23
+ s.extensions << "ext/channel/extconf.rb"
24
+ end
@@ -0,0 +1,157 @@
1
+ #include "ruby.h"
2
+
3
+ typedef struct {
4
+ int size;
5
+ int defer;
6
+ int sbs;
7
+ VALUE *subscribers;
8
+ } RChannel;
9
+
10
+ typedef struct{
11
+ RChannel *chs;
12
+ VALUE *obj;
13
+ } deferred_push_args;
14
+
15
+ #define MAX_CHANNEL_SIZE 32
16
+ #define GetChannelStruct(obj) (Check_Type(obj, T_DATA), (RChannel*)DATA_PTR(obj))
17
+
18
+ VALUE rb_cChannel;
19
+ static ID id_call;
20
+
21
+ static void mark_channel(RChannel* ch)
22
+ {
23
+ int i;
24
+ for (i=0; i < ch->sbs; i++) {
25
+ rb_gc_mark(ch->subscribers[i]);
26
+ }
27
+ }
28
+
29
+ static void free_channel(RChannel* ch)
30
+ {
31
+ xfree(ch);
32
+ }
33
+
34
+ static VALUE channel_alloc _((VALUE));
35
+ static VALUE
36
+ channel_alloc( VALUE klass )
37
+ {
38
+ VALUE ch;
39
+ RChannel* chs;
40
+ ch = Data_Make_Struct(klass, RChannel, mark_channel, free_channel, chs);
41
+ chs->size = 0;
42
+ chs->defer = 0;
43
+ chs->sbs = 0;
44
+ chs->subscribers = 0;
45
+
46
+ return ch;
47
+ }
48
+
49
+ static VALUE
50
+ rb_channel_new(VALUE ch, int size, int defer)
51
+ {
52
+ RChannel* chs = GetChannelStruct(ch);
53
+ if (size < 0 || size > MAX_CHANNEL_SIZE){
54
+ rb_raise(rb_eArgError, "Invalid channel size!");
55
+ }
56
+ if (size == 0) size++;
57
+ chs->size = size;
58
+ chs->defer = defer;
59
+ chs->subscribers = ALLOC_N(VALUE, size);
60
+ return ch;
61
+ }
62
+
63
+ static VALUE
64
+ rb_channel_initialize( int argc, VALUE *argv, VALUE ch )
65
+ {
66
+ VALUE size, defer;
67
+ int channel_size, def;
68
+ rb_scan_args(argc, argv, "02", &size, &defer);
69
+ channel_size = NIL_P(size) ? 0 : FIX2INT(size);
70
+ def = (defer == Qtrue) ? 1 : 0;
71
+ return rb_channel_new(ch, channel_size, def);
72
+ }
73
+
74
+ static VALUE
75
+ rb_channel_size( VALUE ch )
76
+ {
77
+ RChannel* chs = GetChannelStruct(ch);
78
+ return INT2FIX(chs->size);
79
+ }
80
+
81
+ static VALUE
82
+ rb_channel_subscribers( VALUE ch )
83
+ {
84
+ RChannel* chs = GetChannelStruct(ch);
85
+ return INT2FIX(chs->sbs);
86
+ }
87
+
88
+ static VALUE
89
+ rb_channel_deferrable_p( VALUE ch )
90
+ {
91
+ RChannel* chs = GetChannelStruct(ch);
92
+ return (chs->defer == 1) ? Qtrue : Qfalse;
93
+ }
94
+
95
+ static VALUE
96
+ rb_channel_subscribe( int argc, VALUE *argv, VALUE ch )
97
+ {
98
+ VALUE cb;
99
+ RChannel* chs = GetChannelStruct(ch);
100
+ if (!rb_block_given_p()) rb_raise(rb_eArgError, "Block callback required!");
101
+ if (chs->sbs == chs->size) rb_raise(rb_eArgError, "Maximum number of subscribers exceeded!");
102
+ cb = rb_block_proc();
103
+ chs->subscribers[chs->sbs] = cb;
104
+ chs->sbs++;
105
+ return ch;
106
+ }
107
+
108
+ static void
109
+ rb_channel_push0( RChannel* chs, VALUE *obj )
110
+ {
111
+ int i;
112
+ for (i=0; i < chs->sbs; i++) {
113
+ rb_funcall(chs->subscribers[i],id_call,1,*obj);
114
+ }
115
+ }
116
+
117
+ static void
118
+ rb_channel_push1( deferred_push_args *args )
119
+ {
120
+ RChannel* chs = args->chs;
121
+ VALUE obj = *(args->obj);
122
+ int i;
123
+ for (i=0; i < chs->sbs; i++) {
124
+ rb_funcall(chs->subscribers[i],id_call,1,obj);
125
+ }
126
+ }
127
+
128
+ static VALUE
129
+ rb_channel_push( VALUE ch, VALUE obj )
130
+ {
131
+ deferred_push_args args;
132
+ RChannel* chs = GetChannelStruct(ch);
133
+ if (chs->defer == 1){
134
+ args.chs = chs;
135
+ args.obj = &obj;
136
+ rb_thread_create(rb_channel_push1,&args);
137
+ }else{
138
+ rb_channel_push0(chs,&obj);
139
+ }
140
+ return ch;
141
+ }
142
+
143
+ void
144
+ Init_channel()
145
+ {
146
+ id_call = rb_intern("call");
147
+
148
+ rb_cChannel = rb_define_class("Channel", rb_cObject);
149
+ rb_define_alloc_func(rb_cChannel, channel_alloc);
150
+
151
+ rb_define_method(rb_cChannel,"initialize", rb_channel_initialize, -1);
152
+ rb_define_method(rb_cChannel,"size", rb_channel_size, 0);
153
+ rb_define_method(rb_cChannel,"subscribers", rb_channel_subscribers, 0);
154
+ rb_define_method(rb_cChannel,"deferrable?", rb_channel_deferrable_p, 0);
155
+ rb_define_method(rb_cChannel,"subscribe", rb_channel_subscribe, -1);
156
+ rb_define_method(rb_cChannel,"<<", rb_channel_push, 1);
157
+ }
@@ -0,0 +1,12 @@
1
+ require 'mkmf'
2
+
3
+ def add_define(name)
4
+ $defs.push("-D#{name}")
5
+ end
6
+
7
+ dir_config('channel')
8
+
9
+ add_define 'RUBY19' if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
10
+ add_define 'RUBY18' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
11
+
12
+ create_makefile('channel')
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require 'channel'
@@ -0,0 +1,65 @@
1
+ $:.unshift "."
2
+ require File.dirname(__FILE__) + '/helper'
3
+ require 'timeout'
4
+
5
+ class TestChannel < Test::Unit::TestCase
6
+
7
+ def test_initialize
8
+ assert_instance_of Channel, Channel.new
9
+ assert_raises(ArgumentError){ Channel.new(-1) }
10
+ assert_raises(ArgumentError){ Channel.new(33) }
11
+ end
12
+
13
+ def test_initialize_deferrable
14
+ assert !Channel.new(10).deferrable?
15
+ assert Channel.new(10,true).deferrable?
16
+ end
17
+
18
+ def test_size
19
+ assert_equal 10, Channel.new(10).size
20
+ end
21
+
22
+ def test_subscribers
23
+ assert_equal 0, Channel.new(10).subscribers
24
+ end
25
+
26
+ def test_subscribe
27
+ assert_equal 1, Channel.new.subscribe{|obj| obj }.subscribers
28
+ assert_raises(ArgumentError){ Channel.new.subscribe }
29
+ assert_raises(ArgumentError){ Channel.new.subscribe(:arg) }
30
+ end
31
+
32
+ def test_subscriber_threshold
33
+ ch = Channel.new(3)
34
+ assert_raises ArgumentError do
35
+ 4.times{ ch.subscribe{|o| o } }
36
+ end
37
+ end
38
+
39
+ def test_push
40
+ @counter = 0
41
+ ch = Channel.new.subscribe{|obj| @counter += obj }
42
+ ch << 3
43
+ ch << -2
44
+ assert_equal 1, @counter
45
+ end
46
+
47
+ def test_push_deferred
48
+ ch = Channel.new(3).subscribe{|obj| sleep(obj) }
49
+ assert_raises Timeout::Error do
50
+ Timeout.timeout(0.8) do
51
+ ch << 1
52
+ end
53
+ end
54
+ ch = Channel.new(3,true).subscribe{|obj| sleep(obj) }
55
+ assert_nothing_raised do
56
+ Timeout.timeout(0.8) do
57
+ ch << 1
58
+ ch << 2
59
+ ch << 3
60
+ end
61
+ end
62
+ ch << 0.5
63
+ sleep 0.7
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: channel
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - "Lourens Naud\xC3\xA9 (methodmissing)"
8
+ - James Tucker (raggi)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-08-28 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Simple native Channel object for Ruby MRI (1.8.{6,7} and 1.9.2)
18
+ email: lourens@methodmissing.com
19
+ executables: []
20
+
21
+ extensions:
22
+ - ext/channel/extconf.rb
23
+ extra_rdoc_files:
24
+ - README
25
+ files:
26
+ - README
27
+ - Rakefile
28
+ - bench/bench.rb
29
+ - bench/ruby_channel.rb
30
+ - ext/channel/extconf.rb
31
+ - ext/channel/channel.c
32
+ - channel.gemspec
33
+ - test/helper.rb
34
+ - test/test_channel.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/methodmissing/channel
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --main
42
+ - README
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Native MRI channel
64
+ test_files: []
65
+