ntl-actor 0.0.1
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.
- 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: []
|