ntl-actor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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'
@@ -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,13 @@
1
+ module Actor
2
+ module Controls
3
+ module Actor
4
+ def self.example
5
+ Example.new
6
+ end
7
+
8
+ class Example
9
+ include ::Actor
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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,15 @@
1
+ module Actor
2
+ module Controls
3
+ module Statistics
4
+ module ElapsedTime
5
+ def self.configure_timer receiver, elapsed_times
6
+ timer = Timer.example elapsed_times
7
+
8
+ receiver.timer = timer
9
+
10
+ elapsed_times.count
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -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,13 @@
1
+ module Actor
2
+ class Queue
3
+ module Assertions
4
+ def contains? object
5
+ if list.find object then true else false end
6
+ end
7
+
8
+ def empty?
9
+ size.zero?
10
+ end
11
+ end
12
+ end
13
+ 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,4 @@
1
+ require 'test_bench'
2
+
3
+ require 'actor/test_fixtures/parallel_iteration'
4
+ require 'actor/test_fixtures/sample_actor_status'
@@ -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: []