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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 459b67293c89bd2854e96adbaf484ec950774fc2
4
+ data.tar.gz: cdea3e412329800ae7d1553fae14dd509a0c03cc
5
+ SHA512:
6
+ metadata.gz: 0b25766398cac34c1ec62b3ac959381850517afcae1cbbd4b59ef7cd8a67b1c0e3de2bcae698007023a47cec6711a1e523c8b1824a0fc000147c42efb6a981b8
7
+ data.tar.gz: c9972bc77b4e58b94ca2c74367aa1cf116629cf5b916c7a41de5623320ffb2c76dabcff0f856f01296410e87dd609839c143a1e4b6b144e9db7f1ff418a6ed75
@@ -0,0 +1,13 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.add_mapping(/.*\.c/) do |f, _|
7
+ at.files_matching(/test_.*rb$/)
8
+ end
9
+ end
10
+
11
+ Autotest.add_hook :run_command do |at|
12
+ system "rake clean compile"
13
+ end
@@ -0,0 +1,3 @@
1
+ === 0.0.1 / 2018-01-23
2
+
3
+ * Initial alpha release. Simple and efficient high precision timer scheduling implemented in C++.
@@ -0,0 +1,28 @@
1
+ .autotest
2
+ README.md
3
+ History.txt
4
+ Manifest.txt
5
+ Rakefile
6
+ lib/actuator.rb
7
+ lib/actuator/fiber.rb
8
+ lib/actuator/fiber_pool.rb
9
+ lib/actuator/job.rb
10
+ lib/actuator/mutex.rb
11
+ lib/actuator/mutex/replace.rb
12
+ ext/actuator/extconf.rb
13
+ ext/actuator/actuator.h
14
+ ext/actuator/actuator.c
15
+ ext/actuator/clock.h
16
+ ext/actuator/clock.c
17
+ ext/actuator/debug.h
18
+ ext/actuator/debug.c
19
+ ext/actuator/ruby_helpers.h
20
+ ext/actuator/ruby_helpers.c
21
+ ext/actuator/reactor.h
22
+ ext/actuator/reactor.cpp
23
+ ext/actuator/timer.h
24
+ ext/actuator/timer.cpp
25
+ ext/actuator/log.h
26
+ ext/actuator/log.cpp
27
+ test/setup_test.rb
28
+ test/test_actuator.rb
@@ -0,0 +1,105 @@
1
+ ## Welcome to Actuator
2
+
3
+ Code: https://github.com/bawNg/actuator \
4
+ Bugs: https://github.com/bawNg/actuator/issues
5
+
6
+ Actuator provides high precision scheduling of light weight timers for async and fibered real-time Ruby applications.
7
+ Even on Windows where kernel precision is lower, average timer accuracy is ~2 us on modern high end hardware (subject to system load).
8
+
9
+ This C++ Ruby extension allows time-sensitive applications to replace native threads with jobs that run in pooled fibers.
10
+
11
+ This is an alpha release, not recommended for production use. While fairly well tested on Windows and Linux, minimal safety
12
+ and error handling has been implemented in order to minimize overhead. Using the API wrong may result in a segfault.
13
+
14
+ #### Features
15
+
16
+ * Provides a high precision float representing the current reactor time
17
+ * High precision single threaded timer callback scheduling
18
+ * Light weight jobs can be used to replace threads with pooled fibers
19
+ * Job-based implementation of sleep, join, kill, Mutex and ConditionVariable
20
+ * Job-aware sample-based CPU profiling API and execution time warnings
21
+ * Warnings for timers that fire later than the configured threshold
22
+ * Low overhead timestamped logging API which is thread-safe
23
+
24
+ #### Supported platforms
25
+
26
+ * MRI Ruby 2.x (1.9 is probably compatible but has not been tested).
27
+ * High precision timer support is implemented for Windows, Linux and OSX.
28
+
29
+ #### Getting started
30
+
31
+ Install the gem with `gem install actuator` or by adding it to your bundle.
32
+
33
+ ```ruby
34
+ require 'actuator'
35
+
36
+ Actuator.run do
37
+ # Schedule a once off timer which fires after 500 us delay
38
+ Timer.in 0.0005 do
39
+ Log.puts "Reactor time: #{Actuator.now}"
40
+ end
41
+ # Schedule a repeating timer which fires every 50ms
42
+ Timer.every 0.05 do
43
+ Log.puts "50ms have passed"
44
+ end
45
+ # Schedule a timer which we will cancel before it expires
46
+ timer = Timer.in 0.005 do
47
+ Log.warn "This should never be printed"
48
+ end
49
+ # Create a new job which executes inside a pooled fiber
50
+ job1 = Actuator.defer do
51
+ begin
52
+ Log.puts "Job has started"
53
+ # Yield from the job fiber for 200ms
54
+ Job.sleep 0.2
55
+ Log.warn "Job 1 finished sleeping even though it should have been killed"
56
+ ensure
57
+ # The stack is unwound when a job is killed so ensure blocks are executed
58
+ Log.puts "Job 1 is ending"
59
+ end
60
+ end
61
+ # Create another new job
62
+ job2 = Actuator.defer do
63
+ # Yield from job fiber until job1 ends
64
+ job1.join
65
+ Log.puts "Job 2 finished waiting for Job 1"
66
+ end
67
+ Job.sleep 0.001
68
+ # Cancel the 5ms timer that we scheduled above
69
+ timer.destroy
70
+ Job.sleep 0.1
71
+ # Kill job1 before it finishes sleeping
72
+ job1.kill
73
+ # Wait for job2 to end naturally
74
+ job2.join
75
+ Log.puts "Job 2 has ended"
76
+ # Shutdown the reactor
77
+ Actuator.stop
78
+ end
79
+ ```
80
+
81
+ #### Known issues
82
+ - Memory for active timers will not be freed when calling Actuator.stop
83
+ - The profiling API will include time spent yielded from the job.
84
+ The job-aware implementation has been commented out to reduce overhead until the profiling has been rewritten in C++.
85
+ - Minimal safety checks and error handling has been implemented in order to minimize overhead. Using the API wrong may result in a segfault.
86
+ Feel free to open a bug report for any such cases that you may come across.
87
+
88
+
89
+ #### Contributing
90
+
91
+ Actuator is an open source project and any contributions which improve the project are encouraged.
92
+ Feel free to make a pull request for any contributions that you would like to see merged into the project.
93
+
94
+ After cloning the source from this repo, run `rake test` to build the C++ extension and run the reactor and precision tests.
95
+
96
+ Some ways that you can contribute include:
97
+ - Create new [bug reports](https://github.com/bawNg/actuator/issues/new)
98
+ - Reviewing and providing detailed feedback on existing [issues](https://github.com/bawNg/actuator/issues/new)
99
+ - Writing and improving documentation
100
+ - Writing additional tests
101
+ - Implementing new features
102
+
103
+ #### License
104
+
105
+ Actuator is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+ require 'rake/extensiontask'
6
+
7
+ Hoe.plugin :clean
8
+
9
+ Hoe.spec "actuator" do
10
+ developer 'bawNg', 'bawng@intoxicated.co.za'
11
+ license 'MIT'
12
+ self.urls = 'https://github.com/bawNg/actuator'
13
+
14
+ self.extra_dev_deps << ['rake-compiler', '>= 0']
15
+ self.extra_dev_deps << ['minitest', '>= 0']
16
+ self.spec_extras = { extensions: ['ext/actuator/extconf.rb'] }
17
+
18
+ Rake::ExtensionTask.new('actuator', spec) do |ext|
19
+ ext.lib_dir = File.join('lib', 'actuator')
20
+ end
21
+ end
22
+
23
+ Rake::Task[:test].prerequisites << :clean << :compile
@@ -0,0 +1,2 @@
1
+ #include <ruby.h>
2
+ #include "actuator.h"
@@ -0,0 +1,16 @@
1
+ #ifndef ACTUATOR_H
2
+ #define ACTUATOR_H
3
+
4
+ #include "clock.h"
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ extern VALUE ActuatorClass;
11
+
12
+ #ifdef __cplusplus
13
+ }
14
+ #endif
15
+
16
+ #endif
@@ -0,0 +1,81 @@
1
+ #include <stdint.h>
2
+ #include "clock.h"
3
+ #include "debug.h"
4
+
5
+ #if !defined(__APPLE__) && !defined(_WIN32)
6
+ #define HAVE_POSIX_TIMER
7
+ #include <time.h>
8
+ #ifdef CLOCK_MONOTONIC
9
+ #define CLOCKID CLOCK_MONOTONIC
10
+ #else
11
+ #define CLOCKID CLOCK_REALTIME
12
+ #endif
13
+ #endif
14
+
15
+ #ifdef __APPLE__
16
+ #define HAVE_MACH_TIMER
17
+ #include <mach/mach_time.h>
18
+ #endif
19
+
20
+ #if defined(_WIN32) || defined(_MSC_VER)
21
+ #define HAVE_WIN32_TIMER
22
+ #include <windows.h>
23
+
24
+ #ifdef _MSC_VER
25
+ #include <Strsafe.h>
26
+ #endif
27
+ #endif
28
+
29
+ static uint64_t last_count;
30
+ static uint64_t frequency;
31
+
32
+ void clock_init()
33
+ {
34
+ #ifdef HAVE_POSIX_TIMER
35
+ struct timespec freq, time;
36
+ clock_getres(CLOCKID, &freq);
37
+ clock_gettime(CLOCKID, &time);
38
+ frequency = 1000000000LL * freq.tv_nsec;
39
+ last_count = time.tv_sec * 1000000000LL + time.tv_nsec;
40
+ #endif
41
+ #ifdef HAVE_MACH_TIMER
42
+ mach_timebase_info_data_t frequency_nsec;
43
+ mach_timebase_info(&frequency_nsec);
44
+ frequency = 1000000000LL * frequency_nsec.numer / frequency_nsec.denom;
45
+ last_count = mach_absolute_time();
46
+ #endif
47
+ #ifdef HAVE_WIN32_TIMER
48
+ {
49
+ LARGE_INTEGER tmp;
50
+ QueryPerformanceFrequency(&tmp);
51
+ frequency = tmp.QuadPart;
52
+ QueryPerformanceCounter(&tmp);
53
+ last_count = tmp.QuadPart;
54
+ }
55
+ #endif
56
+ //puts("Timer resolution: %g ns", 1e9 / (double)frequency);
57
+ }
58
+
59
+ double clock_time()
60
+ {
61
+ uint64_t now;
62
+ double delta;
63
+ #ifdef HAVE_POSIX_TIMER
64
+ struct timespec time;
65
+ clock_gettime(CLOCKID, &time);
66
+ now = time.tv_sec * 1000000000LL + time.tv_nsec;
67
+ delta = (now - last_count) / (double)frequency;
68
+ #endif
69
+ #ifdef HAVE_MACH_TIMER
70
+ now = mach_absolute_time();
71
+ #endif
72
+ #ifdef HAVE_WIN32_TIMER
73
+ {
74
+ LARGE_INTEGER tmp;
75
+ QueryPerformanceCounter(&tmp);
76
+ now = tmp.QuadPart;
77
+ delta = (now - last_count) / (double)frequency;
78
+ }
79
+ #endif
80
+ return delta;
81
+ }
@@ -0,0 +1,15 @@
1
+ #ifndef ACTUATOR_CLOCK_H
2
+ #define ACTUATOR_CLOCK_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C"{
6
+ #endif
7
+
8
+ void clock_init();
9
+ double clock_time();
10
+
11
+ #ifdef __cplusplus
12
+ }
13
+ #endif
14
+
15
+ #endif
@@ -0,0 +1,4 @@
1
+ #include "debug.h"
2
+
3
+ FILE *debug_file = 0;
4
+ FILE *log_file = 0;
@@ -0,0 +1,56 @@
1
+ #ifndef ACTUATOR_DEBUG_H
2
+ #define ACTUATOR_DEBUG_H
3
+
4
+ #include <stdio.h>
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ #define DEBUG 0
11
+
12
+ #ifdef DEBUG
13
+ #define puts(...) do { \
14
+ fprintf(stdout, __VA_ARGS__); \
15
+ fprintf(stdout, "\n"); \
16
+ fflush(stdout); \
17
+ } while (0)
18
+ #else
19
+ #define puts(...)
20
+ #endif
21
+
22
+ #ifdef DEBUG
23
+ #define debug(...) do { \
24
+ if (!debug_file) debug_file = fopen("debug.txt", "w"); \
25
+ fprintf(debug_file, __VA_ARGS__); \
26
+ fflush(debug_file); \
27
+ } while (0)
28
+ #define DEBUG_LOG(...) do { \
29
+ if (!debug_file) debug_file = fopen("debug.txt", "w"); \
30
+ fprintf(debug_file, __VA_ARGS__); \
31
+ fflush(debug_file); \
32
+ } while (0)
33
+ #else
34
+ #define debug(...)
35
+ #define DEBUG_LOG(...)
36
+ #endif
37
+
38
+ #define INFO(...) do { \
39
+ fprintf(log_file, "%010.3f INFO ", clock_time() * 1000); \
40
+ fprintf(log_file, __VA_ARGS__); \
41
+ fprintf(log_file, "\n"); \
42
+ fflush(log_file); \
43
+ } while (0)
44
+
45
+ #define WARN(...) do { \
46
+ fprintf(log_file, "%010.3f WARN ", clock_time() * 1000); \
47
+ fprintf(log_file, __VA_ARGS__); \
48
+ fprintf(log_file, "\n"); \
49
+ fflush(log_file); \
50
+ } while (0)
51
+
52
+ #ifdef __cplusplus
53
+ }
54
+ #endif
55
+
56
+ #endif
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ $CXXFLAGS += " -std=c++11 "
4
+
5
+ create_makefile 'actuator/actuator'
@@ -0,0 +1,134 @@
1
+ #include <ruby.h>
2
+ #include "reactor.h"
3
+
4
+ enum class LogLevel
5
+ {
6
+ None,
7
+ Error,
8
+ Warn,
9
+ Info,
10
+ Debug
11
+ };
12
+
13
+ FILE *Log::debug_file = 0;
14
+ FILE *Log::log_file = 0;
15
+
16
+ static VALUE LogClass;
17
+ static LogLevel Level = LogLevel::Info;
18
+
19
+ static void Print(const char *tag, const char *format, va_list args)
20
+ {
21
+ if (!Log::log_file) return;
22
+ fprintf(Log::log_file, "%010.3f %s ", clock_time() * 1000, tag);
23
+ vfprintf(Log::log_file, format, args);
24
+ fprintf(Log::log_file, "\n");
25
+ fflush(Log::log_file);
26
+ }
27
+
28
+ static VALUE Log_Debug(VALUE self, VALUE message)
29
+ {
30
+ Log::Debug("%s", StringValueCStr(message));
31
+ }
32
+
33
+ static VALUE Log_Info(VALUE self, VALUE message)
34
+ {
35
+ Log::Info("%s", StringValueCStr(message));
36
+ }
37
+
38
+ static VALUE Log_Warn(VALUE self, VALUE message)
39
+ {
40
+ Log::Warn("%s", StringValueCStr(message));
41
+ }
42
+
43
+ static VALUE Log_Error(VALUE self, VALUE message)
44
+ {
45
+ Log::Error("%s", StringValueCStr(message));
46
+ }
47
+
48
+ static VALUE Log_SetFilePath(VALUE self, VALUE path)
49
+ {
50
+ if (NIL_P(path)) {
51
+ Log::log_file = 0;
52
+ } else if (SYMBOL_P(path) && SYM2ID(path) == rb_intern("stdout")) {
53
+ Log::log_file = stdout;
54
+ } else if (RB_TYPE_P(path, T_STRING) && CLASS_OF(path) == rb_cString) {
55
+ Log::log_file = fopen(rb_id2name(SYM2ID(path)), "w");
56
+ } else {
57
+ rb_raise(rb_eRuntimeError, "path must be a string, :stdout or nil");
58
+ }
59
+ return Qnil;
60
+ }
61
+
62
+ static VALUE Log_SetLevel(VALUE self, VALUE level)
63
+ {
64
+ if (SYMBOL_P(level)) {
65
+ if (SYM2ID(level) == rb_intern("debug")) {
66
+ Level = LogLevel::Debug;
67
+ return Qnil;
68
+ }
69
+ if (SYM2ID(level) == rb_intern("info")) {
70
+ Level = LogLevel::Info;
71
+ return Qnil;
72
+ }
73
+ if (SYM2ID(level) == rb_intern("warn")) {
74
+ Level = LogLevel::Warn;
75
+ return Qnil;
76
+ }
77
+ if (SYM2ID(level) == rb_intern("error")) {
78
+ Level = LogLevel::Error;
79
+ return Qnil;
80
+ }
81
+ }
82
+ rb_raise(rb_eRuntimeError, "level must be :debug, :info, :warn or :error");
83
+ return Qnil;
84
+ }
85
+
86
+ void Log::Setup()
87
+ {
88
+ log_file = stdout;
89
+
90
+ LogClass = rb_define_module("Log");
91
+
92
+ rb_define_singleton_method(LogClass, "debug", RUBY_METHOD_FUNC(Log_Debug), 1);
93
+ rb_define_singleton_method(LogClass, "puts", RUBY_METHOD_FUNC(Log_Info), 1);
94
+ rb_define_singleton_method(LogClass, "warn", RUBY_METHOD_FUNC(Log_Warn), 1);
95
+ rb_define_singleton_method(LogClass, "error", RUBY_METHOD_FUNC(Log_Error), 1);
96
+ rb_define_singleton_method(LogClass, "file_path=", RUBY_METHOD_FUNC(Log_SetFilePath), 1);
97
+ rb_define_singleton_method(LogClass, "level=", RUBY_METHOD_FUNC(Log_SetLevel), 1);
98
+ }
99
+
100
+ void Log::Debug(const char *format, ...)
101
+ {
102
+ if (Level < LogLevel::Debug) return;
103
+ va_list args;
104
+ va_start(args, format);
105
+ Print("DEBUG", format, args);
106
+ va_end(args);
107
+ }
108
+
109
+ void Log::Info(const char *format, ...)
110
+ {
111
+ if (Level < LogLevel::Info) return;
112
+ va_list args;
113
+ va_start(args, format);
114
+ Print("INFO", format, args);
115
+ va_end(args);
116
+ }
117
+
118
+ void Log::Warn(const char *format, ...)
119
+ {
120
+ if (Level < LogLevel::Warn) return;
121
+ va_list args;
122
+ va_start(args, format);
123
+ Print("WARN", format, args);
124
+ va_end(args);
125
+ }
126
+
127
+ void Log::Error(const char *format, ...)
128
+ {
129
+ if (Level < LogLevel::Error) return;
130
+ va_list args;
131
+ va_start(args, format);
132
+ Print("ERROR", format, args);
133
+ va_end(args);
134
+ }