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