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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +135 -0
- data/Rakefile +33 -0
- data/TESTING.rdoc +11 -0
- data/empathy.gemspec +23 -0
- data/empathy.mspec +34 -0
- data/lib/empathy.rb +162 -0
- data/lib/empathy/em/condition_variable.rb +57 -0
- data/lib/empathy/em/mutex.rb +70 -0
- data/lib/empathy/em/queue.rb +84 -0
- data/lib/empathy/em/thread.rb +363 -0
- data/lib/empathy/object.rb +19 -0
- data/lib/empathy/thread.rb +8 -0
- data/lib/empathy/version.rb +3 -0
- data/mspec/lib/mspec/empathy.rb +19 -0
- data/mspec/lib/mspec/guards/empathy.rb +10 -0
- data/rubyspec/core/kernel/fixtures/__method__.rb +25 -0
- data/rubyspec/core/kernel/fixtures/autoload_b.rb +5 -0
- data/rubyspec/core/kernel/fixtures/autoload_c.rb +5 -0
- data/rubyspec/core/kernel/fixtures/autoload_d.rb +5 -0
- data/rubyspec/core/kernel/fixtures/caller_fixture1.rb +42 -0
- data/rubyspec/core/kernel/fixtures/caller_fixture2.rb +26 -0
- data/rubyspec/core/kernel/fixtures/chomp.rb +4 -0
- data/rubyspec/core/kernel/fixtures/chomp_f.rb +4 -0
- data/rubyspec/core/kernel/fixtures/chop.rb +4 -0
- data/rubyspec/core/kernel/fixtures/chop_f.rb +4 -0
- data/rubyspec/core/kernel/fixtures/classes.rb +410 -0
- data/rubyspec/core/kernel/fixtures/eval_locals.rb +6 -0
- data/rubyspec/core/kernel/fixtures/eval_return_with_lambda.rb +12 -0
- data/rubyspec/core/kernel/fixtures/eval_return_without_lambda.rb +14 -0
- data/rubyspec/core/kernel/fixtures/test.rb +362 -0
- data/rubyspec/core/kernel/sleep_spec.rb +43 -0
- data/rubyspec/core/mutex/lock_spec.rb +8 -0
- data/rubyspec/core/mutex/locked_spec.rb +8 -0
- data/rubyspec/core/mutex/sleep_spec.rb +56 -0
- data/rubyspec/core/mutex/synchronize_spec.rb +8 -0
- data/rubyspec/core/mutex/try_lock_spec.rb +8 -0
- data/rubyspec/core/mutex/unlock_spec.rb +8 -0
- data/rubyspec/core/thread/abort_on_exception_spec.rb +126 -0
- data/rubyspec/core/thread/add_trace_func_spec.rb +7 -0
- data/rubyspec/core/thread/alive_spec.rb +60 -0
- data/rubyspec/core/thread/allocate_spec.rb +9 -0
- data/rubyspec/core/thread/backtrace_spec.rb +7 -0
- data/rubyspec/core/thread/critical_spec.rb +96 -0
- data/rubyspec/core/thread/current_spec.rb +15 -0
- data/rubyspec/core/thread/element_reference_spec.rb +53 -0
- data/rubyspec/core/thread/element_set_spec.rb +46 -0
- data/rubyspec/core/thread/exclusive_spec.rb +20 -0
- data/rubyspec/core/thread/exit_spec.rb +21 -0
- data/rubyspec/core/thread/fixtures/classes.rb +291 -0
- data/rubyspec/core/thread/fork_spec.rb +9 -0
- data/rubyspec/core/thread/group_spec.rb +5 -0
- data/rubyspec/core/thread/initialize_spec.rb +26 -0
- data/rubyspec/core/thread/inspect_spec.rb +48 -0
- data/rubyspec/core/thread/join_spec.rb +63 -0
- data/rubyspec/core/thread/key_spec.rb +64 -0
- data/rubyspec/core/thread/keys_spec.rb +47 -0
- data/rubyspec/core/thread/kill_spec.rb +21 -0
- data/rubyspec/core/thread/list_spec.rb +38 -0
- data/rubyspec/core/thread/main_spec.rb +10 -0
- data/rubyspec/core/thread/new_spec.rb +56 -0
- data/rubyspec/core/thread/pass_spec.rb +8 -0
- data/rubyspec/core/thread/priority_spec.rb +9 -0
- data/rubyspec/core/thread/raise_spec.rb +225 -0
- data/rubyspec/core/thread/run_spec.rb +9 -0
- data/rubyspec/core/thread/safe_level_spec.rb +6 -0
- data/rubyspec/core/thread/set_trace_func_spec.rb +7 -0
- data/rubyspec/core/thread/shared/exit.rb +173 -0
- data/rubyspec/core/thread/shared/start.rb +51 -0
- data/rubyspec/core/thread/shared/wakeup.rb +59 -0
- data/rubyspec/core/thread/start_spec.rb +9 -0
- data/rubyspec/core/thread/status_spec.rb +48 -0
- data/rubyspec/core/thread/stop_spec.rb +66 -0
- data/rubyspec/core/thread/terminate_spec.rb +11 -0
- data/rubyspec/core/thread/value_spec.rb +36 -0
- data/rubyspec/core/thread/wakeup_spec.rb +7 -0
- data/rubyspec/empathy_spec.rb +26 -0
- data/rubyspec/library/conditionvariable/broadcast_spec.rb +62 -0
- data/rubyspec/library/conditionvariable/signal_spec.rb +64 -0
- data/rubyspec/library/conditionvariable/wait_spec.rb +21 -0
- data/rubyspec/library/mutex/lock_spec.rb +10 -0
- data/rubyspec/library/mutex/locked_spec.rb +10 -0
- data/rubyspec/library/mutex/synchronize_spec.rb +10 -0
- data/rubyspec/library/mutex/try_lock_spec.rb +10 -0
- data/rubyspec/library/mutex/unlock_spec.rb +10 -0
- data/rubyspec/library/queue/append_spec.rb +7 -0
- data/rubyspec/library/queue/clear_spec.rb +15 -0
- data/rubyspec/library/queue/deq_spec.rb +7 -0
- data/rubyspec/library/queue/empty_spec.rb +15 -0
- data/rubyspec/library/queue/enq_spec.rb +7 -0
- data/rubyspec/library/queue/length_spec.rb +7 -0
- data/rubyspec/library/queue/num_waiting_spec.rb +19 -0
- data/rubyspec/library/queue/pop_spec.rb +7 -0
- data/rubyspec/library/queue/push_spec.rb +7 -0
- data/rubyspec/library/queue/shared/deque.rb +37 -0
- data/rubyspec/library/queue/shared/enque.rb +10 -0
- data/rubyspec/library/queue/shared/length.rb +9 -0
- data/rubyspec/library/queue/shift_spec.rb +7 -0
- data/rubyspec/library/queue/size_spec.rb +7 -0
- data/rubyspec/shared/kernel/raise.rb +68 -0
- data/rubyspec/shared/mutex/lock.rb +52 -0
- data/rubyspec/shared/mutex/locked.rb +31 -0
- data/rubyspec/shared/mutex/synchronize.rb +23 -0
- data/rubyspec/shared/mutex/try_lock.rb +30 -0
- data/rubyspec/shared/mutex/unlock.rb +35 -0
- data/rubyspec/spec_helper.rb +48 -0
- data/spec/empathy_spec.rb +129 -0
- data/spec/library_spec.rb +79 -0
- data/spec/spec_helper.rb +6 -0
- 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
|