tribe 0.0.2 → 0.0.3

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/Rakefile CHANGED
@@ -55,37 +55,80 @@ end
55
55
 
56
56
  require 'tribe'
57
57
 
58
+ #
59
+ # TODO: Temporary benchmarking/demo code.
60
+ #
61
+
62
+ require 'benchmark'
63
+
64
+ $demo_queue = Queue.new
65
+
66
+ DEMO_ACTOR_COUNT = 100
67
+ DEMO_MSG_COUNT = 3000
68
+
58
69
  class MyActor < Tribe::Actor
70
+
59
71
  def pre_init
60
- @count = 0
72
+ reset_count
61
73
  end
62
74
 
63
75
  def increment
64
76
  @count += 1
65
- puts "#{@name}=#{@count}" if @count >= 50000
77
+
78
+ if @count >= DEMO_MSG_COUNT
79
+ puts "#{@name} done."
80
+ $demo_queue.push(@name)
81
+ end
82
+ end
83
+
84
+ def reset_count
85
+ @count = 0
66
86
  end
67
87
 
68
88
  def go(friend_name)
69
89
  friend = Tribe.registry[friend_name]
70
90
 
71
- 50000.times do
91
+ DEMO_MSG_COUNT.times do
72
92
  friend.increment!
73
93
  end
74
94
  end
75
95
  end
76
96
 
77
97
  def tribe_demo
78
- a1 = Tribe.registry['a1'] || MyActor.new(:name => 'a1')
79
- a2 = Tribe.registry['a2'] || MyActor.new(:name => 'a2')
80
- a3 = Tribe.registry['a3'] || MyActor.new(:name => 'a3')
81
- a4 = Tribe.registry['a4'] || MyActor.new(:name => 'a4')
82
- a5 = Tribe.registry['a5'] || MyActor.new(:name => 'a5')
83
- a6 = Tribe.registry['a6'] || MyActor.new(:name => 'a6')
84
-
85
- a1.go!('a2')
86
- a2.go!('a1')
87
- a3.go!('a4')
88
- a4.go!('a3')
89
- a5.go!('a6')
90
- a6.go!('a5')
98
+ actors = []
99
+
100
+ puts 'Create actors...'
101
+ (0...DEMO_ACTOR_COUNT).each do |i|
102
+ name = i.to_s
103
+ actor = Tribe.registry[name] || MyActor.new(:name => name)
104
+ actors.push(actor)
105
+ puts name
106
+ end
107
+
108
+ puts 'Resetting...'
109
+ actors.each do |actor|
110
+ actor.reset_count!
111
+ end
112
+
113
+ puts 'Go...'
114
+ actors.each do |actor|
115
+ friend = actor.name.to_i
116
+ friend += 1
117
+ friend = 0 if friend == DEMO_ACTOR_COUNT
118
+ friend = friend.to_s
119
+
120
+ puts "pair: #{actor.name}, #{friend}"
121
+ actor.go!(friend)
122
+ end
123
+
124
+ puts 'Benchmark...'
125
+ result = Benchmark.realtime do
126
+ DEMO_ACTOR_COUNT.times do
127
+ $demo_queue.pop
128
+ end
129
+ end
130
+
131
+ puts result
132
+
133
+ nil
91
134
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
data/lib/tribe.rb CHANGED
@@ -3,23 +3,29 @@ require 'thread'
3
3
  require 'set'
4
4
 
5
5
  require 'tribe/actor'
6
- require 'tribe/singleton'
7
6
  require 'tribe/worker'
8
- require 'tribe/scheduler'
7
+ require 'tribe/dispatcher'
9
8
  require 'tribe/registry'
10
- require 'tribe/clock'
9
+ require 'tribe/scheduler'
11
10
  require 'tribe/timer'
11
+ require 'tribe/message'
12
+ require 'tribe/mailbox'
13
+ require 'tribe/thread_pool'
12
14
 
13
15
  module Tribe
14
- def self.scheduler
15
- Tribe::Scheduler.instance
16
+ def self.dispatcher
17
+ @dispatcher ||= Tribe::Dispatcher.new
16
18
  end
17
19
 
18
20
  def self.registry
19
- Tribe::Registry.instance
21
+ @registry ||= Tribe::Registry.new
20
22
  end
21
23
 
22
- def self.clock
23
- Tribe::Clock.instance
24
+ def self.scheduler
25
+ @scheduler ||= Tribe::Scheduler.new
24
26
  end
25
27
  end
28
+
29
+ Tribe.dispatcher
30
+ Tribe.registry
31
+ Tribe.scheduler
data/lib/tribe/actor.rb CHANGED
@@ -6,7 +6,7 @@ module Tribe
6
6
  run_hook(:pre_init)
7
7
 
8
8
  @alive = true
9
- @process_lock = Mutex.new
9
+ @mailbox = Mailbox.new
10
10
  @name = options[:name].freeze
11
11
 
12
12
  Tribe.registry.register(self)
@@ -26,20 +26,25 @@ module Tribe
26
26
  end
27
27
 
28
28
  def tell(method, *args)
29
- Tribe.scheduler.send(:schedule) do
30
- process(:method => method, :args => args)
29
+ message = { :method => method, :args => args }
30
+ @mailbox.deliver(message)
31
+
32
+ Tribe.dispatcher.send(:schedule) do
33
+ process
31
34
  end
32
35
 
33
36
  true
34
37
  end
35
38
 
36
39
  private
37
- def process(message)
38
- @process_lock.synchronize do
40
+ def process
41
+ @mailbox.retrieve_each do |message|
39
42
  begin
40
- send(message[:method], *message[:args]) if @alive
43
+ send(message[:method], *message[:args])
44
+ true
41
45
  rescue Exception => e
42
- @alive = false
46
+ puts "Actor died while processing: #{e.message}\n#{e.backtrace.join("\n")}"
47
+ false
43
48
  end
44
49
  end
45
50
  end
@@ -0,0 +1,16 @@
1
+ module Tribe
2
+ class Dispatcher
3
+ def initialize(options = {})
4
+ @pool = ThreadPool.new(:count => options[:count])
5
+ end
6
+
7
+ def shutdown
8
+ @pool.shutdown
9
+ end
10
+
11
+ private
12
+ def schedule(&block)
13
+ @pool.dispatch(&block)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Tribe
2
+ class Mailbox
3
+ RETRIEVE_CAP = -1 # Disable's the cap by default.
4
+
5
+ def initialize
6
+ @messages = Queue.new
7
+ @retrieve_lock = Mutex.new
8
+ end
9
+
10
+ # Returns true if it ran to completion, false if interupted.
11
+ # Block must return true on success, false on failure.
12
+ def retrieve_each(max = RETRIEVE_CAP, &block)
13
+ @retrieve_lock.synchronize do
14
+ count = 0
15
+
16
+ @messages.length.times do
17
+ if max >= 0 && count >= max
18
+ break
19
+ else
20
+ count += 1
21
+ end
22
+
23
+ message = @messages.pop
24
+
25
+ unless block.call(message)
26
+ return false
27
+ end
28
+ end
29
+
30
+ return true
31
+ end
32
+ end
33
+
34
+ def deliver(message)
35
+ @messages.push(message)
36
+ true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Tribe
2
+ class Message
3
+ attr_reader :from
4
+ attr_reader :to
5
+ attr_reader :method
6
+ attr_reader :args
7
+
8
+ def initialize(from, to, method, *args)
9
+ @from = from
10
+ @to = to
11
+ @method = method
12
+ @args = args
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,5 @@
1
1
  module Tribe
2
- class Registry < Tribe::Singleton
3
-
2
+ class Registry
4
3
  def initialize
5
4
  @lock = Mutex.new
6
5
 
@@ -1,32 +1,77 @@
1
1
  module Tribe
2
- class Scheduler < Tribe::Singleton
3
- def initialize(opts = {})
4
- @count = opts[:count] || 20
5
- @workers = []
6
- @messages = Queue.new
2
+ class Scheduler
3
+ FREQUENCY = 100 # Hz.
7
4
 
8
- spawn(@count)
5
+ def initialize(options = {})
6
+ @frequency = options[:frequency] || FREQUENCY
7
+ @run = true
8
+ @timers = SortedSet.new
9
+ @messages = Queue.new
10
+ @thread = Thread.new { main }
9
11
  end
10
12
 
11
13
  def shutdown
12
- @count.times do
13
- @messages.push({ :command => :shutdown })
14
- end
14
+ message = { :command => :shutdown }
15
+ @messages.push(message)
15
16
 
16
- @workers.each do |worker|
17
- worker.join
18
- end
17
+ @thread.join
18
+ end
19
+
20
+ def schedule(timer)
21
+ message = { :command => :schedule, :timer => timer }
22
+ @messages.push(message)
23
+
24
+ true
25
+ end
26
+
27
+ def unschedule(timer)
28
+ message = { :command => :unschedule, :timer => timer }
29
+ @messages.push(message)
30
+
31
+ true
19
32
  end
20
33
 
21
34
  private
22
- def schedule(&block)
23
- @messages.push({ :command => :perform, :task => block })
35
+ def main
36
+ sleep_val = 1.0 / @frequency
37
+
38
+ while @run
39
+ sleep(sleep_val)
40
+ process_commands
41
+ fire_timers
42
+ end
24
43
  end
25
44
 
26
- def spawn(count)
27
- count.times do
28
- worker = Worker.new(@messages)
29
- @workers.push(worker)
45
+ def process_commands
46
+ @messages.length.times do
47
+ message = @messages.pop
48
+
49
+ case message[:command]
50
+ when :schedule
51
+ @timers.add(message[:timer])
52
+ when :unschedule
53
+ @timers.delete(message[:timer])
54
+ when :shutdown
55
+ @run = false
56
+ else
57
+ raise("Invalid command: #{message[:command]}")
58
+ end
59
+ end
60
+ end
61
+
62
+ def fire_timers
63
+ count = 0
64
+
65
+ while true
66
+ return 0 if @timers.empty?
67
+
68
+ if (timer = @timers.first).send(:fire?)
69
+ @timers.delete(timer)
70
+ timer.send(:fire)
71
+ count += 1
72
+ else
73
+ return count
74
+ end
30
75
  end
31
76
  end
32
77
  end
@@ -0,0 +1,58 @@
1
+ module Tribe
2
+ class ThreadPool
3
+ THREAD_COUNT = 64
4
+
5
+ def initialize(options = {})
6
+ @count = options[:count] || THREAD_COUNT
7
+
8
+ @threads = []
9
+ @lock = Mutex.new
10
+ @queue = Queue.new
11
+
12
+ spawn(@count)
13
+ end
14
+
15
+ def dispatch(&block)
16
+ @queue.push({ :command => :perform, :task => block })
17
+
18
+ true
19
+ end
20
+
21
+ def shutdown
22
+ @lock.synchronize do
23
+ @count.times do
24
+ @queue.push({ :command => :shutdown })
25
+ end
26
+
27
+ @threads.each { |thread| thread.join }
28
+
29
+ true
30
+ end
31
+ end
32
+
33
+ private
34
+ def spawn(count)
35
+ count.times do
36
+ @lock.synchronize do
37
+ thread = Thread.new { thread_main }
38
+ @threads.push(thread)
39
+ end
40
+ end
41
+ end
42
+
43
+ def thread_main
44
+ while (message = @queue.pop)
45
+ case message[:command]
46
+ when :perform
47
+ begin
48
+ message[:task].call
49
+ rescue Exception => e
50
+ puts "Worker caught exception: #{e.message}\n#{e.backtrace.join("\n")}"
51
+ end
52
+ when :shutdown
53
+ return
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/tribe/timer.rb CHANGED
@@ -6,14 +6,14 @@ module Tribe
6
6
  def initialize(seconds, options = {}, &block)
7
7
  @seconds = seconds.to_f
8
8
  @callback = block
9
- @clock = options[:clock] || Tribe.clock
9
+ @scheduler = options[:scheduler] || Tribe.scheduler
10
10
  @repeat = options[:repeat] || false
11
11
 
12
12
  schedule
13
13
  end
14
14
 
15
15
  def cancel
16
- @clock.unschedule(self)
16
+ @scheduler.unschedule(self)
17
17
  end
18
18
 
19
19
  def <=>(timer)
@@ -45,7 +45,7 @@ module Tribe
45
45
 
46
46
  def schedule
47
47
  @fire_at = now + @seconds
48
- @clock.schedule(self)
48
+ @scheduler.schedule(self)
49
49
  end
50
50
  end
51
51
  end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tribe::Dispatcher do
4
+ end
data/tribe.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "tribe"
8
- s.version = "0.0.2"
8
+ s.version = "0.0.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Chad Remesch"]
@@ -28,14 +28,16 @@ Gem::Specification.new do |s|
28
28
  "VERSION",
29
29
  "lib/tribe.rb",
30
30
  "lib/tribe/actor.rb",
31
- "lib/tribe/clock.rb",
31
+ "lib/tribe/dispatcher.rb",
32
+ "lib/tribe/mailbox.rb",
33
+ "lib/tribe/message.rb",
32
34
  "lib/tribe/registry.rb",
33
35
  "lib/tribe/scheduler.rb",
34
- "lib/tribe/singleton.rb",
36
+ "lib/tribe/thread_pool.rb",
35
37
  "lib/tribe/timer.rb",
36
38
  "lib/tribe/worker.rb",
37
39
  "spec/lib/tribe/actor_spec.rb",
38
- "spec/lib/tribe/clock_spec.rb",
40
+ "spec/lib/tribe/dispatcher_spec.rb",
39
41
  "spec/lib/tribe/registry_spec.rb",
40
42
  "spec/lib/tribe/scheduler_spec.rb",
41
43
  "spec/lib/tribe/timer_spec.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -191,14 +191,16 @@ files:
191
191
  - VERSION
192
192
  - lib/tribe.rb
193
193
  - lib/tribe/actor.rb
194
- - lib/tribe/clock.rb
194
+ - lib/tribe/dispatcher.rb
195
+ - lib/tribe/mailbox.rb
196
+ - lib/tribe/message.rb
195
197
  - lib/tribe/registry.rb
196
198
  - lib/tribe/scheduler.rb
197
- - lib/tribe/singleton.rb
199
+ - lib/tribe/thread_pool.rb
198
200
  - lib/tribe/timer.rb
199
201
  - lib/tribe/worker.rb
200
202
  - spec/lib/tribe/actor_spec.rb
201
- - spec/lib/tribe/clock_spec.rb
203
+ - spec/lib/tribe/dispatcher_spec.rb
202
204
  - spec/lib/tribe/registry_spec.rb
203
205
  - spec/lib/tribe/scheduler_spec.rb
204
206
  - spec/lib/tribe/timer_spec.rb
@@ -220,7 +222,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
220
222
  version: '0'
221
223
  segments:
222
224
  - 0
223
- hash: 4061265025390310383
225
+ hash: 1198643590868831764
224
226
  required_rubygems_version: !ruby/object:Gem::Requirement
225
227
  none: false
226
228
  requirements:
data/lib/tribe/clock.rb DELETED
@@ -1,78 +0,0 @@
1
- module Tribe
2
- class Clock < Tribe::Singleton
3
- FREQUENCY = 100 # Hz.
4
-
5
- def initialize(options = {})
6
- @frequency = options[:frequency] || FREQUENCY
7
- @run = true
8
- @timers = SortedSet.new
9
- @messages = Queue.new
10
- @thread = Thread.new { main }
11
- end
12
-
13
- def shutdown
14
- message = { :command => :shutdown }
15
- @messages.push(message)
16
-
17
- @thread.join
18
- end
19
-
20
- def schedule(timer)
21
- message = { :command => :schedule, :timer => timer }
22
- @messages.push(message)
23
-
24
- true
25
- end
26
-
27
- def unschedule(timer)
28
- message = { :command => :unschedule, :timer => timer }
29
- @messages.push(message)
30
-
31
- true
32
- end
33
-
34
- private
35
- def main
36
- sleep_val = 1.0 / @frequency
37
-
38
- while @run
39
- sleep(sleep_val)
40
- process_commands
41
- fire_timers
42
- end
43
- end
44
-
45
- def process_commands
46
- @messages.length.times do
47
- message = @messages.pop
48
-
49
- case message[:command]
50
- when :schedule
51
- @timers.add(message[:timer])
52
- when :unschedule
53
- @timers.delete(message[:timer])
54
- when :shutdown
55
- @run = false
56
- else
57
- raise("Invalid command: #{message[:command]}")
58
- end
59
- end
60
- end
61
-
62
- def fire_timers
63
- count = 0
64
-
65
- while true
66
- return 0 if @timers.empty?
67
-
68
- if (timer = @timers.first).send(:fire?)
69
- @timers.delete(timer)
70
- timer.send(:fire)
71
- count += 1
72
- else
73
- return count
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,5 +0,0 @@
1
- module Tribe
2
- class Singleton
3
- include ::Singleton
4
- end
5
- end
@@ -1,4 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Tribe::Clock do
4
- end