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 +52 -0
- data/Rakefile +69 -0
- data/bench/bench.rb +15 -0
- data/bench/ruby_channel.rb +19 -0
- data/channel.gemspec +24 -0
- data/ext/channel/channel.c +157 -0
- data/ext/channel/extconf.rb +12 -0
- data/test/helper.rb +2 -0
- data/test/test_channel.rb +65 -0
- metadata +65 -0
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
|
data/Rakefile
ADDED
@@ -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
|
data/bench/bench.rb
ADDED
@@ -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
|
data/channel.gemspec
ADDED
@@ -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')
|
data/test/helper.rb
ADDED
@@ -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
|
+
|