Skip to content

Commit

Permalink
feat: disposable Deno resources
Browse files Browse the repository at this point in the history
This commit implements Symbol.dispose and Symbol.asyncDispose for
the relevant resources.
  • Loading branch information
lucacasonato committed Oct 9, 2023
1 parent ae81065 commit e2d11ca
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 27 deletions.
6 changes: 5 additions & 1 deletion ext/fs/30_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
ReadableStreamPrototype,
writableStreamForRid,
} from "ext:deno_web/06_streams.js";
import { pathFromURL } from "ext:deno_web/00_infra.js";
import { pathFromURL, SymbolDispose } from "ext:deno_web/00_infra.js";

function chmodSync(path, mode) {
ops.op_fs_chmod_sync(pathFromURL(path), mode);
Expand Down Expand Up @@ -669,6 +669,10 @@ class FsFile {
}
return this.#writable;
}

[SymbolDispose]() {
core.tryClose(this.rid);
}
}

function checkOpenOptions(options) {
Expand Down
22 changes: 15 additions & 7 deletions ext/http/00_serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from "ext:deno_web/06_streams.js";
import { listen, listenOptionApiName, TcpConn } from "ext:deno_net/01_net.js";
import { listenTls } from "ext:deno_net/02_tls.js";
import { SymbolAsyncDispose } from "ext:deno_web/00_infra.js";
const {
ArrayPrototypePush,
ObjectHasOwn,
Expand Down Expand Up @@ -343,6 +344,7 @@ class CallbackContext {
fallbackHost;
serverRid;
closed;
/** @type {Promise<void> | undefined} */
closing;
listener;

Expand Down Expand Up @@ -682,22 +684,25 @@ function serveHttpOn(context, callback) {
PromisePrototypeCatch(callback(req), promiseErrorHandler);
}

if (!context.closed && !context.closing) {
context.closed = true;
await op_http_close(rid, false);
if (!context.closing && !context.closed) {
context.closing = op_http_close(rid, false);
context.close();
}

await context.closing;
context.close();
context.closed = true;
})();

return {
finished,
async shutdown() {
if (!context.closed && !context.closing) {
if (!context.closing && !context.closed) {
// Shut this HTTP server down gracefully
context.closing = true;
await op_http_close(context.serverRid, true);
context.closed = true;
context.closing = op_http_close(rid, true);
}
await context.closing;
context.closed = true;
},
ref() {
ref = true;
Expand All @@ -711,6 +716,9 @@ function serveHttpOn(context, callback) {
core.unrefOp(currentPromise[promiseIdSymbol]);
}
},
[SymbolAsyncDispose]() {
return this.shutdown();
},
};
}

Expand Down
27 changes: 22 additions & 5 deletions ext/http/01_http.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
ReadableStreamPrototype,
} from "ext:deno_web/06_streams.js";
import { serve } from "ext:deno_http/00_serve.js";
import { SymbolDispose } from "ext:deno_web/00_infra.js";
const {
ArrayPrototypeIncludes,
ArrayPrototypeMap,
Expand All @@ -69,6 +70,9 @@ const {
const connErrorSymbol = Symbol("connError");
const _deferred = Symbol("upgradeHttpDeferred");

/** @type {(self: HttpConn, rid: number) => boolean} */
let deleteManagedResource;

class HttpConn {
#rid = 0;
#closed = false;
Expand All @@ -79,7 +83,12 @@ class HttpConn {
// that were created during lifecycle of this request.
// When the connection is closed these resources should be closed
// as well.
managedResources = new SafeSet();
#managedResources = new SafeSet();

static {
deleteManagedResource = (self, rid) =>
SetPrototypeDelete(self.#managedResources, rid);
}

constructor(rid, remoteAddr, localAddr) {
this.#rid = rid;
Expand Down Expand Up @@ -123,7 +132,7 @@ class HttpConn {
}

const { 0: streamRid, 1: method, 2: url } = nextRequest;
SetPrototypeAdd(this.managedResources, streamRid);
SetPrototypeAdd(this.#managedResources, streamRid);

/** @type {ReadableStream<Uint8Array> | undefined} */
let body = null;
Expand Down Expand Up @@ -167,13 +176,21 @@ class HttpConn {
if (!this.#closed) {
this.#closed = true;
core.close(this.#rid);
for (const rid of new SafeSetIterator(this.managedResources)) {
SetPrototypeDelete(this.managedResources, rid);
for (const rid of new SafeSetIterator(this.#managedResources)) {
SetPrototypeDelete(this.#managedResources, rid);
core.close(rid);
}
}
}

[SymbolDispose]() {
core.tryClose(this.#rid);
for (const rid of new SafeSetIterator(this.#managedResources)) {
SetPrototypeDelete(this.#managedResources, rid);
core.tryClose(rid);
}
}

[SymbolAsyncIterator]() {
// deno-lint-ignore no-this-alias
const httpConn = this;
Expand Down Expand Up @@ -395,7 +412,7 @@ function createRespondWith(
abortController.abort(error);
throw error;
} finally {
if (SetPrototypeDelete(httpConn.managedResources, streamRid)) {
if (deleteManagedResource(httpConn, streamRid)) {
core.close(streamRid);
}
}
Expand Down
5 changes: 5 additions & 0 deletions ext/io/12_io.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
readableStreamForRid,
writableStreamForRid,
} from "ext:deno_web/06_streams.js";
import { SymbolDispose } from "ext:deno_web/00_infra.js";
const {
Uint8Array,
ArrayPrototypePush,
Expand Down Expand Up @@ -255,6 +256,10 @@ class Stdin {
const cbreak = !!(options.cbreak ?? false);
ops.op_stdin_set_raw(mode, cbreak);
}

[SymbolDispose]() {
core.tryClose(this.rid);
}
}

class Stdout {
Expand Down
5 changes: 5 additions & 0 deletions ext/kv/01_db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const {
SymbolToStringTag,
Uint8ArrayPrototype,
} = globalThis.__bootstrap.primordials;
import { SymbolDispose } from "ext:deno_web/00_infra.js";
const core = Deno.core;
const ops = core.ops;

Expand Down Expand Up @@ -299,6 +300,10 @@ class Kv {
close() {
core.close(this.#rid);
}

[SymbolDispose]() {
core.tryClose(this.#rid);
}
}

class AtomicOperation {
Expand Down
2 changes: 1 addition & 1 deletion ext/kv/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const MAX_TOTAL_MUTATION_SIZE_BYTES: usize = 800 * 1024;
const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024;

deno_core::extension!(deno_kv,
deps = [ deno_console ],
deps = [ deno_console, deno_web ],
parameters = [ DBH: DatabaseHandler ],
ops = [
op_kv_database_open<DBH>,
Expand Down
10 changes: 10 additions & 0 deletions ext/net/01_net.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
writableStreamForRid,
} from "ext:deno_web/06_streams.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
import { SymbolDispose } from "ext:deno_web/00_infra.js";

const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeFilter,
Expand Down Expand Up @@ -160,6 +162,10 @@ class Conn {
(id) => core.unrefOp(id),
);
}

[SymbolDispose]() {
core.tryClose(this.#rid);
}
}

class TcpConn extends Conn {
Expand Down Expand Up @@ -249,6 +255,10 @@ class Listener {
core.close(this.rid);
}

[SymbolDispose]() {
core.tryClose(this.#rid);
}

[SymbolAsyncIterator]() {
return this;
}
Expand Down
5 changes: 5 additions & 0 deletions ext/web/00_infra.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ function pathFromURL(pathOrUrl) {
// it in unit tests
internals.pathFromURL = pathFromURL;

// deno-lint-ignore prefer-primordials
export const SymbolDispose = Symbol.dispose ?? Symbol("Symbol.dispose");
// deno-lint-ignore prefer-primordials
export const SymbolAsyncDispose = Symbol.asyncDispose ?? Symbol("Symbol.asyncDispose");

export {
ASCII_ALPHA,
ASCII_ALPHANUMERIC,
Expand Down
6 changes: 6 additions & 0 deletions runtime/js/40_fs_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const {
PromiseResolve,
SymbolAsyncIterator,
} = primordials;
import { SymbolDispose } from "ext:deno_web/00_infra.js";

class FsWatcher {
#rid = 0;

Expand Down Expand Up @@ -51,6 +53,10 @@ class FsWatcher {
[SymbolAsyncIterator]() {
return this;
}

[SymbolDispose]() {
core.tryClose(this.#rid);
}
}

function watchFs(
Expand Down
18 changes: 10 additions & 8 deletions runtime/js/40_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {
} = primordials;
import { FsFile } from "ext:deno_fs/30_fs.js";
import { readAll } from "ext:deno_io/12_io.js";
import { assert, pathFromURL } from "ext:deno_web/00_infra.js";
import { assert, pathFromURL, SymbolAsyncDispose } from "ext:deno_web/00_infra.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
import {
readableStreamCollectIntoUint8Array,
Expand Down Expand Up @@ -201,7 +201,6 @@ class ChildProcess {
#rid;
#waitPromiseId;
#waitComplete = false;
#unrefed = false;

#pid;
get pid() {
Expand All @@ -216,7 +215,6 @@ class ChildProcess {
return this.#stdin;
}

#stdoutRid;
#stdout = null;
get stdout() {
if (this.#stdout == null) {
Expand All @@ -225,7 +223,6 @@ class ChildProcess {
return this.#stdout;
}

#stderrRid;
#stderr = null;
get stderr() {
if (this.#stderr == null) {
Expand Down Expand Up @@ -254,12 +251,10 @@ class ChildProcess {
}

if (stdoutRid !== null) {
this.#stdoutRid = stdoutRid;
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
}

if (stderrRid !== null) {
this.#stderrRid = stderrRid;
this.#stderr = readableStreamForRidUnrefable(stderrRid);
}

Expand Down Expand Up @@ -324,15 +319,22 @@ class ChildProcess {
ops.op_spawn_kill(this.#rid, signo);
}

async [SymbolAsyncDispose]() {
try {
ops.op_spawn_kill(this.#rid, "SIGTERM")
} catch {
// ignore errors from killing the process (such as ESRCH or BadResource)
}
await this.#status;
}

ref() {
this.#unrefed = false;
core.refOp(this.#waitPromiseId);
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
}

unref() {
this.#unrefed = true;
core.unrefOp(this.#waitPromiseId);
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
Expand Down
22 changes: 17 additions & 5 deletions runtime/js/99_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,23 @@ import {
windowOrWorkerGlobalScope,
workerRuntimeGlobalProperties,
} from "ext:runtime/98_global_scope.js";

// deno-lint-ignore prefer-primordials
Symbol.dispose ??= Symbol("Symbol.dispose");
// deno-lint-ignore prefer-primordials
Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");
import { SymbolAsyncDispose, SymbolDispose } from "ext:deno_web/00_infra.js";

if (Symbol.dispose) throw "V8 supports Symbol.dispose now, no need to shim it!";
ObjectDefineProperties(Symbol, {
dispose: {
value: SymbolAsyncDispose,
enumerable: false,
writable: false,
configurable: false,
},
asyncDispose: {
value: SymbolDispose,
enumerable: false,
writable: false,
configurable: false,
},
});

let windowIsClosing = false;
let globalThis_;
Expand Down

0 comments on commit e2d11ca

Please sign in to comment.