actuator 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.
@@ -0,0 +1,8 @@
1
+ require_relative '../mutex'
2
+
3
+ old_verbose, $VERBOSE = $VERBOSE, nil
4
+
5
+ Mutex = Actuator::Mutex
6
+ ConditionVariable = Actuator::ConditionVariable
7
+
8
+ $VERBOSE = old_verbose
@@ -0,0 +1,63 @@
1
+ require 'minitest'
2
+ require 'minitest/autorun'
3
+
4
+ #require_relative '../lib/actuator'
5
+ gem 'actuator'
6
+ require 'actuator'
7
+
8
+ module Minitest
9
+ class << self
10
+ alias_method :run_without_actuator, :run
11
+
12
+ def run(args=[])
13
+ result = nil
14
+ Actuator.run do
15
+ Actuator.defer do
16
+ begin
17
+ result = run_without_actuator(args)
18
+ rescue => ex
19
+ Kernel.puts "#{ex.class}: #{ex.message}\n#{ex.backtrace.join "\n"}"
20
+ raise ex
21
+ end
22
+ Actuator.next_tick { Actuator.stop }
23
+ end
24
+ end
25
+ result
26
+ end
27
+ end
28
+ end
29
+
30
+ module Actuator
31
+ class Test < Minitest::Test
32
+ @@_assert_queue = Queue.new
33
+ @@assert_thread = nil
34
+
35
+ # Defer to a thread so that we can sleep reliably without the reactor
36
+ def assert_async
37
+ fiber = Fiber.current
38
+ assert_threaded do
39
+ begin
40
+ yield
41
+ rescue => ex
42
+ Actuator.next_tick { fiber.resume(ex) }
43
+ else
44
+ Actuator.next_tick { fiber.resume }
45
+ end
46
+ end
47
+ result = Fiber.yield
48
+ raise result if result.is_a? Exception
49
+ end
50
+
51
+ # Reuse a single thread to reduce the variance of start delay
52
+ def assert_threaded(&block)
53
+ if @@assert_thread
54
+ @@_assert_queue << block
55
+ else
56
+ @@_assert_queue << block
57
+ @@assert_thread = Thread.new do
58
+ @@_assert_queue.pop.() while true
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,90 @@
1
+ require_relative "setup_test"
2
+
3
+ module Actuator
4
+ class TestActuator < Test
5
+ PrecisionTestDuration = 10.0
6
+ PrecisionTestInterval = 0.002
7
+ PrecisionTotalSamples = (PrecisionTestDuration / PrecisionTestInterval).to_i
8
+
9
+ def setup
10
+ # If GC desyncs the assert thread too much it will cause false positives
11
+ GC.start full_mark: true, immediate_sweep: true
12
+ end
13
+
14
+ def test_once_off_timer
15
+ called = called2 = 0
16
+ timer = Timer.in(0.1) { called += 1 }
17
+ timer2 = Timer.in(0.1) { called2 += 1 }
18
+ assert_async do
19
+ timer2.destroy
20
+ assert timer2.destroyed?, 'destroyed? returned false after calling Timer#destroy'
21
+ Kernel.sleep 0.02
22
+ assert !timer.destroyed?, 'timer destroyed too early'
23
+ Kernel.sleep 0.1
24
+ assert timer.destroyed?, 'timer not destroyed within 20ms'
25
+ Kernel.sleep 0.1
26
+ assert called == 1, 'timer callback not called'
27
+ assert called2 == 0, 'timer callback called after being destroyed'
28
+ end
29
+ end
30
+
31
+ def test_interval_timer
32
+ called = called2 = 0
33
+ timer = Timer.every(0.005) { called += 1 }
34
+ timer2 = Timer.every(0.005) { called2 += 1; timer2.destroy }
35
+ assert_async do
36
+ Kernel.sleep 0.1
37
+ assert called > 10, "5ms interval timer only fired #{called} times in 100ms"
38
+ timer.destroy
39
+ total_called = called
40
+ Kernel.sleep 0.02
41
+ assert called == total_called, 'interval timer fired after being destroyed'
42
+ assert called2 > 0, '5ms interval timer never called'
43
+ assert called2 == 1, 'Timer#destroy did not work in interval callback'
44
+ end
45
+ end
46
+
47
+ #TODO: Implement sampling in the C++ extension to eliminate profiling overhead
48
+ def test_timer_precision
49
+ fiber = Fiber.current
50
+ timer = nil
51
+ lates = Array.new(PrecisionTotalSamples)
52
+ # Try skip initial warmup slowdowns so that they aren't included in the samples
53
+ Timer.in(0.5) do
54
+ attempts = 0
55
+ i = -1
56
+ scheduled_at = Actuator.now
57
+ timer = Timer.every(PrecisionTestInterval) do
58
+ now = Actuator.now
59
+ lates[i += 1] = (now - scheduled_at - PrecisionTestInterval) * 1_000_000
60
+ scheduled_at = Actuator.now
61
+ next if i < PrecisionTotalSamples - 1
62
+ lates.sort!
63
+ below_1000_count = 0
64
+ sum = 0.0; lates.each {|late| below_1000_count += 1 if late < 1000; sum += late }
65
+ mean = sum / lates.size
66
+ median = lates.size % 2 == 0 ? (lates[lates.size/2] + lates[lates.size/2-1]) / 2.0 : lates[(lates.size-1)/2]
67
+ sum = 0.0; lates.each {|late| sum += (late - mean) ** 2 }
68
+ std_deviation = Math.sqrt(sum / (lates.size - 1).to_f)
69
+ Log.puts "[Timer Precision] Total: #{lates.size}, Over 1ms: #{lates.size - below_1000_count}, Low: #{lates.first.round(1)} us, High: #{lates.last.round(1)} us, Median: #{median.round(1)} us, Variance: #{std_deviation.round(1)} us"
70
+ begin
71
+ # Most hardware will provide good precision but this should be able to pass on a Raspberry Pi v1 or a VM
72
+ assert lates.last < 10000, "a timer was over 10 ms late (#{lates.last.round(2)} us)"
73
+ assert median < 200, "average timer precision is worse than 200 us (#{median.round(2)} us)"
74
+ assert std_deviation < 500, "too much jitter, variance is over 500 us (#{std_deviation.round(2)} us)"
75
+ rescue Minitest::Assertion => ex
76
+ Log.puts "Retrying due to bad precision most likely caused by loaded hardware: #{ex.message}"
77
+ attempts += 1
78
+ raise if attempts > 9
79
+ i = -1
80
+ scheduled_at = Actuator.now
81
+ next
82
+ end
83
+ timer.destroy
84
+ fiber.resume
85
+ end
86
+ end
87
+ Fiber.yield
88
+ end
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: actuator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - bawNg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: hoe
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.16'
69
+ description: ''
70
+ email:
71
+ - bawng@intoxicated.co.za
72
+ executables: []
73
+ extensions:
74
+ - ext/actuator/extconf.rb
75
+ extra_rdoc_files:
76
+ - History.txt
77
+ - Manifest.txt
78
+ - README.md
79
+ files:
80
+ - ".autotest"
81
+ - History.txt
82
+ - Manifest.txt
83
+ - README.md
84
+ - Rakefile
85
+ - ext/actuator/actuator.c
86
+ - ext/actuator/actuator.h
87
+ - ext/actuator/clock.c
88
+ - ext/actuator/clock.h
89
+ - ext/actuator/debug.c
90
+ - ext/actuator/debug.h
91
+ - ext/actuator/extconf.rb
92
+ - ext/actuator/log.cpp
93
+ - ext/actuator/log.h
94
+ - ext/actuator/reactor.cpp
95
+ - ext/actuator/reactor.h
96
+ - ext/actuator/ruby_helpers.c
97
+ - ext/actuator/ruby_helpers.h
98
+ - ext/actuator/timer.cpp
99
+ - ext/actuator/timer.h
100
+ - lib/actuator.rb
101
+ - lib/actuator/fiber.rb
102
+ - lib/actuator/fiber_pool.rb
103
+ - lib/actuator/job.rb
104
+ - lib/actuator/mutex.rb
105
+ - lib/actuator/mutex/replace.rb
106
+ - test/setup_test.rb
107
+ - test/test_actuator.rb
108
+ homepage:
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options:
114
+ - "--main"
115
+ - README.md
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.6.12
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: ''
134
+ test_files: []