add [Symbol.dispose] in some bun apis (#10818)

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
dave caruso
2024-05-06 19:49:23 -07:00
committed by GitHub
parent 5cbb1ba96a
commit f9be0bef45
10 changed files with 102 additions and 2 deletions

View File

@@ -96,6 +96,10 @@ export default [
fn: "kill",
length: 1,
},
"@@asyncDispose": {
fn: "asyncDispose",
length: 1,
},
killed: {
getter: "getKilled",

View File

@@ -574,6 +574,34 @@ pub const Subprocess = struct {
return this.stdout.toJS(globalThis, this.hasExited());
}
pub fn asyncDispose(
this: *Subprocess,
global: *JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSValue {
if (this.process.hasExited()) {
// rely on GC to clean everything up in this case
return .undefined;
}
// unref streams so that this disposed process will not prevent
// the process from exiting causing a hang
this.stdin.unref();
this.stdout.unref();
this.stderr.unref();
switch (this.tryKill(SignalCode.default)) {
.result => {},
.err => |err| {
// Signal 9 should always be fine, but just in case that somehow fails.
global.throwValue(err.toJSC(global));
return .zero;
},
}
return this.getExited(global);
}
pub fn kill(
this: *Subprocess,
globalThis: *JSGlobalObject,

View File

@@ -20,6 +20,10 @@ function generate(name) {
fn: "doReload",
length: 2,
},
"@@dispose": {
fn: "dispose",
length: 0,
},
stop: {
fn: "doStop",
length: 1,

View File

@@ -5122,6 +5122,7 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
} = .{},
pub const doStop = JSC.wrapInstanceMethod(ThisServer, "stopFromJS", false);
pub const dispose = JSC.wrapInstanceMethod(ThisServer, "disposeFromJS", false);
pub const doUpgrade = JSC.wrapInstanceMethod(ThisServer, "onUpgrade", false);
pub const doPublish = JSC.wrapInstanceMethod(ThisServer, "publish", false);
pub const doReload = onReload;
@@ -5547,6 +5548,16 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
return JSC.JSValue.jsUndefined();
}
pub fn disposeFromJS(this: *ThisServer) JSC.JSValue {
if (this.listener != null) {
JSC.C.JSValueUnprotect(this.globalThis, this.thisObject.asObjectRef());
this.thisObject = JSC.JSValue.jsUndefined();
this.stop(true);
}
return JSC.JSValue.jsUndefined();
}
pub fn getPort(
this: *ThisServer,
_: *JSC.JSGlobalObject,

View File

@@ -111,6 +111,11 @@ function generate(ssl) {
length: 0,
},
"@@dispose": {
fn: "shutdown",
length: 0,
},
shutdown: {
fn: "shutdown",
length: 1,
@@ -181,6 +186,10 @@ export default [
fn: "stop",
length: 1,
},
"@@dispose": {
fn: "stop",
length: 0,
},
ref: {
fn: "ref",
@@ -239,6 +248,10 @@ export default [
fn: "close",
length: 0,
},
"@@dispose": {
fn: "close",
length: 0,
},
reload: {
fn: "reload",
length: 1,

View File

@@ -106,6 +106,10 @@ class JSCallback {
closeCallback(ctx);
}
}
[Symbol.dispose]() {
this.close();
}
}
class CString extends String {

View File

@@ -995,6 +995,12 @@ class ChildProcess extends EventEmitter {
pid;
channel;
[Symbol.dispose]() {
if (!this.killed) {
this.kill();
}
}
get killed() {
if (this.#handle == null) return false;
}
@@ -1310,7 +1316,7 @@ class ChildProcess extends EventEmitter {
this.#handle.disconnect();
}
kill(sig) {
kill(sig?) {
const signal = sig === 0 ? sig : convertToValidSignal(sig === undefined ? "SIGTERM" : sig);
if (this.#handle) {

View File

@@ -1513,3 +1513,18 @@ it("should resolve pending promise if requested ended with pending read", async
},
);
});
it("should work with dispose keyword", async () => {
let url: string;
{
using server = Bun.serve({
port: 0,
fetch() {
return new Response("OK");
},
});
url = server.url;
expect((await fetch(url)).status).toBe(200);
}
expect(fetch(url)).rejects.toThrow();
});

View File

@@ -782,3 +782,18 @@ describe("close handling", () => {
}
}
});
it("dispose keyword works", async () => {
let captured;
{
await using proc = spawn({
cmd: [bunExe(), "-e", "await Bun.sleep(100000)"],
});
captured = proc;
await Bun.sleep(100);
}
await Bun.sleep(0);
expect(captured.killed).toBe(true);
expect(captured.exitCode).toBe(null);
expect(captured.signalCode).toBe("SIGTERM");
});