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.
@@ -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