knitting 0.1.46
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.
- package/LICENSE +202 -0
- package/README.md +632 -0
- package/knitting.d.ts +4 -0
- package/knitting.js +5 -0
- package/map.md +264 -0
- package/package.json +77 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shm.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shm.node +0 -0
- package/process-shared-buffer.d.ts +1 -0
- package/process-shared-buffer.js +1 -0
- package/scripts/build-native-addons.ts +295 -0
- package/src/api.d.ts +55 -0
- package/src/api.js +384 -0
- package/src/common/envelope.d.ts +11 -0
- package/src/common/envelope.js +8 -0
- package/src/common/module-url.d.ts +1 -0
- package/src/common/module-url.js +24 -0
- package/src/common/node-compat.d.ts +20 -0
- package/src/common/node-compat.js +24 -0
- package/src/common/path-canonical.d.ts +6 -0
- package/src/common/path-canonical.js +41 -0
- package/src/common/runtime.d.ts +15 -0
- package/src/common/runtime.js +91 -0
- package/src/common/shared-buffer-region.d.ts +11 -0
- package/src/common/shared-buffer-region.js +21 -0
- package/src/common/shared-buffer-text.d.ts +16 -0
- package/src/common/shared-buffer-text.js +65 -0
- package/src/common/task-source.d.ts +2 -0
- package/src/common/task-source.js +79 -0
- package/src/common/task-symbol.d.ts +1 -0
- package/src/common/task-symbol.js +1 -0
- package/src/common/with-resolvers.d.ts +9 -0
- package/src/common/with-resolvers.js +23 -0
- package/src/common/worker-runtime.d.ts +40 -0
- package/src/common/worker-runtime.js +52 -0
- package/src/connections/bun.d.ts +20 -0
- package/src/connections/bun.js +159 -0
- package/src/connections/deno.d.ts +20 -0
- package/src/connections/deno.js +150 -0
- package/src/connections/file-descriptor.d.ts +37 -0
- package/src/connections/file-descriptor.js +139 -0
- package/src/connections/index.d.ts +3 -0
- package/src/connections/index.js +3 -0
- package/src/connections/node-addons.d.ts +5 -0
- package/src/connections/node-addons.js +43 -0
- package/src/connections/node.d.ts +29 -0
- package/src/connections/node.js +59 -0
- package/src/connections/posix.d.ts +31 -0
- package/src/connections/posix.js +71 -0
- package/src/connections/process-shared-buffer.d.ts +67 -0
- package/src/connections/process-shared-buffer.js +267 -0
- package/src/connections/types.d.ts +48 -0
- package/src/connections/types.js +37 -0
- package/src/error.d.ts +13 -0
- package/src/error.js +49 -0
- package/src/ipc/tools/ring-queue.d.ts +33 -0
- package/src/ipc/tools/ring-queue.js +159 -0
- package/src/ipc/transport/shared-memory.d.ts +25 -0
- package/src/ipc/transport/shared-memory.js +35 -0
- package/src/knitting_shared_memory.cc +436 -0
- package/src/knitting_shm.cc +476 -0
- package/src/memory/byte-carpet.d.ts +73 -0
- package/src/memory/byte-carpet.js +157 -0
- package/src/memory/lock.d.ts +190 -0
- package/src/memory/lock.js +856 -0
- package/src/memory/payload-config.d.ts +22 -0
- package/src/memory/payload-config.js +67 -0
- package/src/memory/payloadCodec.d.ts +46 -0
- package/src/memory/payloadCodec.js +1157 -0
- package/src/memory/regionRegistry.d.ts +17 -0
- package/src/memory/regionRegistry.js +285 -0
- package/src/memory/shared-buffer-io.d.ts +53 -0
- package/src/memory/shared-buffer-io.js +380 -0
- package/src/permission/index.d.ts +2 -0
- package/src/permission/index.js +2 -0
- package/src/permission/protocol.d.ts +166 -0
- package/src/permission/protocol.js +640 -0
- package/src/runtime/balancer.d.ts +19 -0
- package/src/runtime/balancer.js +149 -0
- package/src/runtime/dispatcher.d.ts +34 -0
- package/src/runtime/dispatcher.js +142 -0
- package/src/runtime/inline-executor.d.ts +10 -0
- package/src/runtime/inline-executor.js +270 -0
- package/src/runtime/pool.d.ts +43 -0
- package/src/runtime/pool.js +922 -0
- package/src/runtime/tx-queue.d.ts +25 -0
- package/src/runtime/tx-queue.js +144 -0
- package/src/shared/abortSignal.d.ts +23 -0
- package/src/shared/abortSignal.js +126 -0
- package/src/types.d.ts +283 -0
- package/src/types.js +2 -0
- package/src/worker/composable-runners.d.ts +12 -0
- package/src/worker/composable-runners.js +105 -0
- package/src/worker/loop.d.ts +2 -0
- package/src/worker/loop.js +453 -0
- package/src/worker/rx-queue.d.ts +22 -0
- package/src/worker/rx-queue.js +124 -0
- package/src/worker/safety/index.d.ts +4 -0
- package/src/worker/safety/index.js +4 -0
- package/src/worker/safety/performance.d.ts +1 -0
- package/src/worker/safety/performance.js +17 -0
- package/src/worker/safety/process.d.ts +2 -0
- package/src/worker/safety/process.js +79 -0
- package/src/worker/safety/startup.d.ts +16 -0
- package/src/worker/safety/startup.js +30 -0
- package/src/worker/safety/worker-data.d.ts +2 -0
- package/src/worker/safety/worker-data.js +36 -0
- package/src/worker/task-loader.d.ts +26 -0
- package/src/worker/task-loader.js +66 -0
- package/src/worker/timers.d.ts +18 -0
- package/src/worker/timers.js +97 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
#if defined(__linux__) && !defined(_GNU_SOURCE)
|
|
2
|
+
#define _GNU_SOURCE
|
|
3
|
+
#endif
|
|
4
|
+
|
|
5
|
+
#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
|
|
6
|
+
#error "knitting_shm.cc currently supports Linux, macOS, and Windows best-effort wait/wake."
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#include <node.h>
|
|
10
|
+
#include <v8.h>
|
|
11
|
+
|
|
12
|
+
#include <atomic>
|
|
13
|
+
#include <cerrno>
|
|
14
|
+
#include <chrono>
|
|
15
|
+
#include <climits>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <cstring>
|
|
18
|
+
#include <memory>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <thread>
|
|
21
|
+
|
|
22
|
+
#ifdef __linux__
|
|
23
|
+
#include <linux/futex.h>
|
|
24
|
+
#include <sys/syscall.h>
|
|
25
|
+
#endif
|
|
26
|
+
#include <time.h>
|
|
27
|
+
#ifndef _WIN32
|
|
28
|
+
#include <unistd.h>
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
namespace knitting_shm {
|
|
32
|
+
|
|
33
|
+
#ifdef __APPLE__
|
|
34
|
+
// These are provided by libSystem. We declare them directly so the addon can
|
|
35
|
+
// still compile on SDKs where <sys/ulock.h> is not exposed as a public header.
|
|
36
|
+
extern "C" int __ulock_wait(
|
|
37
|
+
uint32_t operation,
|
|
38
|
+
void* addr,
|
|
39
|
+
uint64_t value,
|
|
40
|
+
uint32_t timeout_us
|
|
41
|
+
);
|
|
42
|
+
extern "C" int __ulock_wake(
|
|
43
|
+
uint32_t operation,
|
|
44
|
+
void* addr,
|
|
45
|
+
uint64_t wake_value
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
#ifndef UL_COMPARE_AND_WAIT_SHARED
|
|
49
|
+
#define UL_COMPARE_AND_WAIT_SHARED 3
|
|
50
|
+
#endif
|
|
51
|
+
|
|
52
|
+
#ifndef ULF_WAKE_ALL
|
|
53
|
+
#define ULF_WAKE_ALL 0x00000100
|
|
54
|
+
#endif
|
|
55
|
+
#endif
|
|
56
|
+
|
|
57
|
+
#ifdef _WIN32
|
|
58
|
+
uint32_t AtomicLoadU32(uint32_t* addr) {
|
|
59
|
+
return std::atomic_ref<uint32_t>(*addr).load(std::memory_order_acquire);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
uint64_t TimeoutToMillis(const struct timespec* timeout) {
|
|
63
|
+
if (timeout == nullptr) return UINT64_MAX;
|
|
64
|
+
|
|
65
|
+
uint64_t millis = static_cast<uint64_t>(timeout->tv_sec) * 1000ULL;
|
|
66
|
+
millis += static_cast<uint64_t>(timeout->tv_nsec) / 1000000ULL;
|
|
67
|
+
if ((timeout->tv_nsec % 1000000L) != 0) millis += 1;
|
|
68
|
+
return millis;
|
|
69
|
+
}
|
|
70
|
+
#endif
|
|
71
|
+
|
|
72
|
+
int FutexWait(uint32_t* addr, uint32_t expected, const struct timespec* timeout) {
|
|
73
|
+
#ifdef __linux__
|
|
74
|
+
return static_cast<int>(syscall(
|
|
75
|
+
SYS_futex,
|
|
76
|
+
reinterpret_cast<int*>(addr),
|
|
77
|
+
FUTEX_WAIT,
|
|
78
|
+
static_cast<int>(expected),
|
|
79
|
+
timeout,
|
|
80
|
+
nullptr,
|
|
81
|
+
0
|
|
82
|
+
));
|
|
83
|
+
#elif defined(_WIN32)
|
|
84
|
+
if (AtomicLoadU32(addr) != expected) {
|
|
85
|
+
errno = EAGAIN;
|
|
86
|
+
return -1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
uint64_t timeout_ms = TimeoutToMillis(timeout);
|
|
90
|
+
const bool infinite = timeout_ms == UINT64_MAX;
|
|
91
|
+
const auto started = std::chrono::steady_clock::now();
|
|
92
|
+
const auto deadline = infinite
|
|
93
|
+
? std::chrono::steady_clock::time_point::max()
|
|
94
|
+
: started + std::chrono::milliseconds(timeout_ms);
|
|
95
|
+
|
|
96
|
+
while (AtomicLoadU32(addr) == expected) {
|
|
97
|
+
if (!infinite && std::chrono::steady_clock::now() >= deadline) {
|
|
98
|
+
errno = ETIMEDOUT;
|
|
99
|
+
return -1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (infinite) {
|
|
103
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
auto now = std::chrono::steady_clock::now();
|
|
108
|
+
if (now >= deadline) {
|
|
109
|
+
errno = ETIMEDOUT;
|
|
110
|
+
return -1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
auto remaining = deadline - now;
|
|
114
|
+
auto one_ms = std::chrono::milliseconds(1);
|
|
115
|
+
std::this_thread::sleep_for(remaining < one_ms ? remaining : one_ms);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return 0;
|
|
119
|
+
#else
|
|
120
|
+
uint32_t timeout_us = 0;
|
|
121
|
+
if (timeout != nullptr) {
|
|
122
|
+
uint64_t micros =
|
|
123
|
+
(static_cast<uint64_t>(timeout->tv_sec) * 1000000ULL) +
|
|
124
|
+
(static_cast<uint64_t>(timeout->tv_nsec) / 1000ULL);
|
|
125
|
+
if (micros == 0) micros = 1;
|
|
126
|
+
timeout_us = micros > UINT32_MAX
|
|
127
|
+
? UINT32_MAX
|
|
128
|
+
: static_cast<uint32_t>(micros);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return __ulock_wait(
|
|
132
|
+
UL_COMPARE_AND_WAIT_SHARED,
|
|
133
|
+
addr,
|
|
134
|
+
static_cast<uint64_t>(expected),
|
|
135
|
+
timeout_us
|
|
136
|
+
);
|
|
137
|
+
#endif
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
int FutexWake(uint32_t* addr, int count) {
|
|
141
|
+
#ifdef __linux__
|
|
142
|
+
return static_cast<int>(syscall(
|
|
143
|
+
SYS_futex,
|
|
144
|
+
reinterpret_cast<int*>(addr),
|
|
145
|
+
FUTEX_WAKE,
|
|
146
|
+
count,
|
|
147
|
+
nullptr,
|
|
148
|
+
nullptr,
|
|
149
|
+
0
|
|
150
|
+
));
|
|
151
|
+
#elif defined(_WIN32)
|
|
152
|
+
(void)addr;
|
|
153
|
+
return count <= 0 ? 0 : 1;
|
|
154
|
+
#else
|
|
155
|
+
uint32_t operation = UL_COMPARE_AND_WAIT_SHARED;
|
|
156
|
+
if (count <= 0 || count == INT_MAX) {
|
|
157
|
+
operation |= ULF_WAKE_ALL;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
int rc = __ulock_wake(operation, addr, 0);
|
|
161
|
+
if (rc == 0) {
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (errno == ENOENT) {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return -1;
|
|
170
|
+
#endif
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
void ThrowErrno(v8::Isolate* isolate, const char* message, int err = errno) {
|
|
174
|
+
std::string full = std::string(message) + ": " + std::strerror(err);
|
|
175
|
+
isolate->ThrowException(v8::Exception::Error(
|
|
176
|
+
v8::String::NewFromUtf8(isolate, full.c_str()).ToLocalChecked()
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
void ThrowType(v8::Isolate* isolate, const char* message) {
|
|
181
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
182
|
+
v8::String::NewFromUtf8(isolate, message).ToLocalChecked()
|
|
183
|
+
));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
void ThrowRange(v8::Isolate* isolate, const char* message) {
|
|
187
|
+
isolate->ThrowException(v8::Exception::RangeError(
|
|
188
|
+
v8::String::NewFromUtf8(isolate, message).ToLocalChecked()
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
bool ReadU32Argument(
|
|
193
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
194
|
+
int index,
|
|
195
|
+
uint32_t* out
|
|
196
|
+
) {
|
|
197
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
198
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
199
|
+
|
|
200
|
+
if (args.Length() <= index || !args[index]->IsNumber()) {
|
|
201
|
+
ThrowType(isolate, "expected a number argument");
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
v8::Maybe<uint32_t> maybe = args[index]->Uint32Value(context);
|
|
206
|
+
if (maybe.IsNothing()) {
|
|
207
|
+
ThrowType(isolate, "expected a valid uint32 argument");
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
*out = maybe.FromJust();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
bool ReadSizeArgument(
|
|
216
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
217
|
+
int index,
|
|
218
|
+
size_t* out
|
|
219
|
+
) {
|
|
220
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
221
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
222
|
+
|
|
223
|
+
if (args.Length() <= index || !args[index]->IsNumber()) {
|
|
224
|
+
ThrowType(isolate, "expected a number argument");
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
v8::Maybe<int64_t> maybe = args[index]->IntegerValue(context);
|
|
229
|
+
if (maybe.IsNothing() || maybe.FromJust() < 0) {
|
|
230
|
+
ThrowRange(isolate, "offset must be non-negative");
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
*out = static_cast<size_t>(maybe.FromJust());
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
bool ReadOptionalMillis(
|
|
239
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
240
|
+
int index,
|
|
241
|
+
double fallback,
|
|
242
|
+
double* out
|
|
243
|
+
) {
|
|
244
|
+
*out = fallback;
|
|
245
|
+
|
|
246
|
+
if (args.Length() <= index || args[index]->IsUndefined() || args[index]->IsNull()) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
251
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
252
|
+
|
|
253
|
+
if (!args[index]->IsNumber()) {
|
|
254
|
+
ThrowType(isolate, "milliseconds must be a number");
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
v8::Maybe<double> maybe = args[index]->NumberValue(context);
|
|
259
|
+
if (maybe.IsNothing()) {
|
|
260
|
+
ThrowType(isolate, "milliseconds must be a valid number");
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
double value = maybe.FromJust();
|
|
265
|
+
if (value != value) {
|
|
266
|
+
ThrowRange(isolate, "milliseconds must not be NaN");
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
*out = value < 0 ? 0 : value;
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
bool GetBackingStore(
|
|
275
|
+
v8::Isolate* isolate,
|
|
276
|
+
v8::Local<v8::Value> value,
|
|
277
|
+
std::shared_ptr<v8::BackingStore>* out
|
|
278
|
+
) {
|
|
279
|
+
if (value->IsSharedArrayBuffer()) {
|
|
280
|
+
*out = value.As<v8::SharedArrayBuffer>()->GetBackingStore();
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (value->IsArrayBuffer()) {
|
|
285
|
+
*out = value.As<v8::ArrayBuffer>()->GetBackingStore();
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ThrowType(isolate, "first argument must be an ArrayBuffer or SharedArrayBuffer");
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
bool GetU32Pointer(
|
|
294
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
295
|
+
uint32_t** out
|
|
296
|
+
) {
|
|
297
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
298
|
+
|
|
299
|
+
if (args.Length() < 2) {
|
|
300
|
+
ThrowType(isolate, "expected buffer and byteOffset");
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
std::shared_ptr<v8::BackingStore> backing;
|
|
305
|
+
if (!GetBackingStore(isolate, args[0], &backing)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
size_t offset = 0;
|
|
310
|
+
if (!ReadSizeArgument(args, 1, &offset)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if ((offset % alignof(uint32_t)) != 0) {
|
|
315
|
+
ThrowRange(isolate, "byteOffset must be uint32-aligned");
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (backing->ByteLength() < sizeof(uint32_t) || offset > backing->ByteLength() - sizeof(uint32_t)) {
|
|
320
|
+
ThrowRange(isolate, "byteOffset out of bounds");
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
*out = reinterpret_cast<uint32_t*>(
|
|
325
|
+
static_cast<uint8_t*>(backing->Data()) + offset
|
|
326
|
+
);
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
bool ReadTimeout(
|
|
331
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
332
|
+
int index,
|
|
333
|
+
struct timespec* timeout,
|
|
334
|
+
struct timespec** out
|
|
335
|
+
) {
|
|
336
|
+
*out = nullptr;
|
|
337
|
+
|
|
338
|
+
if (args.Length() <= index || args[index]->IsUndefined() || args[index]->IsNull()) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
343
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
344
|
+
|
|
345
|
+
if (!args[index]->IsNumber()) {
|
|
346
|
+
ThrowType(isolate, "timeoutMs must be a number");
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
v8::Maybe<double> maybe = args[index]->NumberValue(context);
|
|
351
|
+
if (maybe.IsNothing()) {
|
|
352
|
+
ThrowType(isolate, "timeoutMs must be a valid number");
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
double timeoutMs = maybe.FromJust();
|
|
357
|
+
if (timeoutMs < 0) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
time_t sec = static_cast<time_t>(timeoutMs / 1000.0);
|
|
362
|
+
long nsec = static_cast<long>(
|
|
363
|
+
(timeoutMs - (static_cast<double>(sec) * 1000.0)) * 1000000.0
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
if (nsec < 0) nsec = 0;
|
|
367
|
+
if (nsec > 999999999L) nsec = 999999999L;
|
|
368
|
+
|
|
369
|
+
timeout->tv_sec = sec;
|
|
370
|
+
timeout->tv_nsec = nsec;
|
|
371
|
+
*out = timeout;
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
void WaitU32(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
376
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
377
|
+
|
|
378
|
+
uint32_t* ptr = nullptr;
|
|
379
|
+
if (!GetU32Pointer(args, &ptr)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
uint32_t expected = 0;
|
|
384
|
+
if (!ReadU32Argument(args, 2, &expected)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
struct timespec timeout;
|
|
389
|
+
struct timespec* timeout_ptr = nullptr;
|
|
390
|
+
if (!ReadTimeout(args, 3, &timeout, &timeout_ptr)) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
int rc = FutexWait(ptr, expected, timeout_ptr);
|
|
395
|
+
if (rc == 0) {
|
|
396
|
+
args.GetReturnValue().Set(
|
|
397
|
+
v8::String::NewFromUtf8Literal(isolate, "woken")
|
|
398
|
+
);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
int saved = errno;
|
|
403
|
+
if (saved == EAGAIN) {
|
|
404
|
+
args.GetReturnValue().Set(
|
|
405
|
+
v8::String::NewFromUtf8Literal(isolate, "changed")
|
|
406
|
+
);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (saved == EINTR) {
|
|
410
|
+
args.GetReturnValue().Set(
|
|
411
|
+
v8::String::NewFromUtf8Literal(isolate, "interrupted")
|
|
412
|
+
);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (saved == ETIMEDOUT) {
|
|
416
|
+
args.GetReturnValue().Set(
|
|
417
|
+
v8::String::NewFromUtf8Literal(isolate, "timed-out")
|
|
418
|
+
);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
ThrowErrno(isolate, "futex wait failed", saved);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
void WakeU32(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
426
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
427
|
+
|
|
428
|
+
uint32_t* ptr = nullptr;
|
|
429
|
+
if (!GetU32Pointer(args, &ptr)) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
uint32_t raw_count = 1;
|
|
434
|
+
if (args.Length() >= 3 && !args[2]->IsUndefined()) {
|
|
435
|
+
if (!ReadU32Argument(args, 2, &raw_count)) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
int count = raw_count == 0 ? INT_MAX : static_cast<int>(raw_count);
|
|
441
|
+
int woken = FutexWake(ptr, count);
|
|
442
|
+
if (woken == -1) {
|
|
443
|
+
ThrowErrno(isolate, "futex wake failed");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
args.GetReturnValue().Set(v8::Integer::New(isolate, woken));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
void Sleep(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
451
|
+
double milliseconds = 1;
|
|
452
|
+
if (!ReadOptionalMillis(args, 0, 1, &milliseconds)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
std::this_thread::sleep_for(
|
|
457
|
+
std::chrono::duration<double, std::milli>(milliseconds)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
void Yield(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
462
|
+
(void)args;
|
|
463
|
+
std::this_thread::yield();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
void Initialize(v8::Local<v8::Object> exports) {
|
|
467
|
+
NODE_SET_METHOD(exports, "waitU32", WaitU32);
|
|
468
|
+
NODE_SET_METHOD(exports, "wakeU32", WakeU32);
|
|
469
|
+
NODE_SET_METHOD(exports, "notifyU32", WakeU32);
|
|
470
|
+
NODE_SET_METHOD(exports, "sleep", Sleep);
|
|
471
|
+
NODE_SET_METHOD(exports, "yield", Yield);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
|
|
475
|
+
|
|
476
|
+
} // namespace knitting_shm
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SharedBuffer, SharedBufferRegion } from "../common/shared-buffer-region.js";
|
|
2
|
+
export declare const BYTE_CARPET_ALIGN_BYTES = 64;
|
|
3
|
+
export declare const alignBytes: (value: number, alignment?: number) => number;
|
|
4
|
+
export declare const makeSharedBufferRegion: (sab: SharedBuffer, byteOffset: number, byteLength: number) => SharedBufferRegion;
|
|
5
|
+
export type ByteCarpetSlice = {
|
|
6
|
+
name: string;
|
|
7
|
+
byteOffset: number;
|
|
8
|
+
byteLength: number;
|
|
9
|
+
reservedByteLength: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const createByteCarpet: ({ alignTo, startByteOffset, }?: {
|
|
12
|
+
alignTo?: number;
|
|
13
|
+
startByteOffset?: number;
|
|
14
|
+
}) => {
|
|
15
|
+
slices: ByteCarpetSlice[];
|
|
16
|
+
take: (name: string, byteLength: number, { alignTo: sliceAlignment, reserveByteLength, }?: {
|
|
17
|
+
alignTo?: number;
|
|
18
|
+
reserveByteLength?: number;
|
|
19
|
+
}) => ByteCarpetSlice;
|
|
20
|
+
byteLength: () => number;
|
|
21
|
+
bind: (sab: SharedBuffer, slice: ByteCarpetSlice) => SharedBufferRegion;
|
|
22
|
+
};
|
|
23
|
+
export declare const getStridedSlotOffsetU32: ({ slotIndex, slotStrideU32, baseU32, extraU32, }: {
|
|
24
|
+
slotIndex: number;
|
|
25
|
+
slotStrideU32: number;
|
|
26
|
+
baseU32?: number;
|
|
27
|
+
extraU32?: number;
|
|
28
|
+
}) => number;
|
|
29
|
+
export declare const getStridedSlotByteOffset: ({ slotIndex, slotStrideU32, baseByteOffset, baseU32, extraU32, }: {
|
|
30
|
+
slotIndex: number;
|
|
31
|
+
slotStrideU32: number;
|
|
32
|
+
baseByteOffset?: number;
|
|
33
|
+
baseU32?: number;
|
|
34
|
+
extraU32?: number;
|
|
35
|
+
}) => number;
|
|
36
|
+
export declare const getStridedRegionSpanBytes: ({ slotCount, slotStrideU32, slotLengthU32, baseU32, }: {
|
|
37
|
+
slotCount: number;
|
|
38
|
+
slotStrideU32: number;
|
|
39
|
+
slotLengthU32: number;
|
|
40
|
+
baseU32?: number;
|
|
41
|
+
}) => number;
|
|
42
|
+
export declare const getInterleavedSlotStrideU32: (slotStrideU32: number) => number;
|
|
43
|
+
export declare const getHeaderBlockByteLength: ({ slotCount, slotStrideU32, queues, alignTo, }: {
|
|
44
|
+
slotCount: number;
|
|
45
|
+
slotStrideU32: number;
|
|
46
|
+
queues?: number;
|
|
47
|
+
alignTo?: number;
|
|
48
|
+
}) => number;
|
|
49
|
+
export type HeaderLayoutMode = "split" | "interleaved";
|
|
50
|
+
export type QueueControlByteLayout = {
|
|
51
|
+
headers: SharedBufferRegion;
|
|
52
|
+
headerSlotStrideU32: number;
|
|
53
|
+
lockSector: SharedBufferRegion;
|
|
54
|
+
payloadSector: SharedBufferRegion;
|
|
55
|
+
};
|
|
56
|
+
export type LockControlCarpet = {
|
|
57
|
+
controlSAB: SharedBuffer;
|
|
58
|
+
signals: SharedBufferRegion;
|
|
59
|
+
abortSignals: SharedBufferRegion;
|
|
60
|
+
lock: QueueControlByteLayout;
|
|
61
|
+
returnLock: QueueControlByteLayout;
|
|
62
|
+
slices: readonly ByteCarpetSlice[];
|
|
63
|
+
};
|
|
64
|
+
export declare const createLockControlCarpet: ({ signalBytes, abortBytes, lockSectorBytes, headerSlotStrideU32, slotCount, headerLayout, alignTo, createBuffer, }: {
|
|
65
|
+
signalBytes: number;
|
|
66
|
+
abortBytes: number;
|
|
67
|
+
lockSectorBytes: number;
|
|
68
|
+
headerSlotStrideU32: number;
|
|
69
|
+
slotCount: number;
|
|
70
|
+
headerLayout?: HeaderLayoutMode;
|
|
71
|
+
alignTo?: number;
|
|
72
|
+
createBuffer?: (byteLength: number) => SharedBuffer;
|
|
73
|
+
}) => LockControlCarpet;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export const BYTE_CARPET_ALIGN_BYTES = 64;
|
|
2
|
+
const U32_BYTES = Uint32Array.BYTES_PER_ELEMENT;
|
|
3
|
+
const toNonNegativeInteger = (value, label) => {
|
|
4
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
5
|
+
throw new RangeError(`${label} must be a non-negative integer`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
export const alignBytes = (value, alignment = BYTE_CARPET_ALIGN_BYTES) => {
|
|
10
|
+
const safeValue = toNonNegativeInteger(value, "value");
|
|
11
|
+
const safeAlignment = toNonNegativeInteger(alignment, "alignment");
|
|
12
|
+
if (safeAlignment === 0) {
|
|
13
|
+
throw new RangeError("alignment must be greater than zero");
|
|
14
|
+
}
|
|
15
|
+
return Math.ceil(safeValue / safeAlignment) * safeAlignment;
|
|
16
|
+
};
|
|
17
|
+
export const makeSharedBufferRegion = (sab, byteOffset, byteLength) => ({
|
|
18
|
+
sab,
|
|
19
|
+
byteOffset: toNonNegativeInteger(byteOffset, "byteOffset"),
|
|
20
|
+
byteLength: toNonNegativeInteger(byteLength, "byteLength"),
|
|
21
|
+
});
|
|
22
|
+
export const createByteCarpet = ({ alignTo = BYTE_CARPET_ALIGN_BYTES, startByteOffset = 0, } = {}) => {
|
|
23
|
+
const defaultAlignment = toNonNegativeInteger(alignTo, "alignTo");
|
|
24
|
+
if (defaultAlignment === 0) {
|
|
25
|
+
throw new RangeError("alignTo must be greater than zero");
|
|
26
|
+
}
|
|
27
|
+
let cursor = toNonNegativeInteger(startByteOffset, "startByteOffset");
|
|
28
|
+
const slices = [];
|
|
29
|
+
const take = (name, byteLength, { alignTo: sliceAlignment = defaultAlignment, reserveByteLength, } = {}) => {
|
|
30
|
+
const logicalByteLength = toNonNegativeInteger(byteLength, `${name} byteLength`);
|
|
31
|
+
const safeSliceAlignment = toNonNegativeInteger(sliceAlignment, `${name} alignTo`);
|
|
32
|
+
if (safeSliceAlignment === 0) {
|
|
33
|
+
throw new RangeError(`${name} alignTo must be greater than zero`);
|
|
34
|
+
}
|
|
35
|
+
const reserved = reserveByteLength == null
|
|
36
|
+
? alignBytes(logicalByteLength, safeSliceAlignment)
|
|
37
|
+
: toNonNegativeInteger(reserveByteLength, `${name} reserveByteLength`);
|
|
38
|
+
if (reserved < logicalByteLength) {
|
|
39
|
+
throw new RangeError(`${name} reserveByteLength must cover byteLength`);
|
|
40
|
+
}
|
|
41
|
+
const byteOffset = alignBytes(cursor, safeSliceAlignment);
|
|
42
|
+
const slice = {
|
|
43
|
+
name,
|
|
44
|
+
byteOffset,
|
|
45
|
+
byteLength: logicalByteLength,
|
|
46
|
+
reservedByteLength: reserved,
|
|
47
|
+
};
|
|
48
|
+
slices.push(slice);
|
|
49
|
+
cursor = byteOffset + reserved;
|
|
50
|
+
return slice;
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
slices,
|
|
54
|
+
take,
|
|
55
|
+
byteLength: () => cursor,
|
|
56
|
+
bind: (sab, slice) => makeSharedBufferRegion(sab, slice.byteOffset, slice.byteLength),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export const getStridedSlotOffsetU32 = ({ slotIndex, slotStrideU32, baseU32 = 0, extraU32 = 0, }) => (slotIndex * slotStrideU32) + baseU32 + extraU32;
|
|
60
|
+
export const getStridedSlotByteOffset = ({ slotIndex, slotStrideU32, baseByteOffset = 0, baseU32 = 0, extraU32 = 0, }) => baseByteOffset +
|
|
61
|
+
(getStridedSlotOffsetU32({
|
|
62
|
+
slotIndex,
|
|
63
|
+
slotStrideU32,
|
|
64
|
+
baseU32,
|
|
65
|
+
extraU32,
|
|
66
|
+
}) * U32_BYTES);
|
|
67
|
+
export const getStridedRegionSpanBytes = ({ slotCount, slotStrideU32, slotLengthU32, baseU32 = 0, }) => {
|
|
68
|
+
const safeSlotCount = toNonNegativeInteger(slotCount, "slotCount");
|
|
69
|
+
if (safeSlotCount === 0)
|
|
70
|
+
return 0;
|
|
71
|
+
return (getStridedSlotOffsetU32({
|
|
72
|
+
slotIndex: safeSlotCount - 1,
|
|
73
|
+
slotStrideU32,
|
|
74
|
+
baseU32,
|
|
75
|
+
}) + slotLengthU32) * U32_BYTES;
|
|
76
|
+
};
|
|
77
|
+
export const getInterleavedSlotStrideU32 = (slotStrideU32) => slotStrideU32 * 2;
|
|
78
|
+
export const getHeaderBlockByteLength = ({ slotCount, slotStrideU32, queues = 1, alignTo = BYTE_CARPET_ALIGN_BYTES, }) => alignBytes(slotCount * slotStrideU32 * U32_BYTES * queues, alignTo);
|
|
79
|
+
const createInterleavedHeaderPair = ({ sab, byteOffset, slotCount, slotStrideU32, }) => {
|
|
80
|
+
const headerSlotStrideU32 = getInterleavedSlotStrideU32(slotStrideU32);
|
|
81
|
+
const slotBytes = slotStrideU32 * U32_BYTES;
|
|
82
|
+
const spanBytes = getStridedRegionSpanBytes({
|
|
83
|
+
slotCount,
|
|
84
|
+
slotStrideU32: headerSlotStrideU32,
|
|
85
|
+
slotLengthU32: slotStrideU32,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
headerSlotStrideU32,
|
|
89
|
+
requestHeaders: makeSharedBufferRegion(sab, byteOffset, spanBytes),
|
|
90
|
+
returnHeaders: makeSharedBufferRegion(sab, byteOffset + slotBytes, spanBytes),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
export const createLockControlCarpet = ({ signalBytes, abortBytes, lockSectorBytes, headerSlotStrideU32, slotCount, headerLayout = "interleaved", alignTo = BYTE_CARPET_ALIGN_BYTES, createBuffer = (byteLength) => new SharedArrayBuffer(byteLength), }) => {
|
|
94
|
+
const carpet = createByteCarpet({ alignTo });
|
|
95
|
+
const signalsSlice = carpet.take("signals", signalBytes);
|
|
96
|
+
const requestLockSlice = carpet.take("requestLockSector", lockSectorBytes);
|
|
97
|
+
const returnLockSlice = carpet.take("returnLockSector", lockSectorBytes);
|
|
98
|
+
let requestHeadersSlice;
|
|
99
|
+
let returnHeadersSlice;
|
|
100
|
+
let interleavedHeadersSlice;
|
|
101
|
+
if (headerLayout === "interleaved") {
|
|
102
|
+
interleavedHeadersSlice = carpet.take("interleavedHeaders", getHeaderBlockByteLength({
|
|
103
|
+
slotCount,
|
|
104
|
+
slotStrideU32: headerSlotStrideU32,
|
|
105
|
+
queues: 2,
|
|
106
|
+
alignTo,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
requestHeadersSlice = carpet.take("requestHeaders", getHeaderBlockByteLength({
|
|
111
|
+
slotCount,
|
|
112
|
+
slotStrideU32: headerSlotStrideU32,
|
|
113
|
+
alignTo,
|
|
114
|
+
}));
|
|
115
|
+
returnHeadersSlice = carpet.take("returnHeaders", getHeaderBlockByteLength({
|
|
116
|
+
slotCount,
|
|
117
|
+
slotStrideU32: headerSlotStrideU32,
|
|
118
|
+
alignTo,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
const abortSignalsSlice = carpet.take("abortSignals", abortBytes);
|
|
122
|
+
const controlSAB = createBuffer(carpet.byteLength());
|
|
123
|
+
const signals = carpet.bind(controlSAB, signalsSlice);
|
|
124
|
+
const abortSignals = carpet.bind(controlSAB, abortSignalsSlice);
|
|
125
|
+
const requestLockSector = carpet.bind(controlSAB, requestLockSlice);
|
|
126
|
+
const returnLockSector = carpet.bind(controlSAB, returnLockSlice);
|
|
127
|
+
const headerPair = headerLayout === "interleaved"
|
|
128
|
+
? createInterleavedHeaderPair({
|
|
129
|
+
sab: controlSAB,
|
|
130
|
+
byteOffset: interleavedHeadersSlice.byteOffset,
|
|
131
|
+
slotCount,
|
|
132
|
+
slotStrideU32: headerSlotStrideU32,
|
|
133
|
+
})
|
|
134
|
+
: {
|
|
135
|
+
headerSlotStrideU32,
|
|
136
|
+
requestHeaders: carpet.bind(controlSAB, requestHeadersSlice),
|
|
137
|
+
returnHeaders: carpet.bind(controlSAB, returnHeadersSlice),
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
controlSAB,
|
|
141
|
+
signals,
|
|
142
|
+
abortSignals,
|
|
143
|
+
lock: {
|
|
144
|
+
headers: headerPair.requestHeaders,
|
|
145
|
+
headerSlotStrideU32: headerPair.headerSlotStrideU32,
|
|
146
|
+
lockSector: requestLockSector,
|
|
147
|
+
payloadSector: requestLockSector,
|
|
148
|
+
},
|
|
149
|
+
returnLock: {
|
|
150
|
+
headers: headerPair.returnHeaders,
|
|
151
|
+
headerSlotStrideU32: headerPair.headerSlotStrideU32,
|
|
152
|
+
lockSector: returnLockSector,
|
|
153
|
+
payloadSector: returnLockSector,
|
|
154
|
+
},
|
|
155
|
+
slices: carpet.slices,
|
|
156
|
+
};
|
|
157
|
+
};
|