Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830#include <compat.h> // IWYU pragma: keep
#include <libgen.h>#include <stdlib.h>#include <string.h>
#include "ant.h"#include "arena.h"#include "base64.h"#include "bootstrap.h"#include "errors.h"#include "internal.h"#include "interrupt.h"#include "list.h"#include "ptr.h"#include "reactor.h"#include "runtime.h"#include "output.h"#include "silver/call.h"
#include "esm/commonjs.h"#include "esm/loader.h"#include "esm/remote.h"#include "modules/atomics.h"#include "modules/blob.h"#include "modules/process.h"#include "modules/url.h"#include "modules/worker_threads.h"
#include "worker_internal.h"
static constexpr uint32_t WORKER_NATIVE_TAG = Ant_NativeTag("WRKR");
static _Atomic uint64_t next_thread_id = 1;static constexpr double WORKER_DEFAULT_STACK_MB = 4.0;static constexpr double WORKER_DEFAULT_OLD_MB = 512.0;
const char *const worker_limit_names[WORKER_LIMIT_COUNT] = { "maxYoungGenerationSizeMb", "maxOldGenerationSizeMb", "codeRangeSizeMb", "stackSizeMb",};
ant_worker_t *worker_new(ant_t *js, ant_value_t obj, const worker_ops_t *ops) { ant_worker_t *w = calloc(1, sizeof(*w)); if (!w) return NULL;
w->lock = (ant_mutex_t)ANT_MUTEX_INIT; atomic_init(&w->refs, 1);
w->ops = ops; w->obj = obj; w->parent = js; w->bundle = js_esm_bundle(js); w->exit_promise = js_mkundef(); w->stdin_obj = js_mkundef(); w->stdout_obj = js_mkundef(); w->stderr_obj = js_mkundef(); w->child_stdin = js_mkundef(); w->pipe_stdout = true; w->pipe_stderr = true;
for (int i = 0; i < WORKER_LIMIT_COUNT; i++) w->limits[i] = w->requested_limits[i] = -1; w->requested_limits[WORKER_LIMIT_STACK] = WORKER_DEFAULT_STACK_MB; w->limits[WORKER_LIMIT_OLD] = WORKER_DEFAULT_OLD_MB; w->limits[WORKER_LIMIT_STACK] = WORKER_DEFAULT_STACK_MB;
return w;}
ant_worker_t *worker_data(ant_value_t obj) { return is_object_type(obj) ? (ant_worker_t *)js_get_native(obj, WORKER_NATIVE_TAG) : NULL;}
static void worker_free_events(worker_event_t *e) { for (worker_event_t *next; e; e = next) { next = e->next; ant_wire_release(e->wire); free(e); }}
void worker_release(ant_worker_t *w) { if (atomic_fetch_sub_explicit(&w->refs, 1, memory_order_acq_rel) != 1) return; worker_free_events(w->events_head); worker_free_events(w->stdin_head);
if (w->child_end) ant_port_end_release(w->child_end); ant_wire_release(w->worker_data); ant_wire_release(w->environment_data);
for (int i = 0; i < w->argc; i++) free(w->argv[i]); free(w->argv); for (char **e = w->env; e && *e; e++) free(*e);
free(w->env); free(w->entry); free(w->name); free(w);}
void worker_post_event(ant_worker_t *w, worker_event_t *e) { ant_mutex_lock(&w->lock); if (w->events_tail) w->events_tail->next = e; else w->events_head = e; w->events_tail = e; if (w->parent_async_open) uv_async_send(&w->parent_async); ant_mutex_unlock(&w->lock);}
void worker_post(ant_worker_t *w, worker_event_kind_t kind, int code, ant_wire_t *wire) { worker_event_t *e = calloc(1, sizeof(*e)); if (!e) { ant_wire_release(wire); return; } e->kind = kind; e->code = code; e->wire = wire; worker_post_event(w, e);}
static void worker_child_wake(uv_async_t *handle) { ant_worker_t *w = handle->data; ant_t *js = w->child; if (!js) return;
if (!worker_stop_requested(w, NULL)) { worker_stdio_child_wake(js, w); return; }
js->terminating = true; atomic_store_explicit(&js->interrupt_pending, 1, memory_order_relaxed); uv_stop(js->loop);}
void worker_request_stop(ant_worker_t *w, int code) { ant_mutex_lock(&w->lock); if (!w->terminate_requested) { w->terminate_requested = true; w->requested_exit_code = code; } if (w->child) { atomic_store_explicit(&w->child->interrupt_pending, 1, memory_order_relaxed); atomics_wake_isolate(w->child); if (w->child_async_open) uv_async_send(&w->child_async); } ant_mutex_unlock(&w->lock);}
bool worker_stop_requested(ant_worker_t *w, int *code) { ant_mutex_lock(&w->lock); bool requested = w->terminate_requested; if (requested && code) *code = w->requested_exit_code; ant_mutex_unlock(&w->lock); return requested;}
void worker_set_ref(ant_worker_t *w, bool refed) { if (!w->parent_async_open) return; w->refed = refed; if (refed) uv_ref((uv_handle_t *)&w->parent_async); else uv_unref((uv_handle_t *)&w->parent_async);}
void worker_flush_online(ant_t *js, ant_worker_t *w) { for (;;) { ant_mutex_lock(&w->lock); worker_event_t *e = w->events_head; bool online = e && e->kind == WORKER_EVENT_ONLINE; if (online) { w->events_head = e->next; if (!w->events_head) w->events_tail = NULL; } ant_mutex_unlock(&w->lock); if (!online) return; free(e); w->ops->online(js, w); }}
static ant_wire_t *worker_serialize_error(ant_t *js, ant_value_t error) { ant_wire_t *wire = NULL; ant_value_t r = ant_wire_serialize(js, error, js_mkundef(), js_mkundef(), &wire); if (!is_err(r)) return wire;
js_take_thrown(js, r); ant_value_t text = js_tostring_val(js, error); if (is_err(text)) { js_take_thrown(js, text); text = js_mkstr(js, "Uncaught exception in worker", 28); }
r = ant_wire_serialize(js, text, js_mkundef(), js_mkundef(), &wire); if (is_err(r)) { js_take_thrown(js, r); return NULL; }
return wire;}
bool ant_worker_report_uncaught(ant_t *js, ant_value_t reason) { ant_worker_t *w = js->worker; if (!w) return false; if (worker_stop_requested(w, NULL)) return true;
bool oom = is_err(js->exception_oom) && reason == Ant_Exception_Value(js, js->exception_oom); ant_wire_t *wire = NULL; if (!oom) { GC_ROOT_SAVE(mark, js); GC_ROOT_PIN(js, reason); wire = worker_serialize_error(js, reason); GC_ROOT_RESTORE(js, mark); }
worker_post(w, WORKER_EVENT_ERROR, oom ? WORKER_ERROR_OOM : WORKER_ERROR_THROWN, wire); worker_request_stop(w, 1); js->terminating = true; return true;}
ant_value_t ant_worker_exit(ant_t *js, int code) { ant_worker_t *w = js->worker; if (w) worker_request_stop(w, code); return js_interrupt_raise(js);}
bool ant_worker_is_worker(ant_t *js) { return js->worker != NULL;}
uint64_t ant_worker_thread_id(ant_t *js) { return js->worker ? js->worker->thread_id : 0;}
ant_value_t worker_parent_port(ant_t *js) { ant_worker_t *w = js->worker; if (is_object_type(js->mutable_roots.worker_parent_port)) return js->mutable_roots.worker_parent_port; if (!w || !w->child_end) return js_mknull();
ant_port_end_t *end = w->child_end; w->child_end = NULL;
ant_value_t port = ant_message_port_adopt(js, end); if (is_err(port)) return js_take_thrown(js, port); js->mutable_roots.worker_parent_port = port;
return port;}
static ant_value_t worker_run_entry(ant_t *js, ant_worker_t *w) { if (w->is_eval) { const char *tag = "[worker eval]"; js_set_filename(js, tag); js_setup_import_meta(js, tag); ant_value_t ns = Ant_Object_CreateBare(js); if (is_err(ns)) return ns; return esm_load_commonjs_module(js, tag, w->entry, strlen(w->entry), ns, js_mkundef()); }
ant_value_t specifier; if (strncmp(w->entry, "data:", 5) == 0) { js_set_global_builtin(js, "__dirname", js_mkundef()); specifier = js_mkstr(js, w->entry, strlen(w->entry)); js_set_filename(js, w->entry); js_setup_import_meta(js, w->entry); } else { char *copy = strdup(w->entry); if (copy) { char *dir = dirname(copy); js_set_global_builtin(js, "__dirname", js_mkstr(js, dir, strlen(dir))); free(copy); }
const char *interned = intern_string(js, w->entry, strlen(w->entry)); js_set_filename(js, interned ? interned : w->entry); js_setup_import_meta(js, interned ? interned : w->entry); specifier = js_esm_make_file_url(js, w->entry); }
js_set_global_builtin(js, "__filename", js_mkstr(js, w->entry, strlen(w->entry))); return js_esm_import_sync(js, specifier);}
static void worker_report_pending(ant_t *js) { if (!Ant_Exception_Pending(js)) return; ant_value_t thrown = js_take_thrown(js, js_mkundef()); if (js_is_termination(thrown)) return; ant_worker_report_uncaught(js, thrown);}
static void worker_child_attach(ant_t *js, ant_worker_t *w) { if (uv_async_init(js->loop, &w->child_async, worker_child_wake) != 0) return; w->child_async.data = w; uv_unref((uv_handle_t *)&w->child_async);
ant_mutex_lock(&w->lock); w->child = js; w->child_async_open = true; if (w->terminate_requested) atomic_store_explicit(&js->interrupt_pending, 1, memory_order_relaxed); ant_mutex_unlock(&w->lock);}
static void worker_child_detach(ant_worker_t *w) { ant_mutex_lock(&w->lock); w->child = NULL; bool close_async = w->child_async_open; w->child_async_open = false; ant_mutex_unlock(&w->lock); if (close_async) uv_close((uv_handle_t *)&w->child_async, NULL);}
static void worker_child_boot(ant_t *js, ant_worker_t *w) { ant_output_set_thread_writer(worker_stdio_writer, w); ant_runtime_init(js, w->argc, w->argv, NULL);
if (!w->share_env) process_set_private_env(js, w->env); ant_bootstrap_modules(js); worker_stdio_child_init(js, w);
if (w->bundle && !js_esm_bundle_activate(js, w->bundle)) { worker_post(w, WORKER_EVENT_ERROR, WORKER_ERROR_INIT, NULL); worker_request_stop(w, 1); }
if (w->ops->child_init) w->ops->child_init(js, w);}
static int worker_child_run(ant_t *js, ant_worker_t *w) { int code = 0; if (worker_stop_requested(w, &code)) return 0;
worker_post(w, WORKER_EVENT_ONLINE, 0, NULL); ant_value_t result = worker_run_entry(js, w); if (is_err(result) && !Ant_Exception_Pending(js)) ant_worker_report_uncaught(js, Ant_Exception_Value(js, result)); worker_report_pending(js);
if (!worker_stop_requested(w, NULL)) { atomic_store_explicit(&w->loop_start_ns, uv_hrtime(), memory_order_release); js_run_event_loop(js); worker_report_pending(js); }
if (worker_stop_requested(w, &code)) return code; code = process_run_exit_handlers(js, process_exit_code(js)); worker_stop_requested(w, &code);
return code;}
static void worker_thread_main(void *arg) { ant_worker_t *w = arg; volatile char stack_base;
ant_t *js = ant_create(&(ant_isolate_options_t){ .object_arena_bytes = (size_t)(w->limits[WORKER_LIMIT_OLD] * 1024 * 1024), });
if (!js) { worker_post(w, WORKER_EVENT_ERROR, WORKER_ERROR_INIT, NULL); worker_post(w, WORKER_EVENT_EXIT, 1, NULL); worker_release(w); return; }
js->worker = w; js->interrupts_armed = true; uv_loop_configure(js->loop, UV_METRICS_IDLE_TIME); js_setstackbase(js, (void *)&stack_base); js_setstacklimit(js, (size_t)(w->limits[WORKER_LIMIT_STACK] * 1024 * 1024) * 3 / 4);
worker_child_attach(js, w); worker_child_boot(js, w); int exit_code = worker_child_run(js, w); worker_child_detach(w);
js->terminating = false; atomic_store_explicit(&js->interrupt_pending, 0, memory_order_relaxed); js_destroy(js); ant_output_set_thread_writer(NULL, NULL); ant_thread_cleanup();
worker_post(w, WORKER_EVENT_EXIT, exit_code, NULL); worker_release(w);}
static void worker_finalize(ant_t *js, ant_object_t *obj) { ant_value_t value = js_obj_from_ptr(obj); ant_worker_t *w = worker_data(value); if (!w) return; js_clear_native(value, WORKER_NATIVE_TAG); w->obj = js_mkundef(); if (w->binding) { ant_port_binding_free(w->binding); w->binding = NULL; } worker_release(w);}
static void worker_parent_async_closed(uv_handle_t *handle) { worker_release(handle->data);}
static bool worker_close_parent_async(ant_worker_t *w) { ant_mutex_lock(&w->lock); bool close_async = w->parent_async_open; w->parent_async_open = false; ant_mutex_unlock(&w->lock); if (close_async) uv_close((uv_handle_t *)&w->parent_async, worker_parent_async_closed); return close_async;}
static void worker_join(ant_worker_t *w) { if (!w->thread_started) return; uv_thread_join(&w->thread); w->thread_started = false;}
static void worker_finish(ant_t *js, ant_worker_t *w, int code) { if (w->exited) return; w->exited = true; w->exit_code = code;
if (w->binding) { ant_port_binding_drain(w->binding); ant_port_binding_free(w->binding); w->binding = NULL; }
worker_join(w); worker_close_parent_async(w);
GC_ROOT_SAVE(mark, js); ant_value_t obj = w->obj; ant_value_t promise = w->exit_promise; GC_ROOT_PIN(js, obj); GC_ROOT_PIN(js, promise); Ant_List_Remove(w);
worker_stdio_end(js, w); w->ops->exit(js, w, code);
if (vtype(promise) == kTypePromise) js_resolve_promise(js, promise, js_mknum(code)); w->exit_promise = js_mkundef(); GC_ROOT_RESTORE(js, mark);}
ant_value_t worker_error_value(ant_t *js, worker_event_t *e) { if (e->wire) return ant_wire_deserialize(js, e->wire, NULL); if (e->code == WORKER_ERROR_OOM) return js_mkerr_code( js, JS_ERR_GENERIC, "ERR_WORKER_OUT_OF_MEMORY", "Worker terminated due to reaching memory limit: JS heap out of memory" ); return js_mkerr_code(js, JS_ERR_GENERIC, "ERR_WORKER_INIT_FAILED", "Worker initialization failure: out of memory");}
static void worker_dispatch_error(ant_t *js, ant_worker_t *w, worker_event_t *e) { ant_value_t error = worker_error_value(js, e); if (is_err(error)) error = js_take_thrown(js, error); GC_ROOT_SAVE(mark, js); GC_ROOT_PIN(js, error); w->ops->error(js, w, error); GC_ROOT_RESTORE(js, mark);}
static void worker_parent_wake(uv_async_t *handle) { ant_worker_t *w = handle->data; ant_t *js = w->parent;
for (;;) { ant_mutex_lock(&w->lock); worker_event_t *e = w->events_head; if (e) { w->events_head = e->next; if (!w->events_head) w->events_tail = NULL; e->next = NULL; } ant_mutex_unlock(&w->lock); if (!e) break;
if (e->kind == WORKER_EVENT_EXIT) { int code = e->code; worker_free_events(e); worker_finish(js, w, code); return; }
switch (e->kind) { case WORKER_EVENT_ONLINE: w->ops->online(js, w); break; case WORKER_EVENT_STDOUT: case WORKER_EVENT_STDERR: worker_stdio_forward(js, w, e); break; case WORKER_EVENT_ERROR: worker_dispatch_error(js, w, e); break; default: break; }
worker_free_events(e); }}
static char *worker_blob_entry(ant_t *js, const char *url, ant_value_t *error) { ant_value_t blob = url_resolve_object_url(js, url); blob_data_t *data = blob_is_blob(js, blob) ? blob_get_data(blob) : NULL; if (!data) { *error = js_mkerr_typed(js, JS_ERR_TYPE, "Cannot find blob URL \"%s\"", url); return NULL; }
size_t encoded_len = 0; char *encoded = ant_base64_encode(data->data, data->size, &encoded_len); if (!encoded) return NULL;
static const char prefix[] = "data:text/javascript;base64,"; char *entry = malloc(sizeof(prefix) + encoded_len); if (entry) { memcpy(entry, prefix, sizeof(prefix) - 1); memcpy(entry + sizeof(prefix) - 1, encoded, encoded_len + 1); }
free(encoded); return entry;}
static char *worker_bundle_entry(ant_t *js, const char *path, bool relative) { const ant_bundle_t *vfs = js_esm_bundle(js); if (!vfs) return NULL; if (!relative) return ant_bundle_has_key(vfs, path) ? strdup(path) : NULL;
const char *key = NULL; const char *parent = js_module_eval_active_filename(js); const ant_bundle_module_t *entry = ant_bundle_entry(vfs);
if (parent) key = ant_bundle_resolve(vfs, parent, path, false); if (!key && entry) key = ant_bundle_resolve(vfs, entry->key, path, false);
return key ? strdup(key) : NULL;}
static char *worker_entry_text(ant_t *js, ant_value_t spec, ant_value_t *error) { ant_value_t href = spec; if (is_object_type(spec)) href = js_get(js, spec, "href");
if (vtype(href) != kTypeString) { *error = js_mkerr_typed(js, JS_ERR_TYPE, "The \"filename\" argument must be of type string or an instance of URL"); return NULL; }
size_t len = 0; const char *str = js_getstr(js, href, &len);
return strndup(str, len);}
char *worker_resolve_entry(ant_t *js, ant_value_t spec, worker_entry_mode_t mode, ant_value_t *error) { *error = js_mkundef(); char *path = worker_entry_text(js, spec, error); if (!path) return NULL; if (strncmp(path, "data:", 5) == 0) return path;
if (mode == WORKER_ENTRY_WEB && strncmp(path, "blob:", 5) == 0) { char *entry = worker_blob_entry(js, path, error); free(path); return entry; }
if (strncmp(path, "file:", 5) == 0) { char *file = esm_file_url_to_path(js, path); free(path); if (!file) *error = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid file URL"); return file; }
if (esm_is_url(path)) { *error = js_mkerr_typed(js, JS_ERR_TYPE, "The URL must be of scheme file or data. Received \"%s\"", path); free(path); return NULL; }
bool absolute = path[0] == '/'; bool relative = strncmp(path, "./", 2) == 0 || strncmp(path, "../", 3) == 0; if (!absolute && !relative && mode == WORKER_ENTRY_NODE) { *error = js_mkerr_typed( js, JS_ERR_TYPE, "The worker script or module filename must be an absolute path or a relative path starting with './' or '../'. Received \"%s\"", path ); free(path); return NULL; }
if (!absolute && !relative) { char *dotted = malloc(strlen(path) + 3); if (!dotted) { free(path); return NULL; } memcpy(dotted, "./", 2); strcpy(dotted + 2, path); free(path); path = dotted; }
char *bundled = worker_bundle_entry(js, path, !absolute); if (bundled) { free(path); return bundled; }
char *resolved = realpath(path, NULL); if (!resolved) return path; free(path);
return resolved;}
static char **worker_env_from_object(ant_t *js, ant_value_t env_obj) { ant_value_t keys = js_own_property_keys(js, env_obj, false, true); if (is_err(keys) || vtype(keys) != kTypeArray) return NULL;
ant_offset_t len = js_arr_len(js, keys); char **env = calloc((size_t)len + 1, sizeof(char *)); if (!env) return NULL;
size_t out = 0; for (ant_offset_t i = 0; i < len; i++) { ant_value_t key = js_arr_get(js, keys, i); if (vtype(key) != kTypeString) continue; ant_value_t value = js_tostring_val(js, js_getprop_fallback(js, env_obj, js_getstr(js, key, NULL)));
if (is_err(value)) { js_take_thrown(js, value); continue; }
size_t klen = 0, vlen = 0; const char *k = js_getstr(js, key, &klen); const char *v = js_getstr(js, value, &vlen);
char *entry = malloc(klen + vlen + 2); if (!entry) continue;
memcpy(entry, k, klen); entry[klen] = '='; memcpy(entry + klen + 1, v, vlen); entry[klen + vlen + 1] = '\0'; env[out++] = entry; }
return env;}
ant_value_t worker_environment_map(ant_t *js) { if (!is_object_type(js->mutable_roots.worker_environment_data)) { ant_value_t ctor = js_get(js, js->global, "Map"); ant_value_t map = sv_vm_call(js->vm, js, ctor, js_mkundef(), NULL, 0, NULL, ctor); if (is_err(map)) return map; js->mutable_roots.worker_environment_data = map; } return js->mutable_roots.worker_environment_data;}
static ant_value_t worker_read_limits(ant_t *js, ant_worker_t *w, ant_value_t limits) { for (int i = 0; i < WORKER_LIMIT_COUNT && is_object_type(limits); i++) { ant_value_t v = js_get(js, limits, worker_limit_names[i]); if (vtype(v) == kTypeUndefined) continue; if (vtype(v) != kTypeNumber) return js_mkerr_typed(js, JS_ERR_TYPE, "The \"options.resourceLimits.%s\" property must be of type number", worker_limit_names[i]); if (js_getnum(v) > 0) w->limits[i] = w->requested_limits[i] = js_getnum(v); }
double old_min = (double)(ANT_ARENA_MIN >> 20), old_max = (double)(ANT_ARENA_MAX >> 20); if (w->limits[WORKER_LIMIT_OLD] < old_min) w->limits[WORKER_LIMIT_OLD] = old_min; if (w->limits[WORKER_LIMIT_OLD] > old_max) w->limits[WORKER_LIMIT_OLD] = old_max;
return js_mkundef();}
ant_value_t worker_limits_object(ant_t *js, const double limits[WORKER_LIMIT_COUNT], double unset) { ant_value_t obj = Ant_Object_Create(js); for (int i = 0; i < WORKER_LIMIT_COUNT; i++) js_set(js, obj, worker_limit_names[i], js_mknum(limits[i] > 0 ? limits[i] : unset)); return obj;}
static ant_value_t worker_build_argv(ant_t *js, ant_worker_t *w, ant_value_t argv_opt) { ant_offset_t extra = vtype(argv_opt) == kTypeArray ? js_arr_len(js, argv_opt) : 0; w->argv = calloc((size_t)extra + 3, sizeof(char *)); if (!w->argv) return js_mkerr(js, "out of memory");
const char *exe = js->runtime.argc > 0 && js->runtime.argv ? js->runtime.argv[0] : "ant"; w->argv[w->argc++] = strdup(exe); w->argv[w->argc++] = strdup(w->is_eval ? "[worker eval]" : w->entry);
for (ant_offset_t i = 0; i < extra; i++) { ant_value_t s = js_tostring_val(js, js_arr_get(js, argv_opt, i)); if (is_err(s)) return s; size_t len = 0; const char *str = js_getstr(js, s, &len); w->argv[w->argc++] = strndup(str, len); }
return js_mkundef();}
static ant_value_t worker_option(ant_t *js, ant_value_t options, const char *name) { return is_object_type(options) ? js_get(js, options, name) : js_mkundef();}
ant_value_t worker_read_options(ant_t *js, ant_worker_t *w, ant_value_t options) { ant_value_t r = worker_read_limits(js, w, worker_option(js, options, "resourceLimits")); if (!is_err(r)) r = worker_build_argv(js, w, worker_option(js, options, "argv")); if (is_err(r)) return r;
ant_value_t env = worker_option(js, options, "env"); if (vtype(env) == kTypeSymbol && env == js->builtins.worker_share_env) w->share_env = true; else if (is_object_type(env)) w->env = worker_env_from_object(js, env); else w->env = process_env_snapshot(js);
ant_value_t name = worker_option(js, options, "name"); if (vtype(name) == kTypeString) { size_t len = 0; const char *s = js_getstr(js, name, &len); w->name = strndup(s, len); }
ant_value_t data = worker_option(js, options, "workerData"); ant_value_t transfer = worker_option(js, options, "transferList"); r = ant_wire_serialize(js, data, transfer, js_mkundef(), &w->worker_data); if (is_err(r)) return r;
ant_value_t map = worker_environment_map(js); return is_err(map) ? map : ant_wire_serialize(js, map, js_mkundef(), js_mkundef(), &w->environment_data);}
ant_value_t worker_transfer_arg(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkundef(); ant_value_t opt = args[1]; if (!is_object_type(opt) || vtype(opt) == kTypeArray) return opt; ant_value_t transfer = js_get(js, opt, "transfer"); return vtype(transfer) == kTypeUndefined ? js_get(js, opt, "transferList") : transfer;}
static ant_value_t worker_bind_parent(ant_t *js, ant_worker_t *w) { ant_port_end_t *parent_end = NULL; if (!ant_port_end_pair(&parent_end, &w->child_end)) return js_mkerr(js, "out of memory");
w->binding = ant_port_end_bind(js, parent_end, w->obj, w->ops->on_message); if (!w->binding) { ant_port_end_release(parent_end); return js_mkerr(js, "out of memory"); }
if (uv_async_init(js->loop, &w->parent_async, worker_parent_wake) != 0) { ant_port_binding_free(w->binding); w->binding = NULL; return js_mkerr(js, "failed to initialize Worker"); }
w->parent_async.data = w; w->parent_async_open = true; w->refed = true; atomic_fetch_add_explicit(&w->refs, 1, memory_order_relaxed);
return js_mkundef();}
ant_value_t worker_start(ant_t *js, ant_worker_t *w) { ant_value_t r = worker_bind_parent(js, w); if (is_err(r)) return r;
w->thread_id = atomic_fetch_add_explicit(&next_thread_id, 1, memory_order_relaxed); js_set_native(w->obj, w, WORKER_NATIVE_TAG); js_set_finalizer(w->obj, worker_finalize); Ant_List_Insert(&js->workers, w);
uv_thread_options_t opts = { .flags = UV_THREAD_HAS_STACK_SIZE, .stack_size = (size_t)(w->limits[WORKER_LIMIT_STACK] * 1024 * 1024), };
atomic_fetch_add_explicit(&w->refs, 1, memory_order_relaxed); if (uv_thread_create_ex(&w->thread, &opts, worker_thread_main, w) != 0) { atomic_fetch_sub_explicit(&w->refs, 1, memory_order_relaxed); worker_post(w, WORKER_EVENT_EXIT, 1, NULL); return js_mkundef(); }
w->thread_started = true; return js_mkundef();}
void cleanup_worker_threads_module(ant_t *js) { while (js->workers) { ant_worker_t *w = js->workers; Ant_List_Remove(w); worker_request_stop(w, 1);
ant_mutex_lock(&w->lock); bool close_async = w->parent_async_open; w->parent_async_open = false; ant_mutex_unlock(&w->lock);
worker_join(w); w->exited = true; w->exit_promise = js_mkundef();
if (w->binding) { ant_port_binding_free(w->binding); w->binding = NULL; }
if (close_async) uv_close((uv_handle_t *)&w->parent_async, worker_parent_async_closed); }
worker_messaging_close(js);}
void gc_mark_worker_threads(ant_t *js, gc_mark_fn mark) { for (ant_worker_t *w = js->workers; w; w = w->next) { if (is_object_type(w->obj)) mark(js, w->obj); if (vtype(w->exit_promise) == kTypePromise) mark(js, w->exit_promise); if (is_object_type(w->stdout_obj)) mark(js, w->stdout_obj); if (is_object_type(w->stderr_obj)) mark(js, w->stderr_obj); if (is_object_type(w->stdin_obj)) mark(js, w->stdin_obj); }
if (js->worker && is_object_type(js->worker->child_stdin)) mark(js, js->worker->child_stdin); worker_messaging_gc_mark(js, mark);}