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.
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ -e ./yard/extensions.rb --markup=markdown --no-private -
2
+ LICENSE.txt
3
+ CHANGES.md
4
+ TESTING.md
5
+
data/CHANGES.md ADDED
@@ -0,0 +1,8 @@
1
+ Empathy Changelog
2
+ =====================
3
+
4
+
5
+ 0.0.1
6
+
7
+ * Port from strand - with intention to make empathy encompass more than threads
8
+
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ Empathy
2
+ =======================
3
+
4
+ http://rubygems.org/gems/empathy
5
+
6
+ Make EventMachine behave like standard Ruby
7
+
8
+ Empathic Threads
9
+ ------------------------
10
+
11
+ {Empathy::EM} uses Fibers to provide Thread, Queue, Mutex, ConditionVariable and MonitorMixin classes that behave like the native ruby ones.
12
+
13
+ require 'eventmachine'
14
+ require 'empathy'
15
+
16
+ # start eventmachine and a main EM::Thread
17
+ Empathy.run do
18
+ thread = Empathy::EM::Thread.new do
19
+ my_thread = Empathy::EM::Thread.current
20
+
21
+ #local storage
22
+ Empathy::EM::Thread.current[:my_key] = "some value"
23
+
24
+ #pass control elsewhere
25
+ Empathy::EM::Thread.pass
26
+
27
+ Empathy::EM::Kernel.sleep(1)
28
+
29
+ 1 + 2
30
+ end
31
+
32
+ thread.join
33
+ thread.value # => 3
34
+ end
35
+
36
+ Almost all Thread behaviour is provided except that one thread will never see another as "running". Where ruby's thread API raises ThreadError, Empathy::EM will raise FiberError (which is also available as {Empathy::EM::ThreadError})
37
+
38
+ Empathic code outside of the EventMachine reactor
39
+ --------------------------------------------------
40
+
41
+ If your code may run inside or outside the reactor the {Empathy} module itself provides a set of submodules that delegate to either the native ruby class when called outside of the reactor, or to the {Empathy::EM} class when called inside the reactor.
42
+
43
+ require 'eventmachine'
44
+ require 'empathy'
45
+ Empathy::Thread.current.inspect # => "Thread<...>"
46
+
47
+ Empathy::Kernel.sleep(1)
48
+
49
+ Empathy.event_machine? # => false
50
+
51
+ Empathy.run do
52
+
53
+ Empathy.event_machine? # => true
54
+
55
+ Empathy::Thread.new do
56
+
57
+ Empathy::Thread.current.inspect #=> "Empathy::EM::Thread<...>"
58
+
59
+ Empathy::Kernel.sleep(1)
60
+
61
+ begin
62
+ #...do something with threads...
63
+ rescue Empathy::ThreadError
64
+ # ...
65
+ end
66
+ end
67
+ end
68
+
69
+ Note that since Empathy::Thread and friends are modules, you cannot subclass them
70
+
71
+ Empathise with all ruby code
72
+ -------------------------------
73
+
74
+ Seamlessly Replace Ruby's native classes with the Empathy:EM ones (redefines top level constants), plus monkey patching of
75
+ {Object#sleep} and {Object#at_exit}
76
+
77
+ require 'empathy/with_all_of_ruby'
78
+ # do not run any code that uses threads outside of the reactor after the above require
79
+
80
+ Empathy.run do
81
+ t = Thread.new { 1 + 2 }
82
+
83
+ t.inspect # => "Empathy::EM::Thread<.....>"
84
+
85
+ # this will be a Fiber+EM sleep, not Kernel.sleep
86
+ sleep(4)
87
+
88
+ t.join
89
+ end
90
+
91
+ Caveat: Take care with code that subclasses Thread. This can work as long as the classes are defined after
92
+ 'empathy/thread' is required.
93
+
94
+ Q: But doesn't eventmachine need to use normal threads?
95
+
96
+ A: Indeed, 'empathy/thread' also defines constants in the EventMachine namespace that refer to the original Ruby classes
97
+
98
+ Empathise a library module
99
+ ----------------------------------
100
+
101
+ module MyLibary
102
+ def create_thread
103
+ Thread.new { Thread.current.inspect }
104
+ end
105
+ end
106
+
107
+ # If library will only be used inside the reactor
108
+ Empathy::EM.empathise(MyLibrary)
109
+
110
+ # If library is used both inside and outside the reactor
111
+ Empathy.empathise(MyLibrary)
112
+
113
+ See {Empathy::EM.empathise} and {Empathy.empathise}.
114
+
115
+ In both cases constants are defined in the MyLibrary namespace so that Thread, Queue etc, refer to either Empathy modules
116
+ or Empathy:EM classes. Note that any call to empathise will have the side-effect of monkey patching Object to provide EM
117
+ safe #sleep and #at_exit.
118
+
119
+ Caveat: MyLibrary must not subclass Thread etc...
120
+
121
+ Empathy::EM::IO - Implement Ruby's Socket API over EventMachine
122
+ ---------------------------------------------------------------
123
+
124
+ Work in progress - see experimental socket-io branch
125
+
126
+ Contributing to empathy
127
+ ---------------------------
128
+
129
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
130
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
131
+ * Fork the project
132
+ * Start a feature/bugfix branch
133
+ * Commit and push until you are happy with your contribution
134
+ * Make sure to add specs, preferably based on ruby-spec
135
+
136
+ Copyright
137
+ -----------------
138
+
139
+ Copyright (c) 2011 Christopher J. Bottaro. (Original fiber+EM concept in "strand" library)
140
+
141
+ Copyright (c) 2012,2013 Grant Gardner.
142
+
143
+ See {file:LICENSE.txt} for further details.
144
+
data/Rakefile CHANGED
@@ -2,16 +2,12 @@ require "bundler/gem_tasks"
2
2
 
3
3
  require 'rspec/core'
4
4
  require 'rspec/core/rake_task'
5
- require 'rdoc/task'
5
+ require 'yard'
6
6
  require 'rake/clean'
7
7
 
8
8
  RSpec::Core::RakeTask.new(:spec)
9
9
 
10
- RDoc::Task.new do |rdoc|
11
- rdoc.main = "README.rdoc"
12
- rdoc.rdoc_files.include("README.rdoc", "CHANGELOG","lib/**/*.rb")
13
- rdoc.title = "Empathy"
14
- end
10
+ YARD::Rake::YardocTask.new
15
11
 
16
12
  # Create the test task.
17
13
  desc 'Run mspec'
@@ -29,5 +25,6 @@ desc "Run tests"
29
25
  task :test => [ :spec, :mspec ]
30
26
 
31
27
  task :default => [ :test, :build ]
32
- CLOBBER.include [ "pkg/" ]
28
+
29
+ CLOBBER.include [ "pkg/","doc/" ]
33
30
 
data/TESTING.md ADDED
@@ -0,0 +1,27 @@
1
+ Empathy Testing
2
+ ===================
3
+
4
+ Empathy::EM
5
+ -----------------
6
+ Classes in Empathy::EM module are tested using the fully empathic method - replacing Ruby's ::Thread constant with one that points to Empathy::EM::Thread etc, and then running against the subset of rubyspec to do with threads. The rubyspecs are not changed at all but some tests are skipped (see empathy.mspec)
7
+
8
+ This also tests Empathy.run and the class replacement approach (including subclassing)
9
+
10
+ Empathy module - reactor aware
11
+ --------------------------------
12
+
13
+ We just test that the delegation works as expected, with explicit specs run under rspec - see spec/empathy_spec.rb
14
+
15
+
16
+ Empathising libaries
17
+ -------------------------------
18
+
19
+ We prove that code can reference various classes as normal, and have them actually use the ones injected by {Empathy.empathise} or {Empathy::EM.empathise}. Aside from some magic for the MonitorMixin this is really just standard ruby behaviour.
20
+
21
+ MonitorMixin
22
+ -------------------------------
23
+
24
+ There are no rubyspecs for this module, so I have converted ruby 1.9's unit tests into a simple rubyspec - see rubyspec/monitor_spec.rb
25
+
26
+
27
+
data/empathy.gemspec CHANGED
@@ -16,8 +16,12 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.licenses = %q{MIT}
18
18
 
19
+ # Empathy can be used without eventmachine
20
+ gem.add_development_dependency 'eventmachine', '~> 1.0.0'
21
+
19
22
  gem.add_development_dependency 'mspec', '>= 1.5.18'
20
23
  gem.add_development_dependency 'rspec'
21
24
  gem.add_development_dependency 'rr'
22
- gem.add_development_dependency 'eventmachine', '~> 1.0.0'
25
+ gem.add_development_dependency 'yard'
26
+ gem.add_development_dependency 'redcarpet'
23
27
  end
@@ -11,7 +11,8 @@ module Empathy
11
11
  # Using a mutex for condition variables is meant to protect
12
12
  # against race conditions when the signal occurs between testing whether
13
13
  # a wait is needed and waiting. This situation will never occur with
14
- # fibers, but the semantic is retained
14
+ # fibers, but the semantic is retained for compatibility with ::ConditionVariable
15
+ # @return [ConditionVariable] self
15
16
  def wait(mutex=nil,timeout = nil)
16
17
 
17
18
  if timeout.nil? && (mutex.nil? || Numeric === mutex)
@@ -20,31 +21,37 @@ module Empathy
20
21
  end
21
22
 
22
23
  # Get the fiber (Empathy::EM::Thread) that called us.
23
- empathy = Thread.current
24
+ thread = Thread.current
24
25
  # Add the fiber to the list of waiters.
25
- @waiters << empathy
26
+ @waiters << thread
26
27
  begin
27
28
  sleeper = mutex ? mutex : Kernel
28
29
  if timeout then sleeper.sleep(timeout) else sleeper.sleep() end
29
30
  ensure
30
- # Remove from list of waiters.
31
- @waiters.delete(empathy)
31
+ # Remove from list of waiters. Note this doesn't run if the thread is killed
32
+ @waiters.delete(thread)
32
33
  end
33
34
  self
34
35
  end
35
36
 
37
+ # Like ::ConditionVariable#signal
38
+ # @return [ConditionVariable] self
36
39
  def signal
37
- # If there are no waiters, do nothing.
38
- return self if @waiters.empty?
39
40
 
40
- # Find a waiter to wake up.
41
- waiter = @waiters.shift
41
+ # Find a waiter to wake up
42
+ until @waiters.empty?
43
+ waiter = @waiters.shift
44
+ if waiter.alive?
45
+ ::EM.next_tick{ waiter.wakeup if waiter.alive? }
46
+ break;
47
+ end
48
+ end
42
49
 
43
- # Resume it on next tick.
44
- ::EM.next_tick{ waiter.wakeup }
45
50
  self
46
51
  end
47
52
 
53
+ # Like ::ConditionVariable#broadcast
54
+ # @return [ConditionVariable] self
48
55
  def broadcast
49
56
  all_waiting = @waiters.dup
50
57
  @waiters.clear
@@ -0,0 +1,8 @@
1
+
2
+ # like ::Monitor
3
+ class Empathy::EM::Monitor
4
+ include MonitorMixin
5
+ alias try_enter try_mon_enter
6
+ alias enter mon_enter
7
+ alias exit mon_exit
8
+ end
@@ -1,11 +1,15 @@
1
1
  module Empathy
2
2
  module EM
3
+
4
+ # An Empathy equivalent to ::Mutex
3
5
  class Mutex
4
6
 
5
7
  def initialize()
6
8
  @waiters = []
7
9
  end
8
10
 
11
+ # Like ::Mutex#lock, except allows reentrant locks
12
+ # @return [Mutex] self
9
13
  def lock()
10
14
  em_thread = Thread.current
11
15
  @waiters << em_thread
@@ -16,16 +20,24 @@ module Empathy
16
20
  self
17
21
  end
18
22
 
23
+ # Like ::Mutex#unlock
24
+ # @return [Mutex] self
25
+ # @raise [FiberError] if not owner of the mutex
19
26
  def unlock()
20
27
  em_thread = Thread.current
21
28
  raise FiberError, "not owner" unless @waiters.first == em_thread
22
29
  release()
30
+ self
23
31
  end
24
32
 
33
+ # Like ::Mutex#locked?
34
+ # @return [true,false]
25
35
  def locked?
26
36
  !@waiters.empty? && @waiters.first.alive?
27
37
  end
28
38
 
39
+ # Like ::Mutex#try_lock
40
+ # @return [true,false]
29
41
  def try_lock
30
42
  if locked?
31
43
  false
@@ -35,6 +47,8 @@ module Empathy
35
47
  end
36
48
  end
37
49
 
50
+ # Like ::Mutex#synchronize
51
+ # @return the result of the block
38
52
  def synchronize(&block)
39
53
  lock
40
54
  yield
@@ -42,6 +56,9 @@ module Empathy
42
56
  unlock
43
57
  end
44
58
 
59
+ # Like ::Mutex#sleep
60
+ # @param [Numeric] timeout
61
+ # @return [Fixnum]
45
62
  def sleep(timeout=nil)
46
63
  unlock
47
64
  begin
@@ -1,26 +1,7 @@
1
1
  module Empathy
2
2
  module EM
3
+
3
4
  # 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
5
  class Queue
25
6
 
26
7
  # Creates a new queue
@@ -31,7 +12,8 @@ module Empathy
31
12
  @waiting = 0
32
13
  end
33
14
 
34
- # Pushes +obj+ to the queue
15
+ # @param [Object] obj
16
+ # @return [void]
35
17
  def push(obj)
36
18
  @q << obj
37
19
  @mutex.synchronize { @cv.signal }
@@ -40,12 +22,8 @@ module Empathy
40
22
  alias :enq :push
41
23
 
42
24
  # 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
- #
25
+ # @param [Boolean] non_block
26
+ # @raise FiberError if non_block is true and the queue is empty
49
27
  def pop(non_block=false)
50
28
  raise FiberError, "queue empty" if non_block && empty?
51
29
  if empty?
@@ -59,23 +37,25 @@ module Empathy
59
37
  alias :shift :pop
60
38
  alias :deq :pop
61
39
 
62
- # Returns the length of the queue
40
+ # @return [Fixnum] the length of the queue
63
41
  def length
64
42
  @q.length
65
43
  end
66
44
  alias :size :length
67
45
 
68
- # Returns +true+ if the queue is empty
46
+ # @return [true] if the queue is empty
47
+ # @return [false] otherwise
69
48
  def empty?
70
49
  @q.empty?
71
50
  end
72
51
 
73
52
  # Removes all objects from the queue
53
+ # @return [void]
74
54
  def clear
75
55
  @q.clear
76
56
  end
77
57
 
78
- # Returns the number of fibers waiting on the queue
58
+ # @return [Fixnum] the number of fibers waiting on the queue
79
59
  def num_waiting
80
60
  @waiting
81
61
  end