knitting 0.1.51 → 0.1.53
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/README.md +213 -54
- package/knitting.d.ts +1 -0
- package/map.md +15 -3
- package/package.json +14 -2
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +5 -0
- package/src/api.d.ts +29 -16
- package/src/api.js +98 -28
- package/src/common/envelope.d.ts +9 -3
- package/src/common/envelope.js +14 -0
- package/src/common/worker-runtime.d.ts +2 -0
- package/src/common/worker-runtime.js +9 -0
- package/src/connections/buffer-reference-native.d.ts +56 -0
- package/src/connections/buffer-reference-native.js +217 -0
- package/src/connections/buffer-reference.d.ts +78 -0
- package/src/connections/buffer-reference.js +461 -0
- package/src/connections/index.d.ts +1 -0
- package/src/connections/index.js +1 -0
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/node-buffer-pointer.d.ts +20 -0
- package/src/connections/node-buffer-pointer.js +16 -0
- package/src/connections/process-shared-buffer.d.ts +6 -0
- package/src/connections/process-shared-buffer.js +6 -0
- package/src/connections/shared-array-buffer-payload.d.ts +36 -0
- package/src/connections/shared-array-buffer-payload.js +235 -0
- package/src/debug/env-diff.d.ts +26 -0
- package/src/debug/env-diff.js +49 -0
- package/src/debug/gate.d.ts +18 -0
- package/src/debug/gate.js +69 -0
- package/src/debug/handle.d.ts +23 -0
- package/src/debug/handle.js +48 -0
- package/src/ipc/transport/shared-memory.d.ts +1 -3
- package/src/knitting_buffer_pointer.cc +425 -0
- package/src/memory/lock.d.ts +12 -1
- package/src/memory/lock.js +47 -4
- package/src/memory/payload-config.d.ts +9 -0
- package/src/memory/payloadCodec.js +220 -37
- package/src/permission/protocol.d.ts +1 -1
- package/src/permission/protocol.js +30 -20
- package/src/runtime/pool.d.ts +3 -5
- package/src/runtime/pool.js +18 -18
- package/src/runtime/process-worker.js +5 -2
- package/src/runtime/tx-queue.d.ts +3 -2
- package/src/runtime/tx-queue.js +18 -13
- package/src/types.d.ts +54 -50
- package/src/utils/http.d.ts +29 -0
- package/src/utils/http.js +100 -0
- package/src/worker/loop.js +76 -14
- package/src/worker/rx-queue.d.ts +4 -1
- package/src/worker/rx-queue.js +53 -4
- package/src/worker/safety/startup.d.ts +2 -3
- package/src/worker/safety/startup.js +1 -4
- package/src/worker/timers.js +7 -2
- package/unsafe.d.ts +1 -0
- package/unsafe.js +1 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
|
|
2
|
+
#error "knitting_buffer_pointer.cc currently supports Linux, macOS, and Windows."
|
|
3
|
+
#endif
|
|
4
|
+
|
|
5
|
+
#include <node.h>
|
|
6
|
+
#include <v8.h>
|
|
7
|
+
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <unordered_map>
|
|
12
|
+
|
|
13
|
+
namespace knitting_buffer_pointer {
|
|
14
|
+
|
|
15
|
+
struct RetainedReference {
|
|
16
|
+
v8::Isolate* isolate;
|
|
17
|
+
v8::Global<v8::Value> value;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
std::mutex retained_mutex;
|
|
21
|
+
uint64_t next_retained_id = 1;
|
|
22
|
+
std::unordered_map<uint64_t, std::unique_ptr<RetainedReference>> retained_refs;
|
|
23
|
+
|
|
24
|
+
// BackingStore is isolate-independent, unlike v8::Global<Value>.
|
|
25
|
+
// Consumers can co-own moved bytes while the registry keeps producer liveness.
|
|
26
|
+
struct RetainedBackingStore {
|
|
27
|
+
std::shared_ptr<v8::BackingStore> store;
|
|
28
|
+
size_t byte_offset;
|
|
29
|
+
size_t byte_length;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
std::mutex backing_mutex;
|
|
33
|
+
uint64_t next_backing_id = 1;
|
|
34
|
+
std::unordered_map<uint64_t, RetainedBackingStore> retained_backings;
|
|
35
|
+
|
|
36
|
+
void ThrowType(v8::Isolate* isolate, const char* message) {
|
|
37
|
+
isolate->ThrowException(v8::Exception::TypeError(
|
|
38
|
+
v8::String::NewFromUtf8(isolate, message).ToLocalChecked()
|
|
39
|
+
));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
v8::Local<v8::String> DetachKey(v8::Isolate* isolate) {
|
|
43
|
+
return v8::String::NewFromUtf8Literal(
|
|
44
|
+
isolate,
|
|
45
|
+
"knitting.bufferReference.detachKey"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
bool DetachWithKey(
|
|
50
|
+
v8::Local<v8::ArrayBuffer> buffer,
|
|
51
|
+
v8::Local<v8::Value> key
|
|
52
|
+
) {
|
|
53
|
+
v8::Maybe<bool> detached = buffer->Detach(key);
|
|
54
|
+
return (detached.IsJust() && detached.FromJust()) || buffer->WasDetached();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
bool DetachDefaultArrayBuffer(v8::Local<v8::ArrayBuffer> buffer) {
|
|
58
|
+
if (buffer->WasDetached()) return true;
|
|
59
|
+
if (!buffer->IsDetachable()) return false;
|
|
60
|
+
return DetachWithKey(buffer, v8::Local<v8::Value>());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bool DetachKnittingArrayBuffer(
|
|
64
|
+
v8::Isolate* isolate,
|
|
65
|
+
v8::Local<v8::ArrayBuffer> buffer
|
|
66
|
+
) {
|
|
67
|
+
if (buffer->WasDetached()) return true;
|
|
68
|
+
if (!buffer->IsDetachable()) return false;
|
|
69
|
+
if (DetachWithKey(buffer, DetachKey(isolate))) return true;
|
|
70
|
+
return DetachWithKey(buffer, v8::Local<v8::Value>());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool ReadPointerInfo(
|
|
74
|
+
v8::Isolate* isolate,
|
|
75
|
+
v8::Local<v8::Value> value,
|
|
76
|
+
void** data,
|
|
77
|
+
size_t* byte_length
|
|
78
|
+
) {
|
|
79
|
+
size_t byte_offset = 0;
|
|
80
|
+
|
|
81
|
+
if (value->IsArrayBufferView()) {
|
|
82
|
+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
|
|
83
|
+
*data = view->Buffer()->Data();
|
|
84
|
+
byte_offset = view->ByteOffset();
|
|
85
|
+
*byte_length = view->ByteLength();
|
|
86
|
+
} else if (value->IsArrayBuffer()) {
|
|
87
|
+
v8::Local<v8::ArrayBuffer> buffer = value.As<v8::ArrayBuffer>();
|
|
88
|
+
*data = buffer->Data();
|
|
89
|
+
*byte_length = buffer->ByteLength();
|
|
90
|
+
} else if (value->IsSharedArrayBuffer()) {
|
|
91
|
+
v8::Local<v8::SharedArrayBuffer> buffer = value.As<v8::SharedArrayBuffer>();
|
|
92
|
+
*data = buffer->Data();
|
|
93
|
+
*byte_length = buffer->ByteLength();
|
|
94
|
+
} else {
|
|
95
|
+
ThrowType(
|
|
96
|
+
isolate,
|
|
97
|
+
"getPointer expects an ArrayBuffer, SharedArrayBuffer, or typed array"
|
|
98
|
+
);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
*data = static_cast<uint8_t*>(*data) + byte_offset;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
uint64_t ReadToken(
|
|
107
|
+
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
108
|
+
int index,
|
|
109
|
+
const char* message
|
|
110
|
+
) {
|
|
111
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
112
|
+
if (args.Length() <= index || !args[index]->IsBigInt()) {
|
|
113
|
+
ThrowType(isolate, message);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
bool lossless = false;
|
|
118
|
+
uint64_t token = args[index].As<v8::BigInt>()->Uint64Value(&lossless);
|
|
119
|
+
if (token == 0) {
|
|
120
|
+
ThrowType(isolate, "retained pointer token must be non-zero");
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
return token;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void GetPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
127
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
128
|
+
|
|
129
|
+
if (args.Length() < 1) {
|
|
130
|
+
ThrowType(isolate, "getPointer(buffer) requires an ArrayBuffer or view");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
void* data = nullptr;
|
|
135
|
+
size_t byte_length = 0;
|
|
136
|
+
if (!ReadPointerInfo(isolate, args[0], &data, &byte_length)) return;
|
|
137
|
+
|
|
138
|
+
uintptr_t address = reinterpret_cast<uintptr_t>(data);
|
|
139
|
+
args.GetReturnValue().Set(
|
|
140
|
+
v8::BigInt::NewFromUnsigned(isolate, static_cast<uint64_t>(address))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
void RetainPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
145
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
146
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
147
|
+
|
|
148
|
+
if (args.Length() < 1) {
|
|
149
|
+
ThrowType(isolate, "retainPointer(buffer) requires an ArrayBuffer or view");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void* data = nullptr;
|
|
154
|
+
size_t byte_length = 0;
|
|
155
|
+
if (!ReadPointerInfo(isolate, args[0], &data, &byte_length)) return;
|
|
156
|
+
|
|
157
|
+
auto retained = std::make_unique<RetainedReference>();
|
|
158
|
+
retained->isolate = isolate;
|
|
159
|
+
retained->value.Reset(isolate, args[0]);
|
|
160
|
+
|
|
161
|
+
uint64_t token = 0;
|
|
162
|
+
{
|
|
163
|
+
std::lock_guard<std::mutex> lock(retained_mutex);
|
|
164
|
+
token = next_retained_id++;
|
|
165
|
+
if (token == 0) token = next_retained_id++;
|
|
166
|
+
retained_refs.emplace(token, std::move(retained));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
v8::Local<v8::Object> result = v8::Object::New(isolate);
|
|
170
|
+
result->Set(
|
|
171
|
+
context,
|
|
172
|
+
v8::String::NewFromUtf8Literal(isolate, "pointer"),
|
|
173
|
+
v8::BigInt::NewFromUnsigned(
|
|
174
|
+
isolate,
|
|
175
|
+
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data))
|
|
176
|
+
)
|
|
177
|
+
).ToChecked();
|
|
178
|
+
result->Set(
|
|
179
|
+
context,
|
|
180
|
+
v8::String::NewFromUtf8Literal(isolate, "byteLength"),
|
|
181
|
+
v8::Number::New(isolate, static_cast<double>(byte_length))
|
|
182
|
+
).ToChecked();
|
|
183
|
+
result->Set(
|
|
184
|
+
context,
|
|
185
|
+
v8::String::NewFromUtf8Literal(isolate, "token"),
|
|
186
|
+
v8::BigInt::NewFromUnsigned(isolate, token)
|
|
187
|
+
).ToChecked();
|
|
188
|
+
args.GetReturnValue().Set(result);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
void ReleasePointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
192
|
+
uint64_t token = ReadToken(args, 0, "releasePointer(token) requires a bigint");
|
|
193
|
+
if (token == 0) return;
|
|
194
|
+
|
|
195
|
+
std::unique_ptr<RetainedReference> retained;
|
|
196
|
+
{
|
|
197
|
+
std::lock_guard<std::mutex> lock(retained_mutex);
|
|
198
|
+
auto found = retained_refs.find(token);
|
|
199
|
+
if (found == retained_refs.end()) {
|
|
200
|
+
args.GetReturnValue().Set(false);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
retained = std::move(found->second);
|
|
204
|
+
retained_refs.erase(found);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
retained->value.Reset();
|
|
208
|
+
args.GetReturnValue().Set(true);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
void NoopDeleter(void*, size_t, void*) {}
|
|
212
|
+
|
|
213
|
+
void WrapPointer(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
214
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
215
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
216
|
+
|
|
217
|
+
if (args.Length() < 2 || !args[0]->IsBigInt() || !args[1]->IsNumber()) {
|
|
218
|
+
ThrowType(
|
|
219
|
+
isolate,
|
|
220
|
+
"wrapPointer(pointer: bigint, byteLength: number) requires both arguments"
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
bool lossless = false;
|
|
226
|
+
uint64_t address = args[0].As<v8::BigInt>()->Uint64Value(&lossless);
|
|
227
|
+
if (address == 0) {
|
|
228
|
+
ThrowType(isolate, "wrapPointer received a null pointer");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
v8::Maybe<int64_t> maybe_length = args[1]->IntegerValue(context);
|
|
233
|
+
if (maybe_length.IsNothing() || maybe_length.FromJust() < 0) {
|
|
234
|
+
ThrowType(isolate, "byteLength must be a non-negative integer");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
size_t byte_length = static_cast<size_t>(maybe_length.FromJust());
|
|
238
|
+
|
|
239
|
+
void* data = reinterpret_cast<void*>(static_cast<uintptr_t>(address));
|
|
240
|
+
std::unique_ptr<v8::BackingStore> backing = v8::ArrayBuffer::NewBackingStore(
|
|
241
|
+
data,
|
|
242
|
+
byte_length,
|
|
243
|
+
NoopDeleter,
|
|
244
|
+
nullptr
|
|
245
|
+
);
|
|
246
|
+
std::shared_ptr<v8::BackingStore> shared(std::move(backing));
|
|
247
|
+
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, shared);
|
|
248
|
+
buffer->SetDetachKey(DetachKey(isolate));
|
|
249
|
+
args.GetReturnValue().Set(buffer);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void DetachArrayBuffer(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
253
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
254
|
+
|
|
255
|
+
if (args.Length() < 1 || !args[0]->IsArrayBuffer()) {
|
|
256
|
+
ThrowType(isolate, "detachArrayBuffer(buffer) requires an ArrayBuffer");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
v8::Local<v8::ArrayBuffer> buffer = args[0].As<v8::ArrayBuffer>();
|
|
261
|
+
if (buffer->WasDetached()) {
|
|
262
|
+
args.GetReturnValue().Set(true);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
args.GetReturnValue().Set(DetachKnittingArrayBuffer(isolate, buffer));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Read the underlying ArrayBuffer + region of an ArrayBuffer or typed-array view.
|
|
269
|
+
// SharedArrayBuffer is rejected: it is already shareable and must not be detached.
|
|
270
|
+
bool ReadArrayBufferRegion(
|
|
271
|
+
v8::Isolate* isolate,
|
|
272
|
+
v8::Local<v8::Value> value,
|
|
273
|
+
v8::Local<v8::ArrayBuffer>* buffer,
|
|
274
|
+
size_t* byte_offset,
|
|
275
|
+
size_t* byte_length
|
|
276
|
+
) {
|
|
277
|
+
if (value->IsArrayBufferView()) {
|
|
278
|
+
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
|
|
279
|
+
*buffer = view->Buffer();
|
|
280
|
+
*byte_offset = view->ByteOffset();
|
|
281
|
+
*byte_length = view->ByteLength();
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (value->IsArrayBuffer()) {
|
|
285
|
+
v8::Local<v8::ArrayBuffer> ab = value.As<v8::ArrayBuffer>();
|
|
286
|
+
*buffer = ab;
|
|
287
|
+
*byte_offset = 0;
|
|
288
|
+
*byte_length = ab->ByteLength();
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
ThrowType(
|
|
292
|
+
isolate,
|
|
293
|
+
"retainBackingStore expects an ArrayBuffer or typed array (not a SharedArrayBuffer)"
|
|
294
|
+
);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// retainBackingStore(buffer) -> { pointer, byteOffset, byteLength, token }
|
|
299
|
+
// Registry-pins the backing store, then detaches the source for the move.
|
|
300
|
+
void RetainBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
301
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
302
|
+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
303
|
+
|
|
304
|
+
if (args.Length() < 1) {
|
|
305
|
+
ThrowType(isolate, "retainBackingStore(buffer) requires an ArrayBuffer or view");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
v8::Local<v8::ArrayBuffer> buffer;
|
|
310
|
+
size_t byte_offset = 0;
|
|
311
|
+
size_t byte_length = 0;
|
|
312
|
+
if (!ReadArrayBufferRegion(isolate, args[0], &buffer, &byte_offset, &byte_length)) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
std::shared_ptr<v8::BackingStore> store = buffer->GetBackingStore();
|
|
317
|
+
uint8_t* base = static_cast<uint8_t*>(store->Data());
|
|
318
|
+
uintptr_t address = reinterpret_cast<uintptr_t>(base + byte_offset);
|
|
319
|
+
|
|
320
|
+
// Detach only after taking the shared_ptr; ordinary JS buffers use no key.
|
|
321
|
+
DetachDefaultArrayBuffer(buffer);
|
|
322
|
+
|
|
323
|
+
uint64_t token = 0;
|
|
324
|
+
{
|
|
325
|
+
std::lock_guard<std::mutex> lock(backing_mutex);
|
|
326
|
+
token = next_backing_id++;
|
|
327
|
+
if (token == 0) token = next_backing_id++;
|
|
328
|
+
retained_backings.emplace(
|
|
329
|
+
token,
|
|
330
|
+
RetainedBackingStore{ std::move(store), byte_offset, byte_length }
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
v8::Local<v8::Object> result = v8::Object::New(isolate);
|
|
335
|
+
result->Set(
|
|
336
|
+
context,
|
|
337
|
+
v8::String::NewFromUtf8Literal(isolate, "pointer"),
|
|
338
|
+
v8::BigInt::NewFromUnsigned(isolate, static_cast<uint64_t>(address))
|
|
339
|
+
).ToChecked();
|
|
340
|
+
result->Set(
|
|
341
|
+
context,
|
|
342
|
+
v8::String::NewFromUtf8Literal(isolate, "byteOffset"),
|
|
343
|
+
v8::Number::New(isolate, static_cast<double>(byte_offset))
|
|
344
|
+
).ToChecked();
|
|
345
|
+
result->Set(
|
|
346
|
+
context,
|
|
347
|
+
v8::String::NewFromUtf8Literal(isolate, "byteLength"),
|
|
348
|
+
v8::Number::New(isolate, static_cast<double>(byte_length))
|
|
349
|
+
).ToChecked();
|
|
350
|
+
result->Set(
|
|
351
|
+
context,
|
|
352
|
+
v8::String::NewFromUtf8Literal(isolate, "token"),
|
|
353
|
+
v8::BigInt::NewFromUnsigned(isolate, token)
|
|
354
|
+
).ToChecked();
|
|
355
|
+
args.GetReturnValue().Set(result);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// adoptBackingStore(token) -> ArrayBuffer
|
|
359
|
+
// Returns an ArrayBuffer in the caller's isolate that co-owns the registry store.
|
|
360
|
+
// The registry entry remains until producer release.
|
|
361
|
+
void AdoptBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
362
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
363
|
+
uint64_t token = ReadToken(args, 0, "adoptBackingStore(token) requires a bigint");
|
|
364
|
+
if (token == 0) return;
|
|
365
|
+
|
|
366
|
+
std::shared_ptr<v8::BackingStore> store;
|
|
367
|
+
{
|
|
368
|
+
std::lock_guard<std::mutex> lock(backing_mutex);
|
|
369
|
+
auto found = retained_backings.find(token);
|
|
370
|
+
if (found == retained_backings.end()) {
|
|
371
|
+
ThrowType(isolate, "adoptBackingStore: unknown or released token");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
store = found->second.store;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(isolate, store);
|
|
378
|
+
buffer->SetDetachKey(DetachKey(isolate));
|
|
379
|
+
args.GetReturnValue().Set(buffer);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// releaseBackingStore(token) -> boolean
|
|
383
|
+
// Drops the registry's reference. The store is freed only if no consumer adopted.
|
|
384
|
+
void ReleaseBackingStore(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
385
|
+
uint64_t token =
|
|
386
|
+
ReadToken(args, 0, "releaseBackingStore(token) requires a bigint");
|
|
387
|
+
if (token == 0) return;
|
|
388
|
+
|
|
389
|
+
RetainedBackingStore released;
|
|
390
|
+
bool found = false;
|
|
391
|
+
{
|
|
392
|
+
std::lock_guard<std::mutex> lock(backing_mutex);
|
|
393
|
+
auto it = retained_backings.find(token);
|
|
394
|
+
if (it != retained_backings.end()) {
|
|
395
|
+
released = std::move(it->second);
|
|
396
|
+
retained_backings.erase(it);
|
|
397
|
+
found = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// released.store drops here, outside the lock.
|
|
401
|
+
args.GetReturnValue().Set(found);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// SAB transport avoids SharedArrayBuffer::New across isolates: worker teardown
|
|
405
|
+
// can corrupt the shared store. JS pins plus non-owning aliases keep native
|
|
406
|
+
// handles from outliving their isolate.
|
|
407
|
+
|
|
408
|
+
void Initialize(
|
|
409
|
+
v8::Local<v8::Object> exports,
|
|
410
|
+
v8::Local<v8::Value>,
|
|
411
|
+
v8::Local<v8::Context>
|
|
412
|
+
) {
|
|
413
|
+
NODE_SET_METHOD(exports, "getPointer", GetPointer);
|
|
414
|
+
NODE_SET_METHOD(exports, "retainPointer", RetainPointer);
|
|
415
|
+
NODE_SET_METHOD(exports, "releasePointer", ReleasePointer);
|
|
416
|
+
NODE_SET_METHOD(exports, "wrapPointer", WrapPointer);
|
|
417
|
+
NODE_SET_METHOD(exports, "detachArrayBuffer", DetachArrayBuffer);
|
|
418
|
+
NODE_SET_METHOD(exports, "retainBackingStore", RetainBackingStore);
|
|
419
|
+
NODE_SET_METHOD(exports, "adoptBackingStore", AdoptBackingStore);
|
|
420
|
+
NODE_SET_METHOD(exports, "releaseBackingStore", ReleaseBackingStore);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
|
|
424
|
+
|
|
425
|
+
}
|
package/src/memory/lock.d.ts
CHANGED
|
@@ -49,7 +49,13 @@ export declare enum PayloadBuffer {
|
|
|
49
49
|
EnvelopeDynamicHeaderString = 43,
|
|
50
50
|
ExternalPayload = 44,
|
|
51
51
|
StaticExternalPayload = 45,
|
|
52
|
-
ProcessSharedBuffer = 46
|
|
52
|
+
ProcessSharedBuffer = 46,
|
|
53
|
+
BufferReference = 47,
|
|
54
|
+
SharedArrayBuffer = 48,
|
|
55
|
+
EnvelopeStaticHeaderExternal = 49,
|
|
56
|
+
EnvelopeDynamicHeaderExternal = 50,
|
|
57
|
+
EnvelopeStaticHeaderStringExternal = 51,
|
|
58
|
+
EnvelopeDynamicHeaderStringExternal = 52
|
|
53
59
|
}
|
|
54
60
|
export declare enum LockBound {
|
|
55
61
|
paddingLock = 0,
|
|
@@ -70,15 +76,20 @@ export type Task = [
|
|
|
70
76
|
number
|
|
71
77
|
] & {
|
|
72
78
|
value: unknown;
|
|
79
|
+
finalize?: (() => void) | undefined;
|
|
73
80
|
resolve: (value?: unknown) => void;
|
|
74
81
|
reject: (reason?: unknown) => void;
|
|
75
82
|
};
|
|
83
|
+
export declare const PayloadTransportFinalizer: unique symbol;
|
|
76
84
|
export declare const PromisePayloadMarker: unique symbol;
|
|
77
85
|
export type PromisePayloadHandler = (task: Task, isRejected: boolean, value: unknown) => void;
|
|
78
86
|
export declare const beginPromisePayload: (task: Task) => boolean;
|
|
79
87
|
export declare const finishPromisePayload: (task: Task) => void;
|
|
80
88
|
export declare const isPromisePayloadPending: (task: Task) => boolean;
|
|
81
89
|
export declare const resetTaskLocalFlags: (task: Task) => void;
|
|
90
|
+
export declare const addTaskFinalizer: (task: Task, finalizer: () => void) => void;
|
|
91
|
+
export declare const attachPayloadTransportFinalizer: (task: Task, value: unknown) => void;
|
|
92
|
+
export declare const runTaskFinalizers: (task: Task) => void;
|
|
82
93
|
export declare enum TaskIndex {
|
|
83
94
|
/**
|
|
84
95
|
* Worker -> host response flags word.
|
package/src/memory/lock.js
CHANGED
|
@@ -54,6 +54,12 @@ export var PayloadBuffer;
|
|
|
54
54
|
PayloadBuffer[PayloadBuffer["ExternalPayload"] = 44] = "ExternalPayload";
|
|
55
55
|
PayloadBuffer[PayloadBuffer["StaticExternalPayload"] = 45] = "StaticExternalPayload";
|
|
56
56
|
PayloadBuffer[PayloadBuffer["ProcessSharedBuffer"] = 46] = "ProcessSharedBuffer";
|
|
57
|
+
PayloadBuffer[PayloadBuffer["BufferReference"] = 47] = "BufferReference";
|
|
58
|
+
PayloadBuffer[PayloadBuffer["SharedArrayBuffer"] = 48] = "SharedArrayBuffer";
|
|
59
|
+
PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderExternal"] = 49] = "EnvelopeStaticHeaderExternal";
|
|
60
|
+
PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderExternal"] = 50] = "EnvelopeDynamicHeaderExternal";
|
|
61
|
+
PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderStringExternal"] = 51] = "EnvelopeStaticHeaderStringExternal";
|
|
62
|
+
PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderStringExternal"] = 52] = "EnvelopeDynamicHeaderStringExternal";
|
|
57
63
|
})(PayloadBuffer || (PayloadBuffer = {}));
|
|
58
64
|
export var LockBound;
|
|
59
65
|
(function (LockBound) {
|
|
@@ -64,6 +70,7 @@ export var LockBound;
|
|
|
64
70
|
})(LockBound || (LockBound = {}));
|
|
65
71
|
export const LOCK_CACHE_LINE_BYTES = 64;
|
|
66
72
|
export const LOCK_SECTOR_BYTES = 256;
|
|
73
|
+
export const PayloadTransportFinalizer = Symbol.for("knitting.payloadCodec.transportFinalizer");
|
|
67
74
|
export const PromisePayloadMarker = Symbol.for("knitting.promise.payload");
|
|
68
75
|
const TASK_LOCAL_FLAGS_INDEX = 7;
|
|
69
76
|
const TASK_LOCAL_PROMISE_PENDING_FLAG = 1 << 0;
|
|
@@ -72,7 +79,8 @@ export const beginPromisePayload = (task) => {
|
|
|
72
79
|
const flags = task[TASK_LOCAL_FLAGS_INDEX];
|
|
73
80
|
if ((flags & TASK_LOCAL_PROMISE_PENDING_FLAG) !== 0)
|
|
74
81
|
return false;
|
|
75
|
-
task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_PENDING_FLAG) >>>
|
|
82
|
+
task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_PENDING_FLAG) >>>
|
|
83
|
+
0;
|
|
76
84
|
return true;
|
|
77
85
|
};
|
|
78
86
|
export const finishPromisePayload = (task) => {
|
|
@@ -83,6 +91,36 @@ export const isPromisePayloadPending = (task) => (task[TASK_LOCAL_FLAGS_INDEX] &
|
|
|
83
91
|
export const resetTaskLocalFlags = (task) => {
|
|
84
92
|
task[TASK_LOCAL_FLAGS_INDEX] = 0;
|
|
85
93
|
};
|
|
94
|
+
export const addTaskFinalizer = (task, finalizer) => {
|
|
95
|
+
const previous = task.finalize;
|
|
96
|
+
task.finalize = previous === undefined ? finalizer : () => {
|
|
97
|
+
try {
|
|
98
|
+
previous();
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
finalizer();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
export const attachPayloadTransportFinalizer = (task, value) => {
|
|
106
|
+
if (task.finalize !== undefined || value === null || typeof value !== "object") {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const finalizer = value[PayloadTransportFinalizer]?.();
|
|
110
|
+
if (typeof finalizer === "function")
|
|
111
|
+
addTaskFinalizer(task, finalizer);
|
|
112
|
+
};
|
|
113
|
+
export const runTaskFinalizers = (task) => {
|
|
114
|
+
const finalizer = task.finalize;
|
|
115
|
+
task.finalize = undefined;
|
|
116
|
+
if (finalizer !== undefined) {
|
|
117
|
+
try {
|
|
118
|
+
finalizer();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
86
124
|
export var TaskIndex;
|
|
87
125
|
(function (TaskIndex) {
|
|
88
126
|
/**
|
|
@@ -186,6 +224,7 @@ const def = (_) => { };
|
|
|
186
224
|
const createTaskShell = () => {
|
|
187
225
|
const task = new Uint32Array(TaskIndex.Size);
|
|
188
226
|
task.value = null;
|
|
227
|
+
task.finalize = undefined;
|
|
189
228
|
task.resolve = def;
|
|
190
229
|
task.reject = def;
|
|
191
230
|
task[TASK_LOCAL_FLAGS_INDEX] = 0;
|
|
@@ -266,10 +305,12 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
266
305
|
lockSector: payloadLockRegion,
|
|
267
306
|
textCompat: resolvedTextCompat,
|
|
268
307
|
onPromise: (task, isRejected, value) => {
|
|
269
|
-
if ((task[TASK_LOCAL_FLAGS_INDEX] & TASK_LOCAL_PROMISE_TRACKED_FLAG) !==
|
|
308
|
+
if ((task[TASK_LOCAL_FLAGS_INDEX] & TASK_LOCAL_PROMISE_TRACKED_FLAG) !==
|
|
309
|
+
0 &&
|
|
270
310
|
pendingPromiseCount > 0) {
|
|
271
311
|
task[TASK_LOCAL_FLAGS_INDEX] =
|
|
272
|
-
(task[TASK_LOCAL_FLAGS_INDEX] & ~TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
|
|
312
|
+
(task[TASK_LOCAL_FLAGS_INDEX] & ~TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
|
|
313
|
+
0;
|
|
273
314
|
pendingPromiseCount = (pendingPromiseCount - 1) | 0;
|
|
274
315
|
}
|
|
275
316
|
promiseHandler(task, isRejected, value);
|
|
@@ -322,7 +363,8 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
322
363
|
const flags = task[TASK_LOCAL_FLAGS_INDEX];
|
|
323
364
|
if ((flags & TASK_LOCAL_PROMISE_TRACKED_FLAG) !== 0)
|
|
324
365
|
return;
|
|
325
|
-
task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
|
|
366
|
+
task[TASK_LOCAL_FLAGS_INDEX] = (flags | TASK_LOCAL_PROMISE_TRACKED_FLAG) >>>
|
|
367
|
+
0;
|
|
326
368
|
pendingPromiseCount = (pendingPromiseCount + 1) | 0;
|
|
327
369
|
};
|
|
328
370
|
const encodeTaskValue = (task, slotIndex) => encodeTask(task, slotIndex);
|
|
@@ -633,6 +675,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
633
675
|
if (recycled) {
|
|
634
676
|
fillTaskFrom(recycled, headersBuffer, off);
|
|
635
677
|
recycled.value = null;
|
|
678
|
+
recycled.finalize = undefined;
|
|
636
679
|
recycled.resolve = def;
|
|
637
680
|
recycled.reject = def;
|
|
638
681
|
task = recycled;
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { type SharedBufferSource } from "../common/shared-buffer-region.js";
|
|
2
2
|
export declare const PAYLOAD_DEFAULT_MAX_BYTE_LENGTH: number;
|
|
3
3
|
export declare const PAYLOAD_DEFAULT_INITIAL_BYTES: number;
|
|
4
|
+
/** Payload backing mode. Growable uses SAB growth when the runtime supports it. */
|
|
4
5
|
export type PayloadBufferMode = "growable" | "fixed";
|
|
5
6
|
export type PayloadBufferOptions = {
|
|
7
|
+
/** Shared payload buffer mode. Defaults to growable when available. */
|
|
6
8
|
mode?: PayloadBufferMode;
|
|
9
|
+
/** Initial shared payload bytes per direction. Defaults to 4 MiB when growable. */
|
|
7
10
|
payloadInitialBytes?: number;
|
|
11
|
+
/** Shared payload growth cap. Defaults to 64 MiB. */
|
|
8
12
|
payloadMaxByteLength?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Max dynamic payload bytes for one call. Defaults to `payloadMaxByteLength >> 3`
|
|
15
|
+
* (about 8 MiB by default) and must stay `<= payloadMaxByteLength >> 3`.
|
|
16
|
+
* Over-cap payloads reject with `KNT_ERROR_3`.
|
|
17
|
+
*/
|
|
9
18
|
maxPayloadBytes?: number;
|
|
10
19
|
};
|
|
11
20
|
export type ResolvedPayloadBufferOptions = {
|