xbintsc 0.5.20 → 0.5.21
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/AGENTS.md +5 -3
- package/dist/src/driver/compiler.js +1 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/node/fs/index.d.ts +4 -3
- package/dist/src/extensions/node/fs/index.js +12 -3
- package/dist/src/extensions/node/fs/index.js.map +1 -1
- package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
- package/dist/src/extensions/node/fs-promises/index.js +15 -6
- package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +1 -1
- package/dist/tests/e2e/async-fs.test.d.ts +9 -0
- package/dist/tests/e2e/async-fs.test.js +119 -0
- package/dist/tests/e2e/async-fs.test.js.map +1 -0
- package/dist/tests/e2e/fetch.test.js +24 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
- package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
- package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
- package/doc/ai/contributing.md +2 -1
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/language-support.md +2 -1
- package/doc/ai/zh-CN/contributing.md +1 -1
- package/doc/ai/zh-CN/extensions.md +4 -3
- package/doc/ai/zh-CN/language-support.md +2 -1
- package/doc/gui/api.md +8 -0
- package/doc/gui/known-issues.md +10 -9
- package/doc/gui/media.md +32 -3
- package/doc/gui.md +24 -2
- package/doc/implemented/async.md +16 -3
- package/doc/implemented/fetch.md +38 -6
- package/doc/implemented/node/fs-promises.md +19 -8
- package/doc/implemented/node.md +1 -1
- package/doc/implemented.md +1 -1
- package/doc/unimplemented/deviations.md +1 -1
- package/doc/unimplemented/node/async.md +6 -3
- package/doc/unimplemented/node/fs-promises.md +10 -6
- package/doc/unimplemented/node/fs.md +7 -8
- package/doc/unimplemented/node.md +2 -2
- package/doc/unimplemented/toolchain.md +1 -1
- package/doc/zh-CN/gui/api.md +7 -0
- package/doc/zh-CN/gui/known-issues.md +8 -7
- package/doc/zh-CN/gui/media.md +26 -2
- package/doc/zh-CN/gui.md +18 -1
- package/doc/zh-CN/implemented/async.md +13 -2
- package/doc/zh-CN/implemented/fetch.md +23 -4
- package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
- package/doc/zh-CN/implemented/node.md +1 -1
- package/doc/zh-CN/implemented.md +1 -1
- package/doc/zh-CN/unimplemented/deviations.md +1 -1
- package/doc/zh-CN/unimplemented/node/async.md +6 -3
- package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
- package/doc/zh-CN/unimplemented/node/fs.md +2 -2
- package/doc/zh-CN/unimplemented/node.md +2 -2
- package/doc/zh-CN/unimplemented/toolchain.md +1 -1
- package/package.json +1 -1
- package/runtime/ext_gui/inline_svg.cpp +219 -0
- package/runtime/ext_gui/inline_svg.h +41 -0
- package/runtime/ext_gui/layout.cpp +18 -0
- package/runtime/ext_gui/layout.h +7 -0
- package/runtime/ext_gui/layout_flow.cpp +4 -3
- package/runtime/ext_gui/paint.cpp +14 -0
- package/runtime/ext_gui/paint.h +5 -1
- package/runtime/ext_gui/renderer_image.cpp +53 -25
- package/runtime/ext_gui/style_ua.cpp +3 -0
- package/runtime/ext_gui/svg.cpp +122 -39
- package/runtime/ext_gui/svg.h +14 -3
- package/runtime/ext_gui/window.cpp +28 -0
- package/runtime/ext_node/fs/async_api.c +314 -0
- package/runtime/ext_node/fs/async_complete.c +99 -0
- package/runtime/ext_node/fs/async_fh.c +206 -0
- package/runtime/ext_node/fs/async_job.c +81 -0
- package/runtime/ext_node/fs/async_job.h +132 -0
- package/runtime/ext_node/fs/async_worker.c +534 -0
- package/runtime/ext_node/fs/async_worker_dir.c +439 -0
- package/runtime/ext_node/fs/async_worker_dir.h +33 -0
- package/runtime/ext_node/fs/async_worker_fd.c +74 -0
- package/runtime/ext_node/fs/dir.c +60 -15
- package/runtime/ext_node/fs/fs_common.h +9 -0
- package/runtime/ext_node/fs/fs_ops.c +18 -29
- package/runtime/ext_node/fs/parts/builtins.inc +34 -0
- package/runtime/ext_node/fs/parts/platform.inc +66 -0
- package/runtime/ext_node/fs/promises.c +44 -234
- package/runtime/rt_internal.h +2 -0
- package/runtime/xt_async_io.c +333 -0
- package/runtime/xt_async_io.h +47 -0
- package/runtime/xt_fetch/async.inc +295 -0
- package/runtime/xt_fetch/request.inc +6 -183
- package/runtime/xt_fetch/socket.inc +5 -4
- package/runtime/xt_fetch.c +6 -4
- package/runtime/xt_loop.c +44 -3
- package/src/driver/compiler.ts +1 -1
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/node/fs/index.ts +12 -3
- package/src/extensions/node/fs-promises/index.ts +15 -6
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* -- request / response exchange ------------------------------------------ */
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Request serialisation, response framing and header parsing for `fetch`. The
|
|
5
|
+
* blocking exchange itself runs on an async I/O worker (see `async.inc`); these
|
|
6
|
+
* helpers are shared by the main thread (building the request, parsing the
|
|
7
|
+
* completed response) and the worker (deciding when the response is complete).
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/** Index just past the blank line that terminates the response headers. */
|
|
@@ -204,183 +205,5 @@ static void xt_fetch_build_request(const char *method, const xt_fetch_url *url,
|
|
|
204
205
|
if (body && body->length > 0) xt_fetch_buffer_append(out, body->data, body->length);
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return xt_promise_reject(xt_type_error_ctor(1, &argument));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
xt_value xt_fetch(int32_t argc, xt_value *argv) {
|
|
213
|
-
xt_value input = xt_arg_at(argc, argv, 0);
|
|
214
|
-
xt_value init = xt_arg_at(argc, argv, 1);
|
|
215
|
-
if (input == XT_UNDEFINED || input == XT_NULL) return xt_fetch_reject("Failed to parse URL from undefined");
|
|
216
|
-
|
|
217
|
-
xt_value urlValue = input;
|
|
218
|
-
if (XT_IS_OBJECT(input)) {
|
|
219
|
-
xt_value candidate = xt_object_get_cstr(input, "url");
|
|
220
|
-
if (XT_IS_STRING(candidate)) urlValue = candidate;
|
|
221
|
-
}
|
|
222
|
-
const char *urlText = xt_string_data(xt_to_string(urlValue));
|
|
223
|
-
if (!urlText) return xt_fetch_reject("Failed to parse URL");
|
|
224
|
-
|
|
225
|
-
char currentUrl[4096];
|
|
226
|
-
snprintf(currentUrl, sizeof(currentUrl), "%s", urlText);
|
|
227
|
-
|
|
228
|
-
char method[16];
|
|
229
|
-
snprintf(method, sizeof(method), "GET");
|
|
230
|
-
xt_value headersSource = XT_UNDEFINED;
|
|
231
|
-
xt_value bodySource = XT_UNDEFINED;
|
|
232
|
-
const char *redirectMode = "follow";
|
|
233
|
-
if (XT_IS_OBJECT(init)) {
|
|
234
|
-
xt_value methodValue = xt_object_get_cstr(init, "method");
|
|
235
|
-
if (XT_IS_STRING(methodValue)) {
|
|
236
|
-
const char *text = xt_string_data(methodValue);
|
|
237
|
-
size_t i = 0;
|
|
238
|
-
for (; text && text[i] && i + 1 < sizeof(method); i++) {
|
|
239
|
-
char c = text[i];
|
|
240
|
-
method[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
|
|
241
|
-
}
|
|
242
|
-
method[i] = '\0';
|
|
243
|
-
}
|
|
244
|
-
headersSource = xt_object_get_cstr(init, "headers");
|
|
245
|
-
bodySource = xt_object_get_cstr(init, "body");
|
|
246
|
-
xt_value redirectValue = xt_object_get_cstr(init, "redirect");
|
|
247
|
-
if (XT_IS_STRING(redirectValue)) redirectMode = xt_string_data(redirectValue);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
xt_fetch_buffer body;
|
|
251
|
-
xt_fetch_buffer_init(&body);
|
|
252
|
-
if (bodySource != XT_UNDEFINED && bodySource != XT_NULL) {
|
|
253
|
-
xt_value text = xt_to_string(bodySource);
|
|
254
|
-
const char *data = xt_string_data(text);
|
|
255
|
-
if (data) xt_fetch_buffer_append(&body, data, (size_t)xt_string_length_value(text));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
for (int redirects = 0; redirects <= 20; redirects++) {
|
|
259
|
-
xt_fetch_url parsed;
|
|
260
|
-
if (xt_fetch_parse_url(currentUrl, &parsed) != 0) {
|
|
261
|
-
xt_fetch_buffer_free(&body);
|
|
262
|
-
return xt_fetch_reject("Failed to parse URL");
|
|
263
|
-
}
|
|
264
|
-
if (parsed.secure) {
|
|
265
|
-
xt_fetch_buffer_free(&body);
|
|
266
|
-
return xt_fetch_reject("fetch failed: https is not supported");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
int fd = xt_fetch_connect(&parsed);
|
|
270
|
-
if (fd < 0) {
|
|
271
|
-
xt_fetch_buffer_free(&body);
|
|
272
|
-
return xt_fetch_reject("fetch failed");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
xt_value requestHeaders = xt_headers_from_source(headersSource);
|
|
276
|
-
xt_fetch_buffer request;
|
|
277
|
-
xt_fetch_buffer_init(&request);
|
|
278
|
-
xt_fetch_build_request(method, &parsed, requestHeaders, body.length > 0 ? &body : NULL, &request);
|
|
279
|
-
if (xt_fetch_send_all(fd, request.data, request.length) != 0) {
|
|
280
|
-
xt_fetch_close_socket(fd);
|
|
281
|
-
xt_fetch_buffer_free(&request);
|
|
282
|
-
xt_fetch_buffer_free(&body);
|
|
283
|
-
return xt_fetch_reject("fetch failed");
|
|
284
|
-
}
|
|
285
|
-
xt_fetch_buffer_free(&request);
|
|
286
|
-
|
|
287
|
-
xt_fetch_buffer raw;
|
|
288
|
-
xt_fetch_buffer_init(&raw);
|
|
289
|
-
char chunk[16384];
|
|
290
|
-
for (;;) {
|
|
291
|
-
int headEnd = xt_fetch_head_end(&raw);
|
|
292
|
-
if (headEnd > 0) {
|
|
293
|
-
int status = 0;
|
|
294
|
-
char statusText[256];
|
|
295
|
-
size_t headerStart = 0;
|
|
296
|
-
if (xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) == 0) {
|
|
297
|
-
int stop = 0;
|
|
298
|
-
if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
|
|
299
|
-
stop = 1;
|
|
300
|
-
} else {
|
|
301
|
-
size_t contentLength = 0;
|
|
302
|
-
int hasContentLength = 0;
|
|
303
|
-
int chunked = 0;
|
|
304
|
-
xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
|
|
305
|
-
if (chunked) {
|
|
306
|
-
size_t decoded = 0;
|
|
307
|
-
if (xt_fetch_chunked_complete(raw.data, raw.length, (size_t)headEnd, &decoded) == 1) stop = 1;
|
|
308
|
-
} else if (hasContentLength && raw.length - (size_t)headEnd >= contentLength) {
|
|
309
|
-
stop = 1;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (stop) break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
int count = xt_fetch_recv(fd, chunk, sizeof(chunk));
|
|
316
|
-
if (count > 0) {
|
|
317
|
-
xt_fetch_buffer_append(&raw, chunk, (size_t)count);
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
xt_fetch_close_socket(fd);
|
|
323
|
-
|
|
324
|
-
int headEnd = xt_fetch_head_end(&raw);
|
|
325
|
-
int status = 0;
|
|
326
|
-
char statusText[256];
|
|
327
|
-
size_t headerStart = 0;
|
|
328
|
-
if (headEnd <= 0 ||
|
|
329
|
-
xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) != 0) {
|
|
330
|
-
xt_fetch_buffer_free(&raw);
|
|
331
|
-
xt_fetch_buffer_free(&body);
|
|
332
|
-
return xt_fetch_reject("fetch failed: invalid response");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
xt_value responseHeaders = xt_headers_new();
|
|
336
|
-
xt_headers_parse_block(responseHeaders, raw.data + headerStart, (size_t)headEnd - 2 - headerStart);
|
|
337
|
-
|
|
338
|
-
int isRedirect = status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
|
|
339
|
-
if (isRedirect && strcmp(redirectMode, "error") == 0) {
|
|
340
|
-
xt_fetch_buffer_free(&raw);
|
|
341
|
-
xt_fetch_buffer_free(&body);
|
|
342
|
-
return xt_fetch_reject("fetch failed: redirect not allowed");
|
|
343
|
-
}
|
|
344
|
-
if (isRedirect && strcmp(redirectMode, "manual") != 0 && redirects < 20) {
|
|
345
|
-
const char *location = xt_headers_lookup(responseHeaders, "location");
|
|
346
|
-
char resolved[4096];
|
|
347
|
-
if (location && xt_fetch_resolve_location(&parsed, location, resolved, sizeof(resolved)) == 0) {
|
|
348
|
-
if (status == 303 || ((status == 301 || status == 302) && strcmp(method, "POST") == 0)) {
|
|
349
|
-
snprintf(method, sizeof(method), "GET");
|
|
350
|
-
body.length = 0;
|
|
351
|
-
}
|
|
352
|
-
snprintf(currentUrl, sizeof(currentUrl), "%s", resolved);
|
|
353
|
-
xt_fetch_buffer_free(&raw);
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
size_t contentLength = 0;
|
|
359
|
-
int hasContentLength = 0;
|
|
360
|
-
int chunked = 0;
|
|
361
|
-
xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
|
|
362
|
-
xt_fetch_buffer responseBody;
|
|
363
|
-
xt_fetch_buffer_init(&responseBody);
|
|
364
|
-
if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
|
|
365
|
-
/* No body by definition. */
|
|
366
|
-
} else if (chunked) {
|
|
367
|
-
xt_fetch_decode_chunked(raw.data, raw.length, (size_t)headEnd, &responseBody);
|
|
368
|
-
} else if (hasContentLength) {
|
|
369
|
-
size_t available = raw.length - (size_t)headEnd;
|
|
370
|
-
size_t take = contentLength < available ? contentLength : available;
|
|
371
|
-
xt_fetch_buffer_append(&responseBody, raw.data + headEnd, take);
|
|
372
|
-
} else {
|
|
373
|
-
xt_fetch_buffer_append(&responseBody, raw.data + headEnd, raw.length - (size_t)headEnd);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
xt_value response = xt_response_new(responseHeaders, status, statusText, currentUrl,
|
|
377
|
-
responseBody.data ? responseBody.data : "", responseBody.length);
|
|
378
|
-
xt_fetch_buffer_free(&responseBody);
|
|
379
|
-
xt_fetch_buffer_free(&raw);
|
|
380
|
-
xt_fetch_buffer_free(&body);
|
|
381
|
-
return xt_promise_resolve(response);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
xt_fetch_buffer_free(&body);
|
|
385
|
-
return xt_fetch_reject("fetch failed: too many redirects");
|
|
386
|
-
}
|
|
208
|
+
/* The async exchange itself lives in `async.inc`; it reuses the parsers and
|
|
209
|
+
* request builder above. */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* -- TCP socket helpers --------------------------------------------------- */
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
* `fetch` performs a conventional blocking request/response exchange
|
|
5
|
-
* write the request, read until the response is
|
|
6
|
-
* The socket carries a receive timeout so a
|
|
7
|
-
*
|
|
4
|
+
* `fetch` performs a conventional blocking request/response exchange on an
|
|
5
|
+
* async I/O worker: connect, write the request, read until the response is
|
|
6
|
+
* complete (or the peer closes). The socket carries a receive timeout so a
|
|
7
|
+
* stalled server cannot pin a worker forever; the timeout surfaces as a
|
|
8
|
+
* rejected promise.
|
|
8
9
|
*/
|
|
9
10
|
#define XT_FETCH_TIMEOUT_SECONDS 30
|
|
10
11
|
|
package/runtime/xt_fetch.c
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* xbintsc runtime — the global `fetch` API.
|
|
3
3
|
*
|
|
4
4
|
* This is a small, dependency-free HTTP/1.1 client exposed as the platform
|
|
5
|
-
* `fetch` global. The
|
|
6
|
-
*
|
|
7
|
-
* and
|
|
8
|
-
* `http://` URLs are accepted;
|
|
5
|
+
* `fetch` global. The blocking socket exchange runs on the async I/O pool
|
|
6
|
+
* (`xt_async_io.c`), so a request in flight does not stall the reactor: timers,
|
|
7
|
+
* sockets and other `await`s keep running until the response is dispatched on
|
|
8
|
+
* the main thread. TLS is not implemented, so only `http://` URLs are accepted;
|
|
9
|
+
* `https://` rejects with a `TypeError`.
|
|
9
10
|
*
|
|
10
11
|
* The implementation is grouped by concern under `xt_fetch/` and `#include`d
|
|
11
12
|
* here as a single translation unit, following the other runtime modules.
|
|
@@ -37,3 +38,4 @@ typedef socklen_t xt_fetch_socklen_t;
|
|
|
37
38
|
#include "xt_fetch/headers.inc"
|
|
38
39
|
#include "xt_fetch/response.inc"
|
|
39
40
|
#include "xt_fetch/request.inc"
|
|
41
|
+
#include "xt_fetch/async.inc"
|
package/runtime/xt_loop.c
CHANGED
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
|
|
34
34
|
#define XT_LOOP_MAX_WATCHERS 1024
|
|
35
35
|
|
|
36
|
+
/* Fallback wait bound while offloaded I/O is outstanding; the completion
|
|
37
|
+
* channel normally wakes `select` immediately. */
|
|
38
|
+
#define XT_ASYNC_POLL_MS 10
|
|
39
|
+
|
|
36
40
|
typedef struct {
|
|
37
41
|
int fd;
|
|
38
42
|
int events;
|
|
@@ -294,6 +298,11 @@ xt_value xt_clear_immediate(int32_t argc, xt_value *argv) {
|
|
|
294
298
|
return xt_clear_timeout(argc, argv);
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
/* Reset to 1 while the reactor is dispatching handlers. Reusing a removed slot
|
|
302
|
+
* mid-dispatch would make the new watcher observe the current `fd_set`, so a
|
|
303
|
+
* registration from inside a callback appends instead. */
|
|
304
|
+
static int xt_loop_dispatching = 0;
|
|
305
|
+
|
|
297
306
|
int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
|
|
298
307
|
if (fd < 0 || !handler) return -1;
|
|
299
308
|
xt_loop_gc_register();
|
|
@@ -304,6 +313,21 @@ int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
|
|
|
304
313
|
existing->userdata = userdata;
|
|
305
314
|
return 0;
|
|
306
315
|
}
|
|
316
|
+
/* Reuse a slot left by a removed watcher (the async pool toggles its wakeup
|
|
317
|
+
* descriptor on and off, and sockets are opened and closed repeatedly). */
|
|
318
|
+
if (!xt_loop_dispatching) {
|
|
319
|
+
for (int i = 0; i < xt_loop_watcher_count; i++) {
|
|
320
|
+
xt_loop_watcher *slot = &xt_loop_watchers[i];
|
|
321
|
+
if (slot->active) continue;
|
|
322
|
+
slot->fd = fd;
|
|
323
|
+
slot->events = events;
|
|
324
|
+
slot->handler = handler;
|
|
325
|
+
slot->userdata = userdata;
|
|
326
|
+
slot->active = 1;
|
|
327
|
+
xt_loop_active_count++;
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
307
331
|
if (xt_loop_watcher_count >= XT_LOOP_MAX_WATCHERS) return -1;
|
|
308
332
|
xt_loop_watcher *watcher = &xt_loop_watchers[xt_loop_watcher_count++];
|
|
309
333
|
watcher->fd = fd;
|
|
@@ -337,7 +361,8 @@ void xt_loop_set_main(xt_main_loop_fn fn) { xt_loop_main = fn; }
|
|
|
337
361
|
int xt_loop_poll(int timeout_ms) {
|
|
338
362
|
int has_fds = xt_loop_active_count > 0;
|
|
339
363
|
int64_t next_due = xt_timer_next_due();
|
|
340
|
-
|
|
364
|
+
int async_pending = xt_async_pending() > 0;
|
|
365
|
+
if (!has_fds && next_due < 0 && !async_pending) {
|
|
341
366
|
xt_drain_microtasks();
|
|
342
367
|
return 0;
|
|
343
368
|
}
|
|
@@ -350,11 +375,15 @@ int xt_loop_poll(int timeout_ms) {
|
|
|
350
375
|
if (delta > 100000000) delta = 100000000;
|
|
351
376
|
if (wait_ms < 0 || delta < (int64_t)wait_ms) wait_ms = (int)delta;
|
|
352
377
|
}
|
|
378
|
+
/* The completion channel normally wakes `select` immediately; polling every
|
|
379
|
+
* few milliseconds keeps progress even if it could not be registered. */
|
|
380
|
+
if (async_pending && (wait_ms < 0 || wait_ms > XT_ASYNC_POLL_MS)) wait_ms = XT_ASYNC_POLL_MS;
|
|
353
381
|
|
|
354
382
|
/* Timers only: sleep, then run whatever is due. */
|
|
355
383
|
if (!has_fds) {
|
|
356
384
|
xt_loop_sleep_ms(wait_ms);
|
|
357
385
|
xt_timer_run_due();
|
|
386
|
+
xt_async_poll();
|
|
358
387
|
xt_drain_microtasks();
|
|
359
388
|
return (int)(xt_loop_active_count + xt_timer_active_count());
|
|
360
389
|
}
|
|
@@ -372,7 +401,9 @@ int xt_loop_poll(int timeout_ms) {
|
|
|
372
401
|
if (watcher->fd > maxFd) maxFd = watcher->fd;
|
|
373
402
|
}
|
|
374
403
|
if (maxFd < 0) {
|
|
404
|
+
xt_loop_sleep_ms(wait_ms);
|
|
375
405
|
xt_timer_run_due();
|
|
406
|
+
xt_async_poll();
|
|
376
407
|
xt_drain_microtasks();
|
|
377
408
|
return (int)(xt_loop_active_count + xt_timer_active_count());
|
|
378
409
|
}
|
|
@@ -390,7 +421,13 @@ int xt_loop_poll(int timeout_ms) {
|
|
|
390
421
|
|
|
391
422
|
if (ready > 0) {
|
|
392
423
|
/* Collect the ready watchers first: handlers may add/remove descriptors. */
|
|
393
|
-
|
|
424
|
+
int previousDispatching = xt_loop_dispatching;
|
|
425
|
+
xt_loop_dispatching = 1;
|
|
426
|
+
/* Snapshot the table: a handler may register a new descriptor, and it must
|
|
427
|
+
* wait for the next iteration rather than being tested against this
|
|
428
|
+
* iteration's `fd_set`. */
|
|
429
|
+
int dispatchCount = xt_loop_watcher_count;
|
|
430
|
+
for (int i = 0; i < dispatchCount; i++) {
|
|
394
431
|
xt_loop_watcher *watcher = &xt_loop_watchers[i];
|
|
395
432
|
if (!watcher->active) continue;
|
|
396
433
|
int events = 0;
|
|
@@ -398,9 +435,11 @@ int xt_loop_poll(int timeout_ms) {
|
|
|
398
435
|
if ((watcher->events & XT_IO_WRITE) && FD_ISSET(watcher->fd, &writeSet)) events |= XT_IO_WRITE;
|
|
399
436
|
if (events) watcher->handler(watcher->userdata, events);
|
|
400
437
|
}
|
|
438
|
+
xt_loop_dispatching = previousDispatching;
|
|
401
439
|
}
|
|
402
440
|
|
|
403
441
|
xt_timer_run_due();
|
|
442
|
+
xt_async_poll();
|
|
404
443
|
xt_drain_microtasks();
|
|
405
444
|
return (int)(xt_loop_active_count + xt_timer_active_count());
|
|
406
445
|
}
|
|
@@ -417,4 +456,6 @@ void xt_run_event_loop(void) {
|
|
|
417
456
|
}
|
|
418
457
|
}
|
|
419
458
|
|
|
420
|
-
int xt_loop_has_work(void) {
|
|
459
|
+
int xt_loop_has_work(void) {
|
|
460
|
+
return xt_loop_active_count > 0 || xt_timer_active_count() > 0 || xt_async_pending() > 0;
|
|
461
|
+
}
|
package/src/driver/compiler.ts
CHANGED
|
@@ -259,7 +259,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
259
259
|
outputPath,
|
|
260
260
|
linkerFlags: [
|
|
261
261
|
...toolchain.linkerArgs,
|
|
262
|
-
...(process.platform === "win32" ? ["-lws2_32"] : ["-lm"]),
|
|
262
|
+
...(process.platform === "win32" ? ["-lws2_32"] : ["-lm", "-pthread"]),
|
|
263
263
|
...registry.linkerFlags(),
|
|
264
264
|
/* A GUI program is a Windows-subsystem executable: no console window is
|
|
265
265
|
* created when it is launched. Console output (stdout/stderr) is lost in
|
package/src/driver/version.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node's `fs` module: synchronous file and directory operations backed by
|
|
3
|
-
* `runtime/ext_node/fs
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* `runtime/ext_node/fs`, plus the `fs.promises` namespace (implemented in
|
|
4
|
+
* `async_*.c` on top of the asynchronous I/O pool). The callback-style
|
|
5
|
+
* `readFile`/`writeFile` forms are not supported; `*Sync` and `fs.promises` are.
|
|
6
|
+
* `watch`/`watchFile` return API-shaped watchers that never emit.
|
|
6
7
|
*
|
|
7
8
|
* `fs.constants` and `fs.promises` are exposed as value getters, so both
|
|
8
9
|
* `import { constants } from "fs"` and the `fs.constants.F_OK` property form
|
|
@@ -40,6 +41,14 @@ export const fsModule: NodeModule = {
|
|
|
40
41
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/watch.c"),
|
|
41
42
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/constants.c"),
|
|
42
43
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/streams.c"),
|
|
44
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_job.c"),
|
|
45
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker.c"),
|
|
46
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_dir.c"),
|
|
47
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_fd.c"),
|
|
48
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_complete.c"),
|
|
49
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_api.c"),
|
|
50
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_fh.c"),
|
|
51
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/promises.c"),
|
|
43
52
|
],
|
|
44
53
|
builtins: () => builtins,
|
|
45
54
|
exports: () => exports,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node's `fs/promises` module.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Each function submits a job to the asynchronous I/O pool: the blocking
|
|
5
|
+
* syscall runs on a worker thread and the promise settles on the event loop, so
|
|
6
|
+
* awaiting `readFile`, `writeFile`, `stat`, `rm`, `open` (a `FileHandle`), ...
|
|
7
|
+
* yields to timers and sockets while the I/O is in flight. `constants` is
|
|
8
|
+
* re-exported as a value getter.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { NodeModule } from "../module.js";
|
|
@@ -54,7 +54,7 @@ const exports: ModuleExports = {
|
|
|
54
54
|
export const fsPromisesModule: NodeModule = {
|
|
55
55
|
name: "fs/promises",
|
|
56
56
|
runtimeSources: () => [
|
|
57
|
-
//
|
|
57
|
+
// Synchronous builtins (used by `watch` and the sync module) plus the async job split.
|
|
58
58
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/read_file.c"),
|
|
59
59
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/write_file.c"),
|
|
60
60
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/fs_ops.c"),
|
|
@@ -63,6 +63,15 @@ export const fsPromisesModule: NodeModule = {
|
|
|
63
63
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/link_ops.c"),
|
|
64
64
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/copy_ops.c"),
|
|
65
65
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/constants.c"),
|
|
66
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/dir.c"),
|
|
67
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/watch.c"),
|
|
68
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_job.c"),
|
|
69
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker.c"),
|
|
70
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_dir.c"),
|
|
71
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_fd.c"),
|
|
72
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_complete.c"),
|
|
73
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_api.c"),
|
|
74
|
+
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_fh.c"),
|
|
66
75
|
resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/promises.c"),
|
|
67
76
|
],
|
|
68
77
|
builtins: () => builtins,
|