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/.yardopts +5 -0
- data/CHANGES.md +8 -0
- data/README.md +144 -0
- data/Rakefile +4 -7
- data/TESTING.md +27 -0
- data/empathy.gemspec +5 -1
- data/lib/empathy/em/condition_variable.rb +18 -11
- data/lib/empathy/em/monitor.rb +8 -0
- data/lib/empathy/em/mutex.rb +17 -0
- data/lib/empathy/em/queue.rb +10 -30
- data/lib/empathy/em/thread.rb +91 -31
- data/lib/empathy/object.rb +3 -3
- data/lib/empathy/version.rb +1 -1
- data/lib/empathy/with_all_of_ruby.rb +8 -0
- data/lib/empathy.rb +81 -45
- data/lib/monitor.rb +209 -0
- data/mspec/lib/mspec/empathy.rb +1 -1
- data/rubyspec/monitor_spec.rb +162 -0
- data/spec/empathy_spec.rb +7 -2
- data/spec/library_spec.rb +95 -48
- data/yard/extensions.rb +24 -0
- metadata +50 -11
- data/README.rdoc +0 -135
- data/TESTING.rdoc +0 -11
- data/lib/empathy/thread.rb +0 -8
data/lib/empathy/em/thread.rb
CHANGED
@@ -2,19 +2,35 @@ require "fiber"
|
|
2
2
|
require "eventmachine"
|
3
3
|
require 'empathy/em/mutex'
|
4
4
|
require 'empathy/em/condition_variable'
|
5
|
+
require 'empathy/em/queue'
|
5
6
|
|
6
7
|
module Empathy
|
7
8
|
|
8
9
|
module EM
|
9
10
|
|
11
|
+
@empathised = self
|
12
|
+
# Thread like errors are actually raw Fiber errors
|
13
|
+
ThreadError = ::FiberError
|
14
|
+
|
15
|
+
# Create alias constants in each of the supplied modules so that code within those modules
|
16
|
+
# will use modules from the Empathy::EM namespace instead of the native ruby ones
|
17
|
+
#
|
18
|
+
# Also monkey patches {Object} to provide EM safe Kernel methods
|
19
|
+
# @return [void]
|
10
20
|
def self.empathise(*modules)
|
11
21
|
modules.each do |m|
|
12
|
-
Empathy::map_classes(m,self
|
22
|
+
Empathy::map_classes(m,self)
|
13
23
|
end
|
14
24
|
end
|
15
25
|
|
16
26
|
module Kernel
|
17
|
-
|
27
|
+
|
28
|
+
# Like ::Kernel.sleep
|
29
|
+
# @overload sleep()
|
30
|
+
# Sleep forever
|
31
|
+
# @overload sleep(seconds)
|
32
|
+
# @param [Numeric] seconds The number of seconds (including fractional seconds) to sleep
|
33
|
+
# @return [Fixnum] rounded number of seconds actually slept, which can be less than specified if woken early
|
18
34
|
def self.sleep(seconds=:__empathy_sleep_forever)
|
19
35
|
|
20
36
|
::Kernel.raise TypeError, "seconds #{seconds} must be a number" unless seconds == :__empathy_sleep_forever or seconds.is_a? Numeric
|
@@ -27,30 +43,50 @@ module Empathy
|
|
27
43
|
(Time.now - n).round()
|
28
44
|
end
|
29
45
|
|
46
|
+
# Like ::Kernel.at_exit
|
47
|
+
#
|
48
|
+
# Queues block to run at shutdown of the reactor
|
49
|
+
# @return [void]
|
30
50
|
def self.at_exit(&block)
|
31
51
|
EventMachine.add_shutdown_hook(&block)
|
32
52
|
end
|
53
|
+
|
33
54
|
end
|
34
55
|
|
35
|
-
#Acts like a ::Thread using Fibers and EventMachine
|
56
|
+
# Acts like a ::Thread using Fibers and EventMachine
|
57
|
+
#
|
58
|
+
# {::Thread} methods not implemented by Empathy
|
59
|
+
#
|
60
|
+
# * .exclusive - not implemented
|
61
|
+
# * #critical - not implemented
|
62
|
+
# * #set_trace_func - not implemented
|
63
|
+
# * #safe_level - not implemented
|
64
|
+
# * #priority - not implemented
|
65
|
+
|
36
66
|
class Thread
|
37
67
|
|
38
68
|
@@em_threads = {}
|
39
69
|
|
40
|
-
# The underlying fiber.
|
70
|
+
# @return [Fiber] The underlying fiber.
|
41
71
|
attr_reader :fiber
|
42
72
|
|
43
73
|
# Like ::Thread::list. Return an array of all EM::Threads that are alive.
|
74
|
+
#
|
75
|
+
# @return [Array<Thread>]
|
44
76
|
def self.list
|
45
77
|
@@em_threads.values.select { |s| s.alive? }
|
46
78
|
end
|
47
79
|
|
80
|
+
# Like ::Thread.main
|
81
|
+
#
|
82
|
+
# @return [Thread]
|
48
83
|
def self.main
|
49
84
|
@@main
|
50
85
|
end
|
51
86
|
|
52
87
|
# Like ::Thread::current. Get the currently running EM::Thread, eg to access thread local
|
53
88
|
# variables
|
89
|
+
# @return [Thread] representing the current Fiber
|
54
90
|
def self.current
|
55
91
|
@@em_threads[Fiber.current] || ProxyThread.new(Fiber.current)
|
56
92
|
end
|
@@ -68,12 +104,15 @@ module Empathy
|
|
68
104
|
|
69
105
|
|
70
106
|
# Like ::Thread::stop. Sleep forever (until woken)
|
107
|
+
# @return [void]
|
71
108
|
def self.stop
|
72
109
|
Kernel.sleep()
|
73
110
|
end
|
74
111
|
|
75
112
|
# Like ::Thread::pass.
|
76
|
-
# The fiber is resumed on the next_tick of EM's event loop
|
113
|
+
# The fiber is paused and resumed on the next_tick of EM's event loop
|
114
|
+
#
|
115
|
+
# @return [nil]
|
77
116
|
def self.pass
|
78
117
|
em_thread = current
|
79
118
|
::EM.next_tick{ em_thread.__send__(:wake_resume) }
|
@@ -82,20 +121,27 @@ module Empathy
|
|
82
121
|
end
|
83
122
|
|
84
123
|
# Like ::Thread.exit
|
124
|
+
# @return [Thread]
|
85
125
|
def self.exit
|
86
126
|
current.exit
|
87
127
|
end
|
88
128
|
|
129
|
+
# Like ::Thread.kill
|
130
|
+
# @return [Thread]
|
89
131
|
def self.kill(thread)
|
90
132
|
thread.exit
|
91
133
|
end
|
92
134
|
|
135
|
+
# @private
|
93
136
|
def self.new(*args,&block)
|
94
137
|
instance = super(*args,&block)
|
95
|
-
::Kernel.raise
|
138
|
+
::Kernel.raise ThreadError, "super not called for subclass of Thread" unless instance.instance_variable_defined?("@fiber")
|
96
139
|
instance
|
97
140
|
end
|
98
141
|
|
142
|
+
# Like ::Thread.start
|
143
|
+
#
|
144
|
+
# @return [Thread]
|
99
145
|
def self.start(*args,&block)
|
100
146
|
::Kernel.raise ArgumentError, "no block" unless block_given?
|
101
147
|
c = if self != Thread
|
@@ -106,22 +152,20 @@ module Empathy
|
|
106
152
|
end
|
107
153
|
else
|
108
154
|
self
|
109
|
-
end
|
155
|
+
end
|
110
156
|
c.new(*args,&block)
|
111
157
|
end
|
112
158
|
|
113
|
-
#
|
159
|
+
# Like ::Thread#initialize
|
114
160
|
def initialize(*args,&block)
|
115
161
|
|
116
|
-
::Kernel.raise
|
162
|
+
::Kernel.raise ThreadError, "no block" unless block_given?
|
117
163
|
initialize_fiber(*args,&block)
|
118
164
|
end
|
119
165
|
|
120
166
|
# Like ::Thread#join.
|
121
|
-
#
|
122
|
-
#
|
123
|
-
# s1.join
|
124
|
-
# s2.join
|
167
|
+
# @param [Numeric] limit seconds to wait for thread to expire
|
168
|
+
# @return [nil,Thread] nil if timeout expires, otherwise this Thread
|
125
169
|
def join(limit = nil)
|
126
170
|
@mutex.synchronize { @join_cond.wait(@mutex,limit) } if alive?
|
127
171
|
::Kernel.raise @exception if @exception
|
@@ -136,16 +180,22 @@ module Empathy
|
|
136
180
|
end
|
137
181
|
|
138
182
|
# Like ::Thread#alive? or Fiber#alive?
|
183
|
+
# @return [true,false]
|
139
184
|
def alive?
|
140
185
|
fiber.alive?
|
141
186
|
end
|
142
187
|
|
143
|
-
# Like ::Thread#stop?
|
188
|
+
# Like ::Thread#stop?
|
189
|
+
# @return [false] if called on the current fiber
|
190
|
+
# @return [true] otherwise
|
144
191
|
def stop?
|
145
192
|
Fiber.current != fiber
|
146
193
|
end
|
147
194
|
|
148
195
|
# Like ::Thread#status
|
196
|
+
# @return [String]
|
197
|
+
# @return [false]
|
198
|
+
# @return [nil]
|
149
199
|
def status
|
150
200
|
case @status
|
151
201
|
when :run
|
@@ -165,13 +215,13 @@ module Empathy
|
|
165
215
|
end
|
166
216
|
|
167
217
|
# Like ::Thread#value. Implicitly calls #join.
|
168
|
-
# em_thread = Empathy.new{ 1+2 }
|
169
|
-
# em_thread.value # => 3
|
170
218
|
def value
|
171
219
|
join and @value
|
172
220
|
end
|
173
221
|
|
174
222
|
# Like ::Thread#exit. Signals thread to wakeup and die
|
223
|
+
#
|
224
|
+
# @return [nil, Thread]
|
175
225
|
def exit
|
176
226
|
case @status
|
177
227
|
when :sleep
|
@@ -185,13 +235,25 @@ module Empathy
|
|
185
235
|
alias :terminate :exit
|
186
236
|
|
187
237
|
# Like ::Thread#wakeup Wakes a sleeping Thread
|
238
|
+
# @return [Thread]
|
188
239
|
def wakeup
|
189
|
-
::Kernel.raise
|
190
|
-
wake_resume()
|
240
|
+
::Kernel.raise ThreadError, "dead em_thread" unless status
|
241
|
+
wake_resume()
|
191
242
|
self
|
192
243
|
end
|
193
244
|
|
194
245
|
# Like ::Thread#raise, raise an exception on a sleeping Thread
|
246
|
+
# @overload raise()
|
247
|
+
# @raise RuntimeError
|
248
|
+
# @overload raise(string)
|
249
|
+
# @param [String] string
|
250
|
+
# @raise RuntimeError
|
251
|
+
# @overload raise(exception,string=nil,array=caller())
|
252
|
+
# @param [Class,String,Object) exception
|
253
|
+
# @param [String] string exception message
|
254
|
+
# @param [Array<String>] array caller information
|
255
|
+
# @raise Exception
|
256
|
+
# @return [void]
|
195
257
|
def raise(*args)
|
196
258
|
args << RuntimeError if args.empty?
|
197
259
|
if fiber == Fiber.current
|
@@ -207,45 +269,43 @@ module Empathy
|
|
207
269
|
|
208
270
|
|
209
271
|
# Access to "fiber local" variables, akin to "thread local" variables.
|
210
|
-
#
|
211
|
-
#
|
212
|
-
# Empathy.current[:connection].send(data)
|
213
|
-
# ...
|
214
|
-
# end
|
272
|
+
# @param [Symbol] name
|
273
|
+
# @return [Object,nil]
|
215
274
|
def [](name)
|
216
275
|
::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
|
217
276
|
@locals[name.to_sym]
|
218
277
|
end
|
219
278
|
|
220
279
|
# Access to "fiber local" variables, akin to "thread local" variables.
|
221
|
-
# Empathy.new do
|
222
|
-
# ...
|
223
|
-
# Empathy.current[:connection] = SomeConnectionClass.new(host, port)
|
224
|
-
# ...
|
225
|
-
# end
|
226
280
|
def []=(name, value)
|
227
281
|
::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
|
228
282
|
@locals[name.to_sym] = value
|
229
283
|
end
|
230
284
|
|
231
285
|
# Like ::Thread#key? Is there a "fiber local" variable defined called +name+
|
286
|
+
# @param [Symbol] name
|
287
|
+
# @return [true,false]
|
232
288
|
def key?(name)
|
233
289
|
::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
|
234
290
|
@locals.has_key?(name.to_sym)
|
235
291
|
end
|
236
292
|
|
237
293
|
# Like ::Thread#keys The set of "em_thread local" variable keys
|
294
|
+
# @return [Array<Symbol>]
|
238
295
|
def keys()
|
239
296
|
@locals.keys
|
240
297
|
end
|
241
298
|
|
242
|
-
|
299
|
+
# Like ::Thread#inspect
|
300
|
+
# @return [String]
|
301
|
+
def inspect
|
243
302
|
"#<Empathy::EM::Thread:0x%s %s %s" % [object_id, @fiber == Fiber.current ? "run" : "yielded", status || "dead" ]
|
244
303
|
end
|
245
304
|
|
246
305
|
# Do something when the fiber completes.
|
306
|
+
# @return [void]
|
247
307
|
def ensure_hook(key,&block)
|
248
|
-
if block_given? then
|
308
|
+
if block_given? then
|
249
309
|
@ensure_hooks[key] = block
|
250
310
|
else
|
251
311
|
@ensure_hooks.delete(key)
|
@@ -286,7 +346,7 @@ module Empathy
|
|
286
346
|
private
|
287
347
|
|
288
348
|
def initialize_fiber(*args,&block)
|
289
|
-
::Kernel.raise
|
349
|
+
::Kernel.raise ThreadError, "already initialized" if @fiber
|
290
350
|
# Create our fiber.
|
291
351
|
fiber = Fiber.new{ fiber_body(*args,&block) }
|
292
352
|
|
data/lib/empathy/object.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
|
2
|
-
warn "empathy/object: Monkey patching Object#sleep and Object
|
2
|
+
warn "empathy/object: Monkey patching Object#sleep and Object#at_exit for EventMachine reactor awareness"
|
3
3
|
|
4
4
|
# Monkey patch object to make EM safe
|
5
5
|
class Object
|
6
6
|
|
7
|
-
# use EM
|
7
|
+
# use {Empathy::EM::Kernel.sleep} if we are in the reactor, Kernel.sleep otherwise
|
8
8
|
def sleep(*args)
|
9
9
|
kernel = Empathy.event_machine? ? Empathy::EM::Kernel : Kernel
|
10
10
|
kernel.sleep(*args)
|
11
11
|
end
|
12
12
|
|
13
|
-
#
|
13
|
+
# use {Empathy::EM::Kernel.at_exit} if we are in the reactor, Kernel.sleep otherwise
|
14
14
|
def at_exit(&block)
|
15
15
|
kernel = Empathy.event_machine? ? Empathy::EM::Kernel : Kernel
|
16
16
|
kernel.at_exit(&block)
|
data/lib/empathy/version.rb
CHANGED
data/lib/empathy.rb
CHANGED
@@ -1,61 +1,62 @@
|
|
1
1
|
require 'empathy/version'
|
2
2
|
require 'thread'
|
3
|
+
require 'monitor'
|
3
4
|
require 'fiber'
|
4
5
|
|
5
6
|
# This module provides a shim between using standard ruby Threads
|
6
7
|
# and the thread-like behaviour for Fibers provided by classes in
|
7
|
-
# the Empathy::EM
|
8
|
+
# the {Empathy::EM} namespace.
|
8
9
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# # For the Empathy::EM classes to be available
|
11
|
+
# # you must first load EventMachine
|
12
|
+
# 'require eventmachine'
|
12
13
|
#
|
13
|
-
#
|
14
|
+
# 'require empathy'
|
14
15
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# end
|
16
|
+
# t = Empathy::Thread.new() do
|
17
|
+
# # "t" is a standard ::Thread
|
18
|
+
# # ...something...
|
19
|
+
# end
|
20
20
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
21
|
+
# EventMachine.run do
|
22
|
+
# t = Empathy::Thread.new() do
|
23
|
+
# # "t" is a ::Empathy::EM::Thread
|
24
|
+
# # which wraps a ::Fiber
|
25
|
+
# end
|
26
|
+
# end
|
27
27
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
28
|
+
# # Outside of event machine
|
29
|
+
# t = Empathy::Thread.new() do
|
30
|
+
# # "t" is a raw ::Thread
|
31
|
+
# end
|
32
32
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# *[FiberError,ThreadError]
|
33
|
+
# #Code using Empathy that may be used in both Fiber or Thread contexts
|
34
|
+
# #should take care to rescue Empathy::ThreadError
|
36
35
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
36
|
+
# def maybe_em_method
|
37
|
+
# # ...
|
38
|
+
# rescue Empathy::ThreadError
|
39
|
+
# # ... will rescue either ::FiberError or ::ThreadError
|
40
|
+
# end
|
40
41
|
#
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# {::Thread} methods not implemented by Empathy
|
44
|
-
# * #exclusive - not implemented
|
45
|
-
# * #critical - not implemented
|
46
|
-
# * #set_trace_func - not implemented
|
47
|
-
# * #safe_level - not implemented
|
48
|
-
# * #priority - not implemented
|
49
42
|
module Empathy
|
50
43
|
|
44
|
+
@empathised = self
|
45
|
+
@empathic_classes = []
|
46
|
+
|
47
|
+
#This is never thrown but can be used to rescue both ThreadError and FiberError
|
51
48
|
class ThreadError < StandardError
|
49
|
+
RUBY_ThreadError = ::ThreadError
|
52
50
|
def self.===(other)
|
53
|
-
super || ::FiberError === other ||
|
51
|
+
super || ::FiberError === other || RUBY_ThreadError === other
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
55
|
+
@empathic_classes << 'ThreadError'
|
56
|
+
|
57
57
|
# Start EventMachine and run reactor block within a surrounding Empathy::EM::Thread (Fiber).
|
58
58
|
# The reactor loop is terminated when the supplied block finishes
|
59
|
+
# @return the value of the block
|
59
60
|
def self.run
|
60
61
|
reload()
|
61
62
|
exception = nil
|
@@ -75,17 +76,21 @@ module Empathy
|
|
75
76
|
value
|
76
77
|
end
|
77
78
|
|
79
|
+
# Create alias constants in each of the supplied modules so that code witin those modules
|
80
|
+
# will use modules from the Empathy namespace instaad of the native ruby ones
|
81
|
+
#
|
82
|
+
# Also monkey patches {Object} to provide EM safe Kernel methods
|
83
|
+
# @param [Array<Module>] modules
|
84
|
+
# @return [void]
|
78
85
|
def self.empathise(*modules)
|
79
|
-
modules.each
|
80
|
-
map_classes(m, self, "Thread","Queue","Mutex","ConditionVariable", "ThreadError")
|
81
|
-
end
|
86
|
+
modules.each { |m| map_classes(m, self) }
|
82
87
|
end
|
83
88
|
|
84
|
-
|
85
|
-
def self.map_classes(into,from
|
89
|
+
# @private
|
90
|
+
def self.map_classes(into,from)
|
86
91
|
# Make Object reactor aware
|
87
92
|
require 'empathy/object'
|
88
|
-
|
93
|
+
@empathic_classes.each do |cname|
|
89
94
|
case cname
|
90
95
|
when Hash
|
91
96
|
cname.each { |cn,replace_class| replace_class_constant(into,cn,replace_class) }
|
@@ -93,8 +98,10 @@ module Empathy
|
|
93
98
|
replace_class_constant(into,cname,from.const_get(cname))
|
94
99
|
end
|
95
100
|
end
|
101
|
+
into.instance_variable_set(:@empathised,from)
|
96
102
|
end
|
97
103
|
|
104
|
+
private
|
98
105
|
def self.replace_class_constant(into,cname,replace_class)
|
99
106
|
if into != Object && into.const_defined?(cname,false)
|
100
107
|
existing_const = into.const_get(cname)
|
@@ -107,7 +114,7 @@ module Empathy
|
|
107
114
|
warn "empathy: Defined fake class constant #{into}::#{cname} => #{replace_class.name}"
|
108
115
|
into.const_set(cname,replace_class)
|
109
116
|
end
|
110
|
-
|
117
|
+
public
|
111
118
|
# Test whether we have real fibers or a thread based fiber implmentation
|
112
119
|
t = Thread.current
|
113
120
|
ft = nil
|
@@ -121,7 +128,7 @@ module Empathy
|
|
121
128
|
@loaded ||= false
|
122
129
|
if !@loaded && defined?(EventMachine)
|
123
130
|
require 'empathy/em/thread.rb'
|
124
|
-
require 'empathy/em/
|
131
|
+
require 'empathy/em/monitor.rb'
|
125
132
|
@loaded = true
|
126
133
|
end
|
127
134
|
return @loaded
|
@@ -142,16 +149,44 @@ module Empathy
|
|
142
149
|
end
|
143
150
|
|
144
151
|
private
|
152
|
+
|
153
|
+
# Create a module under the Empathy namespace that delegates class methods (potentially including #new)
|
154
|
+
# to either the top level class, or to its equivalent under the {Empathy::EM} namespace
|
155
|
+
# based on being in/out of the reactor
|
156
|
+
#
|
157
|
+
# Library authors can use this to define an eventmachine aware replacement class for their own purposes
|
158
|
+
# @example
|
159
|
+
#
|
160
|
+
# class MyBinding
|
161
|
+
# #...code that does not use event machine
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# module Empathy
|
165
|
+
# module EM
|
166
|
+
# class MyBinding
|
167
|
+
# # ... code that uses event machine ...
|
168
|
+
# end
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# create_delegate_module('MyBinding',:new, :my_class_method)
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# @visibility public
|
175
|
+
# @param [String] cname name of the top level class or module to delegate, there must
|
176
|
+
# also be a class with the same name in the Empathy::EM namespace
|
177
|
+
# @param [Array<Sumbol>] methods names of class/module methods to delegate
|
145
178
|
def self.create_delegate_module(cname,*methods)
|
146
179
|
mod = Module.new
|
180
|
+
native_mod = Object.const_get(cname)
|
181
|
+
em_mod = Empathy::EM.const_get(cname)
|
147
182
|
self.const_set(cname,mod)
|
148
183
|
methods.each do |m|
|
149
184
|
mod.define_singleton_method(m) do |*args,&block|
|
150
|
-
|
151
|
-
delegate = parent.const_get(cname)
|
185
|
+
delegate = Empathy.event_machine? ? em_mod : native_mod
|
152
186
|
delegate.send(m,*args,&block)
|
153
187
|
end
|
154
188
|
end
|
189
|
+
@empathic_classes << cname unless cname == 'Kernel'
|
155
190
|
end
|
156
191
|
|
157
192
|
create_delegate_module('Kernel',:sleep,:at_exit)
|
@@ -159,4 +194,5 @@ module Empathy
|
|
159
194
|
create_delegate_module('Queue',:new)
|
160
195
|
create_delegate_module('ConditionVariable',:new)
|
161
196
|
create_delegate_module('Mutex',:new)
|
197
|
+
create_delegate_module('Monitor',:new)
|
162
198
|
end
|