mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Add AsyncDisposableStack
new AsyncDisposableStack() compiles to a native stack handle with use, adopt, defer, move, disposeAsync, disposed and [Symbol.asyncDispose], and works with `await using`. disposeAsync runs the resources in reverse order and awaits each result as Node does: the next resource runs one microtask turn after the result settles, a callback that throws is not awaited, and use() of null or undefined costs one await at the end when nothing else was awaited. Failures chain into a SuppressedError, and an invalid receiver rejects the returned promise. use() of a class instance calls its [Symbol.asyncDispose] method, or its [Symbol.dispose] method when it has no async one. The stack lives in a new runtime unit, scr_async_disposable.c, linked when a program uses both symbols and checked-dynamic promises; it shares DisposableStack's storage through scr_disposable.h. Library mode refuses it with the rest of the checked-dynamic async surface.
This commit is contained in:
@@ -1 +1 @@
|
||||
- **Resource disposal:** Top-level and switch-clause `using` declarations, and `AsyncDisposableStack`, are unsupported. Block- and function-scoped `using` and `await using` support compiled classes with zero-parameter disposal methods, object literals and other `Disposable`- or `AsyncDisposable`-typed values, and supported FileHandle, timer, immediate, child-process, and readline handles. A class instance reached through a `Disposable`- or `unknown`-typed value throws an SC2020 error when its declaration runs; declare the variable with the class type. Array `for (using ... of ...)` loops also compile. `SuppressedError` values expose `error` and `suppressed`; `extends SuppressedError` and casting `unknown` to `SuppressedError` are unsupported (narrow with `instanceof`). `DisposableStack` supports `use`, `adopt`, `defer`, `move`, `dispose`, `disposed` and `[Symbol.dispose]`, and works with `using`; `use` accepts class instances, object literals and other `Disposable`-typed values, and refuses unions that include class instances (narrow the value first). Disposal runs in reverse order and raises Node's `SuppressedError` chain when more than one callback throws.
|
||||
- **Resource disposal:** Top-level and switch-clause `using` declarations are unsupported. Block- and function-scoped `using` and `await using` support compiled classes with zero-parameter disposal methods, object literals and other `Disposable`- or `AsyncDisposable`-typed values, and supported FileHandle, timer, immediate, child-process, and readline handles. A class instance reached through a `Disposable`- or `unknown`-typed value throws an SC2020 error when its declaration runs; declare the variable with the class type. Array `for (using ... of ...)` loops also compile. `SuppressedError` values expose `error` and `suppressed`; `extends SuppressedError` and casting `unknown` to `SuppressedError` are unsupported (narrow with `instanceof`). `DisposableStack` supports `use`, `adopt`, `defer`, `move`, `dispose`, `disposed` and `[Symbol.dispose]`, and works with `using`; `use` accepts class instances, object literals and other `Disposable`-typed values, and refuses unions that include class instances (narrow the value first). Disposal runs in reverse order and raises Node's `SuppressedError` chain when more than one callback throws. `AsyncDisposableStack` supports `use`, `adopt`, `defer`, `move`, `disposeAsync`, `disposed` and `[Symbol.asyncDispose]`, and works with `await using`; `disposeAsync` awaits each disposal's result on the microtask turns Node uses, and `use` of a class instance calls its `[Symbol.asyncDispose]` method, or its `[Symbol.dispose]` method when it has no async one. In library mode `AsyncDisposableStack` is unsupported.
|
||||
|
||||
@@ -1328,6 +1328,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.usingDispose": "scr_dyn_using_dispose",
|
||||
"disposable.stackNew": "scr_disposable_stack_new",
|
||||
"disposable.stackIs": "scr_disposable_stack_is",
|
||||
"disposable.asyncStackNew": "scr_async_disposable_stack_new",
|
||||
"disposable.asyncStackIs": "scr_async_disposable_stack_is",
|
||||
"disposable.stackUseCallback": "scr_disposable_stack_use_callback",
|
||||
"dyn.reflectSet": "scr_dyn_reflect_set",
|
||||
"dyn.reflectDefine": "scr_dyn_reflect_define",
|
||||
|
||||
@@ -538,6 +538,8 @@ export async function compileCInternal(
|
||||
// their exact size class.
|
||||
...(opts.emitter || opts.workers || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []),
|
||||
...(opts.symbol ? [rt(join(rtDir, "scr_symbol.c"))] : []),
|
||||
// AsyncDisposableStack calls into both scr_symbol.c and scr_async_dyn.c.
|
||||
...(opts.symbol && opts.dynAsync ? [rt(join(rtDir, "scr_async_disposable.c"))] : []),
|
||||
...(opts.assert && opts.bigint ? [rt(join(rtDir, "scr_bigint_assert.c"))] : []),
|
||||
...(opts.qs ? [rt(join(rtDir, "scr_qs.c"))] : []),
|
||||
// The legacy url unit references the regex, querystring and dyn-async
|
||||
|
||||
@@ -5,44 +5,66 @@ import { newFnCtx } from "../function-context.js";
|
||||
import { BOOL, DYN, VOID, type IrExpr, type IrType } from "../../../ir/ir.js";
|
||||
import { varRef } from "../../../ir/build.js";
|
||||
|
||||
/** Whether `node`'s static type is the standard library's DisposableStack. */
|
||||
function isStdlibDisposableStack(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
type StackClass = "DisposableStack" | "AsyncDisposableStack";
|
||||
|
||||
const STACK_CLASSES: readonly StackClass[] = ["DisposableStack", "AsyncDisposableStack"];
|
||||
|
||||
/** The standard-library stack class `node`'s static type names, or null. */
|
||||
function stdlibStackClassOf(lowerer: Lowerer, node: ts.Expression): StackClass | null {
|
||||
const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node));
|
||||
for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) {
|
||||
if (symbol?.name === "DisposableStack" && lowerer.isStdlibSymbol(symbol)) return true;
|
||||
const name = STACK_CLASSES.find((cls) => cls === symbol?.name);
|
||||
if (name && symbol && lowerer.isStdlibSymbol(symbol)) return name;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** `new DisposableStack()`: a native stack handle (scr_symbol.c). Its
|
||||
* members are function values and getters of the handle, reached through
|
||||
* the checked-dynamic method and property paths. Null for other
|
||||
* constructors. */
|
||||
/** `new DisposableStack()` and `new AsyncDisposableStack()`: native stack
|
||||
* handles (scr_symbol.c, scr_async_disposable.c). Their members are
|
||||
* function values and getters of the handle, reached through the
|
||||
* checked-dynamic method and property paths. Null for other constructors. */
|
||||
export function lowerDisposableStackNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
|
||||
if (!ts.isIdentifier(expr.expression) || expr.expression.text !== "DisposableStack") return null;
|
||||
if (!ts.isIdentifier(expr.expression)) return null;
|
||||
const text = expr.expression.text;
|
||||
const name = STACK_CLASSES.find((cls) => cls === text);
|
||||
if (!name) return null;
|
||||
const symbol = lowerer.resolveValueSymbol(expr.expression);
|
||||
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
|
||||
if ((expr.arguments ?? []).length !== 0) return null;
|
||||
return { kind: "libCall", fn: "disposable.stackNew", args: [], type: DYN, loc: locOf(expr) };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: name === "DisposableStack" ? "disposable.stackNew" : "disposable.asyncStackNew",
|
||||
args: [],
|
||||
type: DYN,
|
||||
loc: locOf(expr),
|
||||
};
|
||||
}
|
||||
|
||||
/** `x instanceof DisposableStack`, or null when the right side is not the
|
||||
* standard library's DisposableStack. */
|
||||
/** `x instanceof DisposableStack` and `x instanceof AsyncDisposableStack`,
|
||||
* or null when the right side is neither standard-library class. */
|
||||
export function lowerDisposableStackInstanceOf(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.BinaryExpression,
|
||||
): IrExpr | null {
|
||||
if (!lowerer.isStdlibGlobal(expr.right, "DisposableStack")) return null;
|
||||
const name = STACK_CLASSES.find((cls) => lowerer.isStdlibGlobal(expr.right, cls));
|
||||
if (!name) return null;
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return { kind: "libCall", fn: "disposable.stackIs", args: [value], type: BOOL, loc: locOf(expr) };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: name === "DisposableStack" ? "disposable.stackIs" : "disposable.asyncStackIs",
|
||||
args: [value],
|
||||
type: BOOL,
|
||||
loc: locOf(expr),
|
||||
};
|
||||
}
|
||||
|
||||
/** `stack.use(value)` where `value` is a class instance. The dyn view of a
|
||||
* class instance has no methods, so the stack receives a function that
|
||||
* calls the class's [Symbol.dispose] method on the value, and the call
|
||||
* answers the value at its static type. Other values take the
|
||||
* checked-dynamic method path, which reads the value's [Symbol.dispose].
|
||||
* Null when the call is not that form. */
|
||||
* calls the class's [Symbol.dispose] method on the value (on an
|
||||
* AsyncDisposableStack, its [Symbol.asyncDispose] method when it has one,
|
||||
* whose promise the disposal awaits), and the call answers the value at
|
||||
* its static type. Other values take the checked-dynamic method path,
|
||||
* which reads the value's methods. Null when the call is not that form. */
|
||||
export function lowerDisposableStackUseCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
@@ -52,7 +74,8 @@ export function lowerDisposableStackUseCall(
|
||||
if (access.name.text !== "use" || call.arguments.length !== 1) return null;
|
||||
const argNode = call.arguments[0]!;
|
||||
if (ts.isSpreadElement(argNode)) return null;
|
||||
if (!isStdlibDisposableStack(lowerer, access.expression)) return null;
|
||||
const stackClass = stdlibStackClassOf(lowerer, access.expression);
|
||||
if (!stackClass) return null;
|
||||
const valueT = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
||||
if (valueT?.kind === "union") {
|
||||
const arms = lowerer.unions.get(valueT.unionId)?.arms ?? [];
|
||||
@@ -69,34 +92,41 @@ export function lowerDisposableStackUseCall(
|
||||
const className = valueT.className;
|
||||
const info = lowerer.classes.get(className);
|
||||
if (!info) lowerer.flushDeferredClass(className);
|
||||
const found = info ? lowerer.findMethodOn(info, "sym:dispose") : null;
|
||||
const asyncFound =
|
||||
stackClass === "AsyncDisposableStack" && info
|
||||
? lowerer.findMethodOn(info, "sym:asyncDispose")
|
||||
: null;
|
||||
const member = asyncFound ? "sym:asyncDispose" : "sym:dispose";
|
||||
const found = asyncFound ?? (info ? lowerer.findMethodOn(info, "sym:dispose") : null);
|
||||
if (!found) return null;
|
||||
const memberText = asyncFound ? "[Symbol.asyncDispose]" : "[Symbol.dispose]";
|
||||
if (found.sig.params.length !== 0) {
|
||||
lowerer.unsupported("SC1090", argNode, "[Symbol.dispose] methods with parameters");
|
||||
lowerer.unsupported("SC1090", argNode, `${memberText} methods with parameters`);
|
||||
}
|
||||
if (asyncFound && found.sig.ret.kind !== "promise") {
|
||||
lowerer.unsupported("SC1090", argNode, `${memberText} methods whose result is not a Promise`);
|
||||
}
|
||||
const helperRet: IrType = asyncFound ? found.sig.ret : VOID;
|
||||
const stack = lowerer.lowerExpr(access.expression);
|
||||
const value = lowerer.lowerExpr(argNode);
|
||||
const key = `disposableStack.use:${className}`;
|
||||
const key = `disposableStack.use:${member}:${className}`;
|
||||
let helper = lowerer.valueHelpers.get(key);
|
||||
if (!helper) {
|
||||
helper = `%disposableStack.use.${lowerer.valueHelpers.size}`;
|
||||
lowerer.valueHelpers.set(key, helper);
|
||||
const context = newFnCtx(false, null, null, VOID);
|
||||
const context = newFnCtx(false, null, null, helperRet);
|
||||
lowerer.fnStack.push(context);
|
||||
try {
|
||||
const param = lowerer.declareHiddenLocal("value", valueT);
|
||||
const self = varRef(param.id, valueT, loc);
|
||||
const call = lowerer.accessorCall(className, member, self, [], found.sig.ret, loc);
|
||||
lowerer.liftedFns.push({
|
||||
name: helper,
|
||||
params: [{ localId: param.id, name: "value", type: valueT }],
|
||||
returnType: VOID,
|
||||
returnType: helperRet,
|
||||
locals: context.locals,
|
||||
body: [
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: lowerer.accessorCall(className, "sym:dispose", self, [], found.sig.ret, loc),
|
||||
loc,
|
||||
},
|
||||
asyncFound ? { kind: "return", value: call, loc } : { kind: "exprStmt", expr: call, loc },
|
||||
],
|
||||
loc,
|
||||
});
|
||||
@@ -106,7 +136,7 @@ export function lowerDisposableStackUseCall(
|
||||
}
|
||||
const stackLocal = lowerer.declareHiddenLocal("%disposableStack", DYN);
|
||||
const valueLocal = lowerer.declareHiddenLocal("%disposableValue", valueT);
|
||||
const fnT: IrType = { kind: "func", params: [valueT], ret: VOID };
|
||||
const fnT: IrType = { kind: "func", params: [valueT], ret: helperRet };
|
||||
const dispose: IrExpr = { kind: "closure", fnName: helper, captures: [], type: fnT, loc };
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
|
||||
@@ -2377,13 +2377,15 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
// representation and consumed by the console method lowerings. The
|
||||
// stdlib Disposable (events.addAbortListener's result) and
|
||||
// AsyncDisposable are checked objects carrying their Symbol.dispose or
|
||||
// Symbol.asyncDispose method. A DisposableStack is a native handle whose
|
||||
// methods are function values of its checked-dynamic view.
|
||||
// Symbol.asyncDispose method. DisposableStack and AsyncDisposableStack
|
||||
// are native handles whose methods are function values of their
|
||||
// checked-dynamic views.
|
||||
if (
|
||||
(psym?.name === "Console" ||
|
||||
psym?.name === "Disposable" ||
|
||||
psym?.name === "AsyncDisposable" ||
|
||||
psym?.name === "DisposableStack") &&
|
||||
psym?.name === "DisposableStack" ||
|
||||
psym?.name === "AsyncDisposableStack") &&
|
||||
checker
|
||||
.declarationsOf(psym)
|
||||
.some((d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()))
|
||||
|
||||
@@ -3214,12 +3214,15 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* `await using`. Both throw Node's TypeErrors or the method's error. */
|
||||
"dyn.usingMethod": { argTypes: [DYN, BOOL], result: DYN },
|
||||
"dyn.usingDispose": { argTypes: [DYN, DYN], result: DYN },
|
||||
/** DisposableStack (scr_symbol.c): a new stack handle; whether a value is
|
||||
* one; and `stack.use(value)` for a class instance, registering the
|
||||
* compiler's disposal function for it. The last throws Node's
|
||||
* ReferenceError on a disposed stack. */
|
||||
/** DisposableStack (scr_symbol.c) and AsyncDisposableStack
|
||||
* (scr_async_disposable.c): a new stack handle; whether a value is one;
|
||||
* and `stack.use(value)` for a class instance on either stack,
|
||||
* registering the compiler's disposal function for it. The last throws
|
||||
* Node's ReferenceError on a disposed stack. */
|
||||
"disposable.stackNew": { argTypes: [], result: DYN },
|
||||
"disposable.stackIs": { argTypes: [DYN], result: BOOL },
|
||||
"disposable.asyncStackNew": { argTypes: [], result: DYN },
|
||||
"disposable.asyncStackIs": { argTypes: [DYN], result: BOOL },
|
||||
"disposable.stackUseCallback": { argTypes: [DYN, DYN, DYN], result: BOOL },
|
||||
"dyn.reflectSet": { argTypes: [DYN, DYN, DYN, DYN], result: BOOL },
|
||||
"dyn.reflectDefine": { argTypes: [DYN, DYN, DYN], result: BOOL },
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface RuntimeFeatures {
|
||||
}
|
||||
|
||||
const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"disposable.asyncStackNew",
|
||||
"fs.callbackValue",
|
||||
"fs.callbackCall",
|
||||
"fs.callbackGlob",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5902-async-disposable-stack.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5903-async-disposable-stack-js.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -80,6 +80,8 @@ const optional = [
|
||||
["scr_events_emitter.c", "emitter"],
|
||||
["scr_dyn_handle.c", any("emitter", "netEffective")],
|
||||
["scr_symbol.c", "symbol"],
|
||||
// AsyncDisposableStack calls into both scr_symbol.c and scr_async_dyn.c.
|
||||
["scr_async_disposable.c", all("symbol", "dynAsync")],
|
||||
["scr_bigint_assert.c", all("assert", "bigint")],
|
||||
["scr_qs.c", "qs"],
|
||||
// The legacy url unit lowercases and normalizes through the regex unit
|
||||
|
||||
@@ -70,6 +70,8 @@ const OPTIONAL = [
|
||||
["scr_events_emitter.c", "emitter"],
|
||||
["scr_dyn_handle.c", any("emitter", "netEffective")],
|
||||
["scr_symbol.c", "symbol"],
|
||||
// AsyncDisposableStack calls into both scr_symbol.c and scr_async_dyn.c.
|
||||
["scr_async_disposable.c", all("symbol", "dynAsync")],
|
||||
["scr_bigint_assert.c", all("assert", "bigint")],
|
||||
["scr_qs.c", "qs"],
|
||||
// The legacy url unit lowercases and normalizes through the regex unit
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
/* AsyncDisposableStack: the DisposableStack storage (scr_disposable.h)
|
||||
* behind its own handle, with disposeAsync in place of dispose.
|
||||
* disposeAsync runs the entries in reverse order and awaits each result as
|
||||
* an async function would: the next entry runs one microtask turn after
|
||||
* the result settles, a callback that throws is not awaited, and an absent
|
||||
* entry (use() of null or undefined) costs one await at the end when no
|
||||
* other entry was awaited. Failures chain into a SuppressedError as in
|
||||
* DisposableStack, and the returned promise settles after the last entry.
|
||||
* Linked with scr_symbol.c and scr_async_dyn.c, whose functions it calls. */
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_disposable.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
ScrDisposableStack *storage; /* the entries, moved out of the stack */
|
||||
size_t next; /* entries [0, next) are still to run */
|
||||
ScrCaught *error; /* the disposal error so far, or NULL */
|
||||
bool needs_await, has_awaited;
|
||||
ScrPromise *result; /* the promise disposeAsync returned */
|
||||
ScrPromise *awaited; /* the result being awaited */
|
||||
} ScrAdsRun;
|
||||
|
||||
static void scr_ads_step(ScrAdsRun *run);
|
||||
|
||||
static void scr_ads_fail(ScrAdsRun *run) {
|
||||
if (run->error) scr_exc_suppress(run->error);
|
||||
run->error = scr_exc_take();
|
||||
}
|
||||
|
||||
static void scr_ads_resume(ScrFiber *self, void *opaque) {
|
||||
(void)self;
|
||||
ScrAdsRun *run = opaque;
|
||||
run->has_awaited = true;
|
||||
ScrDyn *value = scr_await_dyn(run->awaited);
|
||||
scr_promise_release(run->awaited);
|
||||
run->awaited = NULL;
|
||||
if (value) scr_dyn_release(value);
|
||||
else scr_ads_fail(run);
|
||||
scr_ads_step(run);
|
||||
}
|
||||
|
||||
/* Await `value` (moved in): a promise is awaited itself, any other value
|
||||
* through a fulfilled promise, as `await` takes one turn for either. */
|
||||
static void scr_ads_await(ScrAdsRun *run, ScrDyn *value) {
|
||||
ScrPromise *p;
|
||||
if (value->kind == SCR_DYN_PROMISE) {
|
||||
p = scr_promise_retain(value->v.promise);
|
||||
} else {
|
||||
p = scr_promise_new();
|
||||
scr_promise_fulfill_ref(p, scr_dyn_retain(value), scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
|
||||
}
|
||||
scr_dyn_release(value);
|
||||
scr_promise_mark_handled(p);
|
||||
run->awaited = p;
|
||||
ScrPromise *waiter = scr_async_spawn_after(p, scr_ads_resume, run);
|
||||
scr_promise_release(waiter);
|
||||
}
|
||||
|
||||
static void scr_ads_finish(ScrAdsRun *run) {
|
||||
if (run->error) {
|
||||
scr_rethrow(run->error);
|
||||
scr_caught_release(run->error);
|
||||
scr_promise_reject_pending(run->result);
|
||||
} else {
|
||||
scr_promise_resolve_dyn(run->result, scr_dyn_retain(scr_dyn_undefined()));
|
||||
}
|
||||
scr_promise_release(run->result);
|
||||
scr_ds_release(run->storage);
|
||||
free(run);
|
||||
}
|
||||
|
||||
static void scr_ads_step(ScrAdsRun *run) {
|
||||
while (run->next > 0) {
|
||||
ScrDisposableResource *r = &run->storage->items[--run->next];
|
||||
if (r->kind == SCR_DS_ABSENT) {
|
||||
run->needs_await = true;
|
||||
continue;
|
||||
}
|
||||
ScrDyn *result;
|
||||
if (r->kind == SCR_DS_USE) {
|
||||
result = scr_dyn_using_dispose(r->method, r->value);
|
||||
} else {
|
||||
scr_dyn_this_push_dyn(scr_dyn_undefined());
|
||||
result = r->kind == SCR_DS_ADOPT ? scr_dyn_call(r->method, &r->value, 1, "onDisposeAsync")
|
||||
: scr_dyn_call(r->method, NULL, 0, "onDisposeAsync");
|
||||
scr_dyn_this_pop();
|
||||
}
|
||||
scr_dyn_release(r->value);
|
||||
scr_dyn_release(r->method);
|
||||
r->value = r->method = NULL;
|
||||
r->kind = SCR_DS_ABSENT;
|
||||
if (!result) {
|
||||
scr_ads_fail(run);
|
||||
continue;
|
||||
}
|
||||
scr_ads_await(run, result);
|
||||
return;
|
||||
}
|
||||
if (run->needs_await && !run->has_awaited) {
|
||||
run->needs_await = false;
|
||||
scr_ads_await(run, scr_dyn_retain(scr_dyn_undefined()));
|
||||
return;
|
||||
}
|
||||
scr_ads_finish(run);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ads_dispose_async(const ScrDyn *self) {
|
||||
ScrPromise *p = scr_promise_new();
|
||||
ScrDyn *boxed = scr_dyn_new_promise(p);
|
||||
ScrDisposableStack *s = self->kind == SCR_DYN_HANDLE && self->v.handle.tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK
|
||||
? self->v.handle.ptr : NULL;
|
||||
if (!s) {
|
||||
/* An invalid receiver rejects the promise instead of throwing. */
|
||||
static const char message[] = "Receiver is not an AsyncDisposableStack";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
scr_promise_reject_pending(p);
|
||||
} else if (s->disposed) {
|
||||
scr_promise_resolve_dyn(p, scr_dyn_retain(scr_dyn_undefined()));
|
||||
} else {
|
||||
ScrAdsRun *run = calloc(1, sizeof *run);
|
||||
if (!run) scr_trap("scriptc: out of memory\n");
|
||||
run->storage = scr_ds_storage_new();
|
||||
scr_ds_move(run->storage, s);
|
||||
run->next = run->storage->len;
|
||||
run->result = scr_promise_retain(p);
|
||||
scr_ads_step(run);
|
||||
}
|
||||
scr_promise_release(p);
|
||||
return boxed;
|
||||
}
|
||||
|
||||
static SCR_TL ScrDyn *scr_ads_method_values[6];
|
||||
static const char *const scr_ads_method_names[] = {"use", "adopt", "defer", "move", "disposeAsync", "disposed"};
|
||||
static const uint32_t scr_ads_method_lengths[] = {1, 2, 1, 0, 0, 0};
|
||||
enum { SCR_ADS_M_USE, SCR_ADS_M_ADOPT, SCR_ADS_M_DEFER, SCR_ADS_M_MOVE, SCR_ADS_M_DISPOSE, SCR_ADS_M_DISPOSED };
|
||||
|
||||
static ScrDisposableStack *scr_ads_alloc(void);
|
||||
static ScrDyn *scr_ads_box(ScrDisposableStack *s);
|
||||
|
||||
static ScrDyn *scr_ads_method_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
||||
unsigned index = (unsigned)scr_box_get_f64(clo->caps[0]);
|
||||
ScrDyn *self = scr_dyn_this_get();
|
||||
const ScrDyn *arg0 = argc > 0 ? args[0] : scr_dyn_undefined();
|
||||
const ScrDyn *arg1 = argc > 1 ? args[1] : scr_dyn_undefined();
|
||||
const ScrDynHandleTag tag = SCR_DYNH_ASYNC_DISPOSABLE_STACK;
|
||||
ScrDyn *out = NULL;
|
||||
ScrDisposableStack *s;
|
||||
switch (index) {
|
||||
case SCR_ADS_M_USE:
|
||||
if (!(s = scr_ds_receiver(self, tag, "use", false, true))) break;
|
||||
if (arg0->kind == SCR_DYN_NULL || arg0->kind == SCR_DYN_UNDEF) {
|
||||
scr_ds_push(s, NULL, NULL, SCR_DS_ABSENT);
|
||||
} else {
|
||||
ScrDyn *method = scr_dyn_using_method(arg0, true);
|
||||
if (!method) break;
|
||||
scr_ds_push(s, scr_dyn_retain((ScrDyn *)arg0), method, SCR_DS_USE);
|
||||
}
|
||||
out = scr_dyn_retain((ScrDyn *)arg0);
|
||||
break;
|
||||
case SCR_ADS_M_ADOPT:
|
||||
if (!(s = scr_ds_receiver(self, tag, "adopt", false, true)) || !scr_ds_callable(arg1)) break;
|
||||
scr_ds_push(s, scr_dyn_retain((ScrDyn *)arg0), scr_dyn_retain((ScrDyn *)arg1), SCR_DS_ADOPT);
|
||||
out = scr_dyn_retain((ScrDyn *)arg0);
|
||||
break;
|
||||
case SCR_ADS_M_DEFER:
|
||||
if (!(s = scr_ds_receiver(self, tag, "defer", false, true)) || !scr_ds_callable(arg0)) break;
|
||||
scr_ds_push(s, NULL, scr_dyn_retain((ScrDyn *)arg0), SCR_DS_DEFER);
|
||||
out = scr_dyn_retain(scr_dyn_undefined());
|
||||
break;
|
||||
case SCR_ADS_M_MOVE: {
|
||||
if (!(s = scr_ds_receiver(self, tag, "move", false, true))) break;
|
||||
ScrDisposableStack *moved = scr_ads_alloc();
|
||||
scr_ds_move(moved, s);
|
||||
out = scr_ads_box(moved);
|
||||
scr_ds_release(moved);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
out = scr_ads_dispose_async(self);
|
||||
break;
|
||||
}
|
||||
scr_dyn_release(self);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ads_method_value(unsigned index) {
|
||||
if (!scr_ads_method_values[index]) {
|
||||
ScrClosure *clo = scr_closure_new(NULL, 1);
|
||||
clo->caps[0] = scr_box_new(SCR_BOX_F64);
|
||||
scr_box_set_f64(clo->caps[0], (double)index);
|
||||
scr_ads_method_values[index] = scr_dyn_new_func(clo, scr_ads_method_call, scr_ads_method_lengths[index],
|
||||
"", scr_ads_method_names[index]);
|
||||
}
|
||||
return scr_dyn_retain(scr_ads_method_values[index]);
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ads_get(void *ptr, const char *key, size_t len) {
|
||||
ScrDisposableStack *s = ptr;
|
||||
if (len == 8 && memcmp(key, "disposed", 8) == 0) return scr_dyn_new_bool(s->disposed);
|
||||
for (unsigned i = 0; i < SCR_ADS_M_DISPOSED; i++) {
|
||||
if (strlen(scr_ads_method_names[i]) == len && memcmp(key, scr_ads_method_names[i], len) == 0)
|
||||
return scr_ads_method_value(i);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool scr_ads_set(void *ptr, const char *key, size_t len, const ScrDyn *value) {
|
||||
(void)ptr;
|
||||
(void)key;
|
||||
(void)len;
|
||||
(void)value;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* [Symbol.asyncDispose] is disposeAsync itself, as in Node. */
|
||||
static ScrDyn *scr_ads_symbol_get(void *ptr, const ScrDyn *key) {
|
||||
(void)ptr;
|
||||
if (key->kind != SCR_DYN_SYMBOL) return NULL;
|
||||
ScrStr *name = scr_str_new("asyncDispose", 12);
|
||||
ScrSym *async_dispose = scr_sym_well_known(name);
|
||||
scr_str_release(name);
|
||||
bool match = key->v.symbol.value == async_dispose;
|
||||
scr_sym_release(async_dispose);
|
||||
return match ? scr_ads_method_value(SCR_ADS_M_DISPOSE) : NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ads_inspect_view(void *ptr) {
|
||||
(void)ptr;
|
||||
return scr_dyn_new_obj();
|
||||
}
|
||||
|
||||
static void scr_ads_teardown(void) {
|
||||
for (size_t i = 0; i < sizeof scr_ads_method_values / sizeof scr_ads_method_values[0]; i++) {
|
||||
scr_dyn_release(scr_ads_method_values[i]);
|
||||
scr_ads_method_values[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const ScrDynHandleOps scr_ads_ops = {
|
||||
.cls = "AsyncDisposableStack",
|
||||
.retain = scr_ds_retain,
|
||||
.release = scr_ds_release,
|
||||
.get = scr_ads_get,
|
||||
.set = scr_ads_set,
|
||||
.inspect_view = scr_ads_inspect_view,
|
||||
.json_empty_object = true,
|
||||
.inspect_view_empty = true,
|
||||
.symbol_get = scr_ads_symbol_get,
|
||||
};
|
||||
|
||||
static ScrDisposableStack *scr_ads_alloc(void) {
|
||||
static SCR_TL bool installed;
|
||||
if (!installed) {
|
||||
installed = true;
|
||||
scr_dyn_handle_install(SCR_DYNH_ASYNC_DISPOSABLE_STACK, &scr_ads_ops);
|
||||
scr_atexit(scr_ads_teardown);
|
||||
}
|
||||
return scr_ds_storage_new();
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ads_box(ScrDisposableStack *s) {
|
||||
return scr_dyn_new_handle(s, SCR_DYNH_ASYNC_DISPOSABLE_STACK);
|
||||
}
|
||||
|
||||
ScrDyn *scr_async_disposable_stack_new(void) {
|
||||
ScrDisposableStack *s = scr_ads_alloc();
|
||||
ScrDyn *out = scr_ads_box(s);
|
||||
scr_ds_release(s);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool scr_async_disposable_stack_is(const ScrDyn *value) {
|
||||
return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef SCR_DISPOSABLE_H
|
||||
#define SCR_DISPOSABLE_H
|
||||
|
||||
/* Storage shared by the DisposableStack handle (scr_symbol.c) and the
|
||||
* AsyncDisposableStack handle (scr_async_disposable.c). A stack is a
|
||||
* collector-headered object behind a traced dyn handle: its resources hold
|
||||
* callbacks that can capture the stack itself. Each entry records how
|
||||
* disposal calls it: `use` calls the value's dispose method with the value
|
||||
* as `this`, `adopt` calls the callback with the value, `defer` calls the
|
||||
* callback with no arguments, and an absent entry (an AsyncDisposableStack
|
||||
* use() of null or undefined) calls nothing. */
|
||||
|
||||
#include "scr_runtime.h"
|
||||
|
||||
enum { SCR_DS_USE, SCR_DS_ADOPT, SCR_DS_DEFER, SCR_DS_ABSENT };
|
||||
|
||||
typedef struct {
|
||||
ScrDyn *value; /* the resource (use, adopt); NULL for defer and absent */
|
||||
ScrDyn *method; /* NULL for absent */
|
||||
unsigned kind;
|
||||
/* Which members the last trace visited: the collector frees those
|
||||
* itself, and gcfree must not read them afterwards. */
|
||||
bool value_traced, method_traced;
|
||||
} ScrDisposableResource;
|
||||
|
||||
typedef struct {
|
||||
size_t rc;
|
||||
bool disposed;
|
||||
ScrDisposableResource *items;
|
||||
size_t len, cap;
|
||||
} ScrDisposableStack;
|
||||
|
||||
/* A new, empty stack (+1). */
|
||||
ScrDisposableStack *scr_ds_storage_new(void);
|
||||
void *scr_ds_retain(void *stack);
|
||||
void scr_ds_release(void *stack);
|
||||
/* Moves value and method in. */
|
||||
void scr_ds_push(ScrDisposableStack *s, ScrDyn *value, ScrDyn *method, unsigned kind);
|
||||
/* Moves `from`'s entries to `to` and marks `from` disposed. */
|
||||
void scr_ds_move(ScrDisposableStack *to, ScrDisposableStack *from);
|
||||
/* V8's rendering of a value in a TypeError. */
|
||||
void scr_ds_put_value(ScrJsonBuf *b, const ScrDyn *value);
|
||||
/* Whether `value` is callable; false with V8's TypeError pending. */
|
||||
bool scr_ds_callable(const ScrDyn *value);
|
||||
/* The stack behind `self` when it has handle tag `tag`, or NULL with
|
||||
* Node's TypeError (an incompatible receiver of <class>.prototype.<name>)
|
||||
* or ReferenceError (a disposed stack, when `live` is required) pending. */
|
||||
ScrDisposableStack *scr_ds_receiver(const ScrDyn *self, ScrDynHandleTag tag, const char *name, bool getter,
|
||||
bool live);
|
||||
|
||||
#endif
|
||||
@@ -169,6 +169,7 @@ typedef enum {
|
||||
SCR_DYNH_NET_SOCKET_ADDRESS, /* net.SocketAddress (scr_net.c) */
|
||||
SCR_DYNH_NET_BLOCK_LIST, /* net.BlockList (scr_net.c) */
|
||||
SCR_DYNH_DISPOSABLE_STACK, /* DisposableStack (scr_symbol.c) */
|
||||
SCR_DYNH_ASYNC_DISPOSABLE_STACK, /* AsyncDisposableStack (scr_async_disposable.c) */
|
||||
SCR_DYNH_COUNT,
|
||||
} ScrDynHandleTag;
|
||||
|
||||
@@ -633,6 +634,11 @@ bool scr_disposable_stack_is(const ScrDyn *value);
|
||||
bool scr_disposable_stack_use(const ScrDyn *stack, const ScrDyn *value);
|
||||
bool scr_disposable_stack_use_callback(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *dispose);
|
||||
bool scr_disposable_stack_adopt(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *on_dispose);
|
||||
/* AsyncDisposableStack (scr_async_disposable.c): a new stack (+1) and
|
||||
* whether a value is one. scr_disposable_stack_use_callback also registers
|
||||
* on an AsyncDisposableStack, whose disposal awaits the function's result. */
|
||||
ScrDyn *scr_async_disposable_stack_new(void);
|
||||
bool scr_async_disposable_stack_is(const ScrDyn *value);
|
||||
void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored);
|
||||
void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key);
|
||||
bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key);
|
||||
|
||||
@@ -3872,6 +3872,7 @@ ScrDyn *scr_dyn_new_handle(void *h, ScrDynHandleTag tag) {
|
||||
d->v.handle.tag = tag;
|
||||
d->v.handle.traced = tag == SCR_DYNH_WEAK_MAP || tag == SCR_DYNH_WEAK_SET ||
|
||||
tag == SCR_DYNH_WORKER || tag == SCR_DYNH_MESSAGE_PORT || tag == SCR_DYNH_DISPOSABLE_STACK ||
|
||||
tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK ||
|
||||
((tag == SCR_DYNH_SET || tag == SCR_DYNH_MAP) &&
|
||||
((ScrMap *)h)->traced != 0);
|
||||
return d;
|
||||
@@ -5406,7 +5407,8 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) {
|
||||
d->v.handle.tag == SCR_DYNH_WEAK_MAP || d->v.handle.tag == SCR_DYNH_WEAK_SET ||
|
||||
d->v.handle.tag == SCR_DYNH_SET || d->v.handle.tag == SCR_DYNH_MAP || d->v.handle.tag == SCR_DYNH_REGEXP ||
|
||||
d->v.handle.tag == SCR_DYNH_DATE || d->v.handle.tag == SCR_DYNH_BLOB ||
|
||||
d->v.handle.tag == SCR_DYNH_FILE || d->v.handle.tag == SCR_DYNH_DISPOSABLE_STACK) {
|
||||
d->v.handle.tag == SCR_DYNH_FILE || d->v.handle.tag == SCR_DYNH_DISPOSABLE_STACK ||
|
||||
d->v.handle.tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "[object ");
|
||||
@@ -5992,7 +5994,7 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
||||
d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET ||
|
||||
d->v.handle.tag == SCR_DYNH_CUSTOM_EVENT || d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SET || d->v.handle.tag == SCR_DYNH_MAP ||
|
||||
d->v.handle.tag == SCR_DYNH_BLOB || d->v.handle.tag == SCR_DYNH_FILE ||
|
||||
d->v.handle.tag == SCR_DYNH_DISPOSABLE_STACK) {
|
||||
d->v.handle.tag == SCR_DYNH_DISPOSABLE_STACK || d->v.handle.tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "[object ");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* always after scr_init registered the RC audit, so atexit's LIFO order
|
||||
* runs it before the audit and the audited string counts stay exact. */
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_disposable.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -459,31 +460,10 @@ ScrDyn *scr_dyn_using_dispose(const ScrDyn *method, const ScrDyn *value) {
|
||||
}
|
||||
|
||||
/* ── DisposableStack ───────────────────────────────────────────────────
|
||||
* The stack is a collector-headered object behind a traced dyn handle: its
|
||||
* resources hold callbacks that can capture the stack itself. Each entry
|
||||
* records how disposal calls it: `use` calls the value's dispose method
|
||||
* with the value as `this`, `adopt` calls the callback with the value, and
|
||||
* `defer` calls the callback with no arguments. Disposal runs the entries
|
||||
* in reverse order; each later failure becomes a SuppressedError over the
|
||||
* error so far, and a single failure is rethrown as it was thrown. */
|
||||
enum { SCR_DS_USE, SCR_DS_ADOPT, SCR_DS_DEFER };
|
||||
|
||||
typedef struct {
|
||||
ScrDyn *value; /* the resource (use, adopt); NULL for defer */
|
||||
ScrDyn *method;
|
||||
unsigned kind;
|
||||
/* Which members the last trace visited: the collector frees those
|
||||
* itself, and gcfree must not read them afterwards. */
|
||||
bool value_traced, method_traced;
|
||||
} ScrDisposableResource;
|
||||
|
||||
typedef struct {
|
||||
size_t rc;
|
||||
bool disposed;
|
||||
ScrDisposableResource *items;
|
||||
size_t len, cap;
|
||||
} ScrDisposableStack;
|
||||
|
||||
* Storage and entries are described in scr_disposable.h. Disposal runs
|
||||
* the entries in reverse order; each later failure becomes a
|
||||
* SuppressedError over the error so far, and a single failure is rethrown
|
||||
* as it was thrown. */
|
||||
static bool scr_ds_traced(const ScrDyn *value) {
|
||||
return value && value->kind > SCR_DYN_STR && value->kind != SCR_DYN_UNDEF;
|
||||
}
|
||||
@@ -521,12 +501,12 @@ static void scr_ds_destroy(void *ptr) {
|
||||
scr_ds_gcfree(ptr);
|
||||
}
|
||||
|
||||
static void *scr_ds_retain(void *ptr) {
|
||||
void *scr_ds_retain(void *ptr) {
|
||||
((ScrDisposableStack *)ptr)->rc++;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void scr_ds_release(void *ptr) {
|
||||
void scr_ds_release(void *ptr) {
|
||||
ScrDisposableStack *s = ptr;
|
||||
if (!s) return;
|
||||
if (--s->rc == 0) {
|
||||
@@ -535,7 +515,7 @@ static void scr_ds_release(void *ptr) {
|
||||
} else scr_cyc_released(s);
|
||||
}
|
||||
|
||||
static void scr_ds_push(ScrDisposableStack *s, ScrDyn *value, ScrDyn *method, unsigned kind) {
|
||||
void scr_ds_push(ScrDisposableStack *s, ScrDyn *value, ScrDyn *method, unsigned kind) {
|
||||
if (s->len == s->cap) {
|
||||
size_t cap = s->cap ? s->cap * 2 : 4;
|
||||
ScrDisposableResource *items = realloc(s->items, cap * sizeof *items);
|
||||
@@ -548,7 +528,7 @@ static void scr_ds_push(ScrDisposableStack *s, ScrDyn *value, ScrDyn *method, un
|
||||
|
||||
/* V8's rendering of a value in a TypeError: primitives by ToString,
|
||||
* arrays as [object Array], other objects as #<Constructor>. */
|
||||
static void scr_ds_put_value(ScrJsonBuf *b, const ScrDyn *value) {
|
||||
void scr_ds_put_value(ScrJsonBuf *b, const ScrDyn *value) {
|
||||
switch (value->kind) {
|
||||
case SCR_DYN_UNDEF: scr_jb_puts(b, "undefined"); return;
|
||||
case SCR_DYN_NULL: scr_jb_puts(b, "null"); return;
|
||||
@@ -577,22 +557,24 @@ static void scr_ds_put_value(ScrJsonBuf *b, const ScrDyn *value) {
|
||||
}
|
||||
}
|
||||
|
||||
static ScrDisposableStack *scr_ds_of(const ScrDyn *value) {
|
||||
return value && value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_DISPOSABLE_STACK
|
||||
? value->v.handle.ptr : NULL;
|
||||
static ScrDisposableStack *scr_ds_of(const ScrDyn *value, ScrDynHandleTag tag) {
|
||||
return value && value->kind == SCR_DYN_HANDLE && value->v.handle.tag == tag ? value->v.handle.ptr : NULL;
|
||||
}
|
||||
|
||||
bool scr_disposable_stack_is(const ScrDyn *value) { return scr_ds_of(value) != NULL; }
|
||||
bool scr_disposable_stack_is(const ScrDyn *value) {
|
||||
return scr_ds_of(value, SCR_DYNH_DISPOSABLE_STACK) != NULL;
|
||||
}
|
||||
|
||||
/* The receiver of DisposableStack.prototype.<name>, or NULL with Node's
|
||||
* TypeError (an incompatible receiver) or ReferenceError (a disposed
|
||||
* stack, when `live` is required) pending. */
|
||||
static ScrDisposableStack *scr_ds_receiver(const ScrDyn *self, const char *name, bool getter, bool live) {
|
||||
ScrDisposableStack *s = scr_ds_of(self);
|
||||
ScrDisposableStack *scr_ds_receiver(const ScrDyn *self, ScrDynHandleTag tag, const char *name, bool getter,
|
||||
bool live) {
|
||||
ScrDisposableStack *s = scr_ds_of(self, tag);
|
||||
const char *cls = tag == SCR_DYNH_DISPOSABLE_STACK ? "DisposableStack" : "AsyncDisposableStack";
|
||||
ScrJsonBuf message;
|
||||
if (!s) {
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, getter ? "Method get DisposableStack.prototype." : "Method DisposableStack.prototype.");
|
||||
scr_jb_puts(&message, getter ? "Method get " : "Method ");
|
||||
scr_jb_puts(&message, cls);
|
||||
scr_jb_puts(&message, ".prototype.");
|
||||
scr_jb_puts(&message, name);
|
||||
scr_jb_puts(&message, " called on incompatible receiver ");
|
||||
scr_ds_put_value(&message, self);
|
||||
@@ -600,8 +582,11 @@ static ScrDisposableStack *scr_ds_receiver(const ScrDyn *self, const char *name,
|
||||
return NULL;
|
||||
}
|
||||
if (live && s->disposed) {
|
||||
/* V8 names the base class at the end for both stack classes. */
|
||||
scr_jb_init(&message);
|
||||
scr_jb_puts(&message, "Cannot call DisposableStack.prototype.");
|
||||
scr_jb_puts(&message, "Cannot call ");
|
||||
scr_jb_puts(&message, cls);
|
||||
scr_jb_puts(&message, ".prototype.");
|
||||
scr_jb_puts(&message, name);
|
||||
scr_jb_puts(&message, " on an already-disposed DisposableStack");
|
||||
scr_throw_error(SCR_ERR_REFERENCE, scr_jb_finish(&message));
|
||||
@@ -610,7 +595,7 @@ static ScrDisposableStack *scr_ds_receiver(const ScrDyn *self, const char *name,
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool scr_ds_callable(const ScrDyn *value) {
|
||||
bool scr_ds_callable(const ScrDyn *value) {
|
||||
if (scr_using_typeof_is(value, "function", 8)) return true;
|
||||
ScrJsonBuf message;
|
||||
scr_jb_init(&message);
|
||||
@@ -621,7 +606,7 @@ static bool scr_ds_callable(const ScrDyn *value) {
|
||||
}
|
||||
|
||||
bool scr_disposable_stack_use(const ScrDyn *stack, const ScrDyn *value) {
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, "use", false, true);
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, SCR_DYNH_DISPOSABLE_STACK, "use", false, true);
|
||||
if (!s) return false;
|
||||
if (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF) return true;
|
||||
ScrDyn *method = scr_dyn_using_method(value, false);
|
||||
@@ -631,21 +616,23 @@ bool scr_disposable_stack_use(const ScrDyn *stack, const ScrDyn *value) {
|
||||
}
|
||||
|
||||
bool scr_disposable_stack_use_callback(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *dispose) {
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, "use", false, true);
|
||||
ScrDynHandleTag tag = stack->kind == SCR_DYN_HANDLE && stack->v.handle.tag == SCR_DYNH_ASYNC_DISPOSABLE_STACK
|
||||
? SCR_DYNH_ASYNC_DISPOSABLE_STACK : SCR_DYNH_DISPOSABLE_STACK;
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, tag, "use", false, true);
|
||||
if (!s) return false;
|
||||
scr_ds_push(s, scr_dyn_retain((ScrDyn *)value), scr_dyn_retain((ScrDyn *)dispose), SCR_DS_ADOPT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scr_disposable_stack_adopt(const ScrDyn *stack, const ScrDyn *value, const ScrDyn *on_dispose) {
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, "adopt", false, true);
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, SCR_DYNH_DISPOSABLE_STACK, "adopt", false, true);
|
||||
if (!s || !scr_ds_callable(on_dispose)) return false;
|
||||
scr_ds_push(s, scr_dyn_retain((ScrDyn *)value), scr_dyn_retain((ScrDyn *)on_dispose), SCR_DS_ADOPT);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool scr_disposable_stack_defer(const ScrDyn *stack, const ScrDyn *on_dispose) {
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, "defer", false, true);
|
||||
ScrDisposableStack *s = scr_ds_receiver(stack, SCR_DYNH_DISPOSABLE_STACK, "defer", false, true);
|
||||
if (!s || !scr_ds_callable(on_dispose)) return false;
|
||||
scr_ds_push(s, NULL, scr_dyn_retain((ScrDyn *)on_dispose), SCR_DS_DEFER);
|
||||
return true;
|
||||
@@ -714,21 +701,16 @@ static ScrDyn *scr_ds_method_call(ScrClosure *clo, ScrDyn *const *args, size_t a
|
||||
break;
|
||||
case SCR_DS_M_MOVE: {
|
||||
/* The new stack takes the entries; this one becomes disposed. */
|
||||
ScrDisposableStack *s = scr_ds_receiver(self, "move", false, true);
|
||||
ScrDisposableStack *s = scr_ds_receiver(self, SCR_DYNH_DISPOSABLE_STACK, "move", false, true);
|
||||
if (!s) break;
|
||||
ScrDisposableStack *moved = scr_ds_alloc();
|
||||
moved->items = s->items;
|
||||
moved->len = s->len;
|
||||
moved->cap = s->cap;
|
||||
s->items = NULL;
|
||||
s->len = s->cap = 0;
|
||||
s->disposed = true;
|
||||
scr_ds_move(moved, s);
|
||||
out = scr_ds_box(moved);
|
||||
scr_ds_release(moved);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ScrDisposableStack *s = scr_ds_receiver(self, "dispose", false, false);
|
||||
ScrDisposableStack *s = scr_ds_receiver(self, SCR_DYNH_DISPOSABLE_STACK, "dispose", false, false);
|
||||
if (s) out = scr_ds_dispose(s);
|
||||
break;
|
||||
}
|
||||
@@ -803,6 +785,23 @@ static const ScrDynHandleOps scr_ds_ops = {
|
||||
.symbol_get = scr_ds_symbol_get,
|
||||
};
|
||||
|
||||
ScrDisposableStack *scr_ds_storage_new(void) {
|
||||
static ScrCycType cyc_type = {&scr_ds_trace, &scr_ds_gcfree, SCR_CYC_KIND_NONE, 0};
|
||||
ScrDisposableStack *s = scr_cyc_alloc(sizeof *s, &cyc_type);
|
||||
s->rc = 1;
|
||||
scr_obj_alloc_note();
|
||||
return s;
|
||||
}
|
||||
|
||||
void scr_ds_move(ScrDisposableStack *to, ScrDisposableStack *from) {
|
||||
to->items = from->items;
|
||||
to->len = from->len;
|
||||
to->cap = from->cap;
|
||||
from->items = NULL;
|
||||
from->len = from->cap = 0;
|
||||
from->disposed = true;
|
||||
}
|
||||
|
||||
static ScrDisposableStack *scr_ds_alloc(void) {
|
||||
static SCR_TL bool installed;
|
||||
if (!installed) {
|
||||
@@ -810,11 +809,7 @@ static ScrDisposableStack *scr_ds_alloc(void) {
|
||||
scr_dyn_handle_install(SCR_DYNH_DISPOSABLE_STACK, &scr_ds_ops);
|
||||
scr_atexit(scr_ds_teardown);
|
||||
}
|
||||
static ScrCycType cyc_type = {&scr_ds_trace, &scr_ds_gcfree, SCR_CYC_KIND_NONE, 0};
|
||||
ScrDisposableStack *s = scr_cyc_alloc(sizeof *s, &cyc_type);
|
||||
s->rc = 1;
|
||||
scr_obj_alloc_note();
|
||||
return s;
|
||||
return scr_ds_storage_new();
|
||||
}
|
||||
|
||||
static ScrDyn *scr_ds_box(ScrDisposableStack *s) { return scr_dyn_new_handle(s, SCR_DYNH_DISPOSABLE_STACK); }
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// AsyncDisposableStack: disposeAsync interleaved with a microtask ticker (each
|
||||
// awaited result, a throwing callback, absent resources, failures), class
|
||||
// resources with [Symbol.asyncDispose] or [Symbol.dispose], await using over
|
||||
// a stack, move, SuppressedError chains, and stacks whose callbacks capture
|
||||
// the stack. Node.js prints the same.
|
||||
const log: string[] = [];
|
||||
async function ticker(label: string, n: number): Promise<void> {
|
||||
for (let i = 0; i < n; i++) {
|
||||
log.push(label + i);
|
||||
await null;
|
||||
}
|
||||
}
|
||||
async function scenario(name: string, build: (s: AsyncDisposableStack) => void): Promise<void> {
|
||||
log.length = 0;
|
||||
const s = new AsyncDisposableStack();
|
||||
build(s);
|
||||
const tick = ticker("t", 14);
|
||||
const p = s.disposeAsync().then(
|
||||
() => log.push("resolved"),
|
||||
(e: unknown) => log.push("rejected " + (e instanceof Error ? e.message : String(e))),
|
||||
);
|
||||
log.push("sync end");
|
||||
await p;
|
||||
await tick;
|
||||
console.log(name + ": " + log.join(" "));
|
||||
}
|
||||
async function timings(): Promise<void> {
|
||||
await scenario("empty", () => {});
|
||||
await scenario("one sync defer", (s) => s.defer(() => { log.push("d"); }));
|
||||
await scenario("one async defer", (s) => s.defer(async () => { log.push("d"); }));
|
||||
await scenario("two defers", (s) => {
|
||||
s.defer(() => { log.push("d1"); });
|
||||
s.defer(() => { log.push("d2"); });
|
||||
});
|
||||
await scenario("sync use", (s) => { s.use({ [Symbol.dispose]() { log.push("u"); } }); });
|
||||
await scenario("async use", (s) => { s.use({ async [Symbol.asyncDispose]() { log.push("u"); } }); });
|
||||
await scenario("null use", (s) => { s.use(null); });
|
||||
await scenario("null then defer", (s) => { s.defer(() => { log.push("d"); }); s.use(null); });
|
||||
await scenario("defer then null", (s) => { s.use(undefined); s.defer(() => { log.push("d"); }); });
|
||||
await scenario("adopt", (s) => { s.adopt(3, (v) => { log.push("a" + v); }); });
|
||||
await scenario("slow async", (s) => s.defer(async () => { log.push("d start"); await null; await null; log.push("d end"); }));
|
||||
await scenario("throws", (s) => s.defer(() => { throw new Error("boom"); }));
|
||||
await scenario("rejects", (s) => s.defer(async () => { throw new Error("boom"); }));
|
||||
await scenario("two failures", (s) => {
|
||||
s.defer(() => { throw new Error("first"); });
|
||||
s.defer(async () => { throw new Error("second"); });
|
||||
});
|
||||
await scenario("returns promise", (s) => s.defer(() => Promise.resolve()));
|
||||
await scenario("already disposed", (s) => { void s.disposeAsync(); });
|
||||
}
|
||||
|
||||
const events: string[] = [];
|
||||
class AsyncResource {
|
||||
readonly name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
async [Symbol.asyncDispose](): Promise<void> {
|
||||
events.push("async start " + this.name);
|
||||
await null;
|
||||
events.push("async end " + this.name);
|
||||
}
|
||||
}
|
||||
class SyncResource {
|
||||
readonly name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
[Symbol.dispose](): void {
|
||||
events.push("sync " + this.name);
|
||||
}
|
||||
}
|
||||
class Both extends SyncResource {
|
||||
async [Symbol.asyncDispose](): Promise<void> {
|
||||
events.push("both async " + this.name);
|
||||
}
|
||||
}
|
||||
class FailingAsync {
|
||||
async [Symbol.asyncDispose](): Promise<void> {
|
||||
throw new Error("async failure");
|
||||
}
|
||||
}
|
||||
async function eventTicker(n: number): Promise<void> {
|
||||
for (let i = 0; i < n; i++) {
|
||||
events.push("t" + i);
|
||||
await null;
|
||||
}
|
||||
}
|
||||
async function main(): Promise<void> {
|
||||
const tick = eventTicker(10);
|
||||
{
|
||||
await using stack = new AsyncDisposableStack();
|
||||
const a = stack.use(new AsyncResource("a"));
|
||||
stack.use(new SyncResource("s"));
|
||||
stack.use(new Both("b"));
|
||||
stack.adopt(new SyncResource("adopted"), async (r) => {
|
||||
events.push("adopt " + r.name);
|
||||
});
|
||||
stack.defer(() => {
|
||||
events.push("defer");
|
||||
});
|
||||
events.push("got " + a.name + " " + stack.disposed);
|
||||
}
|
||||
events.push("after block");
|
||||
await tick;
|
||||
console.log(events.join("\n"));
|
||||
events.length = 0;
|
||||
|
||||
const outer = new AsyncDisposableStack();
|
||||
outer.use(new SyncResource("moved"));
|
||||
const moved = outer.move();
|
||||
console.log(outer.disposed, moved.disposed, moved instanceof AsyncDisposableStack, moved instanceof DisposableStack);
|
||||
await moved.disposeAsync();
|
||||
await moved.disposeAsync();
|
||||
console.log(events.join(","), moved.disposed);
|
||||
|
||||
const failing = new AsyncDisposableStack();
|
||||
failing.use(new FailingAsync());
|
||||
failing.defer(() => {
|
||||
throw new TypeError("sync failure");
|
||||
});
|
||||
failing.use(new FailingAsync());
|
||||
try {
|
||||
await failing.disposeAsync();
|
||||
} catch (e) {
|
||||
const chain: string[] = [];
|
||||
let cur: unknown = e;
|
||||
while (cur instanceof SuppressedError) {
|
||||
chain.push((cur.error as Error).message);
|
||||
cur = cur.suppressed;
|
||||
}
|
||||
chain.push((cur as Error).message);
|
||||
console.log(chain.join(" <- "));
|
||||
}
|
||||
try {
|
||||
await using s = new AsyncDisposableStack();
|
||||
s.defer(async () => {
|
||||
throw new Error("disposal");
|
||||
});
|
||||
throw new Error("body");
|
||||
} catch (e) {
|
||||
if (e instanceof SuppressedError) console.log(e.name, (e.error as Error).message, (e.suppressed as Error).message);
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const cyc = new AsyncDisposableStack();
|
||||
cyc.defer(() => console.log("never", cyc.disposed));
|
||||
}
|
||||
const shown = new AsyncDisposableStack();
|
||||
console.log(shown, String(shown), JSON.stringify(shown), typeof shown.disposeAsync);
|
||||
const sync = new DisposableStack();
|
||||
sync.defer(() => console.log("sync stack inside async"));
|
||||
await using wrapper = new AsyncDisposableStack();
|
||||
wrapper.use(sync);
|
||||
console.log("end of main");
|
||||
}
|
||||
void timings().then(() => main());
|
||||
@@ -0,0 +1,48 @@
|
||||
// AsyncDisposableStack from JavaScript: untyped resources and callbacks, the
|
||||
// order of async and sync disposals against a microtask ticker, detached
|
||||
// methods, and Node's TypeError, ReferenceError and rejection texts. Node.js
|
||||
// prints the same.
|
||||
const messages = new AsyncDisposableStack();
|
||||
const t = (f) => { try { const r = f(); console.log("ok", r); } catch (e) { console.log(e.name + ": " + e.message); } };
|
||||
const { use, defer, move, disposeAsync, adopt } = messages;
|
||||
t(() => use(null));
|
||||
t(() => defer.call({}, () => {}));
|
||||
t(() => move.call(5));
|
||||
disposeAsync.call({}).catch((e) => console.log("rejected", e.name + ": " + e.message));
|
||||
t(() => messages.adopt(1, 2));
|
||||
t(() => messages.use(5));
|
||||
t(() => messages.use({}));
|
||||
console.log(messages.disposeAsync.name, messages.disposeAsync.length, messages[Symbol.asyncDispose] === messages.disposeAsync, messages.use.length, messages.adopt.length, messages.defer.length, messages.move.length, typeof messages.dispose);
|
||||
console.log(String(messages), JSON.stringify(messages), messages);
|
||||
messages.disposeAsync().then(() => { t(() => messages.defer(() => {})); t(() => messages.move()); console.log(messages.disposed); });
|
||||
const m = new AsyncDisposableStack(); m.defer(() => console.log("moved ran")); const n = m.move(); console.log(m.disposed, n.disposed, n instanceof AsyncDisposableStack, n instanceof DisposableStack); n.disposeAsync();
|
||||
const order = [];
|
||||
async function orderTicker(n) { for (let i = 0; i < n; i++) { order.push("t" + i); await null; } }
|
||||
async function main() {
|
||||
const tick = orderTicker(12);
|
||||
const s = new AsyncDisposableStack();
|
||||
s.defer(() => { order.push("sync defer"); });
|
||||
s.defer(async () => { order.push("async defer start"); await null; order.push("async defer end"); });
|
||||
s.use({ [Symbol.dispose]() { order.push("sync use"); } });
|
||||
s.use({ async [Symbol.asyncDispose]() { order.push("async use"); } });
|
||||
s.adopt(1, (v) => { order.push("adopt " + v); });
|
||||
s.use(null);
|
||||
const p = s.disposeAsync();
|
||||
order.push("returned " + (typeof p.then));
|
||||
await p;
|
||||
order.push("done " + s.disposed);
|
||||
await s.disposeAsync();
|
||||
order.push("again");
|
||||
await tick;
|
||||
console.log(order.join("\n"));
|
||||
const e = new AsyncDisposableStack();
|
||||
e.defer(async () => { throw new Error("a"); });
|
||||
e.defer(() => { throw new Error("b"); });
|
||||
try { await e.disposeAsync(); } catch (err) { console.log(err.name, err.error.message, err.suppressed.message); }
|
||||
try { e.use({}); } catch (err) { console.log(err.name, err.message); }
|
||||
const f = new AsyncDisposableStack(); void 0; console.log(typeof f.dispose, typeof f[Symbol.asyncDispose], String(f));
|
||||
try { new AsyncDisposableStack().use(3); } catch (err) { console.log(err.message); }
|
||||
try { new AsyncDisposableStack().use({}); } catch (err) { console.log(err.message); }
|
||||
console.log(new AsyncDisposableStack());
|
||||
}
|
||||
main();
|
||||
@@ -1849,6 +1849,8 @@
|
||||
"5859-disposable-stack-js.js": 3100,
|
||||
"5882-string-decoder-runtime-encoding.ts": 2541,
|
||||
"5883-os-set-priority.ts": 2380,
|
||||
"5902-async-disposable-stack.ts": 3200,
|
||||
"5903-async-disposable-stack-js.js": 3100,
|
||||
"5904-async-generator-completion.ts": 2950,
|
||||
"5905-array-from-async-generator.ts": 2750,
|
||||
"5906-array-from-async-generator-js.js": 2950,
|
||||
@@ -4496,6 +4498,8 @@
|
||||
"5859-disposable-stack-js.js": 3600,
|
||||
"5882-string-decoder-runtime-encoding.ts": 3854,
|
||||
"5883-os-set-priority.ts": 3417,
|
||||
"5902-async-disposable-stack.ts": 3550,
|
||||
"5903-async-disposable-stack-js.js": 3500,
|
||||
"5904-async-generator-completion.ts": 3800,
|
||||
"5905-array-from-async-generator.ts": 3500,
|
||||
"5906-array-from-async-generator-js.js": 3750,
|
||||
|
||||
Reference in New Issue
Block a user