ntl-actor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/actor.rb +20 -0
- data/lib/actor/actor.rb +146 -0
- data/lib/actor/controls.rb +10 -0
- data/lib/actor/controls/actor.rb +13 -0
- data/lib/actor/controls/statistics.rb +21 -0
- data/lib/actor/controls/statistics/elapsed_time.rb +15 -0
- data/lib/actor/controls/statistics/elapsed_time/average.rb +21 -0
- data/lib/actor/controls/statistics/elapsed_time/maximum.rb +21 -0
- data/lib/actor/controls/statistics/elapsed_time/minimum.rb +21 -0
- data/lib/actor/controls/statistics/elapsed_time/standard_deviation.rb +38 -0
- data/lib/actor/controls/statistics/timer.rb +19 -0
- data/lib/actor/controls/time.rb +24 -0
- data/lib/actor/controls/time/clock.rb +31 -0
- data/lib/actor/messaging/address.rb +40 -0
- data/lib/actor/messaging/reader.rb +31 -0
- data/lib/actor/messaging/reader/substitute.rb +40 -0
- data/lib/actor/messaging/system_message.rb +25 -0
- data/lib/actor/messaging/writer.rb +26 -0
- data/lib/actor/messaging/writer/substitute.rb +26 -0
- data/lib/actor/queue.rb +116 -0
- data/lib/actor/queue/assertions.rb +13 -0
- data/lib/actor/queue/reader.rb +57 -0
- data/lib/actor/statistics.rb +75 -0
- data/lib/actor/statistics/copy.rb +19 -0
- data/lib/actor/statistics/timer.rb +26 -0
- data/lib/actor/test_fixtures.rb +4 -0
- data/lib/actor/test_fixtures/parallel_iteration.rb +143 -0
- data/lib/actor/test_fixtures/sample_actor_status.rb +45 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4bb4e6d1c16a91e05baee4e50e7dfd15f3b1fe9b
|
4
|
+
data.tar.gz: 8bba64e0b7e2cf6bb1333db1a543891d3f39a1f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8515e2123e74493072741078194d021e7ccaa1be0dba3ab7773ef2428aeabc1a05d68b252dbb43a13271b5590b63ebd0aa95521e3c28368ca817df470eaa4ef5
|
7
|
+
data.tar.gz: 6ecccc370a61eb473b0db1ddf7304075b0c0bcb35ca807637bcb8c2d8ffad3deaaaa7d82ff45b9a55f3fa82a40e78c4ad7146fe4c2e8583264d7b0982ad8fc65
|
data/lib/actor.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'actor/queue'
|
6
|
+
require 'actor/queue/assertions'
|
7
|
+
require 'actor/queue/reader'
|
8
|
+
|
9
|
+
require 'actor/statistics'
|
10
|
+
require 'actor/statistics/copy'
|
11
|
+
require 'actor/statistics/timer'
|
12
|
+
|
13
|
+
require 'actor/messaging/address'
|
14
|
+
require 'actor/messaging/reader'
|
15
|
+
require 'actor/messaging/reader/substitute'
|
16
|
+
require 'actor/messaging/writer'
|
17
|
+
require 'actor/messaging/writer/substitute'
|
18
|
+
require 'actor/messaging/system_message'
|
19
|
+
|
20
|
+
require 'actor/actor'
|
data/lib/actor/actor.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Actor
|
2
|
+
def self.included cls
|
3
|
+
cls.class_exec do
|
4
|
+
extend Destructure
|
5
|
+
extend Spawn
|
6
|
+
extend Start
|
7
|
+
|
8
|
+
prepend UpdateStatistics
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :actor_address
|
13
|
+
attr_accessor :actor_state
|
14
|
+
attr_writer :reader
|
15
|
+
|
16
|
+
def action
|
17
|
+
end
|
18
|
+
|
19
|
+
def actor_statistics
|
20
|
+
@actor_statistics ||= Statistics.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle _
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_system_message message
|
27
|
+
case message
|
28
|
+
when Messaging::SystemMessage::Pause then
|
29
|
+
self.actor_state = State::Paused
|
30
|
+
|
31
|
+
when Messaging::SystemMessage::Resume then
|
32
|
+
self.actor_state = State::Running
|
33
|
+
|
34
|
+
when Messaging::SystemMessage::Stop then
|
35
|
+
self.actor_state = State::Stopped
|
36
|
+
raise StopIteration
|
37
|
+
|
38
|
+
when Messaging::SystemMessage::RecordStatus then
|
39
|
+
status = message.status
|
40
|
+
|
41
|
+
Statistics::Copy.(status, actor_statistics)
|
42
|
+
|
43
|
+
status.state = actor_state
|
44
|
+
|
45
|
+
Messaging::Writer.write status, message.reply_address
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def reader
|
50
|
+
@reader ||= Reader::Substitute.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_loop
|
54
|
+
loop do
|
55
|
+
while message = reader.read(wait: actor_state == State::Paused)
|
56
|
+
handle message
|
57
|
+
|
58
|
+
if message.is_a? Messaging::SystemMessage
|
59
|
+
handle_system_message message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
action if actor_state == State::Running
|
64
|
+
|
65
|
+
Thread.pass
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module Destructure
|
70
|
+
def destructure actor, address, thread, include: nil
|
71
|
+
return address if include.nil?
|
72
|
+
|
73
|
+
result = [address]
|
74
|
+
|
75
|
+
include.each do |variable_name|
|
76
|
+
value = binding.local_variable_get variable_name
|
77
|
+
|
78
|
+
result << value
|
79
|
+
end
|
80
|
+
|
81
|
+
return *result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Spawn
|
86
|
+
def spawn *positional_arguments, include: nil, **keyword_arguments, &block
|
87
|
+
address = Messaging::Address.get
|
88
|
+
|
89
|
+
method = if respond_to? :build then :build else :new end
|
90
|
+
|
91
|
+
if keyword_arguments.empty?
|
92
|
+
instance = __send__ method, *positional_arguments, &block
|
93
|
+
else
|
94
|
+
instance = __send__ method, *positional_arguments, **keyword_arguments, &block
|
95
|
+
end
|
96
|
+
|
97
|
+
reader = Messaging::Reader.build address
|
98
|
+
|
99
|
+
instance.actor_address = address
|
100
|
+
instance.actor_state = State::Paused
|
101
|
+
instance.reader = reader
|
102
|
+
|
103
|
+
thread = ::Thread.new do
|
104
|
+
instance.run_loop
|
105
|
+
end
|
106
|
+
|
107
|
+
destructure instance, address, thread, include: include
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module Start
|
112
|
+
def start *positional_arguments, include: nil, **keyword_arguments, &block
|
113
|
+
address, instance, thread = spawn(
|
114
|
+
*positional_arguments,
|
115
|
+
include: %i(actor thread),
|
116
|
+
**keyword_arguments,
|
117
|
+
&block
|
118
|
+
)
|
119
|
+
|
120
|
+
Messaging::Writer.write(
|
121
|
+
Messaging::SystemMessage::Resume.new,
|
122
|
+
address
|
123
|
+
)
|
124
|
+
|
125
|
+
destructure instance, address, thread, include: include
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module State
|
130
|
+
Paused = :paused
|
131
|
+
Running = :running
|
132
|
+
Stopped = :stopped
|
133
|
+
end
|
134
|
+
|
135
|
+
module UpdateStatistics
|
136
|
+
def action
|
137
|
+
actor_statistics.executing_action
|
138
|
+
|
139
|
+
result = super
|
140
|
+
|
141
|
+
actor_statistics.action_executed
|
142
|
+
|
143
|
+
result
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'actor/controls/actor'
|
2
|
+
require 'actor/controls/statistics'
|
3
|
+
require 'actor/controls/statistics/elapsed_time'
|
4
|
+
require 'actor/controls/statistics/elapsed_time/average'
|
5
|
+
require 'actor/controls/statistics/elapsed_time/maximum'
|
6
|
+
require 'actor/controls/statistics/elapsed_time/minimum'
|
7
|
+
require 'actor/controls/statistics/elapsed_time/standard_deviation'
|
8
|
+
require 'actor/controls/statistics/timer'
|
9
|
+
require 'actor/controls/time'
|
10
|
+
require 'actor/controls/time/clock'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Statistics
|
4
|
+
def self.example executions=nil
|
5
|
+
executions ||= 1
|
6
|
+
|
7
|
+
statistics = ::Actor::Statistics.new
|
8
|
+
|
9
|
+
executions.times do
|
10
|
+
timer = Controls::Statistics::Timer.example
|
11
|
+
statistics.timer = timer
|
12
|
+
|
13
|
+
statistics.executing_action
|
14
|
+
statistics.action_executed
|
15
|
+
end
|
16
|
+
|
17
|
+
statistics
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Statistics
|
4
|
+
module ElapsedTime
|
5
|
+
module Average
|
6
|
+
def self.configure_timer receiver
|
7
|
+
ElapsedTime.configure_timer receiver, elapsed_times
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.elapsed_times
|
11
|
+
[1, 2, 6]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.value
|
15
|
+
3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Statistics
|
4
|
+
module ElapsedTime
|
5
|
+
module Maximum
|
6
|
+
def self.configure_timer receiver
|
7
|
+
ElapsedTime.configure_timer receiver, elapsed_times
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.elapsed_times
|
11
|
+
[1, 11, 111]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.value
|
15
|
+
111
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Statistics
|
4
|
+
module ElapsedTime
|
5
|
+
module Minimum
|
6
|
+
def self.configure_timer receiver
|
7
|
+
ElapsedTime.configure_timer receiver, elapsed_times
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.elapsed_times
|
11
|
+
[111, 11, 1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.value
|
15
|
+
1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# See https://rosettacode.org/wiki/Standard_deviation
|
2
|
+
module Actor
|
3
|
+
module Controls
|
4
|
+
module Statistics
|
5
|
+
module ElapsedTime
|
6
|
+
module StandardDeviation
|
7
|
+
def self.configure_timer receiver
|
8
|
+
ElapsedTime.configure_timer receiver, elapsed_times
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.elapsed_times
|
12
|
+
[2, 4, 4, 4, 5, 5, 7, 9]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.average
|
16
|
+
5
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.maximum
|
20
|
+
7
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.minimum
|
24
|
+
3
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.percent
|
28
|
+
40.0
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.value
|
32
|
+
2
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Statistics
|
4
|
+
module Timer
|
5
|
+
def self.example elapsed_times=nil
|
6
|
+
elapsed_times ||= [Controls::Time::ElapsedTime.example]
|
7
|
+
|
8
|
+
steps = elapsed_times.flat_map do |elapsed_time|
|
9
|
+
[elapsed_time, 0]
|
10
|
+
end
|
11
|
+
|
12
|
+
timer = ::Actor::Statistics::Timer.new
|
13
|
+
timer.clock = Time::Clock.build steps
|
14
|
+
timer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Time
|
4
|
+
def self.example offset=nil
|
5
|
+
offset ||= 0
|
6
|
+
offset = ElapsedTime.example offset
|
7
|
+
|
8
|
+
reference + offset
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reference
|
12
|
+
::Time.new 2000, 1, 1, 11, 11, 11, -3600
|
13
|
+
end
|
14
|
+
|
15
|
+
module ElapsedTime
|
16
|
+
def self.example scalar=nil
|
17
|
+
scalar ||= 1
|
18
|
+
|
19
|
+
Rational(scalar, 1000)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Actor
|
2
|
+
module Controls
|
3
|
+
module Time
|
4
|
+
class Clock
|
5
|
+
attr_reader :step_function
|
6
|
+
attr_accessor :time
|
7
|
+
|
8
|
+
def initialize time, step_function
|
9
|
+
@time = time
|
10
|
+
@step_function = step_function
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build steps
|
14
|
+
time = Time.reference
|
15
|
+
|
16
|
+
step_function = steps.cycle
|
17
|
+
|
18
|
+
new time, step_function
|
19
|
+
end
|
20
|
+
|
21
|
+
def now
|
22
|
+
now = time
|
23
|
+
|
24
|
+
self.time += step_function.next
|
25
|
+
|
26
|
+
now
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
class Address
|
4
|
+
attr_reader :id
|
5
|
+
attr_reader :queue
|
6
|
+
|
7
|
+
def initialize id, queue
|
8
|
+
@id = id
|
9
|
+
@queue = queue
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.get
|
13
|
+
id = SecureRandom.uuid
|
14
|
+
queue = Queue.new
|
15
|
+
|
16
|
+
new id, queue
|
17
|
+
end
|
18
|
+
|
19
|
+
def reader_count
|
20
|
+
queue.reader_count
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
%{#<#{self.class.name} id=#{id.inspect}, queue=#{queue_tail}..#{queue_head}, readers=#{reader_count}>}
|
25
|
+
end
|
26
|
+
|
27
|
+
def queue_head
|
28
|
+
queue.head
|
29
|
+
end
|
30
|
+
|
31
|
+
def queue_tail
|
32
|
+
queue.tail
|
33
|
+
end
|
34
|
+
|
35
|
+
def queue_size
|
36
|
+
queue.size
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
class Reader
|
4
|
+
attr_reader :queue_reader
|
5
|
+
|
6
|
+
def initialize queue_reader
|
7
|
+
@queue_reader = queue_reader
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build address
|
11
|
+
queue = address.queue
|
12
|
+
|
13
|
+
queue_reader = Queue::Reader.build queue
|
14
|
+
|
15
|
+
new queue_reader
|
16
|
+
end
|
17
|
+
|
18
|
+
def read wait: nil
|
19
|
+
queue_reader.read wait: wait
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
queue_reader.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
def stopped?
|
27
|
+
queue_reader.stopped?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
class Reader
|
4
|
+
class Substitute
|
5
|
+
attr_accessor :stopped
|
6
|
+
|
7
|
+
def read wait: nil
|
8
|
+
message = messages.shift
|
9
|
+
|
10
|
+
return message if message
|
11
|
+
|
12
|
+
if wait
|
13
|
+
raise Wait, "Next message has not yet been written"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
self.stopped = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def stopped?
|
22
|
+
stopped
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_message message
|
26
|
+
messages << message
|
27
|
+
end
|
28
|
+
|
29
|
+
def messages
|
30
|
+
@messages ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
Wait = Class.new StandardError
|
34
|
+
|
35
|
+
# Eventide compatibility
|
36
|
+
singleton_class.send :alias_method, :build, :new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
module SystemMessage
|
4
|
+
Pause = Class.new { include SystemMessage }
|
5
|
+
Resume = Class.new { include SystemMessage }
|
6
|
+
Stop = Class.new { include SystemMessage }
|
7
|
+
|
8
|
+
class RecordStatus
|
9
|
+
include SystemMessage
|
10
|
+
|
11
|
+
attr_reader :reply_address
|
12
|
+
attr_reader :status
|
13
|
+
|
14
|
+
def initialize reply_address
|
15
|
+
@reply_address = reply_address
|
16
|
+
@status = Status.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Status < OpenStruct
|
21
|
+
include SystemMessage
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
class Writer
|
4
|
+
attr_reader :queue
|
5
|
+
|
6
|
+
def initialize queue
|
7
|
+
@queue = queue
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build address
|
11
|
+
queue = address.queue
|
12
|
+
|
13
|
+
new queue
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.write message, address
|
17
|
+
instance = build address
|
18
|
+
instance.write message
|
19
|
+
end
|
20
|
+
|
21
|
+
def write message
|
22
|
+
queue.write message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Actor
|
2
|
+
module Messaging
|
3
|
+
class Writer
|
4
|
+
class Substitute
|
5
|
+
def write message
|
6
|
+
messages << message
|
7
|
+
end
|
8
|
+
|
9
|
+
def messages
|
10
|
+
@messages ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
module Assertions
|
14
|
+
def written? &block
|
15
|
+
block ||= proc { true }
|
16
|
+
|
17
|
+
messages.any? &block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Eventide compatibility
|
22
|
+
singleton_class.send :alias_method, :build, :new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/actor/queue.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
module Actor
|
2
|
+
class Queue
|
3
|
+
attr_reader :blocked_threads
|
4
|
+
attr_reader :reader_positions
|
5
|
+
attr_reader :list
|
6
|
+
attr_reader :mutex
|
7
|
+
attr_accessor :tail
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@blocked_threads = Set.new
|
11
|
+
@reader_positions = Hash.new 0
|
12
|
+
@list = []
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@tail = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def head
|
18
|
+
tail + size
|
19
|
+
end
|
20
|
+
|
21
|
+
def read position, wait: nil
|
22
|
+
mutex.synchronize do
|
23
|
+
relative_position = position - tail
|
24
|
+
|
25
|
+
until list.count > relative_position
|
26
|
+
return nil unless wait
|
27
|
+
|
28
|
+
blocked_threads << Thread.current
|
29
|
+
mutex.sleep
|
30
|
+
blocked_threads.delete Thread.current
|
31
|
+
end
|
32
|
+
|
33
|
+
object = list[relative_position]
|
34
|
+
|
35
|
+
up position.next
|
36
|
+
down position
|
37
|
+
|
38
|
+
object
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reader_started position=nil
|
43
|
+
mutex.synchronize do
|
44
|
+
position ||= tail
|
45
|
+
|
46
|
+
up position
|
47
|
+
|
48
|
+
position
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def readers?
|
53
|
+
reader_positions.any?
|
54
|
+
end
|
55
|
+
|
56
|
+
def reader_count
|
57
|
+
return 0 unless readers?
|
58
|
+
|
59
|
+
reader_positions.values.reduce &:+
|
60
|
+
end
|
61
|
+
|
62
|
+
def reader_stopped position
|
63
|
+
mutex.synchronize do
|
64
|
+
down position
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def size
|
69
|
+
list.size
|
70
|
+
end
|
71
|
+
|
72
|
+
def write object
|
73
|
+
# Owning the mutex is not necessary here; the worst that can happen is
|
74
|
+
# that occasionally we write a object when there aren't any readers.
|
75
|
+
return unless readers?
|
76
|
+
|
77
|
+
mutex.synchronize do
|
78
|
+
list << object
|
79
|
+
end
|
80
|
+
|
81
|
+
blocked_threads.each &:wakeup
|
82
|
+
end
|
83
|
+
|
84
|
+
# These command methods are highly unsafe to call without owning the mutex;
|
85
|
+
# therefore, they are marked as private.
|
86
|
+
private
|
87
|
+
|
88
|
+
def down position
|
89
|
+
ref_count = reader_positions[position] - 1
|
90
|
+
|
91
|
+
if ref_count.zero?
|
92
|
+
reader_positions.delete position
|
93
|
+
else
|
94
|
+
reader_positions[position] = ref_count
|
95
|
+
end
|
96
|
+
|
97
|
+
if ref_count.zero?
|
98
|
+
if readers? and position == tail
|
99
|
+
new_tail = reader_positions.keys.min
|
100
|
+
|
101
|
+
expired_objects = new_tail - tail
|
102
|
+
|
103
|
+
list.slice! 0, expired_objects
|
104
|
+
|
105
|
+
self.tail = new_tail
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
ref_count
|
110
|
+
end
|
111
|
+
|
112
|
+
def up position
|
113
|
+
reader_positions[position] += 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Actor
|
2
|
+
class Queue
|
3
|
+
class Reader
|
4
|
+
attr_reader :queue
|
5
|
+
attr_accessor :position
|
6
|
+
attr_accessor :stopped
|
7
|
+
|
8
|
+
def initialize queue, position
|
9
|
+
@queue = queue
|
10
|
+
@position = position
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.build queue
|
14
|
+
position = queue.reader_started
|
15
|
+
|
16
|
+
new queue, position
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.start queue, &block
|
20
|
+
instance = build queue
|
21
|
+
|
22
|
+
begin
|
23
|
+
block.(instance)
|
24
|
+
return instance
|
25
|
+
ensure
|
26
|
+
instance.stop
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def read wait: nil
|
31
|
+
if stopped?
|
32
|
+
raise Stopped, "Reader has stopped"
|
33
|
+
end
|
34
|
+
|
35
|
+
object = queue.read position, wait: wait
|
36
|
+
|
37
|
+
return nil if object.nil?
|
38
|
+
|
39
|
+
self.position = position.next
|
40
|
+
|
41
|
+
object
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
self.stopped = true
|
46
|
+
|
47
|
+
queue.reader_stopped position
|
48
|
+
end
|
49
|
+
|
50
|
+
def stopped?
|
51
|
+
stopped
|
52
|
+
end
|
53
|
+
|
54
|
+
Stopped = Class.new StandardError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Actor
|
2
|
+
class Statistics
|
3
|
+
attr_accessor :elapsed_time
|
4
|
+
attr_accessor :executions
|
5
|
+
attr_accessor :last_execution_time
|
6
|
+
attr_accessor :maximum_elapsed_time
|
7
|
+
attr_accessor :minimum_elapsed_time
|
8
|
+
attr_accessor :start_time
|
9
|
+
attr_accessor :sum_elapsed_time_squared
|
10
|
+
attr_writer :timer
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@executions = 0
|
14
|
+
@elapsed_time = 0
|
15
|
+
@sum_elapsed_time_squared = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.build actor
|
19
|
+
instance = new
|
20
|
+
actor.add_observer instance
|
21
|
+
instance
|
22
|
+
end
|
23
|
+
|
24
|
+
def executing_action
|
25
|
+
now = timer.reset
|
26
|
+
|
27
|
+
self.start_time ||= now
|
28
|
+
end
|
29
|
+
|
30
|
+
def action_executed
|
31
|
+
start_time, stop_time = timer.stop
|
32
|
+
|
33
|
+
elapsed_time = stop_time - start_time
|
34
|
+
|
35
|
+
self.elapsed_time += elapsed_time
|
36
|
+
self.executions += 1
|
37
|
+
self.last_execution_time = stop_time
|
38
|
+
self.maximum_elapsed_time = [elapsed_time, maximum_elapsed_time].compact.max
|
39
|
+
self.minimum_elapsed_time = [elapsed_time, minimum_elapsed_time].compact.min
|
40
|
+
self.sum_elapsed_time_squared += elapsed_time ** 2
|
41
|
+
end
|
42
|
+
|
43
|
+
def average_elapsed_time
|
44
|
+
return nil if executions.zero?
|
45
|
+
|
46
|
+
Rational elapsed_time, executions
|
47
|
+
end
|
48
|
+
|
49
|
+
def standard_deviation
|
50
|
+
return nil if executions.zero?
|
51
|
+
|
52
|
+
average_elapsed_time = self.average_elapsed_time
|
53
|
+
|
54
|
+
deviation = Math.sqrt(
|
55
|
+
[
|
56
|
+
0,
|
57
|
+
Rational(sum_elapsed_time_squared, executions) - average_elapsed_time ** 2
|
58
|
+
].max
|
59
|
+
)
|
60
|
+
|
61
|
+
min = average_elapsed_time - deviation
|
62
|
+
max = average_elapsed_time + deviation
|
63
|
+
|
64
|
+
unless average_elapsed_time.zero?
|
65
|
+
percent = Rational(deviation * 100, average_elapsed_time)
|
66
|
+
end
|
67
|
+
|
68
|
+
return deviation, min, max, percent
|
69
|
+
end
|
70
|
+
|
71
|
+
def timer
|
72
|
+
@timer ||= Timer.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Actor
|
2
|
+
class Statistics
|
3
|
+
module Copy
|
4
|
+
def self.call target, statistics
|
5
|
+
target.average_elapsed_time = statistics.average_elapsed_time
|
6
|
+
target.elapsed_time = statistics.elapsed_time
|
7
|
+
target.executions = statistics.executions
|
8
|
+
target.last_execution_time = statistics.last_execution_time
|
9
|
+
target.minimum_elapsed_time = statistics.minimum_elapsed_time
|
10
|
+
target.maximum_elapsed_time = statistics.maximum_elapsed_time
|
11
|
+
target.start_time = statistics.start_time
|
12
|
+
|
13
|
+
deviation, _, _, percent = statistics.standard_deviation
|
14
|
+
target.standard_deviation_amount = deviation
|
15
|
+
target.standard_deviation_percent = percent
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Actor
|
2
|
+
class Statistics
|
3
|
+
class Timer
|
4
|
+
attr_writer :clock
|
5
|
+
attr_accessor :start_time
|
6
|
+
attr_accessor :stop_time
|
7
|
+
|
8
|
+
def clock
|
9
|
+
@clock ||= Time
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
self.stop_time = nil
|
14
|
+
self.start_time = clock.now
|
15
|
+
|
16
|
+
start_time
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
self.stop_time = stop_time = clock.now
|
21
|
+
|
22
|
+
return start_time, stop_time
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Actor
|
2
|
+
module TestFixtures
|
3
|
+
class ParallelIteration
|
4
|
+
include TestBench::Fixture
|
5
|
+
|
6
|
+
attr_writer :iteration_count
|
7
|
+
attr_reader :iteration_proc
|
8
|
+
attr_writer :setup_proc
|
9
|
+
attr_writer :setup_thread_proc
|
10
|
+
attr_writer :teardown_proc
|
11
|
+
attr_accessor :test_proc
|
12
|
+
attr_reader :prose
|
13
|
+
attr_writer :thread_count
|
14
|
+
|
15
|
+
def initialize iteration_proc, prose
|
16
|
+
@iteration_proc = iteration_proc
|
17
|
+
@prose = prose
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.build prose, each_iteration:, test: nil, setup: nil, setup_thread: nil, teardown: nil, iterations: nil, threads: nil
|
21
|
+
instance = new each_iteration, prose
|
22
|
+
instance.iteration_count = iterations
|
23
|
+
instance.setup_proc = setup
|
24
|
+
instance.setup_thread_proc = setup_thread
|
25
|
+
instance.teardown_proc = teardown
|
26
|
+
instance.test_proc = test
|
27
|
+
instance.thread_count = threads
|
28
|
+
instance
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.call *arguments
|
32
|
+
instance = build *arguments
|
33
|
+
instance.()
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
instance_exec &setup_proc
|
38
|
+
|
39
|
+
threads = thread_count.times.map do |index|
|
40
|
+
thread = Thread.new do
|
41
|
+
Thread.stop
|
42
|
+
|
43
|
+
instance_exec &setup_thread_proc
|
44
|
+
|
45
|
+
iteration_count.times do
|
46
|
+
instance_exec &iteration_proc
|
47
|
+
end
|
48
|
+
|
49
|
+
prose = "#{self.prose} (Thread: #{index + 1}/#{thread_count})"
|
50
|
+
|
51
|
+
if test_proc
|
52
|
+
test prose do
|
53
|
+
instance_exec &test_proc
|
54
|
+
end
|
55
|
+
else
|
56
|
+
context prose
|
57
|
+
end
|
58
|
+
end
|
59
|
+
thread.abort_on_exception = true
|
60
|
+
thread
|
61
|
+
end
|
62
|
+
|
63
|
+
Thread.pass until threads.all? &:stop?
|
64
|
+
|
65
|
+
context "Started #{thread_count} threads; each will iterate #{iteration_count} times"
|
66
|
+
|
67
|
+
t0 = Time.now
|
68
|
+
|
69
|
+
threads.each &:wakeup
|
70
|
+
threads.each &:join
|
71
|
+
|
72
|
+
t1 = Time.now
|
73
|
+
|
74
|
+
elapsed_time = t1 - t0
|
75
|
+
|
76
|
+
total_iterations = thread_count * iteration_count
|
77
|
+
|
78
|
+
iterations_per_second = Rational total_iterations, elapsed_time
|
79
|
+
|
80
|
+
context "Finished %i iterations across %i threads in %.2fs; %.2f iterations per second" %
|
81
|
+
[total_iterations, thread_count, elapsed_time, iterations_per_second]
|
82
|
+
|
83
|
+
return iteration_count, thread_count
|
84
|
+
|
85
|
+
ensure
|
86
|
+
instance_exec &teardown_proc
|
87
|
+
end
|
88
|
+
|
89
|
+
def iteration_count
|
90
|
+
@iteration_count ||= Defaults.iteration_count
|
91
|
+
end
|
92
|
+
|
93
|
+
def print_debug receiver
|
94
|
+
str = receiver.inspect
|
95
|
+
str << "\n"
|
96
|
+
|
97
|
+
$stdout.write str
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_proc
|
101
|
+
@setup_proc ||= proc {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_thread_proc
|
105
|
+
@setup_thread_proc ||= proc {}
|
106
|
+
end
|
107
|
+
|
108
|
+
def teardown_proc
|
109
|
+
@teardown_proc ||= proc {}
|
110
|
+
end
|
111
|
+
|
112
|
+
def thread
|
113
|
+
Thread.current
|
114
|
+
end
|
115
|
+
|
116
|
+
def thread_count
|
117
|
+
@thread_count ||= Defaults.thread_count
|
118
|
+
end
|
119
|
+
|
120
|
+
module Defaults
|
121
|
+
def self.iteration_count
|
122
|
+
if ENV['STRESS_TEST'] == 'high'
|
123
|
+
10_000
|
124
|
+
elsif ENV['STRESS_TEST'] == 'low'
|
125
|
+
2_500
|
126
|
+
else
|
127
|
+
100
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.thread_count
|
132
|
+
if ENV['STRESS_TEST'] == 'high'
|
133
|
+
100
|
134
|
+
elsif ENV['STRESS_TEST'] == 'low'
|
135
|
+
25
|
136
|
+
else
|
137
|
+
2
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Actor
|
2
|
+
module TestFixtures
|
3
|
+
class SampleActorStatus
|
4
|
+
include TestBench::Fixture
|
5
|
+
|
6
|
+
attr_reader :reader
|
7
|
+
attr_reader :reply_address
|
8
|
+
attr_reader :writer
|
9
|
+
|
10
|
+
def initialize writer, reply_address, reader
|
11
|
+
@reader = reader
|
12
|
+
@reply_address = reply_address
|
13
|
+
@writer = writer
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.call prose, address:, test:
|
17
|
+
reply_address = Messaging::Address.get
|
18
|
+
reader = Messaging::Reader.build reply_address
|
19
|
+
|
20
|
+
writer = Messaging::Writer.build address
|
21
|
+
|
22
|
+
instance = new writer, reply_address, reader
|
23
|
+
instance.(prose, &test)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call test_prose, &block
|
27
|
+
writer.write record_status_message
|
28
|
+
|
29
|
+
status_0 = reader.read wait: true
|
30
|
+
|
31
|
+
writer.write record_status_message
|
32
|
+
|
33
|
+
status_1 = reader.read wait: true
|
34
|
+
|
35
|
+
test test_prose do
|
36
|
+
block.(status_1, status_0)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def record_status_message
|
41
|
+
Messaging::SystemMessage::RecordStatus.new reply_address
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ntl-actor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Ladd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Implementation of actor pattern for ruby designed for simplicity and
|
14
|
+
frugality
|
15
|
+
email: nathanladd+github@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/actor.rb
|
21
|
+
- lib/actor/actor.rb
|
22
|
+
- lib/actor/controls.rb
|
23
|
+
- lib/actor/controls/actor.rb
|
24
|
+
- lib/actor/controls/statistics.rb
|
25
|
+
- lib/actor/controls/statistics/elapsed_time.rb
|
26
|
+
- lib/actor/controls/statistics/elapsed_time/average.rb
|
27
|
+
- lib/actor/controls/statistics/elapsed_time/maximum.rb
|
28
|
+
- lib/actor/controls/statistics/elapsed_time/minimum.rb
|
29
|
+
- lib/actor/controls/statistics/elapsed_time/standard_deviation.rb
|
30
|
+
- lib/actor/controls/statistics/timer.rb
|
31
|
+
- lib/actor/controls/time.rb
|
32
|
+
- lib/actor/controls/time/clock.rb
|
33
|
+
- lib/actor/messaging/address.rb
|
34
|
+
- lib/actor/messaging/reader.rb
|
35
|
+
- lib/actor/messaging/reader/substitute.rb
|
36
|
+
- lib/actor/messaging/system_message.rb
|
37
|
+
- lib/actor/messaging/writer.rb
|
38
|
+
- lib/actor/messaging/writer/substitute.rb
|
39
|
+
- lib/actor/queue.rb
|
40
|
+
- lib/actor/queue/assertions.rb
|
41
|
+
- lib/actor/queue/reader.rb
|
42
|
+
- lib/actor/statistics.rb
|
43
|
+
- lib/actor/statistics/copy.rb
|
44
|
+
- lib/actor/statistics/timer.rb
|
45
|
+
- lib/actor/test_fixtures.rb
|
46
|
+
- lib/actor/test_fixtures/parallel_iteration.rb
|
47
|
+
- lib/actor/test_fixtures/sample_actor_status.rb
|
48
|
+
homepage: https://github.com/ntl/actor
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.6.6
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Implementation of actor pattern for ruby
|
72
|
+
test_files: []
|