empathy 0.0.1.RC0 → 0.0.1.RC2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/monitor.rb ADDED
@@ -0,0 +1,209 @@
1
+ # See MRI Ruby's MonitorMixin
2
+ # This file is derived from MRI Ruby 1.9.3 monitor.rb
3
+ #
4
+ # Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
5
+ #
6
+ # The only changes are constant lookups, it dynamically uses thread constants
7
+ # from the included class/extended object
8
+ #
9
+ # Note this relaces monitor.rb defined by ruby - so even if you don't include empathy
10
+ # you may end up using this module.
11
+ module MonitorMixin
12
+
13
+ # A condition variable associated with a monitor. Do not instantiate directly.
14
+ # @see MonitorMixin#new_cond
15
+ class ConditionVariable
16
+ #
17
+ # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
18
+ # @param [Numeric] timeout maximum time, in seconds, to wait for a signal
19
+ # @return [true]
20
+ def wait(timeout = nil)
21
+ @monitor.__send__(:mon_check_owner)
22
+ count = @monitor.__send__(:mon_exit_for_cond)
23
+ begin
24
+ @cond.wait(@monitor.instance_variable_get("@mon_mutex"), timeout)
25
+ return true
26
+ ensure
27
+ @monitor.__send__(:mon_enter_for_cond, count)
28
+ end
29
+ end
30
+
31
+ #
32
+ # Calls wait repeatedly while the given block yields a truthy value.
33
+ #
34
+ # @return [true]
35
+ def wait_while
36
+ while yield
37
+ wait
38
+ end
39
+ end
40
+
41
+ #
42
+ # Calls wait repeatedly until the given block yields a truthy value.
43
+ #
44
+ # @return [true]
45
+ def wait_until
46
+ until yield
47
+ wait
48
+ end
49
+ end
50
+
51
+ #
52
+ # Wakes up the first thread in line waiting for this lock.
53
+ #
54
+ def signal
55
+ @monitor.__send__(:mon_check_owner)
56
+ @cond.signal
57
+ end
58
+
59
+ #
60
+ # Wakes up all threads waiting for this lock.
61
+ #
62
+ def broadcast
63
+ @monitor.__send__(:mon_check_owner)
64
+ @cond.broadcast
65
+ end
66
+
67
+ private
68
+
69
+ def initialize(monitor)
70
+ @monitor = monitor
71
+ @cond = monitor.__send__(:mon_class_lookup,:ConditionVariable).new
72
+ end
73
+ end
74
+
75
+ # @private
76
+ def self.extend_object(obj)
77
+ super(obj)
78
+ obj.__send__(:mon_initialize)
79
+ end
80
+
81
+ #
82
+ # Attempts to enter exclusive section.
83
+ #
84
+ # @return [Boolean] whether entry was successful
85
+ def mon_try_enter
86
+ if @mon_owner != mon_class_lookup(:Thread).current
87
+ unless @mon_mutex.try_lock
88
+ return false
89
+ end
90
+ @mon_owner = mon_class_lookup(:Thread).current
91
+ end
92
+ @mon_count += 1
93
+ return true
94
+ end
95
+ # For backward compatibility
96
+ alias try_mon_enter mon_try_enter
97
+
98
+ #
99
+ # Enters exclusive section.
100
+ #
101
+ # @return [void]
102
+ def mon_enter
103
+ if @mon_owner != mon_class_lookup(:Thread).current
104
+ @mon_mutex.lock
105
+ @mon_owner = mon_class_lookup(:Thread).current
106
+ end
107
+ @mon_count += 1
108
+ end
109
+
110
+ #
111
+ # Leaves exclusive section.
112
+ #
113
+ # @return [void]
114
+ def mon_exit
115
+ mon_check_owner
116
+ @mon_count -=1
117
+ if @mon_count == 0
118
+ @mon_owner = nil
119
+ @mon_mutex.unlock
120
+ end
121
+ end
122
+
123
+ #
124
+ # Enters exclusive section and executes the block. Leaves the exclusive
125
+ # section automatically when the block exits.
126
+ # @return [void]
127
+ def mon_synchronize
128
+ mon_enter
129
+ begin
130
+ yield
131
+ ensure
132
+ mon_exit
133
+ end
134
+ end
135
+ alias synchronize mon_synchronize
136
+
137
+ #
138
+ # @return [ConditionVariable] a new condition variable associated with the receiver.
139
+ #
140
+ def new_cond
141
+ return ConditionVariable.new(self)
142
+ end
143
+
144
+ private
145
+
146
+ # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
147
+ # of this constructor. Have look at the examples above to understand how to
148
+ # use this module.
149
+ def initialize(*args)
150
+ super
151
+ mon_initialize
152
+ end
153
+
154
+ # Initializes the MonitorMixin after being included in a class or when an
155
+ # object has been extended with the MonitorMixin
156
+ def mon_initialize
157
+ @mon_owner = nil
158
+ @mon_count = 0
159
+
160
+ # Find the appropriate empathised module to use when resolving references to Thread, Mutex etc..
161
+ parts = self.class.name.split("::")[0..-2]
162
+ parents = parts.inject([Object]) { |result,name| result.unshift(result.first.const_get(name,false)) }
163
+ @mon_empathised_module = parents.detect { |p| p.instance_variable_get(:@empathised) } || Object
164
+
165
+ @mon_mutex = mon_class_lookup(:Mutex).new
166
+ end
167
+
168
+ def mon_check_owner
169
+ if @mon_owner != mon_class_lookup(:Thread).current
170
+ raise mon_class_lookup(:ThreadError), "current thread not owner"
171
+ end
172
+ end
173
+
174
+ def mon_enter_for_cond(count)
175
+ @mon_owner = mon_class_lookup(:Thread).current
176
+ @mon_count = count
177
+ end
178
+
179
+ def mon_exit_for_cond
180
+ count = @mon_count
181
+ @mon_owner = nil
182
+ @mon_count = 0
183
+ return count
184
+ end
185
+
186
+ # looks up namespace hierarchy for an empathised module
187
+ def mon_class_lookup(const)
188
+ @mon_empathised_module.const_get(const,false)
189
+ end
190
+ end
191
+
192
+ # Use the Monitor class when you want to have a lock object for blocks with
193
+ # mutual exclusion.
194
+ # @example
195
+ # require 'monitor'
196
+ #
197
+ # lock = Monitor.new
198
+ # lock.synchronize do
199
+ # # exclusive access
200
+ # end
201
+ #
202
+ class Monitor
203
+ include MonitorMixin
204
+ alias try_enter try_mon_enter
205
+ alias enter mon_enter
206
+ alias exit mon_exit
207
+ end
208
+
209
+
@@ -1,4 +1,4 @@
1
- require 'empathy/thread'
1
+ require 'empathy/with_all_of_ruby'
2
2
  require 'mspec'
3
3
  require 'mspec/guards/empathy'
4
4
  module MSpec
@@ -0,0 +1,162 @@
1
+ require "monitor"
2
+ require File.expand_path('../spec_helper', __FILE__)
3
+
4
+ describe "Monitor" do
5
+ before(:each) do
6
+ @monitor = Monitor.new
7
+ end
8
+
9
+ it "controls entry and exit" do
10
+ ary = []
11
+ queue = Queue.new
12
+ th = Thread.start {
13
+ queue.pop
14
+ @monitor.enter
15
+ for i in 6 .. 10
16
+ ary.push(i)
17
+ Thread.pass
18
+ end
19
+ @monitor.exit
20
+ }
21
+ @monitor.enter
22
+ queue.enq(nil)
23
+ for i in 1 .. 5
24
+ ary.push(i)
25
+ Thread.pass
26
+ end
27
+ @monitor.exit
28
+ th.join
29
+ ary.should == (1..10).to_a
30
+ end
31
+
32
+ it "synchronises on itself" do
33
+ ary = []
34
+ queue = Queue.new
35
+ th = Thread.start {
36
+ queue.pop
37
+ @monitor.synchronize do
38
+ for i in 6 .. 10
39
+ ary.push(i)
40
+ Thread.pass
41
+ end
42
+ end
43
+ }
44
+ @monitor.synchronize do
45
+ queue.enq(nil)
46
+ for i in 1 .. 5
47
+ ary.push(i)
48
+ Thread.pass
49
+ end
50
+ end
51
+ th.join
52
+ ary.should == (1..10).to_a
53
+ end
54
+
55
+ it "recovers from thread killed in synchronize" do
56
+ ary = []
57
+ queue = Queue.new
58
+ t1 = Thread.start {
59
+ queue.pop
60
+ @monitor.synchronize {
61
+ ary << :t1
62
+ }
63
+ }
64
+ t2 = Thread.start {
65
+ queue.pop
66
+ @monitor.synchronize {
67
+ ary << :t2
68
+ }
69
+ }
70
+ @monitor.synchronize do
71
+ queue.enq(nil)
72
+ queue.enq(nil)
73
+ ary.empty?.should be_true
74
+ t1.kill
75
+ t2.kill
76
+ ary << :main
77
+ end
78
+ ary.should == [ :main ]
79
+ end
80
+
81
+ it "performs try_enter appropriately" do
82
+ queue1 = Queue.new
83
+ queue2 = Queue.new
84
+ th = Thread.start {
85
+ queue1.deq
86
+ @monitor.enter
87
+ queue2.enq(nil)
88
+ queue1.deq
89
+ @monitor.exit
90
+ queue2.enq(nil)
91
+ }
92
+ @monitor.try_enter.should be_true
93
+ @monitor.exit
94
+ queue1.enq(nil)
95
+ queue2.deq
96
+ @monitor.try_enter.should be_false
97
+ queue1.enq(nil)
98
+ queue2.deq
99
+ @monitor.try_enter.should be_true
100
+ end
101
+
102
+ describe "MonitorMixin::ConditionVariable" do
103
+ it "waits and signals" do
104
+ cond = @monitor.new_cond
105
+
106
+ a = "foo"
107
+ queue1 = Queue.new
108
+ Thread.start do
109
+ queue1.deq
110
+ @monitor.synchronize do
111
+ a = "bar"
112
+ cond.signal
113
+ end
114
+ end
115
+ @monitor.synchronize do
116
+ queue1.enq(nil)
117
+ a.should == "foo"
118
+ result1 = cond.wait
119
+ result1.should == true
120
+ a.should == "bar"
121
+ end
122
+ end
123
+
124
+ it "timesout on wait" do
125
+ cond = @monitor.new_cond
126
+ b = "foo"
127
+ queue2 = Queue.new
128
+ Thread.start do
129
+ queue2.deq
130
+ @monitor.synchronize do
131
+ b = "bar"
132
+ cond.signal
133
+ end
134
+ end
135
+ @monitor.synchronize do
136
+ queue2.enq(nil)
137
+ b.should == "foo"
138
+ cond.wait(0.1).should == true
139
+ b.should == "bar"
140
+ end
141
+
142
+ c = "foo"
143
+ queue3 = Queue.new
144
+ Thread.start do
145
+ queue3.deq
146
+ @monitor.synchronize do
147
+ c = "bar"
148
+ cond.signal
149
+ end
150
+ end
151
+ @monitor.synchronize do
152
+ c.should == "foo"
153
+ cond.wait(0.1).should be_true
154
+ c.should == "foo"
155
+ queue3.enq(nil)
156
+ cond.wait.should be_true
157
+ c.should == "bar"
158
+ end
159
+
160
+ end
161
+ end
162
+ end
data/spec/empathy_spec.rb CHANGED
@@ -69,6 +69,11 @@ describe Empathy do
69
69
  Empathy::Kernel.singleton_methods.should include(:sleep)
70
70
  end
71
71
 
72
+ it "delegates Monitor" do
73
+ m = Empathy::Monitor.new()
74
+ m.should be_kind_of(monitor_class)
75
+ end
76
+
72
77
  it "rescues errors" do
73
78
  lambda do
74
79
  begin
@@ -90,9 +95,9 @@ describe Empathy do
90
95
  let (:condition_variable_class) { Empathy::EM::ConditionVariable }
91
96
  let (:mutex_class) { Empathy::EM::Mutex }
92
97
  let (:error_class) { ::FiberError }
98
+ let (:monitor_class) { Empathy::EM::Monitor }
93
99
 
94
100
  it "delegates to Empathy::EM classes" do
95
- Empathy.run do
96
101
  Empathy.event_machine?.should be_true
97
102
  t = Empathy::EmTest.new
98
103
  t.should be_kind_of(Empathy::EM::EmTest)
@@ -100,7 +105,6 @@ describe Empathy do
100
105
  Empathy::EmTest.test_class_method.should == :em_test_class_method
101
106
  Empathy::EmTest.test_class_method(:em_hello).should == :em_hello
102
107
  Empathy::EmTest.test_class_method() { :em_with_block }.should == :em_with_block
103
- end
104
108
  end
105
109
 
106
110
  include_examples "empathy_delegation"
@@ -112,6 +116,7 @@ describe Empathy do
112
116
  let (:condition_variable_class) { ::ConditionVariable }
113
117
  let (:mutex_class) { ::Mutex }
114
118
  let (:error_class) { ::ThreadError }
119
+ let (:monitor_class) { ::Monitor }
115
120
 
116
121
  it "delegate to ruby top level classes" do
117
122
  EventMachine.reactor_running?.should be_false
data/spec/library_spec.rb CHANGED
@@ -1,79 +1,126 @@
1
1
  require 'spec_helper'
2
2
 
3
- module TestReactorLibrary
4
- def self.test_thread_error
5
- raise ::FiberError, "oops"
6
- rescue ThreadError
7
- :rescued
8
- end
9
- end
3
+ module EmpathyEMLibrary
10
4
 
11
- Empathy::EM.empathise(TestReactorLibrary)
5
+ module SubModule
6
+ def self.thread_class
7
+ Thread
8
+ end
9
+ end
10
+ class TestClass
11
+ include MonitorMixin
12
12
 
13
- module TestLibrary
14
- def self.test_thread_error
15
- begin
16
- raise ::FiberError, "fiber error"
17
- rescue ThreadError
13
+ def self.thread_class
14
+ Thread
18
15
  end
19
16
 
20
- begin
21
- raise ::ThreadError, "thread error"
22
- rescue ThreadError
17
+ def thread_class
18
+ Thread
23
19
  end
24
- :ok
20
+
25
21
  end
26
22
  end
27
23
 
28
- Empathy.empathise(TestLibrary)
24
+ Empathy::EM.empathise(EmpathyEMLibrary)
25
+
26
+ module EmpathyLibrary
27
+ module SubModule
28
+ def self.thread_class
29
+ Thread
30
+ end
31
+ end
32
+ class TestClass
33
+ include MonitorMixin
34
+
35
+ def self.thread_class
36
+ Thread
37
+ end
38
+
39
+ def thread_class
40
+ Thread
41
+ end
29
42
 
30
- shared_examples_for "Empathy.empathise" do
31
- it "rescues thread and fiber exceptions" do
32
- TestLibrary.test_thread_error.should == :ok
33
43
  end
44
+
34
45
  end
35
46
 
36
- describe "Empathy.empathise" do
47
+ Empathy.empathise(EmpathyLibrary)
37
48
 
38
- it "defines constants in Library namespace" do
39
- TestLibrary::Thread.should == Empathy::Thread
40
- TestLibrary::Queue.should == Empathy::Queue
41
- TestLibrary::ConditionVariable.should == Empathy::ConditionVariable
42
- TestLibrary::Mutex.should == Empathy::Mutex
43
- TestLibrary::ThreadError.should == Empathy::ThreadError
49
+ module NonEmpathisedLibrary
50
+ module SubModule
51
+ def self.thread_class
52
+ Thread
53
+ end
44
54
  end
55
+ class TestClass
56
+ include MonitorMixin
45
57
 
46
- context "in reactor" do
47
- around :each do |example|
48
- Empathy.run { example.run }
58
+ def self.thread_class
59
+ Thread
49
60
  end
50
61
 
51
- include_examples "Empathy.empathise"
52
- end
62
+ def thread_class
63
+ Thread
64
+ end
53
65
 
54
- context "outside reactor" do
55
- include_examples "Empathy.empathise"
56
66
  end
67
+ end
68
+
69
+ shared_examples_for "an empathised library" do
57
70
 
71
+ it "has constant references in its namespace pointing to modules in empathy namespace" do
72
+ library_module.const_get('Thread',false).should == empathy_module.const_get('Thread',false)
73
+ library_module.const_get('Queue',false).should == empathy_module.const_get('Queue',false)
74
+ library_module.const_get('Mutex',false).should == empathy_module.const_get('Mutex',false)
75
+ library_module.const_get('ConditionVariable',false).should == empathy_module.const_get('ConditionVariable',false)
76
+ library_module.const_get('Monitor',false).should == empathy_module.const_get('Monitor',false)
77
+ library_module.const_get('ThreadError',false).should == empathy_module.const_get('ThreadError',false)
78
+ end
58
79
  end
59
80
 
60
- describe "Empathy::EM.empathise" do
81
+ shared_examples_for "a possibly empathised library" do
61
82
 
62
- it "defines constants in Library namespace" do
63
- TestReactorLibrary::Thread.should == Empathy::EM::Thread
64
- TestReactorLibrary::Queue.should == Empathy::EM::Queue
65
- TestReactorLibrary::ConditionVariable.should == Empathy::EM::ConditionVariable
66
- TestReactorLibrary::Mutex.should == Empathy::EM::Mutex
67
- TestReactorLibrary::ThreadError.should == FiberError
83
+ it "resolves constant references from submodules in its namepsace to modules in empathy namepsace" do
84
+ submodule = library_module.const_get('SubModule',false)
85
+ submodule.thread_class.should == empathy_module.const_get('Thread',false)
68
86
  end
69
87
 
70
- context "in reactor" do
71
- around :each do |example|
72
- Empathy.run { example.run }
73
- end
88
+ it "resolves constant references from classes within its namespace to modules in empathy namespace" do
89
+ library_class = library_module.const_get('TestClass',false)
90
+ library_class.thread_class.should == empathy_module.const_get('Thread',false)
91
+ obj = library_module.const_get('TestClass',false).new()
92
+ obj.thread_class.should == empathy_module.const_get('Thread',false)
93
+ end
74
94
 
75
- it "rescues fiber errors" do
76
- TestReactorLibrary.test_thread_error.should == :rescued
95
+ context "MonitorMixin" do
96
+ it "uses empathised classes" do
97
+ monitor = library_module.const_get("TestClass",false).new
98
+ monitor.__send__(:mon_class_lookup,'Thread').should == empathy_module.const_get('Thread',false)
77
99
  end
78
100
  end
79
101
  end
102
+
103
+ describe Empathy do
104
+
105
+ let(:empathy_module) { described_class }
106
+ let(:library_module) { EmpathyLibrary }
107
+
108
+ include_examples "an empathised library"
109
+ include_examples "a possibly empathised library"
110
+ end
111
+
112
+ describe Empathy::EM do
113
+
114
+ let(:empathy_module) { described_class }
115
+ let(:library_module) { EmpathyEMLibrary }
116
+
117
+ include_examples "an empathised library"
118
+ include_examples "a possibly empathised library"
119
+ end
120
+
121
+ describe Object do
122
+ let(:empathy_module) { described_class }
123
+ let(:library_module) { NonEmpathisedLibrary }
124
+
125
+ include_examples "a possibly empathised library"
126
+ end
@@ -0,0 +1,24 @@
1
+ class ModuleDelegationHandler< YARD::Handlers::Ruby::Base
2
+ handles method_call(:create_delegate_module)
3
+ namespace_only
4
+
5
+ def process
6
+ module_name = statement.parameters.first.jump(:tstring_content,:ident,:symbol).source
7
+ object = YARD::CodeObjects::ModuleObject.new(namespace,module_name)
8
+ register(object)
9
+
10
+ object.docstring.replace("Delegates to {Empathy::EM::#{module_name}} when in the EM reactor, otherwise plain old ::#{module_name}")
11
+ object.dynamic = true
12
+
13
+ statement.parameters[1..-1].each do |parameter|
14
+ next unless parameter
15
+ method_name = parameter.jump(:symbol_literal).source[1..-1]
16
+ method = YARD::CodeObjects::MethodObject.new(object, method_name, :module)
17
+ register(method)
18
+ method.docstring.replace("Delegates to {Empathy::EM::#{module_name}.#{method_name}} when in the EM reactor, otherwise to plain old ::#{module_name}.#{method_name}")
19
+ method.dynamic=true
20
+ method.visibility=:public
21
+ end
22
+ end
23
+
24
+ end