dispatch 0.0.1pre
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/.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
|