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.
@@ -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,"Thread","Mutex","ConditionVariable","Queue","ThreadError" => FiberError)
22
+ Empathy::map_classes(m,self)
13
23
  end
14
24
  end
15
25
 
16
26
  module Kernel
17
- # Like ::Kernel::sleep. Woken by an ::EM::Timer in +seconds+ if supplied
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 FiberError, "super not called for subclass of Thread" unless instance.instance_variable_defined?("@fiber")
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
- # Create and run
159
+ # Like ::Thread#initialize
114
160
  def initialize(*args,&block)
115
161
 
116
- ::Kernel.raise FiberError, "no block" unless block_given?
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
- # s1 = Empathy.new{ Empathy.sleep(1) }
122
- # s2 = Empathy.new{ Empathy.sleep(1) }
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? Always true unless our fiber is the current fiber
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 FiberError, "dead em_thread" unless status
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
- # Empathy.new do
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
- def inspect #:nodoc:
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 FiberError, "already initialized" if @fiber
349
+ ::Kernel.raise ThreadError, "already initialized" if @fiber
290
350
  # Create our fiber.
291
351
  fiber = Fiber.new{ fiber_body(*args,&block) }
292
352
 
@@ -1,16 +1,16 @@
1
1
 
2
- warn "empathy/object: Monkey patching Object#sleep and Object@at_exit for EM::reactor awareness"
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 safe sleep if we are in the reactor
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
- # run exit blocks created in the reactor as reactor shutdown hooks
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)
@@ -1,3 +1,3 @@
1
1
  module Empathy
2
- VERSION = "0.0.1.RC0"
2
+ VERSION = "0.0.1.RC2"
3
3
  end
@@ -0,0 +1,8 @@
1
+ require 'eventmachine'
2
+ require 'empathy'
3
+ require 'empathy/object'
4
+
5
+ module Empathy
6
+ map_classes(::EventMachine,::Object)
7
+ map_classes(::Object,Empathy::EM)
8
+ end
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 module
8
+ # the {Empathy::EM} namespace.
8
9
  #
9
- # # For the Empathy::EM classes to be available
10
- # # you must first load EventMachine
11
- # 'require eventmachine'
10
+ # # For the Empathy::EM classes to be available
11
+ # # you must first load EventMachine
12
+ # 'require eventmachine'
12
13
  #
13
- # 'require empathy'
14
+ # 'require empathy'
14
15
  #
15
- # t = Empathy::Thread.new() do
16
- # begin
17
- # # "t" is a standard ::Thread
18
- # ...something...
19
- # end
16
+ # t = Empathy::Thread.new() do
17
+ # # "t" is a standard ::Thread
18
+ # # ...something...
19
+ # end
20
20
  #
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
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
- # # Outside of event machine
29
- # t = Empathy::Thread.new() do
30
- # # "t" is a raw ::Thread
31
- # end
28
+ # # Outside of event machine
29
+ # t = Empathy::Thread.new() do
30
+ # # "t" is a raw ::Thread
31
+ # end
32
32
  #
33
- # Code using Empathy that may be used in both Fiber or Thread contexts
34
- # should take care to rescue *Empathy::Errors which is shorthand for
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
- # def maybe_em_method
38
- # # some code
39
- # rescue *Empathy::Errors
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 || ::ThreadError === 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 do |m|
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
- #@api private
85
- def self.map_classes(into,from,*class_names)
89
+ # @private
90
+ def self.map_classes(into,from)
86
91
  # Make Object reactor aware
87
92
  require 'empathy/object'
88
- class_names.each do |cname|
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/queue.rb'
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
- parent = Empathy.event_machine? ? Empathy::EM : Object
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