-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathplatform_timer_kqueue.cpp
139 lines (113 loc) · 4.43 KB
/
platform_timer_kqueue.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <eosio/chain/platform_timer.hpp>
#include <eosio/chain/platform_timer_accuracy.hpp>
#include <fc/time.hpp>
#include <fc/fwd_impl.hpp>
#include <fc/exception/exception.hpp>
#include <fc/log/logger_config.hpp> //set_os_thread_name()
#include <mutex>
#include <thread>
#include <sys/event.h>
// this makes kqueue calls compatible with FreeBSD and this timer can be safely used
#if defined(__FreeBSD__)
#define EV_SET64(kev, ident, filter, flags, fflags, data, udata, ext0, ext1) EV_SET(kev, ident, filter, flags, fflags, data, reinterpret_cast<void*>(udata))
#define kevent64(kq, changelist, nchanges, eventlist, nevents, flags, timeout) kevent(kq, changelist, nchanges, eventlist, nevents, timeout)
#define kevent64_s kevent
#define KEVENT_FLAG_IMMEDIATE 0
#define NOTE_CRITICAL 0
#endif
namespace eosio { namespace chain {
// a kqueue & thread is shared for all platform_timer_macos instances
static std::mutex timer_ref_mutex;
static unsigned next_timerid;
static unsigned refcount;
static int kqueue_fd;
static std::thread kevent_thread;
struct platform_timer::impl {
uint64_t timerid;
constexpr static uint64_t quit_event_id = 1;
};
platform_timer::platform_timer() {
static_assert(sizeof(impl) <= fwd_size);
std::lock_guard guard(timer_ref_mutex);
if(refcount++ == 0) {
kqueue_fd = kqueue();
FC_ASSERT(kqueue_fd != -1, "failed to create kqueue");
//set up a EVFILT_USER which will be signaled to shut down the thread
struct kevent64_s quit_event;
EV_SET64(&quit_event, impl::quit_event_id, EVFILT_USER, EV_ADD|EV_ENABLE, NOTE_FFNOP, 0, 0, 0, 0);
FC_ASSERT(kevent64(kqueue_fd, &quit_event, 1, NULL, 0, KEVENT_FLAG_IMMEDIATE, NULL) == 0, "failed to create quit event");
kevent_thread = std::thread([]() {
fc::set_thread_name("checktime");
while(true) {
struct kevent64_s anEvent;
int c = kevent64(kqueue_fd, NULL, 0, &anEvent, 1, 0, NULL);
if(c == 1 && anEvent.filter == EVFILT_TIMER) {
platform_timer* self = (platform_timer*)anEvent.udata;
self->expire_now();
}
else if(c == 1 && anEvent.filter == EVFILT_USER)
return;
else if(c == -1 && errno == EINTR)
continue;
else if(c == -1)
return; //?? not much we can do now
}
});
}
my->timerid = next_timerid++;
compute_and_print_timer_accuracy(*this);
}
platform_timer::~platform_timer() {
stop();
if(std::lock_guard guard(timer_ref_mutex); --refcount == 0) {
struct kevent64_s signal_quit_event;
EV_SET64(&signal_quit_event, impl::quit_event_id, EVFILT_USER, 0, NOTE_TRIGGER, 0, 0, 0, 0);
if(kevent64(kqueue_fd, &signal_quit_event, 1, NULL, 0, KEVENT_FLAG_IMMEDIATE, NULL) != -1)
kevent_thread.join();
close(kqueue_fd);
}
}
void platform_timer::start(fc::time_point tp) {
assert(_state == state_t::stopped);
timer_running_forever = tp == fc::time_point::maximum();
if(timer_running_forever) {
_state = state_t::running;
return;
}
fc::microseconds x = tp.time_since_epoch() - fc::time_point::now().time_since_epoch();
timer_running_forever = false;
if(x.count() <= 0)
_state = state_t::timed_out;
else {
struct kevent64_s aTimerEvent;
EV_SET64(&aTimerEvent, my->timerid, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT, NOTE_USECONDS|NOTE_CRITICAL, x.count(), (uint64_t)this, 0, 0);
_state = state_t::running;
if(kevent64(kqueue_fd, &aTimerEvent, 1, NULL, 0, KEVENT_FLAG_IMMEDIATE, NULL) != 0)
_state = state_t::timed_out;
}
}
void platform_timer::expire_now() {
state_t expected = state_t::running;
if (_state.compare_exchange_strong(expected, state_t::timed_out)) {
call_expiration_callback();
}
}
void platform_timer::interrupt_timer() {
state_t expected = state_t::running;
if (_state.compare_exchange_strong(expected, state_t::interrupted)) {
call_expiration_callback();
}
}
void platform_timer::stop() {
const state_t prior_state = _state;
if(prior_state == state_t::stopped)
return;
_state = state_t::stopped;
if(prior_state == state_t::timed_out || timer_running_forever)
return;
struct kevent64_s stop_timer_event;
EV_SET64(&stop_timer_event, my->timerid, EVFILT_TIMER, EV_DELETE, 0, 0, 0, 0, 0);
kevent64(kqueue_fd, &stop_timer_event, 1, NULL, 0, KEVENT_FLAG_IMMEDIATE, NULL);
_state = state_t::stopped;
}
}}