empathy 0.0.1.RC0 → 0.0.1.RC2

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/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