mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 07:28:53 +00:00
Compare commits
42 Commits
dylan/byte
...
ben/worker
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0d4f0a38 | ||
|
|
e90cfb3143 | ||
|
|
3283c555bf | ||
|
|
8920316c54 | ||
|
|
f7e59d1fc4 | ||
|
|
bca78ac3a9 | ||
|
|
067dcab6d2 | ||
|
|
89dbacab15 | ||
|
|
e9503d3d13 | ||
|
|
5f45e9389b | ||
|
|
e92096d05f | ||
|
|
b3f9fde022 | ||
|
|
d84edf52b6 | ||
|
|
d465b98884 | ||
|
|
19bb1600f9 | ||
|
|
5bcc2a22cf | ||
|
|
aaa308a75d | ||
|
|
34509cbf1e | ||
|
|
6d8d8dd0b6 | ||
|
|
35982fe167 | ||
|
|
4a4816a3d3 | ||
|
|
215c52f786 | ||
|
|
20baa2ca2c | ||
|
|
cda77ab831 | ||
|
|
326bf1c8fe | ||
|
|
f852a94a92 | ||
|
|
d47929db2c | ||
|
|
6c18ecdd4c | ||
|
|
9ab949428f | ||
|
|
ad1abcef1c | ||
|
|
02e539ef01 | ||
|
|
fd1f4fdc73 | ||
|
|
398a1479e3 | ||
|
|
b9feec098c | ||
|
|
934124fc1a | ||
|
|
989617ee86 | ||
|
|
5526d5c940 | ||
|
|
f44cc7d557 | ||
|
|
fa91d1346c | ||
|
|
657ac6a4a3 | ||
|
|
ea35263dad | ||
|
|
2e225d5326 |
@@ -95,6 +95,7 @@ src/js/internal/util/inspect.js
|
||||
src/js/internal/util/mime.ts
|
||||
src/js/internal/validators.ts
|
||||
src/js/internal/webstreams_adapters.ts
|
||||
src/js/internal/worker_threads.ts
|
||||
src/js/node/_http_agent.ts
|
||||
src/js/node/_http_client.ts
|
||||
src/js/node/_http_common.ts
|
||||
|
||||
@@ -146,27 +146,42 @@ fn messageWithTypeAndLevel_(
|
||||
return;
|
||||
}
|
||||
|
||||
const enable_colors = global.bunVM().worker == null and
|
||||
if (level == .Warning or level == .Error)
|
||||
Output.enable_ansi_colors_stderr
|
||||
else
|
||||
Output.enable_ansi_colors_stdout;
|
||||
|
||||
const use_process_stdio = if (global.bunVM().worker) |w|
|
||||
w.kind == .node
|
||||
else
|
||||
false;
|
||||
var worker_writer = if (use_process_stdio)
|
||||
global.processStdioWriter(if (level == .Warning or level == .Error) .stderr else .stdout)
|
||||
else
|
||||
null;
|
||||
|
||||
const underlying_writer = if (worker_writer) |*w|
|
||||
w.any()
|
||||
else if (level == .Warning or level == .Error) // TODO fix buffering here
|
||||
console.error_writer.unbuffered_writer.any()
|
||||
else
|
||||
console.writer.unbuffered_writer.any();
|
||||
|
||||
var buffered_writer = std.io.bufferedWriter(underlying_writer);
|
||||
var writer = buffered_writer.writer();
|
||||
const Writer = @TypeOf(writer);
|
||||
|
||||
if (message_type == .Assert and len == 0) {
|
||||
const text = if (Output.enable_ansi_colors_stderr)
|
||||
const text = if (enable_colors)
|
||||
Output.prettyFmt("<r><red>Assertion failed<r>\n", true)
|
||||
else
|
||||
"Assertion failed\n";
|
||||
console.error_writer.unbuffered_writer.writeAll(text) catch {};
|
||||
writer.writeAll(text) catch {};
|
||||
buffered_writer.flush() catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
const enable_colors = if (level == .Warning or level == .Error)
|
||||
Output.enable_ansi_colors_stderr
|
||||
else
|
||||
Output.enable_ansi_colors_stdout;
|
||||
|
||||
var buffered_writer = if (level == .Warning or level == .Error)
|
||||
&console.error_writer
|
||||
else
|
||||
&console.writer;
|
||||
var writer = buffered_writer.writer();
|
||||
const Writer = @TypeOf(writer);
|
||||
|
||||
var print_length = len;
|
||||
var print_options: FormatOptions = .{
|
||||
.enable_colors = enable_colors,
|
||||
@@ -230,13 +245,13 @@ fn messageWithTypeAndLevel_(
|
||||
print_options,
|
||||
)
|
||||
else if (message_type == .Log) {
|
||||
_ = console.writer.write("\n") catch 0;
|
||||
console.writer.flush() catch {};
|
||||
_ = writer.write("\n") catch 0;
|
||||
buffered_writer.flush() catch {};
|
||||
} else if (message_type != .Trace)
|
||||
writer.writeAll("undefined\n") catch {};
|
||||
|
||||
if (message_type == .Trace) {
|
||||
writeTrace(Writer, writer, global);
|
||||
writeTrace(Writer, writer, global, enable_colors);
|
||||
buffered_writer.flush() catch {};
|
||||
}
|
||||
}
|
||||
@@ -656,7 +671,7 @@ pub const TablePrinter = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject) void {
|
||||
pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject, enable_ansi_colors: bool) void {
|
||||
var holder = ZigException.Holder.init();
|
||||
var vm = VirtualMachine.get();
|
||||
defer holder.deinit(vm);
|
||||
@@ -676,20 +691,14 @@ pub fn writeTrace(comptime Writer: type, writer: Writer, global: *JSGlobalObject
|
||||
false,
|
||||
);
|
||||
|
||||
if (Output.enable_ansi_colors_stderr)
|
||||
VirtualMachine.printStackTrace(
|
||||
switch (enable_ansi_colors) {
|
||||
inline else => |color| VirtualMachine.printStackTrace(
|
||||
Writer,
|
||||
writer,
|
||||
exception.stack,
|
||||
true,
|
||||
) catch {}
|
||||
else
|
||||
VirtualMachine.printStackTrace(
|
||||
Writer,
|
||||
writer,
|
||||
exception.stack,
|
||||
false,
|
||||
) catch {};
|
||||
color,
|
||||
) catch {},
|
||||
}
|
||||
}
|
||||
|
||||
pub const FormatOptions = struct {
|
||||
|
||||
@@ -2023,6 +2023,11 @@ export fn Bun__VirtualMachine__setOverrideModuleRunMainPromise(vm: *VirtualMachi
|
||||
}
|
||||
}
|
||||
|
||||
export fn Bun__VirtualMachine__getWorker(vm: *VirtualMachine) ?*anyopaque {
|
||||
const worker = vm.worker orelse return null;
|
||||
return worker.cpp_worker;
|
||||
}
|
||||
|
||||
pub fn reloadEntryPointForTestRunner(this: *VirtualMachine, entry_path: []const u8) !*JSInternalPromise {
|
||||
this.has_loaded = false;
|
||||
this.main = entry_path;
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
|
||||
|
||||
Output.flush();
|
||||
if (!globalThis.hasException()) {
|
||||
JSC.ConsoleObject.writeTrace(@TypeOf(&writer), &writer, globalThis);
|
||||
JSC.ConsoleObject.writeTrace(@TypeOf(&writer), &writer, globalThis, Output.enable_ansi_colors_stderr);
|
||||
}
|
||||
Output.flush();
|
||||
}
|
||||
|
||||
@@ -2039,8 +2039,9 @@ enum class BunProcessStdinFdType : int32_t {
|
||||
extern "C" BunProcessStdinFdType Bun__Process__getStdinFdType(void*, int fd);
|
||||
|
||||
extern "C" void Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(JSC::JSGlobalObject*, JSC::EncodedJSValue);
|
||||
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC::JSObject* processObject, int fd)
|
||||
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* jsGlobalObject, JSC::JSObject* processObject, int fd)
|
||||
{
|
||||
auto* globalObject = defaultGlobalObject(jsGlobalObject);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_CATCH_SCOPE(vm);
|
||||
|
||||
@@ -2051,6 +2052,9 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC:
|
||||
args.append(jsBoolean(bun_stdio_tty[fd]));
|
||||
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), fd);
|
||||
args.append(jsNumber(static_cast<int32_t>(fdType)));
|
||||
bool isNodeWorkerThread = globalObject->worker()
|
||||
&& globalObject->worker()->options().kind == WorkerOptions::Kind::Node;
|
||||
args.append(jsBoolean(isNodeWorkerThread));
|
||||
|
||||
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
|
||||
|
||||
@@ -2082,7 +2086,10 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC:
|
||||
forceSync = true;
|
||||
#endif
|
||||
if (forceSync) {
|
||||
Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(globalObject, JSValue::encode(resultObject->getIndex(globalObject, 1)));
|
||||
JSValue underlyingSink = resultObject->getIndex(globalObject, 1);
|
||||
if (!underlyingSink.isUndefined()) {
|
||||
Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(globalObject, JSValue::encode(underlyingSink));
|
||||
}
|
||||
}
|
||||
|
||||
return resultObject->getIndex(globalObject, 0);
|
||||
@@ -2104,7 +2111,7 @@ static JSValue constructStderr(VM& vm, JSObject* processObject)
|
||||
|
||||
static JSValue constructStdin(VM& vm, JSObject* processObject)
|
||||
{
|
||||
auto* globalObject = processObject->globalObject();
|
||||
auto* globalObject = defaultGlobalObject(processObject->globalObject());
|
||||
auto scope = DECLARE_CATCH_SCOPE(vm);
|
||||
JSC::JSFunction* getStdinStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
@@ -2113,6 +2120,9 @@ static JSValue constructStdin(VM& vm, JSObject* processObject)
|
||||
args.append(jsBoolean(bun_stdio_tty[STDIN_FILENO]));
|
||||
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), STDIN_FILENO);
|
||||
args.append(jsNumber(static_cast<int32_t>(fdType)));
|
||||
bool isNodeWorkerThread = globalObject->worker()
|
||||
&& globalObject->worker()->options().kind == WorkerOptions::Kind::Node;
|
||||
args.append(jsBoolean(isNodeWorkerThread));
|
||||
JSC::CallData callData = JSC::getCallData(getStdinStream);
|
||||
|
||||
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdinStream, callData, globalObject, args);
|
||||
@@ -2687,6 +2697,7 @@ void Process::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
thisObject->m_bindingUV.visit(visitor);
|
||||
thisObject->m_bindingNatives.visit(visitor);
|
||||
thisObject->m_emitHelperFunction.visit(visitor);
|
||||
thisObject->m_emitWorkerStdioInParentFunction.visit(visitor);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(Process);
|
||||
@@ -3299,6 +3310,17 @@ JSValue Process::constructNextTickFn(JSC::VM& vm, Zig::GlobalObject* globalObjec
|
||||
return nextTickFunction;
|
||||
}
|
||||
|
||||
void Process::emitWorkerStdioInParent(JSWorker* worker, Worker::PushStdioFd fd, JSUint8Array* data)
|
||||
{
|
||||
auto* fn = m_emitWorkerStdioInParentFunction.getInitializedOnMainThread(this);
|
||||
auto callData = JSC::getCallData(fn);
|
||||
MarkedArgumentBuffer args;
|
||||
args.append(worker);
|
||||
args.append(jsNumber(static_cast<int>(fd)));
|
||||
args.append(data);
|
||||
JSC::call(globalObject(), fn, callData, jsNull(), args);
|
||||
}
|
||||
|
||||
static JSValue constructProcessNextTickFn(VM& vm, JSObject* processObject)
|
||||
{
|
||||
JSGlobalObject* lexicalGlobalObject = processObject->globalObject();
|
||||
@@ -3606,6 +3628,30 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Process__writeToStdio(JSGlobalObject* jsGlobalObject, int fd, const uint8_t* bytes, size_t len)
|
||||
{
|
||||
ASSERT(fd == 1 || fd == 2);
|
||||
auto* globalObject = defaultGlobalObject(jsGlobalObject);
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* process = globalObject->processObject();
|
||||
auto stream = process->get(globalObject, Identifier::fromString(vm, fd == 1 ? "stdout"_s : "stderr"_s));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
auto writeFn = stream.get(globalObject, Identifier::fromString(vm, "write"_s));
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
auto callData = JSC::getCallData(writeFn);
|
||||
if (callData.type == CallData::Type::None) {
|
||||
scope.throwException(globalObject, createNotAFunctionError(globalObject, writeFn));
|
||||
return;
|
||||
}
|
||||
MarkedArgumentBuffer args;
|
||||
auto* buffer = WebCore::createBuffer(globalObject, { bytes, len });
|
||||
args.append(buffer);
|
||||
JSC::call(globalObject, writeFn, callData, stream, args);
|
||||
RETURN_IF_EXCEPTION(scope, );
|
||||
}
|
||||
|
||||
/* Source for Process.lut.h
|
||||
@begin processObjectTable
|
||||
_debugEnd Process_stubEmptyFunction Function 0
|
||||
@@ -3729,6 +3775,9 @@ void Process::finishCreation(JSC::VM& vm)
|
||||
m_emitHelperFunction.initLater([](const JSC::LazyProperty<Process, JSFunction>::Initializer& init) {
|
||||
init.set(JSFunction::create(init.vm, init.owner->globalObject(), 2, "emit"_s, Process_functionEmitHelper, ImplementationVisibility::Private));
|
||||
});
|
||||
m_emitWorkerStdioInParentFunction.initLater([](const JSC::LazyProperty<Process, JSFunction>::Initializer& init) {
|
||||
init.set(JSFunction::create(init.vm, init.owner->globalObject(), processObjectInternalsEmitWorkerStdioInParentCodeGenerator(init.vm), init.owner->globalObject()));
|
||||
});
|
||||
|
||||
putDirect(vm, vm.propertyNames->toStringTagSymbol, jsString(vm, String("process"_s)), 0);
|
||||
putDirect(vm, Identifier::fromString(vm, "_exiting"_s), jsBoolean(false), 0);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "BunBuiltinNames.h"
|
||||
#include "BunClientData.h"
|
||||
#include "JSEventEmitter.h"
|
||||
#include "JSWorker.h"
|
||||
|
||||
namespace Zig {
|
||||
class GlobalObject;
|
||||
@@ -26,6 +27,10 @@ class Process : public WebCore::JSEventEmitter {
|
||||
// Function that looks up "emit" on "process" and calls it with the provided arguments
|
||||
// Only used by internal code via passing to queueNextTick
|
||||
LazyProperty<Process, JSFunction> m_emitHelperFunction;
|
||||
// Function called when a Worker thread sends data from its stdout or stderr stream, to emit
|
||||
// the data in the main thread on the Worker object and possibly (depending on Worker options)
|
||||
// the process stream
|
||||
LazyProperty<Process, JSFunction> m_emitWorkerStdioInParentFunction;
|
||||
WriteBarrier<Unknown> m_uncaughtExceptionCaptureCallback;
|
||||
WriteBarrier<JSObject> m_nextTickFunction;
|
||||
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/bootstrap/switches/does_own_process_state.js#L113-L116
|
||||
@@ -78,6 +83,8 @@ public:
|
||||
JSValue getExecArgv(JSGlobalObject* globalObject);
|
||||
void setExecArgv(JSGlobalObject* globalObject, JSValue execArgv);
|
||||
|
||||
void emitWorkerStdioInParent(JSWorker* worker, Worker::PushStdioFd fd, JSUint8Array* data);
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject,
|
||||
JSC::JSValue prototype)
|
||||
{
|
||||
|
||||
@@ -882,6 +882,36 @@ pub const JSGlobalObject = opaque {
|
||||
return @enumFromInt(ScriptExecutionContextIdentifier__forGlobalObject(global));
|
||||
}
|
||||
|
||||
pub const StdioWriterFd = enum(c_int) {
|
||||
stdout = 1,
|
||||
stderr = 2,
|
||||
};
|
||||
extern fn Process__writeToStdio(global: *JSGlobalObject, fd: StdioWriterFd, bytes: [*]const u8, len: usize) void;
|
||||
|
||||
const ProcessStdioWriterContext = struct {
|
||||
global: *JSGlobalObject,
|
||||
fd: StdioWriterFd,
|
||||
|
||||
fn write(this: ProcessStdioWriterContext, bytes: []const u8) bun.JSError!usize {
|
||||
Process__writeToStdio(this.global, this.fd, bytes.ptr, bytes.len);
|
||||
if (this.global.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
return bytes.len;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ProcessStdioWriter = std.io.Writer(
|
||||
ProcessStdioWriterContext,
|
||||
bun.JSError,
|
||||
ProcessStdioWriterContext.write,
|
||||
);
|
||||
|
||||
/// Get a writer which calls process.stdout.write or process.stderr.write
|
||||
pub fn processStdioWriter(global: *JSGlobalObject, fd: StdioWriterFd) ProcessStdioWriter {
|
||||
return .{ .context = .{ .global = global, .fd = fd } };
|
||||
}
|
||||
|
||||
pub const Extern = [_][]const u8{ "create", "getModuleRegistryMap", "resetModuleRegistryMap" };
|
||||
|
||||
comptime {
|
||||
|
||||
@@ -4410,6 +4410,14 @@ bool GlobalObject::hasNapiFinalizers() const
|
||||
|
||||
void GlobalObject::setNodeWorkerEnvironmentData(JSMap* data) { m_nodeWorkerEnvironmentData.set(vm(), this, data); }
|
||||
|
||||
extern "C" WebCore::Worker* Bun__VirtualMachine__getWorker(VirtualMachine* bunVM);
|
||||
|
||||
WebCore::Worker* GlobalObject::worker()
|
||||
{
|
||||
// TODO make bunVM typed instead of void* everywhere
|
||||
return Bun__VirtualMachine__getWorker(reinterpret_cast<VirtualMachine*>(bunVM()));
|
||||
}
|
||||
|
||||
void GlobalObject::trackFFIFunction(JSC::JSFunction* function)
|
||||
{
|
||||
this->m_ffiFunctions.append(JSC::Strong<JSC::JSFunction> { vm(), function });
|
||||
|
||||
@@ -25,6 +25,7 @@ class WorkerGlobalScope;
|
||||
class SubtleCrypto;
|
||||
class EventTarget;
|
||||
class Performance;
|
||||
class Worker;
|
||||
} // namespace WebCore
|
||||
|
||||
namespace Bun {
|
||||
@@ -398,6 +399,10 @@ public:
|
||||
return func;
|
||||
}
|
||||
|
||||
// Return the Worker object if this global object is running in a worker, or nullptr
|
||||
// if it is not.
|
||||
WebCore::Worker* worker();
|
||||
|
||||
bool asyncHooksNeedsCleanup = false;
|
||||
double INSPECT_MAX_BYTES = 50;
|
||||
bool isInsideErrorPrepareStackTraceCallback = false;
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
#include "CloseEvent.h"
|
||||
#include "JSMessagePort.h"
|
||||
#include "JSBroadcastChannel.h"
|
||||
#include "JSWorker.h"
|
||||
#include "BunProcess.h"
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
@@ -126,7 +128,8 @@ extern "C" void* WebWorker__create(
|
||||
StringImpl** execArgvPtr,
|
||||
size_t execArgvLen,
|
||||
BunString* preloadModulesPtr,
|
||||
size_t preloadModulesLen);
|
||||
size_t preloadModulesLen,
|
||||
WorkerOptions::Kind kind);
|
||||
extern "C" void WebWorker__setRef(
|
||||
void* worker,
|
||||
bool ref);
|
||||
@@ -206,7 +209,8 @@ ExceptionOr<Ref<Worker>> Worker::create(ScriptExecutionContext& context, const S
|
||||
execArgv.data(),
|
||||
execArgv.size(),
|
||||
preloadModules.data(),
|
||||
preloadModules.size());
|
||||
preloadModules.size(),
|
||||
options.kind);
|
||||
// now referenced by Zig
|
||||
worker->ref();
|
||||
|
||||
@@ -519,8 +523,6 @@ extern "C" void WebWorker__dispatchError(Zig::GlobalObject* globalObject, Worker
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" WebCore::Worker* WebWorker__getParentWorker(void* bunVM);
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsReceiveMessageOnPort, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
@@ -547,6 +549,77 @@ JSC_DEFINE_HOST_FUNCTION(jsReceiveMessageOnPort, (JSGlobalObject * lexicalGlobal
|
||||
return Bun::throwError(lexicalGlobalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "The \"port\" argument must be a MessagePort instance"_s);
|
||||
}
|
||||
|
||||
void Worker::pushStdio(Worker& child, PushStdioFd fd, std::span<const uint8_t> bytes)
|
||||
{
|
||||
Vector<uint8_t, 64> vec { bytes };
|
||||
|
||||
auto task = [worker = Ref { child }, fd, vec = WTFMove(vec)](ScriptExecutionContext& ctx) {
|
||||
auto& vm = ctx.vm();
|
||||
auto* destGlobalObject = defaultGlobalObject(ctx.globalObject());
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto reportUncaught = [&destGlobalObject, &scope]() -> void {
|
||||
destGlobalObject->reportUncaughtExceptionAtEventLoop(destGlobalObject, scope.exception());
|
||||
};
|
||||
|
||||
auto* buffer = WebCore::createBuffer(destGlobalObject, vec.span());
|
||||
|
||||
if (fd == PushStdioFd::Stdin) {
|
||||
auto* process = destGlobalObject->processObject();
|
||||
auto processStdin = process->get(destGlobalObject, Identifier::fromString(vm, "stdin"_s));
|
||||
RETURN_IF_EXCEPTION(scope, reportUncaught());
|
||||
auto push = processStdin.get(destGlobalObject, Identifier::fromString(vm, "push"_s));
|
||||
RETURN_IF_EXCEPTION(scope, reportUncaught());
|
||||
auto callData = JSC::getCallData(push);
|
||||
if (callData.type == CallData::Type::None) {
|
||||
scope.throwException(destGlobalObject, createNotAFunctionError(destGlobalObject, push));
|
||||
RETURN_IF_EXCEPTION(scope, reportUncaught());
|
||||
}
|
||||
MarkedArgumentBuffer args;
|
||||
args.append(buffer);
|
||||
JSC::call(destGlobalObject, push, callData, processStdin, args);
|
||||
RETURN_IF_EXCEPTION(scope, reportUncaught());
|
||||
} else {
|
||||
auto* jsWorker = jsCast<JSWorker*>(WebCore::toJS(destGlobalObject, destGlobalObject, worker));
|
||||
destGlobalObject->processObject()->emitWorkerStdioInParent(jsWorker, fd, buffer);
|
||||
RETURN_IF_EXCEPTION(scope, reportUncaught());
|
||||
}
|
||||
};
|
||||
|
||||
if (fd == PushStdioFd::Stdin) {
|
||||
child.postTaskToWorkerGlobalScope(WTFMove(task));
|
||||
} else {
|
||||
child.scriptExecutionContext()->postTaskConcurrently(WTFMove(task));
|
||||
}
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPushStdioToParent, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
// internal binding
|
||||
ASSERT(callFrame->argumentCount() == 2);
|
||||
auto fd = callFrame->argument(0).asInt32();
|
||||
ASSERT(fd == 1 || fd == 2);
|
||||
auto* buf = jsCast<JSUint8Array*>(callFrame->argument(1));
|
||||
auto& sourceWorker = *globalObject->worker();
|
||||
|
||||
Worker::pushStdio(sourceWorker, static_cast<Worker::PushStdioFd>(fd), buf->span());
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPushStdinToChild, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
// internal binding
|
||||
ASSERT(callFrame->argumentCount() == 2);
|
||||
auto* destWorker = jsCast<JSWorker*>(callFrame->argument(0));
|
||||
auto* buf = jsCast<JSUint8Array*>(callFrame->argument(1));
|
||||
|
||||
Worker::pushStdio(destWorker->wrapped(), Worker::PushStdioFd::Stdin, buf->span());
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSValue createNodeWorkerThreadsBinding(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
@@ -556,7 +629,7 @@ JSValue createNodeWorkerThreadsBinding(Zig::GlobalObject* globalObject)
|
||||
JSValue threadId = jsNumber(0);
|
||||
JSMap* environmentData = nullptr;
|
||||
|
||||
if (auto* worker = WebWorker__getParentWorker(globalObject->bunVM())) {
|
||||
if (auto* worker = globalObject->worker()) {
|
||||
auto& options = worker->options();
|
||||
auto ports = MessagePort::entanglePorts(*ScriptExecutionContext::getScriptExecutionContext(worker->clientIdentifier()), WTFMove(options.dataMessagePorts));
|
||||
RefPtr<WebCore::SerializedScriptValue> serialized = WTFMove(options.workerDataAndEnvironmentData);
|
||||
@@ -583,12 +656,14 @@ JSValue createNodeWorkerThreadsBinding(Zig::GlobalObject* globalObject)
|
||||
ASSERT(environmentData);
|
||||
globalObject->setNodeWorkerEnvironmentData(environmentData);
|
||||
|
||||
JSObject* array = constructEmptyArray(globalObject, nullptr, 4);
|
||||
JSObject* array = constructEmptyArray(globalObject, nullptr, 5);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
array->putDirectIndex(globalObject, 0, workerData);
|
||||
array->putDirectIndex(globalObject, 1, threadId);
|
||||
array->putDirectIndex(globalObject, 2, JSFunction::create(vm, globalObject, 1, "receiveMessageOnPort"_s, jsReceiveMessageOnPort, ImplementationVisibility::Public, NoIntrinsic));
|
||||
array->putDirectIndex(globalObject, 3, environmentData);
|
||||
array->putDirectIndex(globalObject, 4, JSFunction::create(vm, globalObject, 2, "pushStdioToParent"_s, jsPushStdioToParent, ImplementationVisibility::Public));
|
||||
array->putDirectIndex(globalObject, 5, JSFunction::create(vm, globalObject, 2, "pushStdinToChild"_s, jsPushStdinToChild, ImplementationVisibility::Public));
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -602,7 +677,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage,
|
||||
if (!globalObject) [[unlikely]]
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
Worker* worker = WebWorker__getParentWorker(globalObject->bunVM());
|
||||
Worker* worker = globalObject->worker();
|
||||
if (worker == nullptr)
|
||||
return JSValue::encode(jsUndefined());
|
||||
|
||||
|
||||
@@ -89,10 +89,17 @@ public:
|
||||
// true if successful
|
||||
bool dispatchErrorWithValue(Zig::GlobalObject* workerGlobalObject, JSValue value);
|
||||
void dispatchExit(int32_t exitCode);
|
||||
// Returns the context of the thread that started this Worker
|
||||
ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); }
|
||||
ScriptExecutionContextIdentifier clientIdentifier() const { return m_clientIdentifier; }
|
||||
WorkerOptions& options() { return m_options; }
|
||||
|
||||
enum class PushStdioFd : int {
|
||||
Stdin = 0,
|
||||
Stdout = 1,
|
||||
Stderr = 2,
|
||||
};
|
||||
|
||||
private:
|
||||
Worker(ScriptExecutionContext&, WorkerOptions&&);
|
||||
|
||||
@@ -101,6 +108,11 @@ private:
|
||||
void derefEventTarget() final { deref(); }
|
||||
void eventListenersDidChange() final {};
|
||||
|
||||
// bytes are cloned
|
||||
// for fd == Stdin: send from parent to child
|
||||
// for fd == Stdout or Stderr: send from child to parent
|
||||
static void pushStdio(Worker& child, PushStdioFd fd, std::span<const uint8_t> bytes);
|
||||
|
||||
static void networkStateChanged(bool isOnLine);
|
||||
|
||||
static constexpr uint8_t OnlineFlag = 1 << 0;
|
||||
@@ -120,10 +132,15 @@ private:
|
||||
std::atomic<uint8_t> m_terminationFlags { 0 };
|
||||
const ScriptExecutionContextIdentifier m_clientIdentifier;
|
||||
void* impl_ { nullptr };
|
||||
|
||||
friend JSC_DECLARE_HOST_FUNCTION(jsPushStdioToParent);
|
||||
friend JSC_DECLARE_HOST_FUNCTION(jsPushStdinToChild);
|
||||
};
|
||||
|
||||
JSValue createNodeWorkerThreadsBinding(Zig::GlobalObject* globalObject);
|
||||
|
||||
JSC_DECLARE_HOST_FUNCTION(jsFunctionPostMessage);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsPushStdioToParent);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsPushStdinToChild);
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -24,7 +24,7 @@ preloads: [][]const u8 = &.{},
|
||||
store_fd: bool = false,
|
||||
arena: ?bun.MimallocArena = null,
|
||||
name: [:0]const u8 = "Worker",
|
||||
cpp_worker: *anyopaque,
|
||||
cpp_worker: *CppWorker,
|
||||
mini: bool,
|
||||
// Most of our code doesn't care whether `eval` was passed, because worker_threads.ts
|
||||
// automatically passes a Blob URL instead of a file path if `eval` is true. But, if `eval` is
|
||||
@@ -44,6 +44,18 @@ execArgv: ?[]const WTFStringImpl,
|
||||
|
||||
/// Used to distinguish between terminate() called by exit(), and terminate() called for other reasons
|
||||
exit_called: bool = false,
|
||||
kind: Kind,
|
||||
|
||||
/// class WebCore::Worker
|
||||
const CppWorker = opaque {};
|
||||
|
||||
/// enum class Kind in WorkerOptions.h
|
||||
pub const Kind = enum(u8) {
|
||||
/// Created by the global Worker constructor
|
||||
web,
|
||||
/// Created by the `require("node:worker_threads").Worker` constructor
|
||||
node,
|
||||
};
|
||||
|
||||
pub const Status = enum(u8) {
|
||||
start,
|
||||
@@ -52,15 +64,10 @@ pub const Status = enum(u8) {
|
||||
terminated,
|
||||
};
|
||||
|
||||
extern fn WebWorker__dispatchExit(?*jsc.JSGlobalObject, *anyopaque, i32) void;
|
||||
extern fn WebWorker__dispatchOnline(cpp_worker: *anyopaque, *jsc.JSGlobalObject) void;
|
||||
extern fn WebWorker__fireEarlyMessages(cpp_worker: *anyopaque, *jsc.JSGlobalObject) void;
|
||||
extern fn WebWorker__dispatchError(*jsc.JSGlobalObject, *anyopaque, bun.String, JSValue) void;
|
||||
|
||||
export fn WebWorker__getParentWorker(vm: *jsc.VirtualMachine) ?*anyopaque {
|
||||
const worker = vm.worker orelse return null;
|
||||
return worker.cpp_worker;
|
||||
}
|
||||
extern fn WebWorker__dispatchExit(?*jsc.JSGlobalObject, *CppWorker, i32) void;
|
||||
extern fn WebWorker__dispatchOnline(cpp_worker: *CppWorker, *jsc.JSGlobalObject) void;
|
||||
extern fn WebWorker__fireEarlyMessages(cpp_worker: *CppWorker, *jsc.JSGlobalObject) void;
|
||||
extern fn WebWorker__dispatchError(*jsc.JSGlobalObject, *CppWorker, bun.String, JSValue) void;
|
||||
|
||||
pub fn hasRequestedTerminate(this: *const WebWorker) bool {
|
||||
return this.requested_terminate.load(.monotonic);
|
||||
@@ -70,7 +77,7 @@ pub fn setRequestedTerminate(this: *WebWorker) bool {
|
||||
return this.requested_terminate.swap(true, .release);
|
||||
}
|
||||
|
||||
export fn WebWorker__updatePtr(worker: *WebWorker, ptr: *anyopaque) bool {
|
||||
export fn WebWorker__updatePtr(worker: *WebWorker, ptr: *CppWorker) bool {
|
||||
worker.cpp_worker = ptr;
|
||||
|
||||
var thread = std.Thread.spawn(
|
||||
@@ -177,7 +184,7 @@ fn resolveEntryPointSpecifier(
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
cpp_worker: *void,
|
||||
cpp_worker: *CppWorker,
|
||||
parent: *jsc.VirtualMachine,
|
||||
name_str: bun.String,
|
||||
specifier_str: bun.String,
|
||||
@@ -194,6 +201,7 @@ pub fn create(
|
||||
execArgv_len: usize,
|
||||
preload_modules_ptr: ?[*]bun.String,
|
||||
preload_modules_len: usize,
|
||||
kind: Kind,
|
||||
) callconv(.c) ?*WebWorker {
|
||||
jsc.markBinding(@src());
|
||||
log("[{d}] WebWorker.create", .{this_context_id});
|
||||
@@ -245,6 +253,7 @@ pub fn create(
|
||||
.argv = if (argv_ptr) |ptr| ptr[0..argv_len] else &.{},
|
||||
.execArgv = if (inherit_execArgv) null else (if (execArgv_ptr) |ptr| ptr[0..execArgv_len] else &.{}),
|
||||
.preloads = preloads.items,
|
||||
.kind = kind,
|
||||
};
|
||||
|
||||
worker.parent_poll_ref.ref(parent);
|
||||
@@ -391,7 +400,6 @@ fn onUnhandledRejection(vm: *jsc.VirtualMachine, globalObject: *jsc.JSGlobalObje
|
||||
var worker = vm.worker orelse @panic("Assertion failure: no worker");
|
||||
|
||||
const writer = buffered_writer.writer();
|
||||
const Writer = @TypeOf(writer);
|
||||
// we buffer this because it'll almost always be < 4096
|
||||
// when it's under 4096, we want to avoid the dynamic allocation
|
||||
jsc.ConsoleObject.format2(
|
||||
@@ -399,8 +407,8 @@ fn onUnhandledRejection(vm: *jsc.VirtualMachine, globalObject: *jsc.JSGlobalObje
|
||||
globalObject,
|
||||
&[_]jsc.JSValue{error_instance},
|
||||
1,
|
||||
Writer,
|
||||
Writer,
|
||||
@TypeOf(writer),
|
||||
@TypeOf(writer),
|
||||
writer,
|
||||
.{
|
||||
.enable_colors = false,
|
||||
|
||||
@@ -35,47 +35,52 @@ export function getStdioWriteStream(
|
||||
fd: number,
|
||||
isTTY: boolean,
|
||||
_fdType: BunProcessStdinFdType,
|
||||
isNodeWorkerThread: boolean,
|
||||
) {
|
||||
$assert(fd === 1 || fd === 2, `Expected fd to be 1 or 2, got ${fd}`);
|
||||
|
||||
let stream;
|
||||
if (isTTY) {
|
||||
const tty = require("node:tty");
|
||||
stream = new tty.WriteStream(fd);
|
||||
// TODO: this is the wrong place for this property.
|
||||
// but the TTY is technically duplex
|
||||
// see test-fs-syncwritestream.js
|
||||
stream.readable = true;
|
||||
process.on("SIGWINCH", () => {
|
||||
stream._refreshSize();
|
||||
});
|
||||
stream._type = "tty";
|
||||
// workers do not handle stdin yet
|
||||
if (!isNodeWorkerThread) {
|
||||
if (isTTY) {
|
||||
const tty = require("node:tty");
|
||||
stream = new tty.WriteStream(fd);
|
||||
// TODO: this is the wrong place for this property.
|
||||
// but the TTY is technically duplex
|
||||
// see test-fs-syncwritestream.js
|
||||
stream.readable = true;
|
||||
process.on("SIGWINCH", () => {
|
||||
stream._refreshSize();
|
||||
});
|
||||
stream._type = "tty";
|
||||
} else {
|
||||
const fs = require("node:fs");
|
||||
stream = new fs.WriteStream(null, { autoClose: false, fd, $fastPath: true });
|
||||
stream.readable = false;
|
||||
stream._type = "fs";
|
||||
}
|
||||
|
||||
if (fd === 1 || fd === 2) {
|
||||
stream.destroySoon = stream.destroy;
|
||||
stream._destroy = function (err, cb) {
|
||||
cb(err);
|
||||
this._undestroy();
|
||||
|
||||
if (!this._writableState.emitClose) {
|
||||
process.nextTick(() => {
|
||||
this.emit("close");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
stream._isStdio = true;
|
||||
stream.fd = fd;
|
||||
} else {
|
||||
const fs = require("node:fs");
|
||||
stream = new fs.WriteStream(null, { autoClose: false, fd, $fastPath: true });
|
||||
stream.readable = false;
|
||||
stream._type = "fs";
|
||||
stream = new (require("internal/worker_threads").WritableWorkerStdio)(fd);
|
||||
}
|
||||
|
||||
if (fd === 1 || fd === 2) {
|
||||
stream.destroySoon = stream.destroy;
|
||||
stream._destroy = function (err, cb) {
|
||||
cb(err);
|
||||
this._undestroy();
|
||||
|
||||
if (!this._writableState.emitClose) {
|
||||
process.nextTick(() => {
|
||||
this.emit("close");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
stream._isStdio = true;
|
||||
stream.fd = fd;
|
||||
|
||||
const underlyingSink = stream[require("internal/fs/streams").kWriteStreamFastPath];
|
||||
$assert(underlyingSink);
|
||||
return [stream, underlyingSink];
|
||||
}
|
||||
|
||||
@@ -84,6 +89,7 @@ export function getStdinStream(
|
||||
fd: number,
|
||||
isTTY: boolean,
|
||||
fdType: BunProcessStdinFdType,
|
||||
isNodeWorkerThread: boolean,
|
||||
) {
|
||||
$assert(fd === 0);
|
||||
const native = Bun.stdin.stream();
|
||||
@@ -126,6 +132,8 @@ export function getStdinStream(
|
||||
}
|
||||
}
|
||||
|
||||
if (isNodeWorkerThread) return new (require("internal/worker_threads").ReadableWorkerStdio)();
|
||||
|
||||
const ReadStream = isTTY ? require("node:tty").ReadStream : require("node:fs").ReadStream;
|
||||
const stream = new ReadStream(null, { fd, autoClose: false });
|
||||
|
||||
@@ -149,7 +157,7 @@ export function getStdinStream(
|
||||
return originalOn.$call(this, event, listener);
|
||||
};
|
||||
|
||||
stream.fd = fd;
|
||||
if (!isNodeWorkerThread) stream.fd = fd;
|
||||
|
||||
// tty.ReadStream is supposed to extend from net.Socket.
|
||||
// but we haven't made that work yet. Until then, we need to manually add some of net.Socket's methods
|
||||
@@ -451,3 +459,17 @@ export function getChannel() {
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function emitWorkerStdioInParent(worker: Worker, fd: number, data: Buffer) {
|
||||
$assert(fd === 1 || fd === 2);
|
||||
const { webWorkerToStdio } = require("internal/worker_threads");
|
||||
const streams = webWorkerToStdio.get(worker);
|
||||
switch (fd) {
|
||||
case 1:
|
||||
streams?.stdout.push(data);
|
||||
break;
|
||||
case 2:
|
||||
streams?.stderr.push(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
86
src/js/internal/worker_threads.ts
Normal file
86
src/js/internal/worker_threads.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
const { Readable, Writable } = require("node:stream");
|
||||
|
||||
const {
|
||||
0: _workerData,
|
||||
1: _threadId,
|
||||
2: _receiveMessageOnPort,
|
||||
3: environmentData,
|
||||
4: pushStdioToParent,
|
||||
5: pushStdinToChild,
|
||||
} = $cpp("Worker.cpp", "createNodeWorkerThreadsBinding") as [
|
||||
unknown,
|
||||
number,
|
||||
(port: unknown) => unknown,
|
||||
Map<unknown, unknown>,
|
||||
(fd: number, data: Buffer) => void,
|
||||
(worker: globalThis.Worker, data: Buffer) => void,
|
||||
];
|
||||
|
||||
// Class exposed as `process.stdout` and `process.stderr` in Worker threads, and `worker.stdin` in the parent thread
|
||||
class WritableWorkerStdio extends Writable {
|
||||
#fd: number;
|
||||
// `undefined` for output streams in the worker thread
|
||||
// Worker instance for stdin stream in the parent thread
|
||||
#worker: Worker | undefined;
|
||||
|
||||
constructor(fd: number, worker?: Worker) {
|
||||
super();
|
||||
$assert(worker === undefined || fd === 0);
|
||||
this.#fd = fd;
|
||||
this.#worker = worker;
|
||||
|
||||
if (worker) {
|
||||
this.on("close", () => {
|
||||
// process.stdin.push(null) in worker
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_write(chunk: unknown, encoding: string, callback: (error?: Error | null) => void): void {
|
||||
$assert(chunk instanceof Buffer);
|
||||
$assert(encoding === "buffer");
|
||||
if (this.#worker) {
|
||||
pushStdinToChild(this.#worker, chunk);
|
||||
} else {
|
||||
pushStdioToParent(this.#fd, chunk);
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// Class exposed as `worker.stdout` and `worker.stderr` in the parent thread, and `process.stdin` in the Worker thread
|
||||
class ReadableWorkerStdio extends Readable {
|
||||
constructor(worker?: Worker) {
|
||||
super();
|
||||
if (worker) {
|
||||
worker.addEventListener("close", () => {
|
||||
this.push(null);
|
||||
});
|
||||
} else {
|
||||
// needs to push null when parent thread calls end() on stdin
|
||||
}
|
||||
}
|
||||
|
||||
_read() {}
|
||||
}
|
||||
|
||||
// Map to access stdio-related options from an internal Web Worker object (not a worker_threads Worker)
|
||||
const webWorkerToStdio = new WeakMap<
|
||||
globalThis.Worker,
|
||||
{
|
||||
// stdout stream exposed in the parent thread
|
||||
stdout: ReadableWorkerStdio;
|
||||
// stderr stream exposed in the parent thread
|
||||
stderr: ReadableWorkerStdio;
|
||||
}
|
||||
>();
|
||||
|
||||
export default {
|
||||
WritableWorkerStdio,
|
||||
ReadableWorkerStdio,
|
||||
_workerData,
|
||||
_threadId,
|
||||
_receiveMessageOnPort,
|
||||
environmentData,
|
||||
webWorkerToStdio,
|
||||
};
|
||||
@@ -21,16 +21,14 @@ const SHARE_ENV = Symbol("nodejs.worker_threads.SHARE_ENV");
|
||||
|
||||
const isMainThread = Bun.isMainThread;
|
||||
const {
|
||||
0: _workerData,
|
||||
1: _threadId,
|
||||
2: _receiveMessageOnPort,
|
||||
3: environmentData,
|
||||
} = $cpp("Worker.cpp", "createNodeWorkerThreadsBinding") as [
|
||||
unknown,
|
||||
number,
|
||||
(port: unknown) => unknown,
|
||||
Map<unknown, unknown>,
|
||||
];
|
||||
_workerData,
|
||||
_threadId,
|
||||
_receiveMessageOnPort,
|
||||
environmentData,
|
||||
webWorkerToStdio,
|
||||
ReadableWorkerStdio,
|
||||
WritableWorkerStdio,
|
||||
} = require("internal/worker_threads");
|
||||
|
||||
type NodeWorkerOptions = import("node:worker_threads").WorkerOptions;
|
||||
|
||||
@@ -223,11 +221,14 @@ function moveMessagePortToContext() {
|
||||
throwNotImplemented("worker_threads.moveMessagePortToContext");
|
||||
}
|
||||
|
||||
const unsupportedOptions = ["stdin", "stdout", "stderr", "trackedUnmanagedFds", "resourceLimits"];
|
||||
const unsupportedOptions = ["trackedUnmanagedFds", "resourceLimits"];
|
||||
|
||||
class Worker extends EventEmitter {
|
||||
#worker: WebWorker;
|
||||
#performance;
|
||||
#stdin: InstanceType<typeof WritableWorkerStdio> | null;
|
||||
#stdout: InstanceType<typeof ReadableWorkerStdio>;
|
||||
#stderr: InstanceType<typeof ReadableWorkerStdio>;
|
||||
|
||||
// this is used by terminate();
|
||||
// either is the exit code if exited, a promise resolving to the exit code, or undefined if we haven't sent .terminate() yet
|
||||
@@ -277,6 +278,23 @@ class Worker extends EventEmitter {
|
||||
}
|
||||
urlRevokeRegistry.register(this.#worker, this.#urlToRevoke);
|
||||
}
|
||||
|
||||
this.#stdout = new ReadableWorkerStdio(this.#worker);
|
||||
this.#stderr = new ReadableWorkerStdio(this.#worker);
|
||||
// TODO maybe bump max listeners?
|
||||
if (!options.stdout) {
|
||||
this.#stdout.pipe(process.stdout);
|
||||
}
|
||||
if (!options.stderr) {
|
||||
this.#stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
if (options.stdin) {
|
||||
this.#stdin = new WritableWorkerStdio(0, this.#worker);
|
||||
} else {
|
||||
this.#stdin = null;
|
||||
}
|
||||
webWorkerToStdio.set(this.#worker, { stdout: this.#stdout, stderr: this.#stderr });
|
||||
}
|
||||
|
||||
get threadId() {
|
||||
@@ -292,18 +310,15 @@ class Worker extends EventEmitter {
|
||||
}
|
||||
|
||||
get stdin() {
|
||||
// TODO:
|
||||
return null;
|
||||
return this.#stdin;
|
||||
}
|
||||
|
||||
get stdout() {
|
||||
// TODO:
|
||||
return null;
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
get stderr() {
|
||||
// TODO:
|
||||
return null;
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
get performance() {
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"yargs": "17.7.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.6.0",
|
||||
"@types/puppeteer": "7.0.4",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
@@ -686,7 +687,7 @@
|
||||
|
||||
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
|
||||
|
||||
"@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
"@types/node": ["@types/node@22.6.0", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ=="],
|
||||
|
||||
"@types/oboe": ["@types/oboe@2.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-bXt4BXSQy0N/buSIak1o0TjYAk2SAeK1aZV9xKcb+xVGWYP8NcMOFy2T7Um3kIvEcQJzrdgJ8R6fpbRcp/LEww=="],
|
||||
|
||||
@@ -2484,7 +2485,7 @@
|
||||
|
||||
"undici": ["undici@5.20.0", "", { "dependencies": { "busboy": "^1.6.0" } }, "sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g=="],
|
||||
|
||||
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
@@ -2694,6 +2695,8 @@
|
||||
|
||||
"@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.7.13", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw=="],
|
||||
|
||||
"@inquirer/core/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@inquirer/core/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
@@ -2790,10 +2793,28 @@
|
||||
|
||||
"@testing-library/react/react": ["react@file:../node_modules/react", {}],
|
||||
|
||||
"@types/cors/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/eslint/@types/estree": ["@types/estree@1.0.5", "", {}, "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="],
|
||||
|
||||
"@types/eslint-scope/@types/estree": ["@types/estree@1.0.5", "", {}, "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="],
|
||||
|
||||
"@types/is-buffer/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/mute-stream/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/oboe/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/superagent/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/utf-8-validate/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/whatwg-url/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/ws/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@types/yauzl/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"@verdaccio/auth/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"@verdaccio/commons-api/http-status-codes": ["http-status-codes@2.2.0", "", {}, "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="],
|
||||
@@ -2890,6 +2911,8 @@
|
||||
|
||||
"browserify-zlib/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"cacache/fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="],
|
||||
|
||||
"cacache/lru-cache": ["lru-cache@10.2.2", "", {}, "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ=="],
|
||||
@@ -2934,6 +2957,8 @@
|
||||
|
||||
"engine.io/@types/cookie": ["@types/cookie@0.4.1", "", {}, "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="],
|
||||
|
||||
"engine.io/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"engine.io/cookie": ["cookie@0.4.2", "", {}, "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="],
|
||||
|
||||
"engine.io/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="],
|
||||
@@ -3002,6 +3027,8 @@
|
||||
|
||||
"jest-diff/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||
|
||||
"jest-worker/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"jsdom/form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
|
||||
@@ -3086,6 +3113,8 @@
|
||||
|
||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"protobufjs/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
|
||||
|
||||
"proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||
@@ -3164,6 +3193,8 @@
|
||||
|
||||
"strip-literal/acorn": ["acorn@8.12.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw=="],
|
||||
|
||||
"stripe/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"superagent/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="],
|
||||
|
||||
"superagent/semver": ["semver@7.6.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w=="],
|
||||
@@ -3188,6 +3219,8 @@
|
||||
|
||||
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||
|
||||
"type-graphql/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"type-graphql/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
|
||||
|
||||
"type-graphql/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -3234,6 +3267,8 @@
|
||||
|
||||
"vite-node/vite": ["vite@4.5.3", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg=="],
|
||||
|
||||
"vitest/@types/node": ["@types/node@20.14.6", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw=="],
|
||||
|
||||
"vitest/acorn": ["acorn@8.12.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw=="],
|
||||
|
||||
"vitest/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="],
|
||||
@@ -3268,6 +3303,8 @@
|
||||
|
||||
"@cypress/request/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
|
||||
|
||||
"@inquirer/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@inquirer/core/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"@inquirer/core/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -3298,6 +3335,24 @@
|
||||
|
||||
"@testing-library/dom/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"@types/cors/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/is-buffer/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/mute-stream/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/oboe/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/superagent/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/utf-8-validate/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/whatwg-url/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@types/yauzl/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"@verdaccio/config/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"@verdaccio/local-storage-legacy/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
@@ -3432,6 +3487,8 @@
|
||||
|
||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"chalk/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"cli-highlight/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -3464,6 +3521,8 @@
|
||||
|
||||
"engine.io-client/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
|
||||
"engine.io/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"engine.io/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
|
||||
"express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
@@ -3492,6 +3551,8 @@
|
||||
|
||||
"jest-diff/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"jest-worker/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"jest-worker/supports-color/has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"jsdom/https-proxy-agent/agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
|
||||
@@ -3512,6 +3573,8 @@
|
||||
|
||||
"peek-stream/duplexify/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"protobufjs/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"proxy-agent/socks-proxy-agent/debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="],
|
||||
|
||||
"pumpify/duplexify/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
@@ -3542,6 +3605,8 @@
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"stripe/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"superagent/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
|
||||
"table/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
@@ -3554,10 +3619,14 @@
|
||||
|
||||
"through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"type-graphql/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"typeorm/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"unstorage/chokidar/readdirp": ["readdirp@4.0.2", "", {}, "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA=="],
|
||||
|
||||
"v8-heapsnapshot/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"verdaccio-audit/express/body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
|
||||
|
||||
"verdaccio-audit/express/cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
|
||||
@@ -3678,6 +3747,8 @@
|
||||
|
||||
"vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.37.0", "", { "os": "win32", "cpu": "x64" }, "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA=="],
|
||||
|
||||
"vitest/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"vitest/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
|
||||
"vitest/magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.4.15", "", {}, "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="],
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { Worker } = require('worker_threads');
|
||||
|
||||
// Like test-async-hooks-worker-promise.js but doing a trivial counter increase
|
||||
// after process.exit(). This should not make a difference, but apparently it
|
||||
// does. This is *also* different from test-async-hooks-worker-promise-3.js,
|
||||
// in that the statement is an ArrayBuffer access rather than a full method,
|
||||
// which *also* makes a difference even though it shouldn’t.
|
||||
|
||||
const workerData = new Int32Array(new SharedArrayBuffer(4));
|
||||
const w = new Worker(`
|
||||
const { createHook } = require('async_hooks');
|
||||
const { workerData } = require('worker_threads');
|
||||
|
||||
setImmediate(async () => {
|
||||
createHook({ init() {} }).enable();
|
||||
await 0;
|
||||
process.exit();
|
||||
workerData[0]++;
|
||||
});
|
||||
`, { eval: true, workerData });
|
||||
|
||||
w.on('exit', common.mustCall(() => assert.strictEqual(workerData[0], 0)));
|
||||
40
test/js/node/test/parallel/test-worker-message-port-drain.js
Normal file
40
test/js/node/test/parallel/test-worker-message-port-drain.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
// This test ensures that the messages from the internal
|
||||
// message port are drained before the call to 'kDispose',
|
||||
// and so all the stdio messages from the worker are processed
|
||||
// in the parent and are pushed to their target streams.
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
Worker,
|
||||
isMainThread,
|
||||
parentPort,
|
||||
threadId,
|
||||
} = require('worker_threads');
|
||||
|
||||
if (isMainThread) {
|
||||
const workerIdsToOutput = new Map();
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const worker = new Worker(__filename, { stdout: true });
|
||||
const workerOutput = [];
|
||||
workerIdsToOutput.set(worker.threadId, workerOutput);
|
||||
worker.on('message', console.log);
|
||||
worker.stdout.on('data', (chunk) => {
|
||||
workerOutput.push(chunk.toString().trim());
|
||||
});
|
||||
}
|
||||
|
||||
process.on('exit', () => {
|
||||
for (const [threadId, workerOutput] of workerIdsToOutput) {
|
||||
assert.ok(workerOutput.includes(`1 threadId: ${threadId}`));
|
||||
assert.ok(workerOutput.includes(`2 threadId: ${threadId}`));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`1 threadId: ${threadId}`);
|
||||
console.log(`2 threadId: ${threadId}`);
|
||||
parentPort.postMessage(Array(100).fill(1));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { Worker, isMainThread } = require('worker_threads');
|
||||
|
||||
// Regression test for https://github.com/nodejs/node/issues/28144.
|
||||
|
||||
if (isMainThread) {
|
||||
const w = new Worker(__filename);
|
||||
w.on('exit', common.mustCall((status) => {
|
||||
assert.strictEqual(status, 0);
|
||||
}));
|
||||
w.stdout.on('data', common.mustCall(10));
|
||||
} else {
|
||||
process.stdin.on('data', () => {});
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
process.stdout.write(`processing(${i})\n`, common.mustSucceed());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { Worker, isMainThread } = require('worker_threads');
|
||||
|
||||
if (isMainThread) {
|
||||
const w = new Worker(__filename, { stdout: true });
|
||||
const expected = 'hello world';
|
||||
|
||||
let data = '';
|
||||
w.stdout.setEncoding('utf8');
|
||||
w.stdout.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
w.on('exit', common.mustCall(() => {
|
||||
assert.strictEqual(data, expected);
|
||||
}));
|
||||
} else {
|
||||
process.stdout.write('hello');
|
||||
process.stdout.write(' ');
|
||||
process.stdout.write('world');
|
||||
process.exit(0);
|
||||
}
|
||||
25
test/js/node/test/parallel/test-worker-stdio-flush.js
Normal file
25
test/js/node/test/parallel/test-worker-stdio-flush.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { Worker, isMainThread } = require('worker_threads');
|
||||
|
||||
if (isMainThread) {
|
||||
const w = new Worker(__filename, { stdout: true });
|
||||
const expected = 'hello world';
|
||||
|
||||
let data = '';
|
||||
w.stdout.setEncoding('utf8');
|
||||
w.stdout.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
w.on('exit', common.mustCall(() => {
|
||||
assert.strictEqual(data, expected);
|
||||
}));
|
||||
} else {
|
||||
process.on('exit', () => {
|
||||
process.stdout.write(' ');
|
||||
process.stdout.write('world');
|
||||
});
|
||||
process.stdout.write('hello');
|
||||
}
|
||||
42
test/js/node/test/parallel/test-worker-stdio.js
Normal file
42
test/js/node/test/parallel/test-worker-stdio.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const { Writable } = require('stream');
|
||||
const { Worker, isMainThread } = require('worker_threads');
|
||||
|
||||
class BufferingWritable extends Writable {
|
||||
constructor() {
|
||||
super();
|
||||
this.chunks = [];
|
||||
}
|
||||
|
||||
_write(chunk, enc, cb) {
|
||||
this.chunks.push(chunk);
|
||||
cb();
|
||||
}
|
||||
|
||||
get buffer() {
|
||||
return Buffer.concat(this.chunks);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
const original = new BufferingWritable();
|
||||
const passed = new BufferingWritable();
|
||||
|
||||
const w = new Worker(__filename, { stdin: true, stdout: true });
|
||||
const source = fs.createReadStream(process.execPath, { end: 1_000_000 });
|
||||
source.pipe(w.stdin);
|
||||
source.pipe(original);
|
||||
w.stdout.pipe(passed);
|
||||
|
||||
passed.on('finish', common.mustCall(() => {
|
||||
assert.strictEqual(original.buffer.compare(passed.buffer), 0,
|
||||
`Original: ${util.inspect(original.buffer)}, ` +
|
||||
`Actual: ${util.inspect(passed.buffer)}`);
|
||||
}));
|
||||
} else {
|
||||
process.stdin.pipe(process.stdout);
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { describe, expect, it, mock, test } from "bun:test";
|
||||
import { bunEnv, bunExe, isDebug } from "harness";
|
||||
import { once } from "node:events";
|
||||
import fs from "node:fs";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import { duplexPair, Readable, Writable } from "node:stream";
|
||||
import wt, {
|
||||
BroadcastChannel,
|
||||
getEnvironmentData,
|
||||
@@ -108,8 +109,10 @@ test("all worker_threads worker instance properties are present", async () => {
|
||||
expect(worker.ref).toBeFunction();
|
||||
expect(worker.unref).toBeFunction();
|
||||
expect(worker.stdin).toBeNull();
|
||||
expect(worker.stdout).toBeNull();
|
||||
expect(worker.stderr).toBeNull();
|
||||
expect(worker.stdout).toBeInstanceOf(Readable);
|
||||
expect(worker.stderr).toBeInstanceOf(Readable);
|
||||
expect(Object.getOwnPropertyDescriptor(Worker.prototype, "stdout")?.get).toBeFunction();
|
||||
expect(Object.getOwnPropertyDescriptor(Worker.prototype, "stderr")?.get).toBeFunction();
|
||||
expect(worker.performance).toBeDefined();
|
||||
expect(worker.terminate).toBeFunction();
|
||||
expect(worker.postMessage).toBeFunction();
|
||||
@@ -280,7 +283,8 @@ describe("execArgv option", async () => {
|
||||
// TODO(@190n) get our handling of non-string array elements in line with Node's
|
||||
});
|
||||
|
||||
test("eval does not leak source code", async () => {
|
||||
// debug builds use way more memory and do not give useful results for this test
|
||||
test.skipIf(isDebug)("eval does not leak source code", async () => {
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "eval-source-leak-fixture.js"],
|
||||
env: bunEnv,
|
||||
@@ -423,6 +427,345 @@ describe("error event", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("stdio", () => {
|
||||
type OutputStream = "stdout" | "stderr";
|
||||
|
||||
function readToEnd(stream: Readable): Promise<string> {
|
||||
let data = "";
|
||||
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
||||
stream.on("error", reject);
|
||||
stream.on("data", chunk => {
|
||||
expect(chunk).toBeInstanceOf(Buffer);
|
||||
data += chunk.toString("utf8");
|
||||
});
|
||||
stream.on("end", () => resolve(data));
|
||||
return promise;
|
||||
}
|
||||
|
||||
function overrideProcessStdio<S extends OutputStream>(which: S, stream: Writable): Disposable {
|
||||
const originalStream = process[which];
|
||||
// stream is missing the `fd` property that the real process streams have, but we
|
||||
// don't need it
|
||||
// @ts-expect-error
|
||||
process[which] = stream;
|
||||
return {
|
||||
[Symbol.dispose]() {
|
||||
process[which] = originalStream;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function captureProcessStdio<S extends OutputStream>(
|
||||
stream: S,
|
||||
): Disposable & { data: Promise<string>; end: () => void } {
|
||||
const [streamToInstall, streamToObserve] = duplexPair();
|
||||
|
||||
return {
|
||||
...overrideProcessStdio(stream, streamToInstall),
|
||||
data: readToEnd(streamToObserve),
|
||||
end: () => streamToInstall.end(),
|
||||
};
|
||||
}
|
||||
|
||||
describe.each<OutputStream>(["stdout", "stderr"])("%s", stream => {
|
||||
it(`process.${stream} written in worker writes to parent process.${stream}`, async () => {
|
||||
using capture = captureProcessStdio(stream);
|
||||
const worker = new Worker(
|
||||
String.raw/* js */ `
|
||||
import assert from "node:assert";
|
||||
process.${stream}.write("hello", (err) => {
|
||||
assert.strictEqual(err, null);
|
||||
process.${stream}.write("\ncallback 1");
|
||||
});
|
||||
// " world"
|
||||
process.${stream}.write(new Uint16Array([0x7720, 0x726f, 0x646c]), (err) => {
|
||||
assert.strictEqual(err, null);
|
||||
process.${stream}.write("\ncallback 2");
|
||||
});
|
||||
`,
|
||||
{ eval: true },
|
||||
);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
capture.end();
|
||||
expect(await capture.data).toBe("hello world\ncallback 1\ncallback 2");
|
||||
});
|
||||
|
||||
it(`process.${stream} written in worker writes to worker.${stream} in parent`, async () => {
|
||||
const worker = new Worker(`process.${stream}.write("hello");`, { eval: true });
|
||||
const resultPromise = readToEnd(worker[stream]);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
expect(await resultPromise).toBe("hello");
|
||||
});
|
||||
|
||||
it(`can still receive data on worker.${stream} if you override it later`, async () => {
|
||||
const worker = new Worker(`process.${stream}.write("hello");`, { eval: true });
|
||||
const resultPromise = readToEnd(worker[stream]);
|
||||
Object.defineProperty(worker, stream, { value: undefined });
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
expect(await resultPromise).toBe("hello");
|
||||
});
|
||||
|
||||
const consoleFunction = stream == "stdout" ? "log" : "error";
|
||||
|
||||
it(`console.${consoleFunction} in worker writes to both streams in parent`, async () => {
|
||||
using capture = captureProcessStdio(stream);
|
||||
const worker = new Worker(`console.${consoleFunction}("hello");`, { eval: true });
|
||||
const resultPromise = readToEnd(worker[stream]);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
capture.end();
|
||||
expect(await capture.data).toBe("hello\n");
|
||||
expect(await resultPromise).toBe("hello\n");
|
||||
});
|
||||
|
||||
describe(`with ${stream}: true option`, () => {
|
||||
it(`writes to worker.${stream} but not process.${stream}`, async () => {
|
||||
using capture = captureProcessStdio(stream);
|
||||
const worker = new Worker(`process.${stream}.write("hello");`, { eval: true, [stream]: true });
|
||||
const resultPromise = readToEnd(worker[stream]);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
capture.end();
|
||||
expect(await capture.data).toBe("");
|
||||
expect(await resultPromise).toBe("hello");
|
||||
});
|
||||
});
|
||||
|
||||
it("worker write() doesn't wait for parent _write() to complete", async () => {
|
||||
const sharedBuffer = new SharedArrayBuffer(4);
|
||||
const sharedArray = new Int32Array(sharedBuffer);
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
|
||||
const writeFn = mock((chunk: Buffer, encoding: string, callback: () => void) => {
|
||||
expect(chunk).toEqual(Buffer.from("hello"));
|
||||
expect(encoding).toBe("buffer");
|
||||
// wait for worker to indicate that its write() callback ran
|
||||
Atomics.wait(sharedArray, 0, 0);
|
||||
// now run the callback
|
||||
callback();
|
||||
// and resolve our promise
|
||||
resolve();
|
||||
});
|
||||
|
||||
class DelayStream extends Writable {
|
||||
_write(data: Buffer, encoding: string, callback: () => void) {
|
||||
return writeFn(data, encoding, callback);
|
||||
}
|
||||
}
|
||||
|
||||
using override = overrideProcessStdio(stream, new DelayStream());
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
import { workerData } from "node:worker_threads";
|
||||
const sharedArray = new Int32Array(workerData);
|
||||
import assert from "node:assert";
|
||||
process.${stream}.write("hello", "utf8", (err) => {
|
||||
assert.strictEqual(err, null);
|
||||
// tell parent that our callback has run
|
||||
Atomics.store(sharedArray, 0, 1);
|
||||
Atomics.notify(sharedArray, 0, 1);
|
||||
});
|
||||
`,
|
||||
{ eval: true, workerData: sharedBuffer },
|
||||
);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
await promise;
|
||||
expect(writeFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`console uses overridden process.${stream} in worker`, async () => {
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
import { Writable } from "node:stream";
|
||||
const original = process.${stream};
|
||||
class WrapStream extends Writable {
|
||||
_write(chunk, encoding, callback) {
|
||||
original.write("[wrapped] " + chunk.toString());
|
||||
callback();
|
||||
}
|
||||
}
|
||||
process.${stream} = new WrapStream();
|
||||
console.${consoleFunction}("hello");
|
||||
`,
|
||||
{ eval: true, [stream]: true },
|
||||
);
|
||||
const resultPromise = readToEnd(worker[stream]);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
expect(await resultPromise).toBe("[wrapped] hello\n");
|
||||
});
|
||||
|
||||
it("has no fd", async () => {
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
import assert from "node:assert";
|
||||
assert.strictEqual(process.${stream}.fd, undefined);
|
||||
`,
|
||||
{ eval: true },
|
||||
);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("console", () => {
|
||||
it("all functions are captured", async () => {
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
console.assert();
|
||||
console.assert(false);
|
||||
// TODO: https://github.com/oven-sh/bun/issues/19953
|
||||
// this should be "Assertion failed: should be true," not "should be true"
|
||||
// but we still want to make sure it is captured in workers
|
||||
console.assert(false, "should be true");
|
||||
console.debug("debug");
|
||||
console.error("error");
|
||||
console.info("info");
|
||||
console.log("log");
|
||||
console.table([{ a: 5 }]);
|
||||
// TODO: https://github.com/oven-sh/bun/issues/19952
|
||||
// this goes to the wrong place but we still want to make sure it is captured in workers
|
||||
console.trace("trace");
|
||||
console.warn("warn");
|
||||
`,
|
||||
{ eval: true, stdout: true, stderr: true },
|
||||
);
|
||||
// normalize the random blob URL and lines and columns from internal modules
|
||||
const stdout = (await readToEnd(worker.stdout))
|
||||
.replace(/blob:[0-9a-f\-]{36}/, "blob:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
|
||||
.replaceAll(/\(\d+:\d+\)$/gm, "(line:col)");
|
||||
const stderr = await readToEnd(worker.stderr);
|
||||
|
||||
let expectedStdout = `debug
|
||||
info
|
||||
log
|
||||
┌───┬───┐
|
||||
│ │ a │
|
||||
├───┼───┤
|
||||
│ 0 │ 5 │
|
||||
└───┴───┘
|
||||
trace
|
||||
at blob:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:15:17
|
||||
at loadAndEvaluateModule (line:col)
|
||||
`;
|
||||
if (isDebug) {
|
||||
expectedStdout += ` at asyncFunctionResume (line:col)
|
||||
at promiseReactionJobWithoutPromiseUnwrapAsyncContext (line:col)
|
||||
at promiseReactionJob (line:col)
|
||||
`;
|
||||
}
|
||||
|
||||
expect(stdout).toBe(expectedStdout);
|
||||
expect(stderr).toBe(`Assertion failed
|
||||
Assertion failed
|
||||
should be true
|
||||
error
|
||||
warn
|
||||
`);
|
||||
});
|
||||
|
||||
it("handles exceptions", async () => {
|
||||
const cases = [
|
||||
{
|
||||
code: /* js */ `process.stdout.write = () => { throw new Error("write()"); }; console.log("hello");`,
|
||||
expectedException: {
|
||||
name: "Error",
|
||||
message: "write()",
|
||||
},
|
||||
},
|
||||
{
|
||||
code: /* js */ `process.stdout.write = 6; console.log("hello");`,
|
||||
expectedException: {
|
||||
name: "TypeError",
|
||||
message: expect.stringMatching(/is not a function.*is 6/),
|
||||
},
|
||||
},
|
||||
{
|
||||
code: /* js */ `Object.defineProperty(process.stdout, "write", { get() { throw new Error("write getter"); } }); console.log("hello");`,
|
||||
expectedException: {
|
||||
name: "Error",
|
||||
message: "write getter",
|
||||
},
|
||||
},
|
||||
{
|
||||
code: /* js */ `Object.defineProperty(process, "stdout", { get() { throw new Error("stdout getter"); } }); console.log("hello");`,
|
||||
expectedException: {
|
||||
name: "Error",
|
||||
message: "stdout getter",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { code, expectedException } of cases) {
|
||||
const worker = new Worker(code, { eval: true, stdout: true });
|
||||
const stdoutPromise = readToEnd(worker.stdout);
|
||||
const [exception] = await once(worker, "error");
|
||||
expect(exception).toMatchObject(expectedException);
|
||||
expect(await stdoutPromise).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
// blocked on more JS internals that use `process` while it has been overridden
|
||||
it.todo("works if the entire process object is overridden", async () => {
|
||||
const worker = new Worker(/* js */ `process = 5; console.log("hello");`, { eval: true, stdout: true });
|
||||
expect(await readToEnd(worker.stdout)).toBe("hello\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("stdin", () => {
|
||||
it("by default, process.stdin is readable and worker.stdin is null", async () => {
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
import assert from "node:assert";
|
||||
assert.strictEqual(process.stdin.constructor.name, "ReadableWorkerStdio");
|
||||
`,
|
||||
{ eval: true },
|
||||
);
|
||||
expect(worker.stdin).toBeNull();
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
});
|
||||
|
||||
it("has no fd", async () => {
|
||||
const worker = new Worker(
|
||||
/* js */ `
|
||||
import assert from "node:assert";
|
||||
assert.strictEqual(process.stdin.fd, undefined);
|
||||
`,
|
||||
{ eval: true },
|
||||
);
|
||||
const [code] = await once(worker, "exit");
|
||||
expect(code).toBe(0);
|
||||
});
|
||||
|
||||
it.todo("does not keep the event loop alive if worker does not listen for events", async () => {});
|
||||
it.todo("hangs if parent does not call end()", async () => {});
|
||||
|
||||
it("child can read data from parent", async () => {
|
||||
const chunks: Buffer[] = [];
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const worker = new Worker("process.stdin.pipe(process.stdout)", { stdin: true, stdout: true, eval: true });
|
||||
expect(worker.stdin!.constructor.name).toBe("WritableWorkerStdio");
|
||||
worker.on("error", reject);
|
||||
worker.stdout.on("data", chunk => {
|
||||
chunks.push(chunk);
|
||||
if (chunks.length == 2) resolve();
|
||||
if (chunks.length > 2) throw new Error("too much data");
|
||||
});
|
||||
worker.stdin!.write("hello");
|
||||
// " world"
|
||||
worker.stdin!.write(new Uint16Array([0x7720, 0x726f, 0x646c]));
|
||||
await promise;
|
||||
expect(chunks).toEqual([Buffer.from("hello"), Buffer.from(" world")]);
|
||||
worker.stdin!.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHeapSnapshot", () => {
|
||||
test("throws if the wrong options are passed", () => {
|
||||
const worker = new Worker("", { eval: true });
|
||||
|
||||
41
test/js/web/workers/worker-fixture-console.js
generated
Normal file
41
test/js/web/workers/worker-fixture-console.js
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Writable } from "node:stream";
|
||||
|
||||
class WrapStream extends Writable {
|
||||
#base;
|
||||
#message;
|
||||
|
||||
constructor(base, message) {
|
||||
super();
|
||||
this.#base = base;
|
||||
this.#message = message;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
const string = chunk.toString("utf8");
|
||||
this.#base.write(`[${this.#message}] ${string}`, "utf8", callback);
|
||||
}
|
||||
}
|
||||
if (Bun.isMainThread) {
|
||||
process.stdout = new WrapStream(process.stdout, "parent process.stdout");
|
||||
process.stderr = new WrapStream(process.stderr, "parent process.stderr");
|
||||
new Worker(import.meta.filename);
|
||||
} else {
|
||||
process.stdout = new WrapStream(process.stdout, "worker process.stdout");
|
||||
process.stderr = new WrapStream(process.stderr, "worker process.stderr");
|
||||
|
||||
console.assert();
|
||||
console.assert(false);
|
||||
// TODO: https://github.com/oven-sh/bun/issues/19953
|
||||
// this should be "Assertion failed: should be true," not "should be true"
|
||||
// but we still want to make sure it is not in workers
|
||||
console.assert(false, "should be true");
|
||||
console.debug("debug");
|
||||
console.error("error");
|
||||
console.info("info");
|
||||
console.log("log");
|
||||
console.table([{ a: 5 }]);
|
||||
// TODO: https://github.com/oven-sh/bun/issues/19952
|
||||
// this goes to the wrong place but we still want to make sure it is not in workers
|
||||
console.trace("trace");
|
||||
console.warn("warn");
|
||||
}
|
||||
26
test/js/web/workers/worker-fixture-process-stdio.js
generated
Normal file
26
test/js/web/workers/worker-fixture-process-stdio.js
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Writable } from "node:stream";
|
||||
|
||||
class WrapStream extends Writable {
|
||||
#base;
|
||||
#message;
|
||||
|
||||
constructor(base, message) {
|
||||
super();
|
||||
this.#base = base;
|
||||
this.#message = message;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
const string = chunk.toString("utf8");
|
||||
this.#base.write(`[${this.#message}] ${string}`, "utf8", callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (Bun.isMainThread) {
|
||||
process.stdout = new WrapStream(process.stdout, "parent process.stdout");
|
||||
process.stderr = new WrapStream(process.stderr, "parent process.stderr");
|
||||
new Worker(import.meta.filename);
|
||||
} else {
|
||||
process.stdout.write("stdout");
|
||||
process.stderr.write("stderr");
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { once } from "events";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { bunEnv, bunExe, isDebug } from "harness";
|
||||
import path from "path";
|
||||
import wt from "worker_threads";
|
||||
|
||||
@@ -296,6 +296,66 @@ describe("web worker", () => {
|
||||
expect(err.error).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("stdio", () => {
|
||||
test("process stdio in worker does not go to process stdio in parent", async () => {
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "worker-fixture-process-stdio.js"],
|
||||
env: bunEnv,
|
||||
cwd: __dirname,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
await proc.exited;
|
||||
expect(proc.exitCode).toBe(0);
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
expect(stdout).toBe("stdout");
|
||||
expect(stderr).toBe("stderr");
|
||||
});
|
||||
|
||||
test("console functions in worker do not go to process stdio in worker or parent", async () => {
|
||||
const proc = Bun.spawn({
|
||||
cmd: [bunExe(), "worker-fixture-console.js"],
|
||||
env: bunEnv,
|
||||
cwd: __dirname,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
await proc.exited;
|
||||
expect(proc.exitCode).toBe(0);
|
||||
// normalize lines and columns from internal modules
|
||||
const stdout = (await new Response(proc.stdout).text()).replaceAll(/\(\d+:\d+\)$/gm, "(line:col)");
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
|
||||
let expectedStdout = `debug
|
||||
info
|
||||
log
|
||||
┌───┬───┐
|
||||
│ │ a │
|
||||
├───┼───┤
|
||||
│ 0 │ 5 │
|
||||
└───┴───┘
|
||||
trace
|
||||
at ${__dirname}${path.sep}worker-fixture-console.js:39:11
|
||||
at loadAndEvaluateModule (line:col)
|
||||
`;
|
||||
if (isDebug) {
|
||||
expectedStdout += ` at asyncFunctionResume (line:col)
|
||||
at promiseReactionJobWithoutPromiseUnwrapAsyncContext (line:col)
|
||||
at promiseReactionJob (line:col)
|
||||
`;
|
||||
}
|
||||
|
||||
expect(stdout).toBe(expectedStdout);
|
||||
expect(stderr).toBe(`Assertion failed
|
||||
Assertion failed
|
||||
should be true
|
||||
error
|
||||
warn
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: move to node:worker_threads tests directory
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"@types/supertest": "2.0.12",
|
||||
"@types/utf-8-validate": "5.0.0",
|
||||
"@types/ws": "8.5.10",
|
||||
"@types/puppeteer": "7.0.4"
|
||||
"@types/puppeteer": "7.0.4",
|
||||
"@types/node": "22.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "9.1.3",
|
||||
|
||||
Reference in New Issue
Block a user