actuator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ #ifndef ACTUATOR_LOG_H
2
+ #define ACTUATOR_LOG_H
3
+
4
+ #include "actuator.h"
5
+
6
+ class Log {
7
+ public:
8
+ static void Setup();
9
+ static void Debug(const char *format, ...);
10
+ static void Info(const char *format, ...);
11
+ static void Warn(const char *format, ...);
12
+ static void Error(const char *format, ...);
13
+
14
+ static FILE *debug_file;
15
+ static FILE *log_file;
16
+ };
17
+
18
+ #endif
@@ -0,0 +1,212 @@
1
+ #include "reactor.h"
2
+
3
+ Actuator *actuator = 0;
4
+
5
+ Actuator::Actuator()
6
+ {
7
+ }
8
+
9
+ Actuator::~Actuator()
10
+ {
11
+ }
12
+
13
+ void Actuator::Start()
14
+ {
15
+ if (is_running) {
16
+ Log::Warn("[Actuator] Start called while already running");
17
+ return;
18
+ }
19
+
20
+ thread = rb_thread_current();
21
+ is_running = true;
22
+
23
+ if (rb_block_given_p()) rb_yield(Qundef);
24
+
25
+ long total_ticks = 0;
26
+
27
+ double now = clock_time();
28
+
29
+ while (is_running)
30
+ {
31
+ total_ticks++;
32
+
33
+ Timer::Update(now);
34
+
35
+ int remaining_dequeues = next_tick_queue.size();
36
+ while (remaining_dequeues--)
37
+ {
38
+ VALUE proc = next_tick_queue.front();
39
+ next_tick_queue.pop();
40
+ rb_proc_call_fast(proc);
41
+ }
42
+
43
+ if (!is_running) break;
44
+
45
+ now = clock_time();
46
+
47
+ timeval delay_duration;
48
+ double next_timer_at = Timer::GetNextEventTime();
49
+ if (next_timer_at) {
50
+ double delay = next_timer_at - now;
51
+ if (delay > 0) {
52
+ if (delay > max_delta) {
53
+ // Never sleep for longer than max_delta
54
+ delay_duration.tv_sec = 0;
55
+ delay_duration.tv_usec = max_delta * 1000000;
56
+ } else {
57
+ // Extra delay avoids most wake ups that would happen before any timers expire due to bad sleep precision
58
+ delay_duration.tv_sec = (long)delay;
59
+ delay_duration.tv_usec = (((long)(delay * 1000000)) % 1000000) + 1;
60
+ }
61
+ } else {
62
+ // Sleep for as few microseconds as possible so that we give other ruby threads
63
+ // GVL time while also waking up to process expired timers as soon as possible
64
+ delay_duration.tv_sec = delay_duration.tv_usec = 0;
65
+ }
66
+ } else {
67
+ delay_duration.tv_sec = 0;
68
+ delay_duration.tv_usec = max_delta * 1000000;
69
+ }
70
+
71
+ is_sleeping = true;
72
+
73
+ // rb_thread_wait_for has far better precision on Windows builds than using undocumented kernel system calls
74
+ rb_thread_wait_for(delay_duration);
75
+
76
+ is_waking = false;
77
+ is_sleeping = false;
78
+
79
+ /*double after = clock_time();
80
+ if (next_timer_at && after < next_timer_at)
81
+ {
82
+ //double delay = (next_timer_at - after) * 1000000;
83
+ //Log::Warn("Woke up %.2f us early - Slept for %.2f / %d us", delay, (after - now) * 1000000.0, delay_duration.tv_usec);
84
+ } else {
85
+ double late = (after - now) * 1000000 - delay_duration.tv_usec;
86
+ if (late > 25) Log::Warn("Woke up %.2f us late - Slept for %.2f / %d us - Timer delay: %.2f", late, (after - now) * 1000000.0, delay_duration.tv_usec, (next_timer_at - now) * 1000000.0);
87
+ }*/
88
+
89
+ /*double sleep_delta = after - now;
90
+ double late = (sleep_delta * 1000000) - (delay_duration.tv_sec * 1000000 + delay_duration.tv_usec);
91
+ if (late > 2) Log::Warn("%.10f - Woke up after %.10f - %.2f us late", after, sleep_delta, late);*/
92
+
93
+ now = clock_time();
94
+ }
95
+
96
+ thread = 0;
97
+ }
98
+
99
+ void Actuator::Stop()
100
+ {
101
+ if (!is_running) {
102
+ Log::Warn("[Actuator] Actuator.stop called while not running");
103
+ return;
104
+ }
105
+ is_running = false;
106
+ Timer::Clear();
107
+ if (is_sleeping) Wake();
108
+ }
109
+
110
+ void Actuator::Wake()
111
+ {
112
+ if (!is_sleeping || is_waking) return;
113
+ is_waking = true;
114
+ rb_thread_wakeup(thread);
115
+ }
116
+
117
+ static VALUE Actuator_now(VALUE klass)
118
+ {
119
+ return DBL2NUM(clock_time());
120
+ }
121
+
122
+ static VALUE Actuator_is_running(VALUE klass)
123
+ {
124
+ return actuator->is_running ? Qtrue : Qfalse;
125
+ }
126
+
127
+ static VALUE Actuator_start(VALUE klass)
128
+ {
129
+ if (actuator->is_running) {
130
+ if (rb_block_given_p()) rb_yield(Qundef);
131
+ } else {
132
+ actuator->Start();
133
+ }
134
+ return Qnil;
135
+ }
136
+
137
+ static VALUE Actuator_stop(VALUE klass)
138
+ {
139
+ actuator->Stop();
140
+ return Qnil;
141
+ }
142
+
143
+ static VALUE Actuator_wake(VALUE klass)
144
+ {
145
+ actuator->Wake();
146
+ return Qnil;
147
+ }
148
+
149
+ static VALUE Actuator_next_tick(VALUE self)
150
+ {
151
+ //TODO: Prevent proc from being GC'd while scheduled
152
+ rb_need_block();
153
+ actuator->next_tick_queue.push(rb_block_proc());
154
+ //rb_gc_register_address(&next_tick_queue.back());
155
+ }
156
+
157
+ static VALUE deferred_fiber(VALUE curr, VALUE block)
158
+ {
159
+ rb_proc_call_fast(block);
160
+ }
161
+
162
+ static VALUE Actuator_defer(VALUE self)
163
+ {
164
+ //TODO: Pool fibers and prevent them from being GC'd while scheduled
165
+ rb_need_block();
166
+ VALUE fiber = rb_fiber_new(RUBY_METHOD_FUNC(deferred_fiber), rb_block_proc());
167
+ if (rb_thread_current() == actuator->thread)
168
+ {
169
+ rb_fiber_resume(fiber, 0, 0);
170
+ }
171
+ else
172
+ {
173
+ Timer *timer = new Timer();
174
+ timer->SetFiber(fiber);
175
+ timer->SetDelay(0);
176
+ timer->Schedule();
177
+ }
178
+ return fiber;
179
+ }
180
+
181
+ static VALUE Actuator_sleep(VALUE self, VALUE delay_value)
182
+ {
183
+ //TODO: Prevent fibers from being GC'd while scheduled
184
+ Timer *timer = new Timer();
185
+ timer->SetFiber(rb_fiber_current());
186
+ timer->SetDelay(NUM2DBL(delay_value));
187
+ timer->Schedule();
188
+ VALUE nil = Qnil;
189
+ return rb_fiber_yield(1, &nil);
190
+ }
191
+
192
+ extern "C"
193
+ void Init_actuator()
194
+ {
195
+ init_ruby_helpers();
196
+ clock_init();
197
+
198
+ actuator = new Actuator();
199
+
200
+ Log::Setup();
201
+ Timer::Setup();
202
+
203
+ VALUE ActuatorClass = rb_define_module("Actuator");
204
+ rb_define_singleton_method(ActuatorClass, "now", RUBY_METHOD_FUNC(Actuator_now), 0);
205
+ rb_define_singleton_method(ActuatorClass, "running?", RUBY_METHOD_FUNC(Actuator_is_running), 0);
206
+ rb_define_singleton_method(ActuatorClass, "start", RUBY_METHOD_FUNC(Actuator_start), 0);
207
+ rb_define_singleton_method(ActuatorClass, "stop", RUBY_METHOD_FUNC(Actuator_stop), 0);
208
+ rb_define_singleton_method(ActuatorClass, "wake", RUBY_METHOD_FUNC(Actuator_wake), 0);
209
+ //rb_define_singleton_method(ActuatorClass, "next_tick", RUBY_METHOD_FUNC(Actuator_next_tick), 0);
210
+ //rb_define_singleton_method(ActuatorClass, "defer", RUBY_METHOD_FUNC(Actuator_defer), 0);
211
+ //rb_define_singleton_method(FiberClass, "sleep", RUBY_METHOD_FUNC(Actuator_sleep), 1);
212
+ }
@@ -0,0 +1,34 @@
1
+ #include <iostream>
2
+ #include <map>
3
+ #include <queue>
4
+ #include <ruby.h>
5
+ #include "actuator.h"
6
+ #include "timer.h"
7
+ #include "log.h"
8
+ #include "ruby_helpers.h"
9
+
10
+ class Actuator
11
+ {
12
+ public:
13
+ bool is_running = false;
14
+ bool is_sleeping = false;
15
+ bool is_waking = false;
16
+ VALUE thread = 0;
17
+
18
+ const double max_delta = 0.05;
19
+
20
+ double sleep_ended_at = 0;
21
+ double total_late = 0;
22
+
23
+ std::queue<VALUE> next_tick_queue;
24
+
25
+ Actuator();
26
+ ~Actuator();
27
+
28
+ void Start();
29
+ void Stop();
30
+ void Wake();
31
+ timeval GetNextEventDelay(double now);
32
+ };
33
+
34
+ extern Actuator *actuator;
@@ -0,0 +1,17 @@
1
+ #include <ruby.h>
2
+ #include <ruby/intern.h>
3
+ #include "actuator.h"
4
+ #include "ruby_helpers.h"
5
+
6
+ VALUE empty_array_value;
7
+
8
+ VALUE rb_proc_call_fast(VALUE block)
9
+ {
10
+ return rb_proc_call(block, empty_array_value);
11
+ }
12
+
13
+ void init_ruby_helpers()
14
+ {
15
+ empty_array_value = rb_ary_new();
16
+ rb_gc_register_address(&empty_array_value);
17
+ }
@@ -0,0 +1,17 @@
1
+ #ifndef ACTUATOR_RUBY_HELPERS_H
2
+ #define ACTUATOR_RUBY_HELPERS_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif
7
+
8
+ extern VALUE empty_array_value;
9
+
10
+ VALUE rb_proc_call_fast(VALUE block);
11
+ void init_ruby_helpers();
12
+
13
+ #ifdef __cplusplus
14
+ }
15
+ #endif
16
+
17
+ #endif
@@ -0,0 +1,450 @@
1
+ #include "reactor.h"
2
+
3
+ const unsigned int MaxOutstandingTimers = 1000000;
4
+
5
+ static int late_warning_us;
6
+
7
+ static int total_count = 0;
8
+ static int current_timer_count = 0;
9
+ static int current_object_count = 0;
10
+ static int current_gc_registered_count = 0;
11
+ static int fired_current_second_count = 0;
12
+ static int fired_last_second_count = 0;
13
+ static int current_second_frame_count = 0;
14
+ static int last_second_frame_count = 0;
15
+ static int current_second_empty_frames = 0;
16
+ static int last_second_empty_frames = 0;
17
+ static int current_second_earliest_fire = INT_MAX;
18
+ static int last_second_earliest_fire = INT_MAX;
19
+ static int current_second_latest_fire = 0;
20
+ static int last_second_latest_fire = 0;
21
+ static int current_second_started_at = 0;
22
+
23
+ //TODO: Consider replacing multimap with a high precision timer wheel implementation like the one I built in C#
24
+ static std::multimap<double, Timer*> all;
25
+ static std::deque<Timer*> expired_queue;
26
+ static std::deque<Timer*> interval_queue;
27
+
28
+ static VALUE TimerClass;
29
+ static VALUE proc_call_args[2];
30
+
31
+ static void Timer_free(Timer *timer)
32
+ {
33
+ current_object_count--;
34
+ timer->Destroy();
35
+ delete timer;
36
+ }
37
+
38
+ static void Timer_mark(Timer *timer)
39
+ {
40
+ if (timer->callback_block)
41
+ rb_gc_mark(timer->callback_block);
42
+ else if (timer->fiber)
43
+ rb_gc_mark(timer->fiber);
44
+ }
45
+
46
+ static VALUE Timer_alloc(VALUE klass)
47
+ {
48
+ Timer *timer = new Timer();
49
+ current_object_count++;
50
+ return timer->instance = Data_Wrap_Struct(klass, Timer_mark, Timer_free, timer);
51
+ }
52
+
53
+ static VALUE Timer_initialize(VALUE self)
54
+ {
55
+ Timer *timer = Timer::Get(self);
56
+ if (rb_block_given_p()) timer->SetCallback(rb_block_proc());
57
+ return self;
58
+ }
59
+
60
+ static VALUE Timer_destroy(VALUE self)
61
+ {
62
+ Timer *timer = Timer::Get(self);
63
+ if (timer)
64
+ timer->Destroy();
65
+ else
66
+ rb_raise(rb_eRuntimeError, "Timer instance has no allocated timer struct");
67
+ return Qnil;
68
+ }
69
+
70
+ static VALUE Timer_expires_at(VALUE self)
71
+ {
72
+ return DBL2NUM(Timer::Get(self)->at);
73
+ }
74
+
75
+ static VALUE Timer_is_destroyed(VALUE self)
76
+ {
77
+ return (Timer::Get(self))->is_destroyed ? Qtrue : Qfalse;
78
+ }
79
+
80
+ static VALUE Timer_fire_bang(VALUE self)
81
+ {
82
+ Timer *timer = Timer::Get(self);
83
+ if (timer->is_destroyed || !timer->is_scheduled) return Qnil;
84
+ timer->ExpireImmediately();
85
+ return Qtrue;
86
+ }
87
+
88
+ static VALUE Timer_in(VALUE self, VALUE delay_value)
89
+ {
90
+ rb_need_block();
91
+ Log::Debug("Timer.in");
92
+ Timer *timer = new Timer(NUM2DBL(delay_value));
93
+ timer->SetCallback(rb_block_proc());
94
+ timer->Schedule();
95
+ current_object_count++;
96
+ // We skip calling initialize on the timer to reduce overhead
97
+ return timer->instance = Data_Wrap_Struct(TimerClass, Timer_mark, Timer_free, timer);
98
+ }
99
+
100
+ static VALUE Timer_every(VALUE self, VALUE delay_value)
101
+ {
102
+ rb_need_block();
103
+ Log::Debug("Timer.every");
104
+ double delay = NUM2DBL(delay_value);
105
+ Timer *timer = new Timer(delay);
106
+ timer->interval = delay;
107
+ timer->SetCallback(rb_block_proc());
108
+ timer->Schedule();
109
+ current_object_count++;
110
+ // We skip calling initialize on the timer to reduce overhead
111
+ return timer->instance = Data_Wrap_Struct(TimerClass, Timer_mark, Timer_free, timer);
112
+ }
113
+
114
+ static VALUE Timer_late_warning_us(VALUE self)
115
+ {
116
+ return INT2NUM(late_warning_us);
117
+ }
118
+
119
+ static VALUE Timer_late_warning_us_set(VALUE self, VALUE value)
120
+ {
121
+ return INT2NUM(late_warning_us = NUM2INT(value));
122
+ }
123
+
124
+ static VALUE Timer_stats(VALUE self)
125
+ {
126
+ return rb_sprintf("Frames: %d, Empty: %d, Fires: %d, Early: %d, Late: %d, Current: %d, Objects: %d, Scheduled: %d, GC: %d, Total: %d", last_second_frame_count, last_second_empty_frames, fired_last_second_count, last_second_earliest_fire < INT_MAX ? last_second_earliest_fire : -1, last_second_latest_fire, current_timer_count, current_object_count, all.size(), current_gc_registered_count, total_count);
127
+ }
128
+
129
+ void Timer::Setup()
130
+ {
131
+ proc_call_args[0] = Qnil;
132
+ proc_call_args[1] = empty_array_value;
133
+
134
+ TimerClass = rb_define_class("Timer", rb_cObject);
135
+ rb_define_singleton_method(TimerClass, "in", RUBY_METHOD_FUNC(Timer_in), 1);
136
+ rb_define_singleton_method(TimerClass, "every", RUBY_METHOD_FUNC(Timer_every), 1);
137
+ rb_define_singleton_method(TimerClass, "stats", RUBY_METHOD_FUNC(Timer_stats), 0);
138
+ rb_define_singleton_method(TimerClass, "late_warning_us", RUBY_METHOD_FUNC(Timer_late_warning_us), 0);
139
+ rb_define_singleton_method(TimerClass, "late_warning_us=", RUBY_METHOD_FUNC(Timer_late_warning_us_set), 1);
140
+ rb_define_alloc_func(TimerClass, Timer_alloc);
141
+ rb_define_method(TimerClass, "initialize", RUBY_METHOD_FUNC(Timer_initialize), 0);
142
+ rb_define_method(TimerClass, "expires_at", RUBY_METHOD_FUNC(Timer_expires_at), 0);
143
+ rb_define_method(TimerClass, "destroy", RUBY_METHOD_FUNC(Timer_destroy), 0);
144
+ rb_define_method(TimerClass, "destroyed?", RUBY_METHOD_FUNC(Timer_is_destroyed), 0);
145
+ rb_define_method(TimerClass, "fire!", RUBY_METHOD_FUNC(Timer_fire_bang), 0);
146
+
147
+ late_warning_us = 0;
148
+ current_second_started_at = clock_time();
149
+ }
150
+
151
+ Timer* Timer::Get(VALUE instance)
152
+ {
153
+ Timer *timer;
154
+ Data_Get_Struct(instance, Timer, timer);
155
+ return timer;
156
+ }
157
+
158
+ void Timer::Update(double now)
159
+ {
160
+ std::multimap<double, Timer*>::iterator it = all.begin();
161
+ for (; it != all.end(); ++it) {
162
+ if (it->first > now) break;
163
+ //if (now - it->first > 0.000005) {
164
+ //long late = (now - it->first) * 1000000;
165
+ //puts("%.10f - Timer expired: %.10f, Late: %d us", now, it->first, late);
166
+ //}
167
+ Timer *timer = it->second;
168
+ if (!timer->is_scheduled) Log::Error("Expired timer %d has is_scheduled set to false!", timer->id);
169
+ expired_queue.push_back(timer);
170
+ }
171
+
172
+ int active_count = all.size();
173
+ int expired_count = expired_queue.size();
174
+
175
+ current_second_frame_count++;
176
+ if (expired_count < 1) current_second_empty_frames++;
177
+ fired_current_second_count += expired_count;
178
+ if (now >= current_second_started_at + 5.0)
179
+ {
180
+ current_second_started_at = now;
181
+ last_second_frame_count = current_second_frame_count;
182
+ current_second_frame_count = 0;
183
+ last_second_empty_frames = current_second_empty_frames;
184
+ current_second_empty_frames = 0;
185
+ fired_last_second_count = fired_current_second_count;
186
+ fired_current_second_count = 0;
187
+ last_second_earliest_fire = current_second_earliest_fire;
188
+ current_second_earliest_fire = INT_MAX;
189
+ last_second_latest_fire = current_second_latest_fire;
190
+ current_second_latest_fire = 0;
191
+ }
192
+
193
+ if (expired_count < 1) return;
194
+
195
+ Log::Debug("Update - %d / %d timers expiring", expired_count, active_count);
196
+
197
+ if (expired_count < active_count)
198
+ all.erase(all.begin(), it);
199
+ else
200
+ all.clear();
201
+
202
+ std::deque<Timer*>::iterator deq = expired_queue.begin();
203
+ while (deq != expired_queue.end()) {
204
+ Timer *timer = (Timer*)*deq++;
205
+ timer->is_scheduled = false;
206
+ Log::Debug("Update - Expired");
207
+ if (timer->interval) {
208
+ //Log::Debug("Update - Firing: %s", RSTRING_PTR(rb_inspect(timer->callback_block)));
209
+ timer->Fire();
210
+ if (timer->is_destroyed)
211
+ {
212
+ Log::Debug("Update - Interval destroyed from it's own callback");
213
+ timer->StoppedBeingScheduled();
214
+ }
215
+ else
216
+ {
217
+ Log::Debug("Update - Adding to interval queue");
218
+ interval_queue.push_back(timer);
219
+ }
220
+ } else {
221
+ timer->is_destroyed = true;
222
+ timer->StoppedBeingScheduled();
223
+ timer->Fire();
224
+ }
225
+ if (!actuator->is_running) break;
226
+ }
227
+ expired_queue.clear();
228
+
229
+ if (!actuator->is_running) return;
230
+
231
+ now = clock_time();
232
+ deq = interval_queue.begin();
233
+ while (deq != interval_queue.end()) {
234
+ Timer *timer = (Timer*)*deq++;
235
+ if (timer->is_destroyed) {
236
+ Log::Debug("Update - Interval destroyed from another timers callback");
237
+ timer->StoppedBeingScheduled();
238
+ continue;
239
+ }
240
+ Log::Debug("Update - Rescheduling interval");
241
+ timer->at = now + timer->interval;
242
+ timer->InsertIntoSchedule();
243
+ }
244
+ interval_queue.clear();
245
+
246
+ Log::Debug("Update - Done");
247
+ }
248
+
249
+ double Timer::GetNextEventTime()
250
+ {
251
+ return all.empty() ? 0 : all.begin()->first;
252
+ }
253
+
254
+ Timer::Timer()
255
+ {
256
+ id = ++total_count;
257
+ delay = 0;
258
+ at = 0;
259
+ interval = 0;
260
+ callback_block = 0;
261
+ fiber = 0;
262
+ is_scheduled = false;
263
+ is_destroyed = false;
264
+ instance = 0;
265
+ inspected = 0;
266
+ current_timer_count++;
267
+ }
268
+
269
+ Timer::Timer(double initial_delay) : Timer()
270
+ {
271
+ delay = initial_delay;
272
+ at = clock_time() + initial_delay;
273
+ }
274
+
275
+ Timer::~Timer()
276
+ {
277
+ if (callback_block) {
278
+ //TODO: Profile overhead of unregister when loaded (walks the entire global object list)
279
+ rb_gc_unregister_address(&callback_block);
280
+ callback_block = 0;
281
+ if (inspected) {
282
+ free(inspected);
283
+ inspected = 0;
284
+ }
285
+ }
286
+ if (is_scheduled) Log::Error("Timer freed while still scheduled");
287
+ if (!is_destroyed) Log::Error("Timer freed before being destroyed");
288
+ current_timer_count--;
289
+ }
290
+
291
+ void Timer::SetDelay(double initial_delay)
292
+ {
293
+ Log::Debug("SetDelay");
294
+ delay = initial_delay;
295
+ at = clock_time() + initial_delay;
296
+ }
297
+
298
+ void Timer::Destroy()
299
+ {
300
+ if (is_destroyed) return;
301
+ Log::Debug("Destroy");
302
+ is_destroyed = true;
303
+ Remove();
304
+ }
305
+
306
+ void Timer::Schedule()
307
+ {
308
+ if (!at) {
309
+ Log::Warn("Timer::Schedule() called before delay was set");
310
+ return;
311
+ }
312
+ if (all.size() > MaxOutstandingTimers) {
313
+ Log::Warn("Error: There are %d / %d active timers!", all.size(), MaxOutstandingTimers);
314
+ return;
315
+ }
316
+ InsertIntoSchedule();
317
+ StartedBeingScheduled();
318
+ }
319
+
320
+ void Timer::Remove()
321
+ {
322
+ Log::Debug("Remove");
323
+ if (RemoveFromSchedule())
324
+ StoppedBeingScheduled();
325
+ }
326
+
327
+ void Timer::InsertIntoSchedule()
328
+ {
329
+ if (is_scheduled) return;
330
+ Log::Debug("InsertIntoSchedule");
331
+ is_scheduled = true;
332
+ iterator = all.insert(std::make_pair(at, this));
333
+ }
334
+
335
+ bool Timer::RemoveFromSchedule()
336
+ {
337
+ if (!is_scheduled) return false;
338
+ Log::Debug("RemoveFromSchedule");
339
+ is_scheduled = false;
340
+ all.erase(iterator);
341
+ return true;
342
+ }
343
+
344
+ void Timer::StartedBeingScheduled()
345
+ {
346
+ Log::Debug("StartedBeingScheduled");
347
+ rb_gc_register_address(&instance);
348
+ current_gc_registered_count++;
349
+ }
350
+
351
+ void Timer::StoppedBeingScheduled()
352
+ {
353
+ Log::Debug("StoppedBeingScheduled");
354
+ rb_gc_unregister_address(&instance);
355
+ current_gc_registered_count--;
356
+ Log::Debug("GC pointer count: %d", current_gc_registered_count);
357
+ }
358
+
359
+ void Timer::SetCallback(VALUE block)
360
+ {
361
+ Log::Debug("SetCallback");
362
+ if (callback_block) {
363
+ rb_gc_unregister_address(&callback_block);
364
+ free(inspected);
365
+ inspected = 0;
366
+ }
367
+ callback_block = block;
368
+ if (block) {
369
+ rb_gc_register_address(&callback_block);
370
+ //VALUE obj = rb_inspect(block);
371
+ //inspected = (char*)malloc(RSTRING_LEN(obj)+1);
372
+ //strcpy(inspected, RSTRING_PTR(obj));
373
+ }
374
+ }
375
+
376
+ void Timer::SetFiber(VALUE current_fiber)
377
+ {
378
+ fiber = current_fiber;
379
+ }
380
+
381
+ void Timer::ExpireImmediately()
382
+ {
383
+ if (is_destroyed || !is_scheduled) return;
384
+ Log::Debug("ExpireImmediately");
385
+ if (interval) {
386
+ RemoveFromSchedule();
387
+ Fire();
388
+ if (is_destroyed) {
389
+ StoppedBeingScheduled();
390
+ } else {
391
+ at = clock_time() + interval;
392
+ InsertIntoSchedule();
393
+ }
394
+ } else {
395
+ is_destroyed = true;
396
+ Remove();
397
+ Fire();
398
+ }
399
+ }
400
+
401
+ static VALUE fire_rescue(VALUE _, VALUE errinfo)
402
+ {
403
+ Log::Debug("Uncaught exception, stopping reactor");
404
+ actuator->Stop();
405
+ Log::Debug("Raising uncaught exception");
406
+ rb_exc_raise(errinfo);
407
+ Log::Debug("Uncaught exception has been raised");
408
+ return Qnil;
409
+ }
410
+
411
+ void Timer::Fire()
412
+ {
413
+ double before_call = clock_time();
414
+
415
+ double before_resume;
416
+ if (callback_block)
417
+ {
418
+ double late_us = (double)((before_call - at) * 1000000);
419
+ if ((int)late_us < current_second_earliest_fire) current_second_earliest_fire = (int)late_us;
420
+ if ((int)late_us > current_second_latest_fire) current_second_latest_fire = (int)late_us;
421
+ if (late_warning_us && late_us > late_warning_us) {
422
+ Log::Warn("Firing %.2f us late - %d active timers, %d fired last second", late_us, all.size(), fired_last_second_count);
423
+ }
424
+ int rescue_state;
425
+ proc_call_args[0] = callback_block;
426
+ rb_rescue(RUBY_METHOD_FUNC(rb_proc_call_fast), callback_block, RUBY_METHOD_FUNC(fire_rescue), Qnil);
427
+ }
428
+ else if (fiber)
429
+ {
430
+ Log::Warn("[Fire] Resuming fiber %.2f us late", (double)((before_call - at) * 1000000));
431
+ if (!rb_fiber_alive_p(fiber))
432
+ {
433
+ Log::Error("[Fire] Unable to resume fiber (not alive)");
434
+ return;
435
+ }
436
+ before_resume = clock_time();
437
+ VALUE ret = DBL2NUM(before_resume);
438
+ rb_fiber_resume(fiber, 1, &ret);
439
+ delete this; //TODO: Shouldn't this happen before calling rb_fiber_resume
440
+ }
441
+ // Assume at least 0.3us overhead for ruby to call into C
442
+ //double after_call = clock_time();
443
+ //puts("[FireTimer] call took %.2f us, resume: %.2f us, resume_total: %.2f us, late: %.2f us", (double)((after_call - before_call) * 1000000), (double)((sleep_ended_at - before_resume) * 1000000) - 0.3, (double)((after_call - before_resume) * 1000000) - 0.3, (double)((sleep_ended_at - at) * 1000000) - 0.3);
444
+ }
445
+
446
+ void Timer::Clear()
447
+ {
448
+ //TODO: Actually clean up and free all timers and next_tick callbacks
449
+ all.clear();
450
+ }