forked from supercollider/supercollider
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathXenomaiLock.cpp
202 lines (175 loc) · 7.01 KB
/
XenomaiLock.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include "XenomaiLock.h"
#include <pthread.h>
#include <error.h>
#include <string.h>
#include <cobalt/sys/cobalt.h>
#include <xenomai/init.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <unistd.h>
#include <sys/syscall.h>
#ifndef __COBALT__
# error This should be compiled with __COBALT__ and the appropriate Xenomai headers in the #include path
#endif // __COBALT__
// All __wrap_* calls below are Xenomai libcobalt calls
//#define PRINT_XENO_LOCK
#ifdef PRINT_XENO_LOCK
# define xprintf printf
# define xfprintf fprintf
#else // PRINT_XENO_LOCK
# define xprintf(...)
# define xfprintf(...)
#endif // PRINT_XENO_LOCK
// Standard Linux `gettid(2)` not available on Bela
static inline pid_t getTid() {
pid_t tid = syscall(SYS_gettid);
return tid;
}
// throughout, we use heuristics to check whether Xenomai needs to be
// initialised and whether the current thread is a Xenomai thread.
// See https://www.xenomai.org/pipermail/xenomai/2019-January/040203.html
static void initializeXenomai() {
xprintf("initializeXenomai\n");
int argc = 2;
char blankOpt[] = "";
#ifdef PRINT_XENO_LOCK
char traceOpt[] = "--trace";
#else // PRINT_XENO_LOCK
char traceOpt[] = "";
#endif // PRINT_XENO_LOCK
char* const argv[argc] = { blankOpt, traceOpt };
char* const* argvPtrs[argc] = { &argv[0], &argv[1] };
xenomai_init(&argc, argvPtrs);
}
static bool turnIntoCobaltThread(bool recurred = false) {
struct sched_param param;
memset(¶m, 0, sizeof(param));
int policy;
// Guaranteed to succeed as pthread_self() cannot fail and pthread_getschedparam()'s only error condition is when
// the given thread does not exist.
pthread_getschedparam(pthread_self(), &policy, ¶m);
pid_t tid = getTid();
if (int ret = __wrap_sched_setscheduler(tid, policy, ¶m)) {
fprintf(stderr, "Warning: unable to turn current thread into a Xenomai thread : (%d) %s\n", -ret,
strerror(-ret));
initializeXenomai();
if (!recurred)
return turnIntoCobaltThread(true);
else
return false;
}
xprintf("Turned thread %d into a Cobalt thread %s\n", tid, recurred ? "with recursion" : "");
return true;
}
XenomaiInitializer::XenomaiInitializer() { initializeXenomai(); }
XenomaiMutex::XenomaiMutex() {
xprintf("Construct mutex\n");
if (int ret = __wrap_pthread_mutex_init(&m_mutex, NULL)) {
if (EPERM != ret) {
xprintf("__wrap_thread_mutex_init failed with %d %s\n", ret, strerror(ret));
return;
} else {
xprintf("mutex init returned EPERM\n");
initializeXenomai();
if (int ret = __wrap_pthread_mutex_init(&m_mutex, NULL)) {
fprintf(stderr, "Error: unable to initialize mutex : (%d) %s\n", ret, strerror(-ret));
return;
}
}
}
m_enabled = true;
}
XenomaiMutex::~XenomaiMutex() {
xprintf("Destroy mutex %p\n", &m_mutex);
if (m_enabled)
__wrap_pthread_mutex_destroy(&m_mutex);
}
// a helper function to try
// - call func()
// - if it fails, try to turn the current thread into a cobalt thread and call func() again
// - if it fails again, just fail
// - return true if it succeeds, or false if it fails
// id and name are just for debugging purposes, while m_enabled is there because it saves duplicating some lines
template <typename F, typename T> static bool tryOrRetryImpl(F&& func, bool m_enabled, T* id, const char* name) {
xprintf("tid: %d ", getTid());
if (!m_enabled) {
xfprintf(stderr, "%s disabled %p\n", name, id);
return false;
}
xprintf("%s %p\n", name, id);
int ret = func();
// 0 is "success" (or at least meaningful failure)
if (0 == ret) {
return true;
} else if (EPERM != ret) {
return false;
} else {
// if we got EPERM, we are not a Xenomai thread
if (!turnIntoCobaltThread()) {
xfprintf(stderr, "%s %p could not turn into cobalt\n", name, id);
return false;
}
}
// retry after becoming a cobalt thread
ret = func();
if (0 == ret) {
return true;
} else {
xfprintf(stderr, "%s %p failed after having turned into cobalt: %d\n", name, id, ret);
return false;
}
}
// Helper macro to insert this-ptr and function name
#define tryOrRetry(_func_, _enabled_) tryOrRetryImpl(_func_, _enabled_, this, __func__)
// condition resource_deadlock_would_occur instead of deadlocking. https://en.cppreference.com/w/cpp/thread/mutex/lock
bool XenomaiMutex::try_lock() {
return tryOrRetry([this]() { return __wrap_pthread_mutex_trylock(&this->m_mutex); }, m_enabled);
// TODO: An implementation that can detect the invalid usage is encouraged to throw a std::system_error with error
// condition resource_deadlock_would_occur instead of deadlocking.
}
void XenomaiMutex::lock() {
tryOrRetry([this]() { return __wrap_pthread_mutex_lock(&this->m_mutex); }, m_enabled);
}
void XenomaiMutex::unlock() {
tryOrRetry([this]() { return __wrap_pthread_mutex_unlock(&this->m_mutex); }, m_enabled);
}
XenomaiConditionVariable::XenomaiConditionVariable() {
xprintf("Construct CondictionVariable\n");
if (int ret = __wrap_pthread_cond_init(&m_cond, NULL)) {
if (EPERM != ret) {
xprintf("__wrap_thread_cond_init failed with %d %s\n", ret, strerror(ret));
return;
} else {
xprintf("mutex init returned EPERM\n");
initializeXenomai();
if (int ret = __wrap_pthread_cond_init(&m_cond, NULL)) {
fprintf(stderr, "Error: unable to create condition variable : (%d) %s\n", ret, strerror(ret));
return;
}
}
}
m_enabled = true;
}
XenomaiConditionVariable::~XenomaiConditionVariable() {
if (m_enabled) {
notify_all();
__wrap_pthread_cond_destroy(&m_cond);
}
}
void XenomaiConditionVariable::wait(std::unique_lock<XenomaiMutex>& lck) {
// If any parameter has a value that is not valid for this function (such as if lck's mutex object is not locked by
// the calling thread), it causes undefined behavior.
// Otherwise, if an exception is thrown, both the condition_variable_any object and the arguments are in a valid
// state (basic guarantee). Additionally, on exception, the state of lck is attempted to be restored before exiting
// the function scope (by calling lck.lock()).
// It may throw system_error in case of failure (transmitting any error condition from the respective call to lock
// or unlock). The predicate version (2) may also throw exceptions thrown by pred.
tryOrRetry(([this, &lck]() { return __wrap_pthread_cond_wait(&this->m_cond, &lck.mutex()->m_mutex); }), m_enabled);
}
void XenomaiConditionVariable::notify_one() noexcept {
tryOrRetry([this]() { return __wrap_pthread_cond_signal(&this->m_cond); }, m_enabled);
}
void XenomaiConditionVariable::notify_all() noexcept {
tryOrRetry([this]() { return __wrap_pthread_cond_broadcast(&this->m_cond); }, m_enabled);
}