-
-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathsync.h
177 lines (154 loc) · 3.82 KB
/
sync.h
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
#pragma once
/* For internal use. Don't include. */
#include "util/types.hpp"
#include "util/dyn_lib.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <ctime>
#elif __linux__
#include <errno.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <algorithm>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#ifdef _WIN32
DYNAMIC_IMPORT("ntdll.dll", NtWaitForKeyedEvent, NTSTATUS(HANDLE, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
DYNAMIC_IMPORT("ntdll.dll", NtReleaseKeyedEvent, NTSTATUS(HANDLE, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
DYNAMIC_IMPORT("ntdll.dll", NtWaitForSingleObject, NTSTATUS(HANDLE Handle, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
DYNAMIC_IMPORT("ntdll.dll", NtDelayExecution, NTSTATUS(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval));
DYNAMIC_IMPORT("ntdll.dll", NtWaitForAlertByThreadId, NTSTATUS(PVOID Address, PLARGE_INTEGER Timeout));
DYNAMIC_IMPORT("ntdll.dll", NtAlertThreadByThreadId, NTSTATUS(DWORD_PTR ThreadId));
constexpr NTSTATUS NTSTATUS_SUCCESS = 0;
constexpr NTSTATUS NTSTATUS_ALERTED = 0x101;
constexpr NTSTATUS NTSTATUS_TIMEOUT = 0x102;
#endif
#ifdef __linux__
#ifndef SYS_futex_waitv
#if defined(ARCH_X64) || defined(ARCH_ARM64)
#define SYS_futex_waitv 449
#endif
#endif
#ifndef FUTEX_32
#define FUTEX_32 2
#endif
#ifndef FUTEX_WAITV_MAX
#define FUTEX_WAITV_MAX 128
struct futex_waitv
{
__u64 val;
__u64 uaddr;
__u32 flags;
__u32 __reserved;
};
#endif
#else
enum
{
FUTEX_PRIVATE_FLAG = 0,
FUTEX_WAIT = 0,
FUTEX_WAIT_PRIVATE = FUTEX_WAIT,
FUTEX_WAKE = 1,
FUTEX_WAKE_PRIVATE = FUTEX_WAKE,
FUTEX_BITSET = 2,
FUTEX_WAIT_BITSET = FUTEX_WAIT | FUTEX_BITSET,
FUTEX_WAIT_BITSET_PRIVATE = FUTEX_WAIT_BITSET,
FUTEX_WAKE_BITSET = FUTEX_WAKE | FUTEX_BITSET,
FUTEX_WAKE_BITSET_PRIVATE = FUTEX_WAKE_BITSET,
};
#endif
inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* timeout = nullptr, uint mask = 0)
{
#ifdef __linux__
return syscall(SYS_futex, uaddr, futex_op, static_cast<int>(val), timeout, nullptr, static_cast<int>(mask));
#else
static struct futex_manager
{
struct waiter
{
uint val;
uint mask;
std::condition_variable cv;
};
std::mutex mutex;
std::unordered_multimap<volatile void*, waiter*> map;
int operator()(volatile void* uaddr, int futex_op, uint val, const timespec* timeout, uint mask)
{
std::unique_lock lock(mutex);
switch (futex_op)
{
case FUTEX_WAIT_PRIVATE:
{
mask = -1;
[[fallthrough]];
}
case FUTEX_WAIT_BITSET_PRIVATE:
{
if (*reinterpret_cast<volatile uint*>(uaddr) != val)
{
errno = EAGAIN;
return -1;
}
waiter rec;
rec.val = val;
rec.mask = mask;
const auto& ref = *map.emplace(uaddr, &rec);
int res = 0;
if (!timeout)
{
rec.cv.wait(lock, FN(!rec.mask));
}
else if (futex_op == FUTEX_WAIT)
{
const auto nsec = std::chrono::nanoseconds(timeout->tv_nsec + timeout->tv_sec * 1000000000ull);
if (!rec.cv.wait_for(lock, nsec, FN(!rec.mask)))
{
res = -1;
errno = ETIMEDOUT;
}
}
else
{
// TODO: absolute timeout
}
map.erase(std::find(map.find(uaddr), map.end(), ref));
return res;
}
case FUTEX_WAKE_PRIVATE:
{
mask = -1;
[[fallthrough]];
}
case FUTEX_WAKE_BITSET_PRIVATE:
{
int res = 0;
for (auto range = map.equal_range(uaddr); val && range.first != range.second; range.first++)
{
auto& entry = *range.first->second;
if (entry.mask & mask)
{
entry.cv.notify_one();
entry.mask = 0;
res++;
val--;
}
}
return res;
}
}
errno = EINVAL;
return -1;
}
} g_futex;
return g_futex(uaddr, futex_op, val, timeout, mask);
#endif
}