empathy 0.0.1.RC0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.rdoc +135 -0
  5. data/Rakefile +33 -0
  6. data/TESTING.rdoc +11 -0
  7. data/empathy.gemspec +23 -0
  8. data/empathy.mspec +34 -0
  9. data/lib/empathy.rb +162 -0
  10. data/lib/empathy/em/condition_variable.rb +57 -0
  11. data/lib/empathy/em/mutex.rb +70 -0
  12. data/lib/empathy/em/queue.rb +84 -0
  13. data/lib/empathy/em/thread.rb +363 -0
  14. data/lib/empathy/object.rb +19 -0
  15. data/lib/empathy/thread.rb +8 -0
  16. data/lib/empathy/version.rb +3 -0
  17. data/mspec/lib/mspec/empathy.rb +19 -0
  18. data/mspec/lib/mspec/guards/empathy.rb +10 -0
  19. data/rubyspec/core/kernel/fixtures/__method__.rb +25 -0
  20. data/rubyspec/core/kernel/fixtures/autoload_b.rb +5 -0
  21. data/rubyspec/core/kernel/fixtures/autoload_c.rb +5 -0
  22. data/rubyspec/core/kernel/fixtures/autoload_d.rb +5 -0
  23. data/rubyspec/core/kernel/fixtures/caller_fixture1.rb +42 -0
  24. data/rubyspec/core/kernel/fixtures/caller_fixture2.rb +26 -0
  25. data/rubyspec/core/kernel/fixtures/chomp.rb +4 -0
  26. data/rubyspec/core/kernel/fixtures/chomp_f.rb +4 -0
  27. data/rubyspec/core/kernel/fixtures/chop.rb +4 -0
  28. data/rubyspec/core/kernel/fixtures/chop_f.rb +4 -0
  29. data/rubyspec/core/kernel/fixtures/classes.rb +410 -0
  30. data/rubyspec/core/kernel/fixtures/eval_locals.rb +6 -0
  31. data/rubyspec/core/kernel/fixtures/eval_return_with_lambda.rb +12 -0
  32. data/rubyspec/core/kernel/fixtures/eval_return_without_lambda.rb +14 -0
  33. data/rubyspec/core/kernel/fixtures/test.rb +362 -0
  34. data/rubyspec/core/kernel/sleep_spec.rb +43 -0
  35. data/rubyspec/core/mutex/lock_spec.rb +8 -0
  36. data/rubyspec/core/mutex/locked_spec.rb +8 -0
  37. data/rubyspec/core/mutex/sleep_spec.rb +56 -0
  38. data/rubyspec/core/mutex/synchronize_spec.rb +8 -0
  39. data/rubyspec/core/mutex/try_lock_spec.rb +8 -0
  40. data/rubyspec/core/mutex/unlock_spec.rb +8 -0
  41. data/rubyspec/core/thread/abort_on_exception_spec.rb +126 -0
  42. data/rubyspec/core/thread/add_trace_func_spec.rb +7 -0
  43. data/rubyspec/core/thread/alive_spec.rb +60 -0
  44. data/rubyspec/core/thread/allocate_spec.rb +9 -0
  45. data/rubyspec/core/thread/backtrace_spec.rb +7 -0
  46. data/rubyspec/core/thread/critical_spec.rb +96 -0
  47. data/rubyspec/core/thread/current_spec.rb +15 -0
  48. data/rubyspec/core/thread/element_reference_spec.rb +53 -0
  49. data/rubyspec/core/thread/element_set_spec.rb +46 -0
  50. data/rubyspec/core/thread/exclusive_spec.rb +20 -0
  51. data/rubyspec/core/thread/exit_spec.rb +21 -0
  52. data/rubyspec/core/thread/fixtures/classes.rb +291 -0
  53. data/rubyspec/core/thread/fork_spec.rb +9 -0
  54. data/rubyspec/core/thread/group_spec.rb +5 -0
  55. data/rubyspec/core/thread/initialize_spec.rb +26 -0
  56. data/rubyspec/core/thread/inspect_spec.rb +48 -0
  57. data/rubyspec/core/thread/join_spec.rb +63 -0
  58. data/rubyspec/core/thread/key_spec.rb +64 -0
  59. data/rubyspec/core/thread/keys_spec.rb +47 -0
  60. data/rubyspec/core/thread/kill_spec.rb +21 -0
  61. data/rubyspec/core/thread/list_spec.rb +38 -0
  62. data/rubyspec/core/thread/main_spec.rb +10 -0
  63. data/rubyspec/core/thread/new_spec.rb +56 -0
  64. data/rubyspec/core/thread/pass_spec.rb +8 -0
  65. data/rubyspec/core/thread/priority_spec.rb +9 -0
  66. data/rubyspec/core/thread/raise_spec.rb +225 -0
  67. data/rubyspec/core/thread/run_spec.rb +9 -0
  68. data/rubyspec/core/thread/safe_level_spec.rb +6 -0
  69. data/rubyspec/core/thread/set_trace_func_spec.rb +7 -0
  70. data/rubyspec/core/thread/shared/exit.rb +173 -0
  71. data/rubyspec/core/thread/shared/start.rb +51 -0
  72. data/rubyspec/core/thread/shared/wakeup.rb +59 -0
  73. data/rubyspec/core/thread/start_spec.rb +9 -0
  74. data/rubyspec/core/thread/status_spec.rb +48 -0
  75. data/rubyspec/core/thread/stop_spec.rb +66 -0
  76. data/rubyspec/core/thread/terminate_spec.rb +11 -0
  77. data/rubyspec/core/thread/value_spec.rb +36 -0
  78. data/rubyspec/core/thread/wakeup_spec.rb +7 -0
  79. data/rubyspec/empathy_spec.rb +26 -0
  80. data/rubyspec/library/conditionvariable/broadcast_spec.rb +62 -0
  81. data/rubyspec/library/conditionvariable/signal_spec.rb +64 -0
  82. data/rubyspec/library/conditionvariable/wait_spec.rb +21 -0
  83. data/rubyspec/library/mutex/lock_spec.rb +10 -0
  84. data/rubyspec/library/mutex/locked_spec.rb +10 -0
  85. data/rubyspec/library/mutex/synchronize_spec.rb +10 -0
  86. data/rubyspec/library/mutex/try_lock_spec.rb +10 -0
  87. data/rubyspec/library/mutex/unlock_spec.rb +10 -0
  88. data/rubyspec/library/queue/append_spec.rb +7 -0
  89. data/rubyspec/library/queue/clear_spec.rb +15 -0
  90. data/rubyspec/library/queue/deq_spec.rb +7 -0
  91. data/rubyspec/library/queue/empty_spec.rb +15 -0
  92. data/rubyspec/library/queue/enq_spec.rb +7 -0
  93. data/rubyspec/library/queue/length_spec.rb +7 -0
  94. data/rubyspec/library/queue/num_waiting_spec.rb +19 -0
  95. data/rubyspec/library/queue/pop_spec.rb +7 -0
  96. data/rubyspec/library/queue/push_spec.rb +7 -0
  97. data/rubyspec/library/queue/shared/deque.rb +37 -0
  98. data/rubyspec/library/queue/shared/enque.rb +10 -0
  99. data/rubyspec/library/queue/shared/length.rb +9 -0
  100. data/rubyspec/library/queue/shift_spec.rb +7 -0
  101. data/rubyspec/library/queue/size_spec.rb +7 -0
  102. data/rubyspec/shared/kernel/raise.rb +68 -0
  103. data/rubyspec/shared/mutex/lock.rb +52 -0
  104. data/rubyspec/shared/mutex/locked.rb +31 -0
  105. data/rubyspec/shared/mutex/synchronize.rb +23 -0
  106. data/rubyspec/shared/mutex/try_lock.rb +30 -0
  107. data/rubyspec/shared/mutex/unlock.rb +35 -0
  108. data/rubyspec/spec_helper.rb +48 -0
  109. data/spec/empathy_spec.rb +129 -0
  110. data/spec/library_spec.rb +79 -0
  111. data/spec/spec_helper.rb +6 -0
  112. metadata +222 -0
@@ -0,0 +1,57 @@
1
+ module Empathy
2
+ module EM
3
+ # Provides for Empathys (Fibers) what Ruby's ConditionVariable provides for Threads.
4
+ class ConditionVariable
5
+
6
+ # Create a new condition variable.
7
+ def initialize
8
+ @waiters = []
9
+ end
10
+
11
+ # Using a mutex for condition variables is meant to protect
12
+ # against race conditions when the signal occurs between testing whether
13
+ # a wait is needed and waiting. This situation will never occur with
14
+ # fibers, but the semantic is retained
15
+ def wait(mutex=nil,timeout = nil)
16
+
17
+ if timeout.nil? && (mutex.nil? || Numeric === mutex)
18
+ timeout = mutex
19
+ mutex = nil
20
+ end
21
+
22
+ # Get the fiber (Empathy::EM::Thread) that called us.
23
+ empathy = Thread.current
24
+ # Add the fiber to the list of waiters.
25
+ @waiters << empathy
26
+ begin
27
+ sleeper = mutex ? mutex : Kernel
28
+ if timeout then sleeper.sleep(timeout) else sleeper.sleep() end
29
+ ensure
30
+ # Remove from list of waiters.
31
+ @waiters.delete(empathy)
32
+ end
33
+ self
34
+ end
35
+
36
+ def signal
37
+ # If there are no waiters, do nothing.
38
+ return self if @waiters.empty?
39
+
40
+ # Find a waiter to wake up.
41
+ waiter = @waiters.shift
42
+
43
+ # Resume it on next tick.
44
+ ::EM.next_tick{ waiter.wakeup }
45
+ self
46
+ end
47
+
48
+ def broadcast
49
+ all_waiting = @waiters.dup
50
+ @waiters.clear
51
+ ::EM.next_tick { all_waiting.each { |w| w.wakeup } }
52
+ self
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ module Empathy
2
+ module EM
3
+ class Mutex
4
+
5
+ def initialize()
6
+ @waiters = []
7
+ end
8
+
9
+ def lock()
10
+ em_thread = Thread.current
11
+ @waiters << em_thread
12
+ #The lock allows reentry but requires matching unlocks
13
+ em_thread.send(:yield_sleep) unless @waiters.first == em_thread
14
+ # Now em_thread has the lock, make sure it is released if the em_thread thread dies
15
+ em_thread.ensure_hook(self) { release() unless waiters.empty? || waiters.first != em_thread }
16
+ self
17
+ end
18
+
19
+ def unlock()
20
+ em_thread = Thread.current
21
+ raise FiberError, "not owner" unless @waiters.first == em_thread
22
+ release()
23
+ end
24
+
25
+ def locked?
26
+ !@waiters.empty? && @waiters.first.alive?
27
+ end
28
+
29
+ def try_lock
30
+ if locked?
31
+ false
32
+ else
33
+ lock
34
+ true
35
+ end
36
+ end
37
+
38
+ def synchronize(&block)
39
+ lock
40
+ yield
41
+ ensure
42
+ unlock
43
+ end
44
+
45
+ def sleep(timeout=nil)
46
+ unlock
47
+ begin
48
+ if timeout then Kernel.sleep(timeout) else Kernel.sleep() end
49
+ ensure
50
+ lock
51
+ end
52
+ end
53
+
54
+ private
55
+ def waiters
56
+ @waiters
57
+ end
58
+
59
+ def release()
60
+ # release the current lock holder, and clear the em_thread death hook
61
+ waiters.shift.ensure_hook(self)
62
+
63
+ ::EM.next_tick do
64
+ waiters.shift until waiters.empty? || waiters.first.alive?
65
+ waiters.first.send(:wake_resume) unless waiters.empty?
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+ module Empathy
2
+ module EM
3
+ # A Empathy equivalent to ::Queue from thread.rb
4
+ # queue = Empathy::Queue.new
5
+ #
6
+ # producer = Empathy::Thread.new do
7
+ # 5.times do |i|
8
+ # Empathy::Kernel.sleep rand(i) # simulate expense
9
+ # queue << i
10
+ # puts "#{i} produced"
11
+ # end
12
+ # end
13
+ #
14
+ # consumer = Empathy.new do
15
+ # 5.times do |i|
16
+ # value = queue.pop
17
+ # Empathy::Kernel.sleep rand(i/2) # simulate expense
18
+ # puts "consumed #{value}"
19
+ # end
20
+ # end
21
+ #
22
+ # consumer.join
23
+ #
24
+ class Queue
25
+
26
+ # Creates a new queue
27
+ def initialize
28
+ @mutex = Mutex.new()
29
+ @cv = ConditionVariable.new()
30
+ @q = []
31
+ @waiting = 0
32
+ end
33
+
34
+ # Pushes +obj+ to the queue
35
+ def push(obj)
36
+ @q << obj
37
+ @mutex.synchronize { @cv.signal }
38
+ end
39
+ alias :<< :push
40
+ alias :enq :push
41
+
42
+ # Retrieves data from the queue.
43
+ #
44
+ #
45
+ # If the queue is empty, the calling fiber is suspended until data is
46
+ # pushed onto the queue, unless +non_block+ is true in which case a
47
+ # +FiberError+ is raised
48
+ #
49
+ def pop(non_block=false)
50
+ raise FiberError, "queue empty" if non_block && empty?
51
+ if empty?
52
+ @waiting += 1
53
+ @mutex.synchronize { @cv.wait(@mutex) if empty? }
54
+ @waiting -= 1
55
+ end
56
+ # array.pop is like a stack, we're a FIFO
57
+ @q.shift
58
+ end
59
+ alias :shift :pop
60
+ alias :deq :pop
61
+
62
+ # Returns the length of the queue
63
+ def length
64
+ @q.length
65
+ end
66
+ alias :size :length
67
+
68
+ # Returns +true+ if the queue is empty
69
+ def empty?
70
+ @q.empty?
71
+ end
72
+
73
+ # Removes all objects from the queue
74
+ def clear
75
+ @q.clear
76
+ end
77
+
78
+ # Returns the number of fibers waiting on the queue
79
+ def num_waiting
80
+ @waiting
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,363 @@
1
+ require "fiber"
2
+ require "eventmachine"
3
+ require 'empathy/em/mutex'
4
+ require 'empathy/em/condition_variable'
5
+
6
+ module Empathy
7
+
8
+ module EM
9
+
10
+ def self.empathise(*modules)
11
+ modules.each do |m|
12
+ Empathy::map_classes(m,self,"Thread","Mutex","ConditionVariable","Queue","ThreadError" => FiberError)
13
+ end
14
+ end
15
+
16
+ module Kernel
17
+ # Like ::Kernel::sleep. Woken by an ::EM::Timer in +seconds+ if supplied
18
+ def self.sleep(seconds=:__empathy_sleep_forever)
19
+
20
+ ::Kernel.raise TypeError, "seconds #{seconds} must be a number" unless seconds == :__empathy_sleep_forever or seconds.is_a? Numeric
21
+ n = Time.now
22
+
23
+ em_thread = Thread.current
24
+ timer = ::EM::Timer.new(seconds){ em_thread.__send__(:wake_resume) } unless seconds == :__empathy_sleep_forever
25
+ em_thread.__send__(:yield_sleep,timer)
26
+
27
+ (Time.now - n).round()
28
+ end
29
+
30
+ def self.at_exit(&block)
31
+ EventMachine.add_shutdown_hook(&block)
32
+ end
33
+ end
34
+
35
+ #Acts like a ::Thread using Fibers and EventMachine
36
+ class Thread
37
+
38
+ @@em_threads = {}
39
+
40
+ # The underlying fiber.
41
+ attr_reader :fiber
42
+
43
+ # Like ::Thread::list. Return an array of all EM::Threads that are alive.
44
+ def self.list
45
+ @@em_threads.values.select { |s| s.alive? }
46
+ end
47
+
48
+ def self.main
49
+ @@main
50
+ end
51
+
52
+ # Like ::Thread::current. Get the currently running EM::Thread, eg to access thread local
53
+ # variables
54
+ def self.current
55
+ @@em_threads[Fiber.current] || ProxyThread.new(Fiber.current)
56
+ end
57
+
58
+ # Alias for Fiber::yield
59
+ # Equivalent to a thread being blocked on IO
60
+ #
61
+ # WARNING: Be very careful about using #yield with the other thread like methods
62
+ # Specifically it is important
63
+ # to ensure user calls to #resume don't conflict with the resumes that are setup via
64
+ # EM.timer or EM.next_tick as a result of #::sleep or #::pass
65
+ def self.yield(*args)
66
+ Fiber.yield(*args)
67
+ end
68
+
69
+
70
+ # Like ::Thread::stop. Sleep forever (until woken)
71
+ def self.stop
72
+ Kernel.sleep()
73
+ end
74
+
75
+ # Like ::Thread::pass.
76
+ # The fiber is resumed on the next_tick of EM's event loop
77
+ def self.pass
78
+ em_thread = current
79
+ ::EM.next_tick{ em_thread.__send__(:wake_resume) }
80
+ em_thread.__send__(:yield_sleep)
81
+ nil
82
+ end
83
+
84
+ # Like ::Thread.exit
85
+ def self.exit
86
+ current.exit
87
+ end
88
+
89
+ def self.kill(thread)
90
+ thread.exit
91
+ end
92
+
93
+ def self.new(*args,&block)
94
+ instance = super(*args,&block)
95
+ ::Kernel.raise FiberError, "super not called for subclass of Thread" unless instance.instance_variable_defined?("@fiber")
96
+ instance
97
+ end
98
+
99
+ def self.start(*args,&block)
100
+ ::Kernel.raise ArgumentError, "no block" unless block_given?
101
+ c = if self != Thread
102
+ Class.new(self) do
103
+ def initialize(*args,&block)
104
+ initialize_fiber(*args,&block)
105
+ end
106
+ end
107
+ else
108
+ self
109
+ end
110
+ c.new(*args,&block)
111
+ end
112
+
113
+ # Create and run
114
+ def initialize(*args,&block)
115
+
116
+ ::Kernel.raise FiberError, "no block" unless block_given?
117
+ initialize_fiber(*args,&block)
118
+ end
119
+
120
+ # Like ::Thread#join.
121
+ # s1 = Empathy.new{ Empathy.sleep(1) }
122
+ # s2 = Empathy.new{ Empathy.sleep(1) }
123
+ # s1.join
124
+ # s2.join
125
+ def join(limit = nil)
126
+ @mutex.synchronize { @join_cond.wait(@mutex,limit) } if alive?
127
+ ::Kernel.raise @exception if @exception
128
+ if alive? then nil else self end
129
+ end
130
+
131
+ # Like Fiber#resume. Refer to warnings on #::yield
132
+ def resume(*args)
133
+ #TODO should only allow if @status is :run, which really means
134
+ # blocked by a call to Yield
135
+ fiber.resume(*args)
136
+ end
137
+
138
+ # Like ::Thread#alive? or Fiber#alive?
139
+ def alive?
140
+ fiber.alive?
141
+ end
142
+
143
+ # Like ::Thread#stop? Always true unless our fiber is the current fiber
144
+ def stop?
145
+ Fiber.current != fiber
146
+ end
147
+
148
+ # Like ::Thread#status
149
+ def status
150
+ case @status
151
+ when :run
152
+ #TODO - if not the current fiber
153
+ # we can only be in this state due to a yield on the
154
+ # underlying fiber, which means we are actually in sleep
155
+ # or we're a ProxyThread that is dead and not yet
156
+ # cleaned up
157
+ "run"
158
+ when :sleep
159
+ "sleep"
160
+ when :dead, :killed
161
+ false
162
+ when :exception
163
+ nil
164
+ end
165
+ end
166
+
167
+ # Like ::Thread#value. Implicitly calls #join.
168
+ # em_thread = Empathy.new{ 1+2 }
169
+ # em_thread.value # => 3
170
+ def value
171
+ join and @value
172
+ end
173
+
174
+ # Like ::Thread#exit. Signals thread to wakeup and die
175
+ def exit
176
+ case @status
177
+ when :sleep
178
+ wake_resume(:exit)
179
+ when :run
180
+ throw :exit
181
+ end
182
+ end
183
+
184
+ alias :kill :exit
185
+ alias :terminate :exit
186
+
187
+ # Like ::Thread#wakeup Wakes a sleeping Thread
188
+ def wakeup
189
+ ::Kernel.raise FiberError, "dead em_thread" unless status
190
+ wake_resume()
191
+ self
192
+ end
193
+
194
+ # Like ::Thread#raise, raise an exception on a sleeping Thread
195
+ def raise(*args)
196
+ args << RuntimeError if args.empty?
197
+ if fiber == Fiber.current
198
+ ::Kernel.raise(*args)
199
+ elsif status
200
+ wake_resume(:raise,*args)
201
+ else
202
+ #dead em_thread, do nothing
203
+ end
204
+ end
205
+
206
+ alias :run :wakeup
207
+
208
+
209
+ # Access to "fiber local" variables, akin to "thread local" variables.
210
+ # Empathy.new do
211
+ # ...
212
+ # Empathy.current[:connection].send(data)
213
+ # ...
214
+ # end
215
+ def [](name)
216
+ ::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
217
+ @locals[name.to_sym]
218
+ end
219
+
220
+ # 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
+ def []=(name, value)
227
+ ::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
228
+ @locals[name.to_sym] = value
229
+ end
230
+
231
+ # Like ::Thread#key? Is there a "fiber local" variable defined called +name+
232
+ def key?(name)
233
+ ::Kernel.raise TypeError, "name #{name} must convert to_sym" unless name and name.respond_to?(:to_sym)
234
+ @locals.has_key?(name.to_sym)
235
+ end
236
+
237
+ # Like ::Thread#keys The set of "em_thread local" variable keys
238
+ def keys()
239
+ @locals.keys
240
+ end
241
+
242
+ def inspect #:nodoc:
243
+ "#<Empathy::EM::Thread:0x%s %s %s" % [object_id, @fiber == Fiber.current ? "run" : "yielded", status || "dead" ]
244
+ end
245
+
246
+ # Do something when the fiber completes.
247
+ def ensure_hook(key,&block)
248
+ if block_given? then
249
+ @ensure_hooks[key] = block
250
+ else
251
+ @ensure_hooks.delete(key)
252
+ end
253
+ end
254
+
255
+ protected
256
+
257
+ def fiber_body(*args,&block) #:nodoc:
258
+ # Run the em_thread's block and capture the return value.
259
+ @status = :run
260
+
261
+ @value, @exception = nil, nil
262
+ catch :exit do
263
+ begin
264
+ @value = block.call(*args)
265
+ @status = :dead
266
+ rescue Exception => e
267
+ @exception = e
268
+ @status = :exception
269
+ ensure
270
+ run_ensure_hooks()
271
+ end
272
+ end
273
+ @status = :dead if @status == :run
274
+
275
+ # Resume anyone who called join on us.
276
+ # the synchronize is not really necessary for fibers
277
+ # but does no harm
278
+ @mutex.synchronize { @join_cond.signal() }
279
+
280
+ # Delete from the list of running stands.
281
+ @@em_threads.delete(@fiber)
282
+
283
+ @value || @exception
284
+ end
285
+
286
+ private
287
+
288
+ def initialize_fiber(*args,&block)
289
+ ::Kernel.raise FiberError, "already initialized" if @fiber
290
+ # Create our fiber.
291
+ fiber = Fiber.new{ fiber_body(*args,&block) }
292
+
293
+ init(fiber)
294
+
295
+ # Finally start the em_thread.
296
+ fiber.resume()
297
+ end
298
+
299
+ def init(fiber)
300
+ @fiber = fiber
301
+ # Add us to the list of living em_threads.
302
+ @@main ||= self
303
+ @@main = self unless @@main.status
304
+
305
+ @@em_threads[@fiber] = self
306
+
307
+ # Initialize our "fiber local" storage.
308
+ @locals = {}
309
+
310
+ # Record the status
311
+ @status = nil
312
+
313
+ # Hooks to run when the em_thread dies (eg by Mutex to release locks)
314
+ @ensure_hooks = {}
315
+
316
+ # Condition variable and mutex for joining.
317
+ @mutex = Mutex.new()
318
+ @join_cond = ConditionVariable.new()
319
+ end
320
+
321
+ def yield_sleep(timer=nil)
322
+ @status = :sleep
323
+ event,*args = Fiber.yield
324
+ timer.cancel if timer
325
+ case event
326
+ when :exit
327
+ @status = :killed
328
+ throw :exit
329
+ when :wake
330
+ @status = :run
331
+ when :raise
332
+ ::Kernel.raise(*args)
333
+ end
334
+ end
335
+
336
+ def wake_resume(event = :wake,*args)
337
+ fiber.resume(event,*args) if @status == :sleep
338
+ #TODO if fiber is still alive? and status = :run
339
+ # then it has been yielded from non Empathy code.
340
+ # if it is not alive, and is a proxy em_thread then
341
+ # we can signal the condition variable from here
342
+ end
343
+
344
+ def run_ensure_hooks()
345
+ #TODO - better not throw exceptions in an ensure hook
346
+ @ensure_hooks.each { |key,hook| hook.call }
347
+ end
348
+ end
349
+
350
+ # This class is used if EM::Thread class methods are called on Fibers that were not created
351
+ # with EM::Thread.new()
352
+ class ProxyThread < Thread
353
+
354
+ #TODO start an EM periodic timer to reap dead proxythreads (running ensurehooks)
355
+ #TODO do something sensible for #value, #kill
356
+
357
+ def initialize(fiber)
358
+ init(fiber)
359
+ end
360
+ end
361
+
362
+ end
363
+ end