channel 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+