mirror of
https://github.com/denoland/deno.git
synced 2025-08-01 09:32:28 +00:00
feat: disposable Deno resources (#20845)
This commit implements Symbol.dispose and Symbol.asyncDispose for the relevant resources. Closes #20839 --------- Signed-off-by: Bartek Iwańczuk <biwanczuk@gmail.com> Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
1d19b1011b
commit
d42f154312
21 changed files with 342 additions and 35 deletions
|
@ -256,6 +256,46 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { run: true, read: true } },
|
||||||
|
// deno lint bug, see https://github.com/denoland/deno_lint/issues/1206
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async function childProcessExplicitResourceManagement() {
|
||||||
|
let dead = undefined;
|
||||||
|
{
|
||||||
|
const command = new Deno.Command(Deno.execPath(), {
|
||||||
|
args: ["eval", "setTimeout(() => {}, 10000)"],
|
||||||
|
stdout: "null",
|
||||||
|
stderr: "null",
|
||||||
|
});
|
||||||
|
await using child = command.spawn();
|
||||||
|
child.status.then(({ signal }) => {
|
||||||
|
dead = signal;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Deno.build.os == "windows") {
|
||||||
|
assertEquals(dead, null);
|
||||||
|
} else {
|
||||||
|
assertEquals(dead, "SIGTERM");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { run: true, read: true } },
|
||||||
|
async function childProcessExplicitResourceManagementManualClose() {
|
||||||
|
const command = new Deno.Command(Deno.execPath(), {
|
||||||
|
args: ["eval", "setTimeout(() => {}, 10000)"],
|
||||||
|
stdout: "null",
|
||||||
|
stderr: "null",
|
||||||
|
});
|
||||||
|
await using child = command.spawn();
|
||||||
|
child.kill("SIGTERM");
|
||||||
|
await child.status;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { run: true, read: true } },
|
{ permissions: { run: true, read: true } },
|
||||||
async function commandKillFailed() {
|
async function commandKillFailed() {
|
||||||
|
|
|
@ -824,3 +824,30 @@ Deno.test(
|
||||||
assertEquals(res, "hello \uFFFD");
|
assertEquals(res, "hello \uFFFD");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true } },
|
||||||
|
async function fsFileExplicitResourceManagement() {
|
||||||
|
let file2: Deno.FsFile;
|
||||||
|
|
||||||
|
{
|
||||||
|
using file = await Deno.open("cli/tests/testdata/assets/hello.txt");
|
||||||
|
file2 = file;
|
||||||
|
|
||||||
|
const stat = file.statSync();
|
||||||
|
assert(stat.isFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(() => file2.statSync(), Deno.errors.BadResource);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true } },
|
||||||
|
async function fsFileExplicitResourceManagementManualClose() {
|
||||||
|
using file = await Deno.open("cli/tests/testdata/assets/hello.txt");
|
||||||
|
file.close();
|
||||||
|
assertThrows(() => file.statSync(), Deno.errors.BadResource); // definitely closed
|
||||||
|
// calling [Symbol.dispose] after manual close is a no-op
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -107,3 +107,33 @@ Deno.test(
|
||||||
assertEquals(events, []);
|
assertEquals(events, []);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true, write: true } },
|
||||||
|
async function watchFsExplicitResourceManagement() {
|
||||||
|
let res;
|
||||||
|
{
|
||||||
|
const testDir = await makeTempDir();
|
||||||
|
using iter = Deno.watchFs(testDir);
|
||||||
|
|
||||||
|
res = iter[Symbol.asyncIterator]().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { done } = await res;
|
||||||
|
assert(done);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true, write: true } },
|
||||||
|
async function watchFsExplicitResourceManagementManualClose() {
|
||||||
|
const testDir = await makeTempDir();
|
||||||
|
using iter = Deno.watchFs(testDir);
|
||||||
|
|
||||||
|
const res = iter[Symbol.asyncIterator]().next();
|
||||||
|
|
||||||
|
iter.close();
|
||||||
|
const { done } = await res;
|
||||||
|
assert(done);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -2817,6 +2817,24 @@ Deno.test({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
async function httpConnExplicitResourceManagement() {
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
{
|
||||||
|
const listen = Deno.listen({ port: listenPort });
|
||||||
|
promise = fetch(`http://localhost:${listenPort}/`).catch(() => null);
|
||||||
|
const serverConn = await listen.accept();
|
||||||
|
listen.close();
|
||||||
|
|
||||||
|
using _httpConn = Deno.serveHttp(serverConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await promise;
|
||||||
|
assertEquals(response, null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
||||||
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
||||||
const tp = new TextProtoReader(r);
|
const tp = new TextProtoReader(r);
|
||||||
|
|
|
@ -2100,3 +2100,30 @@ Deno.test({
|
||||||
db.close();
|
db.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true } },
|
||||||
|
async function kvExplicitResourceManagement() {
|
||||||
|
let kv2: Deno.Kv;
|
||||||
|
|
||||||
|
{
|
||||||
|
using kv = await Deno.openKv(":memory:");
|
||||||
|
kv2 = kv;
|
||||||
|
|
||||||
|
const res = await kv.get(["a"]);
|
||||||
|
assertEquals(res.versionstamp, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertRejects(() => kv2.get(["a"]), Deno.errors.BadResource);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { read: true } },
|
||||||
|
async function kvExplicitResourceManagementManualClose() {
|
||||||
|
using kv = await Deno.openKv(":memory:");
|
||||||
|
kv.close();
|
||||||
|
await assertRejects(() => kv.get(["a"]), Deno.errors.BadResource);
|
||||||
|
// calling [Symbol.dispose] after manual close is a no-op
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -1242,3 +1242,34 @@ Deno.test({
|
||||||
const listener = Deno.listen({ hostname: "localhost", port: "0" });
|
const listener = Deno.listen({ hostname: "localhost", port: "0" });
|
||||||
listener.close();
|
listener.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true } },
|
||||||
|
async function listenerExplicitResourceManagement() {
|
||||||
|
let done: Promise<Deno.errors.BadResource>;
|
||||||
|
|
||||||
|
{
|
||||||
|
using listener = Deno.listen({ port: listenPort });
|
||||||
|
|
||||||
|
done = assertRejects(
|
||||||
|
() => listener.accept(),
|
||||||
|
Deno.errors.BadResource,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await done;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true } },
|
||||||
|
async function listenerExplicitResourceManagementManualClose() {
|
||||||
|
using listener = Deno.listen({ port: listenPort });
|
||||||
|
listener.close();
|
||||||
|
await assertRejects( // definitely closed
|
||||||
|
() => listener.accept(),
|
||||||
|
Deno.errors.BadResource,
|
||||||
|
);
|
||||||
|
// calling [Symbol.dispose] after manual close is a no-op
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -48,7 +48,12 @@ function onListen<T>(
|
||||||
async function makeServer(
|
async function makeServer(
|
||||||
handler: (req: Request) => Response | Promise<Response>,
|
handler: (req: Request) => Response | Promise<Response>,
|
||||||
): Promise<
|
): Promise<
|
||||||
{ finished: Promise<void>; abort: () => void; shutdown: () => Promise<void> }
|
{
|
||||||
|
finished: Promise<void>;
|
||||||
|
abort: () => void;
|
||||||
|
shutdown: () => Promise<void>;
|
||||||
|
[Symbol.asyncDispose](): PromiseLike<void>;
|
||||||
|
}
|
||||||
> {
|
> {
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const listeningPromise = deferred();
|
const listeningPromise = deferred();
|
||||||
|
@ -69,6 +74,9 @@ async function makeServer(
|
||||||
async shutdown() {
|
async shutdown() {
|
||||||
await server.shutdown();
|
await server.shutdown();
|
||||||
},
|
},
|
||||||
|
[Symbol.asyncDispose]() {
|
||||||
|
return server[Symbol.asyncDispose]();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +304,42 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true, write: true, read: true } },
|
||||||
|
async function httpServerExplicitResourceManagement() {
|
||||||
|
let dataPromise;
|
||||||
|
|
||||||
|
{
|
||||||
|
await using _server = await makeServer(async (_req) => {
|
||||||
|
return new Response((await makeTempFile(1024 * 1024)).readable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resp = await fetch(`http://localhost:${servePort}`);
|
||||||
|
dataPromise = resp.arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals((await dataPromise).byteLength, 1048576);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
{ permissions: { net: true, write: true, read: true } },
|
||||||
|
async function httpServerExplicitResourceManagementManualClose() {
|
||||||
|
await using server = await makeServer(async (_req) => {
|
||||||
|
return new Response((await makeTempFile(1024 * 1024)).readable);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resp = await fetch(`http://localhost:${servePort}`);
|
||||||
|
|
||||||
|
const [_, data] = await Promise.all([
|
||||||
|
server.shutdown(),
|
||||||
|
resp.arrayBuffer(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertEquals(data.byteLength, 1048576);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Deno.test(
|
Deno.test(
|
||||||
{ permissions: { read: true, run: true } },
|
{ permissions: { read: true, run: true } },
|
||||||
async function httpServerUnref() {
|
async function httpServerUnref() {
|
||||||
|
|
|
@ -1138,6 +1138,7 @@ delete Object.prototype.__proto__;
|
||||||
`${ASSETS_URL_PREFIX}${specifier}`,
|
`${ASSETS_URL_PREFIX}${specifier}`,
|
||||||
ts.ScriptTarget.ESNext,
|
ts.ScriptTarget.ESNext,
|
||||||
),
|
),
|
||||||
|
`failed to load '${ASSETS_URL_PREFIX}${specifier}'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// this helps ensure as much as possible is in memory that is re-usable
|
// this helps ensure as much as possible is in memory that is re-usable
|
||||||
|
@ -1148,7 +1149,10 @@ delete Object.prototype.__proto__;
|
||||||
options: SNAPSHOT_COMPILE_OPTIONS,
|
options: SNAPSHOT_COMPILE_OPTIONS,
|
||||||
host,
|
host,
|
||||||
});
|
});
|
||||||
assert(ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0);
|
assert(
|
||||||
|
ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0,
|
||||||
|
"lib.d.ts files have errors",
|
||||||
|
);
|
||||||
|
|
||||||
// remove this now that we don't need it anymore for warming up tsc
|
// remove this now that we don't need it anymore for warming up tsc
|
||||||
sourceFileCache.delete(buildSpecifier);
|
sourceFileCache.delete(buildSpecifier);
|
||||||
|
|
16
cli/tsc/dts/lib.deno.ns.d.ts
vendored
16
cli/tsc/dts/lib.deno.ns.d.ts
vendored
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
/// <reference no-default-lib="true" />
|
/// <reference no-default-lib="true" />
|
||||||
/// <reference lib="esnext" />
|
/// <reference lib="esnext" />
|
||||||
|
/// <reference lib="esnext.disposable" />
|
||||||
/// <reference lib="deno.net" />
|
/// <reference lib="deno.net" />
|
||||||
|
|
||||||
/** Deno provides extra properties on `import.meta`. These are included here
|
/** Deno provides extra properties on `import.meta`. These are included here
|
||||||
|
@ -2177,7 +2178,8 @@ declare namespace Deno {
|
||||||
WriterSync,
|
WriterSync,
|
||||||
Seeker,
|
Seeker,
|
||||||
SeekerSync,
|
SeekerSync,
|
||||||
Closer {
|
Closer,
|
||||||
|
Disposable {
|
||||||
/** The resource ID associated with the file instance. The resource ID
|
/** The resource ID associated with the file instance. The resource ID
|
||||||
* should be considered an opaque reference to resource. */
|
* should be considered an opaque reference to resource. */
|
||||||
readonly rid: number;
|
readonly rid: number;
|
||||||
|
@ -2451,6 +2453,8 @@ declare namespace Deno {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
close(): void;
|
close(): void;
|
||||||
|
|
||||||
|
[Symbol.dispose](): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3831,7 +3835,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category File System
|
* @category File System
|
||||||
*/
|
*/
|
||||||
export interface FsWatcher extends AsyncIterable<FsEvent> {
|
export interface FsWatcher extends AsyncIterable<FsEvent>, Disposable {
|
||||||
/** The resource id. */
|
/** The resource id. */
|
||||||
readonly rid: number;
|
readonly rid: number;
|
||||||
/** Stops watching the file system and closes the watcher resource. */
|
/** Stops watching the file system and closes the watcher resource. */
|
||||||
|
@ -4284,7 +4288,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category Sub Process
|
* @category Sub Process
|
||||||
*/
|
*/
|
||||||
export class ChildProcess {
|
export class ChildProcess implements Disposable {
|
||||||
get stdin(): WritableStream<Uint8Array>;
|
get stdin(): WritableStream<Uint8Array>;
|
||||||
get stdout(): ReadableStream<Uint8Array>;
|
get stdout(): ReadableStream<Uint8Array>;
|
||||||
get stderr(): ReadableStream<Uint8Array>;
|
get stderr(): ReadableStream<Uint8Array>;
|
||||||
|
@ -4307,6 +4311,8 @@ declare namespace Deno {
|
||||||
/** Ensure that the status of the child process does not block the Deno
|
/** Ensure that the status of the child process does not block the Deno
|
||||||
* process from exiting. */
|
* process from exiting. */
|
||||||
unref(): void;
|
unref(): void;
|
||||||
|
|
||||||
|
[Symbol.dispose](): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5258,7 +5264,7 @@ declare namespace Deno {
|
||||||
* requests on the HTTP server connection.
|
* requests on the HTTP server connection.
|
||||||
*
|
*
|
||||||
* @category HTTP Server */
|
* @category HTTP Server */
|
||||||
export interface HttpConn extends AsyncIterable<RequestEvent> {
|
export interface HttpConn extends AsyncIterable<RequestEvent>, Disposable {
|
||||||
/** The resource ID associated with this connection. Generally users do not
|
/** The resource ID associated with this connection. Generally users do not
|
||||||
* need to be aware of this identifier. */
|
* need to be aware of this identifier. */
|
||||||
readonly rid: number;
|
readonly rid: number;
|
||||||
|
@ -5911,7 +5917,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category HTTP Server
|
* @category HTTP Server
|
||||||
*/
|
*/
|
||||||
export interface HttpServer {
|
export interface HttpServer extends AsyncDisposable {
|
||||||
/** A promise that resolves once server finishes - eg. when aborted using
|
/** A promise that resolves once server finishes - eg. when aborted using
|
||||||
* the signal passed to {@linkcode ServeOptions.signal}.
|
* the signal passed to {@linkcode ServeOptions.signal}.
|
||||||
*/
|
*/
|
||||||
|
|
4
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
4
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
|
@ -1749,7 +1749,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category KV
|
* @category KV
|
||||||
*/
|
*/
|
||||||
export class Kv {
|
export class Kv implements Disposable {
|
||||||
/**
|
/**
|
||||||
* Retrieve the value and versionstamp for the given key from the database
|
* Retrieve the value and versionstamp for the given key from the database
|
||||||
* in the form of a {@linkcode Deno.KvEntryMaybe}. If no value exists for
|
* in the form of a {@linkcode Deno.KvEntryMaybe}. If no value exists for
|
||||||
|
@ -1945,6 +1945,8 @@ declare namespace Deno {
|
||||||
* operations immediately.
|
* operations immediately.
|
||||||
*/
|
*/
|
||||||
close(): void;
|
close(): void;
|
||||||
|
|
||||||
|
[Symbol.dispose](): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** **UNSTABLE**: New API, yet to be vetted.
|
/** **UNSTABLE**: New API, yet to be vetted.
|
||||||
|
|
|
@ -34,7 +34,7 @@ import {
|
||||||
ReadableStreamPrototype,
|
ReadableStreamPrototype,
|
||||||
writableStreamForRid,
|
writableStreamForRid,
|
||||||
} from "ext:deno_web/06_streams.js";
|
} 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) {
|
function chmodSync(path, mode) {
|
||||||
ops.op_fs_chmod_sync(pathFromURL(path), mode);
|
ops.op_fs_chmod_sync(pathFromURL(path), mode);
|
||||||
|
@ -669,6 +669,10 @@ class FsFile {
|
||||||
}
|
}
|
||||||
return this.#writable;
|
return this.#writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SymbolDispose]() {
|
||||||
|
core.tryClose(this.rid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkOpenOptions(options) {
|
function checkOpenOptions(options) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {
|
||||||
} from "ext:deno_web/06_streams.js";
|
} from "ext:deno_web/06_streams.js";
|
||||||
import { listen, listenOptionApiName, TcpConn } from "ext:deno_net/01_net.js";
|
import { listen, listenOptionApiName, TcpConn } from "ext:deno_net/01_net.js";
|
||||||
import { listenTls } from "ext:deno_net/02_tls.js";
|
import { listenTls } from "ext:deno_net/02_tls.js";
|
||||||
|
import { SymbolAsyncDispose } from "ext:deno_web/00_infra.js";
|
||||||
const {
|
const {
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
ObjectHasOwn,
|
ObjectHasOwn,
|
||||||
|
@ -343,6 +344,7 @@ class CallbackContext {
|
||||||
fallbackHost;
|
fallbackHost;
|
||||||
serverRid;
|
serverRid;
|
||||||
closed;
|
closed;
|
||||||
|
/** @type {Promise<void> | undefined} */
|
||||||
closing;
|
closing;
|
||||||
listener;
|
listener;
|
||||||
|
|
||||||
|
@ -671,22 +673,25 @@ function serveHttpOn(context, callback) {
|
||||||
PromisePrototypeCatch(callback(req), promiseErrorHandler);
|
PromisePrototypeCatch(callback(req), promiseErrorHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.closed && !context.closing) {
|
if (!context.closing && !context.closed) {
|
||||||
context.closed = true;
|
context.closing = op_http_close(rid, false);
|
||||||
await op_http_close(rid, false);
|
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await context.closing;
|
||||||
|
context.close();
|
||||||
|
context.closed = true;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finished,
|
finished,
|
||||||
async shutdown() {
|
async shutdown() {
|
||||||
if (!context.closed && !context.closing) {
|
if (!context.closing && !context.closed) {
|
||||||
// Shut this HTTP server down gracefully
|
// Shut this HTTP server down gracefully
|
||||||
context.closing = true;
|
context.closing = op_http_close(context.serverRid, true);
|
||||||
await op_http_close(context.serverRid, true);
|
|
||||||
context.closed = true;
|
|
||||||
}
|
}
|
||||||
|
await context.closing;
|
||||||
|
context.closed = true;
|
||||||
},
|
},
|
||||||
ref() {
|
ref() {
|
||||||
ref = true;
|
ref = true;
|
||||||
|
@ -700,6 +705,9 @@ function serveHttpOn(context, callback) {
|
||||||
core.unrefOp(currentPromise[promiseIdSymbol]);
|
core.unrefOp(currentPromise[promiseIdSymbol]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[SymbolAsyncDispose]() {
|
||||||
|
return this.shutdown();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ import {
|
||||||
ReadableStreamPrototype,
|
ReadableStreamPrototype,
|
||||||
} from "ext:deno_web/06_streams.js";
|
} from "ext:deno_web/06_streams.js";
|
||||||
import { serve } from "ext:deno_http/00_serve.js";
|
import { serve } from "ext:deno_http/00_serve.js";
|
||||||
|
import { SymbolDispose } from "ext:deno_web/00_infra.js";
|
||||||
const {
|
const {
|
||||||
ArrayPrototypeIncludes,
|
ArrayPrototypeIncludes,
|
||||||
ArrayPrototypeMap,
|
ArrayPrototypeMap,
|
||||||
|
@ -69,6 +70,9 @@ const {
|
||||||
const connErrorSymbol = Symbol("connError");
|
const connErrorSymbol = Symbol("connError");
|
||||||
const _deferred = Symbol("upgradeHttpDeferred");
|
const _deferred = Symbol("upgradeHttpDeferred");
|
||||||
|
|
||||||
|
/** @type {(self: HttpConn, rid: number) => boolean} */
|
||||||
|
let deleteManagedResource;
|
||||||
|
|
||||||
class HttpConn {
|
class HttpConn {
|
||||||
#rid = 0;
|
#rid = 0;
|
||||||
#closed = false;
|
#closed = false;
|
||||||
|
@ -79,7 +83,12 @@ class HttpConn {
|
||||||
// that were created during lifecycle of this request.
|
// that were created during lifecycle of this request.
|
||||||
// When the connection is closed these resources should be closed
|
// When the connection is closed these resources should be closed
|
||||||
// as well.
|
// as well.
|
||||||
managedResources = new SafeSet();
|
#managedResources = new SafeSet();
|
||||||
|
|
||||||
|
static {
|
||||||
|
deleteManagedResource = (self, rid) =>
|
||||||
|
SetPrototypeDelete(self.#managedResources, rid);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(rid, remoteAddr, localAddr) {
|
constructor(rid, remoteAddr, localAddr) {
|
||||||
this.#rid = rid;
|
this.#rid = rid;
|
||||||
|
@ -123,7 +132,7 @@ class HttpConn {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { 0: streamRid, 1: method, 2: url } = nextRequest;
|
const { 0: streamRid, 1: method, 2: url } = nextRequest;
|
||||||
SetPrototypeAdd(this.managedResources, streamRid);
|
SetPrototypeAdd(this.#managedResources, streamRid);
|
||||||
|
|
||||||
/** @type {ReadableStream<Uint8Array> | undefined} */
|
/** @type {ReadableStream<Uint8Array> | undefined} */
|
||||||
let body = null;
|
let body = null;
|
||||||
|
@ -167,13 +176,21 @@ class HttpConn {
|
||||||
if (!this.#closed) {
|
if (!this.#closed) {
|
||||||
this.#closed = true;
|
this.#closed = true;
|
||||||
core.close(this.#rid);
|
core.close(this.#rid);
|
||||||
for (const rid of new SafeSetIterator(this.managedResources)) {
|
for (const rid of new SafeSetIterator(this.#managedResources)) {
|
||||||
SetPrototypeDelete(this.managedResources, rid);
|
SetPrototypeDelete(this.#managedResources, rid);
|
||||||
core.close(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]() {
|
[SymbolAsyncIterator]() {
|
||||||
// deno-lint-ignore no-this-alias
|
// deno-lint-ignore no-this-alias
|
||||||
const httpConn = this;
|
const httpConn = this;
|
||||||
|
@ -395,7 +412,7 @@ function createRespondWith(
|
||||||
abortController.abort(error);
|
abortController.abort(error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
if (SetPrototypeDelete(httpConn.managedResources, streamRid)) {
|
if (deleteManagedResource(httpConn, streamRid)) {
|
||||||
core.close(streamRid);
|
core.close(streamRid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const {
|
||||||
SymbolToStringTag,
|
SymbolToStringTag,
|
||||||
Uint8ArrayPrototype,
|
Uint8ArrayPrototype,
|
||||||
} = globalThis.__bootstrap.primordials;
|
} = globalThis.__bootstrap.primordials;
|
||||||
|
import { SymbolDispose } from "ext:deno_web/00_infra.js";
|
||||||
const core = Deno.core;
|
const core = Deno.core;
|
||||||
const ops = core.ops;
|
const ops = core.ops;
|
||||||
|
|
||||||
|
@ -299,6 +300,10 @@ class Kv {
|
||||||
close() {
|
close() {
|
||||||
core.close(this.#rid);
|
core.close(this.#rid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SymbolDispose]() {
|
||||||
|
core.tryClose(this.#rid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AtomicOperation {
|
class AtomicOperation {
|
||||||
|
|
|
@ -66,7 +66,7 @@ const MAX_TOTAL_MUTATION_SIZE_BYTES: usize = 800 * 1024;
|
||||||
const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024;
|
const MAX_TOTAL_KEY_SIZE_BYTES: usize = 80 * 1024;
|
||||||
|
|
||||||
deno_core::extension!(deno_kv,
|
deno_core::extension!(deno_kv,
|
||||||
deps = [ deno_console ],
|
deps = [ deno_console, deno_web ],
|
||||||
parameters = [ DBH: DatabaseHandler ],
|
parameters = [ DBH: DatabaseHandler ],
|
||||||
ops = [
|
ops = [
|
||||||
op_kv_database_open<DBH>,
|
op_kv_database_open<DBH>,
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {
|
||||||
writableStreamForRid,
|
writableStreamForRid,
|
||||||
} from "ext:deno_web/06_streams.js";
|
} from "ext:deno_web/06_streams.js";
|
||||||
import * as abortSignal from "ext:deno_web/03_abort_signal.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 primordials = globalThis.__bootstrap.primordials;
|
||||||
const {
|
const {
|
||||||
ArrayPrototypeFilter,
|
ArrayPrototypeFilter,
|
||||||
|
@ -160,6 +162,10 @@ class Conn {
|
||||||
(id) => core.unrefOp(id),
|
(id) => core.unrefOp(id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SymbolDispose]() {
|
||||||
|
core.tryClose(this.#rid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TcpConn extends Conn {
|
class TcpConn extends Conn {
|
||||||
|
@ -249,6 +255,10 @@ class Listener {
|
||||||
core.close(this.rid);
|
core.close(this.rid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SymbolDispose]() {
|
||||||
|
core.tryClose(this.#rid);
|
||||||
|
}
|
||||||
|
|
||||||
[SymbolAsyncIterator]() {
|
[SymbolAsyncIterator]() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
6
ext/net/lib.deno_net.d.ts
vendored
6
ext/net/lib.deno_net.d.ts
vendored
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
/// <reference no-default-lib="true" />
|
/// <reference no-default-lib="true" />
|
||||||
/// <reference lib="esnext" />
|
/// <reference lib="esnext" />
|
||||||
|
/// <reference lib="esnext.disposable" />
|
||||||
|
|
||||||
declare namespace Deno {
|
declare namespace Deno {
|
||||||
/** @category Network */
|
/** @category Network */
|
||||||
|
@ -24,7 +25,8 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category Network
|
* @category Network
|
||||||
*/
|
*/
|
||||||
export interface Listener<T extends Conn = Conn> extends AsyncIterable<T> {
|
export interface Listener<T extends Conn = Conn>
|
||||||
|
extends AsyncIterable<T>, Disposable {
|
||||||
/** Waits for and resolves to the next connection to the `Listener`. */
|
/** Waits for and resolves to the next connection to the `Listener`. */
|
||||||
accept(): Promise<T>;
|
accept(): Promise<T>;
|
||||||
/** Close closes the listener. Any pending accept promises will be rejected
|
/** Close closes the listener. Any pending accept promises will be rejected
|
||||||
|
@ -57,7 +59,7 @@ declare namespace Deno {
|
||||||
export type TlsListener = Listener<TlsConn>;
|
export type TlsListener = Listener<TlsConn>;
|
||||||
|
|
||||||
/** @category Network */
|
/** @category Network */
|
||||||
export interface Conn extends Reader, Writer, Closer {
|
export interface Conn extends Reader, Writer, Closer, Disposable {
|
||||||
/** The local address of the connection. */
|
/** The local address of the connection. */
|
||||||
readonly localAddr: Addr;
|
readonly localAddr: Addr;
|
||||||
/** The remote address of the connection. */
|
/** The remote address of the connection. */
|
||||||
|
|
|
@ -32,6 +32,7 @@ const {
|
||||||
StringPrototypeSubstring,
|
StringPrototypeSubstring,
|
||||||
StringPrototypeToLowerCase,
|
StringPrototypeToLowerCase,
|
||||||
StringPrototypeToUpperCase,
|
StringPrototypeToUpperCase,
|
||||||
|
Symbol,
|
||||||
TypeError,
|
TypeError,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
import { URLPrototype } from "ext:deno_url/00_url.js";
|
import { URLPrototype } from "ext:deno_url/00_url.js";
|
||||||
|
@ -458,6 +459,12 @@ function pathFromURL(pathOrUrl) {
|
||||||
// it in unit tests
|
// it in unit tests
|
||||||
internals.pathFromURL = pathFromURL;
|
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 {
|
export {
|
||||||
ASCII_ALPHA,
|
ASCII_ALPHA,
|
||||||
ASCII_ALPHANUMERIC,
|
ASCII_ALPHANUMERIC,
|
||||||
|
|
|
@ -9,6 +9,8 @@ const {
|
||||||
PromiseResolve,
|
PromiseResolve,
|
||||||
SymbolAsyncIterator,
|
SymbolAsyncIterator,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
import { SymbolDispose } from "ext:deno_web/00_infra.js";
|
||||||
|
|
||||||
class FsWatcher {
|
class FsWatcher {
|
||||||
#rid = 0;
|
#rid = 0;
|
||||||
|
|
||||||
|
@ -51,6 +53,10 @@ class FsWatcher {
|
||||||
[SymbolAsyncIterator]() {
|
[SymbolAsyncIterator]() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SymbolDispose]() {
|
||||||
|
core.tryClose(this.#rid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchFs(
|
function watchFs(
|
||||||
|
|
|
@ -18,7 +18,11 @@ const {
|
||||||
} = primordials;
|
} = primordials;
|
||||||
import { FsFile } from "ext:deno_fs/30_fs.js";
|
import { FsFile } from "ext:deno_fs/30_fs.js";
|
||||||
import { readAll } from "ext:deno_io/12_io.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 * as abortSignal from "ext:deno_web/03_abort_signal.js";
|
||||||
import {
|
import {
|
||||||
readableStreamCollectIntoUint8Array,
|
readableStreamCollectIntoUint8Array,
|
||||||
|
@ -201,7 +205,6 @@ class ChildProcess {
|
||||||
#rid;
|
#rid;
|
||||||
#waitPromiseId;
|
#waitPromiseId;
|
||||||
#waitComplete = false;
|
#waitComplete = false;
|
||||||
#unrefed = false;
|
|
||||||
|
|
||||||
#pid;
|
#pid;
|
||||||
get pid() {
|
get pid() {
|
||||||
|
@ -216,7 +219,6 @@ class ChildProcess {
|
||||||
return this.#stdin;
|
return this.#stdin;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stdoutRid;
|
|
||||||
#stdout = null;
|
#stdout = null;
|
||||||
get stdout() {
|
get stdout() {
|
||||||
if (this.#stdout == null) {
|
if (this.#stdout == null) {
|
||||||
|
@ -225,7 +227,6 @@ class ChildProcess {
|
||||||
return this.#stdout;
|
return this.#stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
#stderrRid;
|
|
||||||
#stderr = null;
|
#stderr = null;
|
||||||
get stderr() {
|
get stderr() {
|
||||||
if (this.#stderr == null) {
|
if (this.#stderr == null) {
|
||||||
|
@ -254,12 +255,10 @@ class ChildProcess {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdoutRid !== null) {
|
if (stdoutRid !== null) {
|
||||||
this.#stdoutRid = stdoutRid;
|
|
||||||
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
|
this.#stdout = readableStreamForRidUnrefable(stdoutRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderrRid !== null) {
|
if (stderrRid !== null) {
|
||||||
this.#stderrRid = stderrRid;
|
|
||||||
this.#stderr = readableStreamForRidUnrefable(stderrRid);
|
this.#stderr = readableStreamForRidUnrefable(stderrRid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,15 +323,22 @@ class ChildProcess {
|
||||||
ops.op_spawn_kill(this.#rid, signo);
|
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() {
|
ref() {
|
||||||
this.#unrefed = false;
|
|
||||||
core.refOp(this.#waitPromiseId);
|
core.refOp(this.#waitPromiseId);
|
||||||
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
|
if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
|
||||||
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
|
if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
unref() {
|
unref() {
|
||||||
this.#unrefed = true;
|
|
||||||
core.unrefOp(this.#waitPromiseId);
|
core.unrefOp(this.#waitPromiseId);
|
||||||
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
|
if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
|
||||||
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
|
if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
|
||||||
|
|
|
@ -70,11 +70,24 @@ import {
|
||||||
windowOrWorkerGlobalScope,
|
windowOrWorkerGlobalScope,
|
||||||
workerRuntimeGlobalProperties,
|
workerRuntimeGlobalProperties,
|
||||||
} from "ext:runtime/98_global_scope.js";
|
} from "ext:runtime/98_global_scope.js";
|
||||||
|
import { SymbolAsyncDispose, SymbolDispose } from "ext:deno_web/00_infra.js";
|
||||||
|
|
||||||
// deno-lint-ignore prefer-primordials
|
// deno-lint-ignore prefer-primordials
|
||||||
Symbol.dispose ??= Symbol("Symbol.dispose");
|
if (Symbol.dispose) throw "V8 supports Symbol.dispose now, no need to shim it!";
|
||||||
// deno-lint-ignore prefer-primordials
|
ObjectDefineProperties(Symbol, {
|
||||||
Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");
|
dispose: {
|
||||||
|
value: SymbolDispose,
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
},
|
||||||
|
asyncDispose: {
|
||||||
|
value: SymbolAsyncDispose,
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let windowIsClosing = false;
|
let windowIsClosing = false;
|
||||||
let globalThis_;
|
let globalThis_;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue