empathy 0.0.1.RC0 → 0.0.1.RC2

Sign up to get free protection for your applications and to get access to all the features.
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