actuator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []