empathy 0.0.1.RC0

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