dispatch 0.0.1pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +14 -0
- data/README.rdoc +670 -0
- data/Rakefile +2 -0
- data/dispatch.gemspec +21 -0
- data/examples/benchmarks.rb +90 -0
- data/examples/dispatch_methods.rb +277 -0
- data/examples/dispatch_methods.sh +5 -0
- data/examples/futures.rb +34 -0
- data/examples/ring_buffer.rb +95 -0
- data/examples/sleeping_barber.rb +19 -0
- data/lib/dispatch.rb +22 -0
- data/lib/dispatch/enumerable.rb +108 -0
- data/lib/dispatch/job.rb +52 -0
- data/lib/dispatch/proxy.rb +49 -0
- data/lib/dispatch/queue.rb +34 -0
- data/lib/dispatch/source.rb +83 -0
- data/lib/dispatch/version.rb +3 -0
- data/spec/enumerable_spec.rb +188 -0
- data/spec/job_spec.rb +103 -0
- data/spec/proxy_spec.rb +105 -0
- data/spec/queue_spec.rb +45 -0
- data/spec/source_spec.rb +190 -0
- data/spec/spec_helper.rb +60 -0
- metadata +99 -0
data/spec/job_spec.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if MACOSX_VERSION >= 10.6
|
4
|
+
describe "Dispatch::Job" do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@result = 0
|
8
|
+
@job = Dispatch::Job.new
|
9
|
+
end
|
10
|
+
|
11
|
+
describe :new do
|
12
|
+
it "should return a Job for tracking execution of passed blocks" do
|
13
|
+
@job.should be_kind_of Dispatch::Job
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should use default queue" do
|
17
|
+
@job.add { Dispatch::Queue.current }
|
18
|
+
@job.value.to_s.should == Dispatch::Queue.concurrent.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should take an optional queue" do
|
22
|
+
q = Dispatch::Queue.concurrent(:high)
|
23
|
+
job = Dispatch::Job.new(q) { Dispatch::Queue.current }
|
24
|
+
job.value.to_s.should == q.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe :group do
|
29
|
+
it "should return an instance of Dispatch::Group" do
|
30
|
+
@job.group.should be_kind_of Dispatch::Group
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :values do
|
35
|
+
it "should return an instance of Dispatch::Proxy" do
|
36
|
+
@job.values.should be_kind_of Dispatch::Proxy
|
37
|
+
end
|
38
|
+
|
39
|
+
it "has a __value__ that is Enumerable" do
|
40
|
+
@job.values.__value__.should be_kind_of Enumerable
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe :add do
|
45
|
+
it "should schedule a block for async execution" do
|
46
|
+
@value = 0
|
47
|
+
@job.add { sleep 0.01; @value = 42 }
|
48
|
+
@value.should == 0
|
49
|
+
@job.join
|
50
|
+
@value.should == 42
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe :join do
|
55
|
+
it "should wait when called Synchronously" do
|
56
|
+
@value = 0
|
57
|
+
@job.add { sleep 0.01; @value = 42 }
|
58
|
+
@job.join
|
59
|
+
@value.should == 42
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should invoke passed block Asynchronously" do
|
63
|
+
@value = 0
|
64
|
+
@rval = 0
|
65
|
+
@job.add { sleep 0.01; @value = 42 }
|
66
|
+
q = Dispatch::Queue.for(@value)
|
67
|
+
@job.join(q) { sleep 0.01; @rval = @value }
|
68
|
+
@job.join
|
69
|
+
@rval.should == 0
|
70
|
+
q.sync { }
|
71
|
+
@rval.should == 42
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe :value do
|
76
|
+
it "should return value when called Synchronously" do
|
77
|
+
@job.add { Math.sqrt(2**10) }
|
78
|
+
@job.value.should == 2**5
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should invoke passed block Asynchronously with return value" do
|
82
|
+
@job.add { Math.sqrt(2**10) }
|
83
|
+
@value = 0
|
84
|
+
q = Dispatch::Queue.for(@value)
|
85
|
+
@job.value(q) {|v| @value = v}
|
86
|
+
@job.join
|
87
|
+
q.sync { }
|
88
|
+
@value.should == 2**5
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe :synchronize do
|
93
|
+
it "should return serialization Proxy for passed object" do
|
94
|
+
actee = {}
|
95
|
+
actor = @job.sync(actee)
|
96
|
+
actor.should be_kind_of Dispatch::Proxy
|
97
|
+
actor.__value__.should == actee
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
data/spec/proxy_spec.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if MACOSX_VERSION >= 10.6
|
4
|
+
|
5
|
+
class Delegate
|
6
|
+
def initialize(s); @s = s; end
|
7
|
+
def current_queue; Dispatch::Queue.current; end
|
8
|
+
def takes_block(&block); block.call; end
|
9
|
+
def get_number(); sleep 0.01; 42; end
|
10
|
+
def set_name(s); sleep 0.01; @s = s; end
|
11
|
+
def to_s; @s.to_s; end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "Dispatch::Proxy" do
|
15
|
+
before :each do
|
16
|
+
@delegate_name = "my_delegate"
|
17
|
+
@delegate = Delegate.new(@delegate_name)
|
18
|
+
@proxy = Dispatch::Proxy.new(@delegate)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe :new do
|
22
|
+
it "returns a Dispatch::Proxy" do
|
23
|
+
@proxy.should be_kind_of Dispatch::Proxy
|
24
|
+
@proxy.should be_kind_of SimpleDelegator
|
25
|
+
end
|
26
|
+
|
27
|
+
it "takes an optional group and queue for callbacks" do
|
28
|
+
g = Dispatch::Group.new
|
29
|
+
q = Dispatch::Queue.concurrent(:high)
|
30
|
+
proxy = Dispatch::Proxy.new(@delegate, g, q)
|
31
|
+
proxy.__group__.should == g
|
32
|
+
proxy.__queue__.should == q
|
33
|
+
end
|
34
|
+
|
35
|
+
it "creates a default Group if none specified" do
|
36
|
+
@proxy.__group__.should be_kind_of Dispatch::Group
|
37
|
+
end
|
38
|
+
|
39
|
+
it "uses default queue if none specified" do
|
40
|
+
@proxy.__queue__.should == Dispatch::Queue.concurrent
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe :delegate do
|
45
|
+
it "should be returned by __getobj__" do
|
46
|
+
@proxy.__getobj__.should == @delegate
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be invoked for methods it defines" do
|
50
|
+
@proxy.to_s.should == @delegate_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe :method_missing do
|
55
|
+
it "runs methods on a private serial queue" do
|
56
|
+
q = @proxy.current_queue
|
57
|
+
q.label.should =~ /proxy/
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return Synchronously if block NOT given" do
|
61
|
+
retval = @proxy.get_number
|
62
|
+
retval.should == 42
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should otherwise return Asynchronous to block, if given" do
|
66
|
+
@value = 0
|
67
|
+
retval = @proxy.get_number { |v| @value = v }
|
68
|
+
@value.should == 0
|
69
|
+
retval.should == nil
|
70
|
+
@proxy.__group__.wait
|
71
|
+
@value.should == 42
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe :__value__ do
|
76
|
+
it "should complete work and return delegate" do
|
77
|
+
new_name = "nobody"
|
78
|
+
@proxy.set_name(new_name) { }
|
79
|
+
d = @proxy.__value__
|
80
|
+
d.should be_kind_of Delegate
|
81
|
+
d.to_s.should == new_name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "state" do
|
86
|
+
it "should persist for collection objects" do
|
87
|
+
actor = Dispatch::Proxy.new([])
|
88
|
+
actor.size.should == 0
|
89
|
+
actor << :foo
|
90
|
+
actor.size.should == 1
|
91
|
+
actor[42] = :foo
|
92
|
+
actor.size.should == 43
|
93
|
+
actor.should be_kind_of Dispatch::Proxy
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should NOT persist under assignment" do
|
97
|
+
actor = Dispatch::Proxy.new(0)
|
98
|
+
actor.should be_kind_of Dispatch::Proxy
|
99
|
+
actor += 1
|
100
|
+
actor.should_not be_kind_of Dispatch::Proxy
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/spec/queue_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if MACOSX_VERSION >= 10.6
|
4
|
+
|
5
|
+
describe "Dispatch::Queue" do
|
6
|
+
before :each do
|
7
|
+
@my_object = "Hello, World!"
|
8
|
+
@q = Dispatch::Queue.for(@my_object)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe :labelize do
|
12
|
+
it "should return a unique label for any object" do
|
13
|
+
s1 = Dispatch::Queue.labelize @my_object
|
14
|
+
s2 = Dispatch::Queue.labelize @my_object
|
15
|
+
s1.should_not == s2
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe :for do
|
20
|
+
it "should return a dispatch queue" do
|
21
|
+
@q.should be_kind_of Dispatch::Queue
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return a unique queue for each object" do
|
25
|
+
q = Dispatch::Queue.for(@my_object)
|
26
|
+
@q.should_not == q
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return a unique label for each queue" do
|
30
|
+
q = Dispatch::Queue.for(@my_object)
|
31
|
+
@q.to_s.should_not == q.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe :join do
|
36
|
+
it "should wait until pending blocks execute " do
|
37
|
+
@n = 0
|
38
|
+
@q.async {@n = 42}
|
39
|
+
@n.should == 0
|
40
|
+
@q.join
|
41
|
+
@n.should == 42
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/source_spec.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if MACOSX_VERSION >= 10.6
|
4
|
+
|
5
|
+
describe "Dispatch::Source" do
|
6
|
+
before :each do
|
7
|
+
@q = Dispatch::Queue.new('org.macruby.gcd_spec.prelude')
|
8
|
+
@src = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
after :each do
|
12
|
+
@src.cancel! if not @src.nil? and not @src.cancelled?
|
13
|
+
@q.sync { }
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
describe :event2num do
|
18
|
+
it "converts PROC symbol to int" do
|
19
|
+
Dispatch::Source.event2num(:signal).should == Dispatch::Source::PROC_SIGNAL
|
20
|
+
end
|
21
|
+
|
22
|
+
it "converts VNODE symbol to int" do
|
23
|
+
Dispatch::Source.event2num(:rename).should == Dispatch::Source::VNODE_RENAME
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe :data2events do
|
28
|
+
it "converts PROC bitfields to symbols" do
|
29
|
+
mask = Dispatch::Source::PROC_EXIT | Dispatch::Source::PROC_SIGNAL
|
30
|
+
events = Dispatch::Source.data2events(mask)
|
31
|
+
events.include?(:signal).should == true
|
32
|
+
events.include?(:fork).should == false
|
33
|
+
end
|
34
|
+
|
35
|
+
it "converts VNODE bitfields to symbols" do
|
36
|
+
mask = Dispatch::Source::VNODE_DELETE | Dispatch::Source::VNODE_WRITE
|
37
|
+
events = Dispatch::Source.data2events(mask)
|
38
|
+
events.include?(:delete).should == true
|
39
|
+
events.include?(:rename).should == false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "add" do
|
44
|
+
it "fires with data on summed inputs" do
|
45
|
+
@count = 0
|
46
|
+
@src = Dispatch::Source.add(@q) {|s| @count += s.data}
|
47
|
+
@src << 20
|
48
|
+
@src << 22
|
49
|
+
@q.sync {}
|
50
|
+
@count.should == 42
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "or" do
|
55
|
+
it "fires with data on ORed inputs" do
|
56
|
+
@count = 0
|
57
|
+
@src = Dispatch::Source.or(@q) {|s| @count += s.data}
|
58
|
+
@src << 0b101_000
|
59
|
+
@src << 0b000_010
|
60
|
+
@q.sync {}
|
61
|
+
@count.should == 42
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "PROC" do
|
66
|
+
before :each do
|
67
|
+
@signal = Signal.list["USR1"]
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "process" do
|
71
|
+
|
72
|
+
it "fires with data on process event(s)" do
|
73
|
+
@event = 0
|
74
|
+
@events = []
|
75
|
+
@src = Dispatch::Source.process($$, %w(exit fork exec signal), @q) do |s|
|
76
|
+
@event += s.data
|
77
|
+
@events += Dispatch::Source.data2events(s.data)
|
78
|
+
end
|
79
|
+
Signal.trap(@signal, "IGNORE")
|
80
|
+
Process.kill(@signal, $$)
|
81
|
+
Signal.trap(@signal, "DEFAULT")
|
82
|
+
@q.sync {}
|
83
|
+
@events.include?(:signal).should == true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can use bitfields as well as arrays" do
|
87
|
+
mask = Dispatch::Source::PROC_EXIT | Dispatch::Source::PROC_SIGNAL
|
88
|
+
@event = 0
|
89
|
+
@src = Dispatch::Source.process($$, mask, @q) { |s| @event |= s.data }
|
90
|
+
Signal.trap(@signal, "IGNORE")
|
91
|
+
Process.kill(@signal, $$)
|
92
|
+
Signal.trap(@signal, "DEFAULT")
|
93
|
+
@q.sync {}
|
94
|
+
@event.should == Dispatch::Source.event2num(:signal)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "signal" do
|
99
|
+
it "fires with data on signal count" do
|
100
|
+
@count = 0
|
101
|
+
@src = Dispatch::Source.signal(@signal, @q) {|s| @count += s.data}
|
102
|
+
Signal.trap(@signal, "IGNORE")
|
103
|
+
Process.kill(@signal, $$)
|
104
|
+
Process.kill(@signal, $$)
|
105
|
+
Signal.trap(@signal, "DEFAULT")
|
106
|
+
@q.sync {}
|
107
|
+
@count.should == 2
|
108
|
+
@src.cancel!
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "VNODE" do
|
114
|
+
before :each do
|
115
|
+
@msg = "#{$$}-#{Time.now.to_s.gsub(' ','_')}"
|
116
|
+
@filename = tmp("gcd_spec_source-#{@msg}")
|
117
|
+
@file = nil
|
118
|
+
@src = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
after :each do
|
122
|
+
@src.cancel! if not @src.nil? and not @src.cancelled?
|
123
|
+
@q.sync { }
|
124
|
+
@file.close if not @file.closed?
|
125
|
+
File.delete(@filename)
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "read" do
|
129
|
+
it "fires with data on readable bytes" do
|
130
|
+
File.open(@filename, "w") {|f| f.print @msg}
|
131
|
+
@file = File.open(@filename, "r")
|
132
|
+
@result = ""
|
133
|
+
@src = Dispatch::Source.read(@file, @q) {|s| @result<<@file.read(s.data)}
|
134
|
+
while (@result.size < @msg.size) do; end
|
135
|
+
@q.sync { }
|
136
|
+
@result.should == @msg
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "write" do
|
141
|
+
it "fires with data on writable bytes" do
|
142
|
+
@file = File.open(@filename, "w")
|
143
|
+
@message = @msg
|
144
|
+
@src = Dispatch::Source.write(@file, @q) do |s|
|
145
|
+
if @message.size > 0 then
|
146
|
+
char = @message[0..0]
|
147
|
+
@file.write(char)
|
148
|
+
@message = @message[1..-1]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
while (@message.size > 0) do; end
|
152
|
+
result = File.read(@filename)
|
153
|
+
@result.should == @msg
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "file" do
|
158
|
+
it "fires with data on file events" do
|
159
|
+
write_flag = Dispatch::Source.event2num(:write)
|
160
|
+
@file = File.open(@filename, "w")
|
161
|
+
@fired = false
|
162
|
+
@mask = 0
|
163
|
+
events = %w(delete write extend attrib link rename revoke)
|
164
|
+
@src = Dispatch::Source.file(@file, events, @q) do |s|
|
165
|
+
@mask |= s.data
|
166
|
+
@fired = true
|
167
|
+
end
|
168
|
+
@file.write(@msg)
|
169
|
+
@file.flush
|
170
|
+
@q.sync { }
|
171
|
+
@fired.should == true
|
172
|
+
(@mask & write_flag).should == write_flag
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "periodic" do
|
178
|
+
it "fires with data on how often the timer has fired" do
|
179
|
+
@count = -1
|
180
|
+
repeats = 2
|
181
|
+
@periodic = 0.02
|
182
|
+
@src = Dispatch::Source.periodic(@periodic, @q) {|s| @count += s.data}
|
183
|
+
sleep repeats*@periodic
|
184
|
+
@q.sync { }
|
185
|
+
@count.should == repeats
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "../lib/dispatch")
|
2
|
+
|
3
|
+
framework 'Cocoa'
|
4
|
+
|
5
|
+
SPEC_ROOT = File.dirname(__FILE__)
|
6
|
+
FIXTURES = File.join(SPEC_ROOT, "fixtures")
|
7
|
+
|
8
|
+
class FixtureCompiler
|
9
|
+
def self.require!(fixture)
|
10
|
+
new(fixture).require!
|
11
|
+
end
|
12
|
+
|
13
|
+
FRAMEWORKS = %w{ Foundation }
|
14
|
+
ARCHS = %w{ i386 x86_64 }
|
15
|
+
OPTIONS = %w{ -g -dynamiclib -fobjc-gc -Wl,-undefined,dynamic_lookup }
|
16
|
+
GCC = "/usr/bin/gcc"
|
17
|
+
|
18
|
+
attr_reader :gcc, :frameworks, :archs, :options
|
19
|
+
attr_reader :fixture, :bundle, :bridge_support
|
20
|
+
|
21
|
+
def initialize(fixture)
|
22
|
+
@fixture = File.join(FIXTURES, "#{fixture}.m")
|
23
|
+
@bundle = File.join("/tmp", "#{fixture}.bundle")
|
24
|
+
@bridge_support = File.join(FIXTURES, "#{fixture}.bridgesupport")
|
25
|
+
|
26
|
+
@gcc, @frameworks, @archs, @options = [GCC, FRAMEWORKS, ARCHS, OPTIONS].map { |x| x.dup }
|
27
|
+
end
|
28
|
+
|
29
|
+
def require!
|
30
|
+
compile!
|
31
|
+
load!
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def needs_update?
|
37
|
+
!File.exist?(bundle) or File.mtime(fixture) > File.mtime(bundle)
|
38
|
+
end
|
39
|
+
|
40
|
+
def compile!
|
41
|
+
if needs_update?
|
42
|
+
puts "[!] Compiling fixture `#{fixture}'"
|
43
|
+
|
44
|
+
a = archs.map { |a| "-arch #{a}" }.join(' ')
|
45
|
+
o = options.join(' ')
|
46
|
+
f = frameworks.map { |f| "-framework #{f}" }.join(' ')
|
47
|
+
|
48
|
+
`#{gcc} #{fixture} -o #{bundle} #{f} #{o} #{a}`
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def load!
|
53
|
+
require bundle[0..-8]
|
54
|
+
if File.exist? bridge_support
|
55
|
+
load_bridge_support_file bridge_support
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
MACOSX_VERSION = `sw_vers -productVersion`.to_f
|