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 +59 -16
- data/VERSION +1 -1
- data/lib/tribe.rb +14 -8
- data/lib/tribe/actor.rb +12 -7
- data/lib/tribe/dispatcher.rb +16 -0
- data/lib/tribe/mailbox.rb +39 -0
- data/lib/tribe/message.rb +15 -0
- data/lib/tribe/registry.rb +1 -2
- data/lib/tribe/scheduler.rb +63 -18
- data/lib/tribe/thread_pool.rb +58 -0
- data/lib/tribe/timer.rb +3 -3
- data/spec/lib/tribe/dispatcher_spec.rb +4 -0
- data/tribe.gemspec +6 -4
- metadata +7 -5
- data/lib/tribe/clock.rb +0 -78
- data/lib/tribe/singleton.rb +0 -5
- data/spec/lib/tribe/clock_spec.rb +0 -4
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
|
-
|
72
|
+
reset_count
|
61
73
|
end
|
62
74
|
|
63
75
|
def increment
|
64
76
|
@count += 1
|
65
|
-
|
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
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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.
|
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/
|
7
|
+
require 'tribe/dispatcher'
|
9
8
|
require 'tribe/registry'
|
10
|
-
require 'tribe/
|
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.
|
15
|
-
Tribe::
|
16
|
+
def self.dispatcher
|
17
|
+
@dispatcher ||= Tribe::Dispatcher.new
|
16
18
|
end
|
17
19
|
|
18
20
|
def self.registry
|
19
|
-
Tribe::Registry.
|
21
|
+
@registry ||= Tribe::Registry.new
|
20
22
|
end
|
21
23
|
|
22
|
-
def self.
|
23
|
-
Tribe::
|
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
|
-
@
|
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
|
-
|
30
|
-
|
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
|
38
|
-
@
|
40
|
+
def process
|
41
|
+
@mailbox.retrieve_each do |message|
|
39
42
|
begin
|
40
|
-
send(message[:method], *message[:args])
|
43
|
+
send(message[:method], *message[:args])
|
44
|
+
true
|
41
45
|
rescue Exception => e
|
42
|
-
|
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,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
|
data/lib/tribe/registry.rb
CHANGED
data/lib/tribe/scheduler.rb
CHANGED
@@ -1,32 +1,77 @@
|
|
1
1
|
module Tribe
|
2
|
-
class Scheduler
|
3
|
-
|
4
|
-
@count = opts[:count] || 20
|
5
|
-
@workers = []
|
6
|
-
@messages = Queue.new
|
2
|
+
class Scheduler
|
3
|
+
FREQUENCY = 100 # Hz.
|
7
4
|
|
8
|
-
|
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
|
-
|
13
|
-
|
14
|
-
end
|
14
|
+
message = { :command => :shutdown }
|
15
|
+
@messages.push(message)
|
15
16
|
|
16
|
-
@
|
17
|
-
|
18
|
-
|
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
|
23
|
-
|
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
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
48
|
+
@scheduler.schedule(self)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
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.
|
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/
|
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/
|
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/
|
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.
|
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/
|
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/
|
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/
|
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:
|
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
|
data/lib/tribe/singleton.rb
DELETED