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 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: []