From 7832516bf84bbb95b2dfbbde82bf25cc4a258d12 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Wed, 24 Jan 2024 16:27:12 -0500 Subject: [PATCH] document how to handle setInterval with newFunction (#141) * document how to handle setInterval with newFunction * rebuild docs --- .../classes/QuickJSAsyncContext.md | 120 ++++++++++++++---- .../classes/QuickJSContext.md | 120 ++++++++++++++---- .../classes/QuickJSAsyncContext.md | 120 ++++++++++++++---- .../classes/QuickJSAsyncWASMModule.md | 8 +- .../classes/QuickJSContext.md | 120 ++++++++++++++---- .../classes/QuickJSWASMModule.md | 6 +- .../classes/TestQuickJSWASMModule.md | 16 +-- doc/quickjs-emscripten/exports.md | 14 +- .../interfaces/CustomizeVariantOptions.md | 16 +-- .../interfaces/ModuleEvalOptions.md | 8 +- .../namespaces/errors/README.md | 36 +++--- .../quickjs-emscripten-core/src/context.ts | 78 +++++++++++- 12 files changed, 521 insertions(+), 141 deletions(-) diff --git a/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md b/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md index dee944cf..5e6d94b1 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md @@ -275,7 +275,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:714](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L714) +[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) *** @@ -308,7 +308,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1005](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1005) +[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) *** @@ -339,7 +339,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:665](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L665) +[packages/quickjs-emscripten-core/src/context.ts:741](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L741) *** @@ -389,7 +389,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:831](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L831) +[packages/quickjs-emscripten-core/src/context.ts:907](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L907) *** @@ -423,7 +423,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:988](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L988) +[packages/quickjs-emscripten-core/src/context.ts:1064](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1064) *** @@ -473,7 +473,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:761](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L761) +[packages/quickjs-emscripten-core/src/context.ts:837](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L837) *** @@ -523,7 +523,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:564](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L564) +[packages/quickjs-emscripten-core/src/context.ts:640](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L640) *** @@ -547,7 +547,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:555](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L555) +[packages/quickjs-emscripten-core/src/context.ts:631](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L631) *** @@ -573,7 +573,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:526](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L526) +[packages/quickjs-emscripten-core/src/context.ts:602](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L602) *** @@ -603,7 +603,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:629](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L629) +[packages/quickjs-emscripten-core/src/context.ts:705](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L705) *** @@ -627,7 +627,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:534](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L534) +[packages/quickjs-emscripten-core/src/context.ts:610](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L610) *** @@ -652,7 +652,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:543](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L543) +[packages/quickjs-emscripten-core/src/context.ts:619](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L619) *** @@ -781,7 +781,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:481](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L481) +[packages/quickjs-emscripten-core/src/context.ts:557](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L557) #### newError(message) @@ -801,7 +801,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:482](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L482) +[packages/quickjs-emscripten-core/src/context.ts:558](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L558) #### newError(undefined) @@ -817,7 +817,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:483](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L483) +[packages/quickjs-emscripten-core/src/context.ts:559](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L559) *** @@ -832,11 +832,87 @@ A [VmFunctionImplementation](../exports.md#vmfunctionimplementationvmhandle) sho value. A VmFunctionImplementation should also not retain any references to its return value. +The function argument handles are automatically disposed when the function +returns. If you want to retain a handle beyond the end of the function, you +can call [Lifetime#dup](Lifetime.md#dup) to create a copy of the handle that you own +and must dispose manually. For example, you need to use this API and do some +extra book keeping to implement `setInterval`: + +```typescript +// This won't work because `callbackHandle` expires when the function returns, +// so when the interval fires, the callback handle is already disposed. +const WRONG_setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + const delayMs = context.getNumber(delayHandle) + const intervalId = globalThis.setInterval(() => { + // ERROR: callbackHandle is already disposed here. + context.callFunction(callbackHandle) + }, intervalId) + return context.newNumber(intervalId) +}) + +// This works since we dup the callbackHandle. +// We just need to make sure we clean it up manually when the interval is cleared -- +// so we need to keep track of those interval IDs, and make sure we clean all +// of them up when we dispose the owning context. + +const setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + // Ensure the guest can't overload us by scheduling too many intervals. + if (QuickJSInterval.INTERVALS.size > 100) { + throw new Error(`Too many intervals scheduled already`) + } + + const delayMs = context.getNumber(delayHandle) + const longLivedCallbackHandle = callbackHandle.dup() + const intervalId = globalThis.setInterval(() => { + context.callFunction(longLivedCallbackHandle) + }, intervalId) + const disposable = new QuickJSInterval(longLivedCallbackHandle, context, intervalId) + QuickJSInterval.INTERVALS.set(intervalId, disposable) + return context.newNumber(intervalId) +}) + +const clearIntervalHandle = context.newFunction("clearInterval", (intervalIdHandle) => { + const intervalId = context.getNumber(intervalIdHandle) + const disposable = QuickJSInterval.INTERVALS.get(intervalId) + disposable?.dispose() +}) + +class QuickJSInterval extends UsingDisposable { + static INTERVALS = new Map() + + static disposeContext(context: QuickJSContext) { + for (const interval of QuickJSInterval.INTERVALS.values()) { + if (interval.context === context) { + interval.dispose() + } + } + } + + constructor( + public fnHandle: QuickJSHandle, + public context: QuickJSContext, + public intervalId: number, + ) { + super() + } + + dispose() { + globalThis.clearInterval(this.intervalId) + this.fnHandle.dispose() + QuickJSInterval.INTERVALS.delete(this.fnHandle.value) + } + + get alive() { + return this.fnHandle.alive + } +} +``` + To implement an async function, create a promise with [newPromise](QuickJSAsyncContext.md#newpromise), then return the deferred promise handle from `deferred.handle` from your function implementation: -``` +```typescript const deferred = vm.newPromise() someNativeAsyncFunction().then(deferred.resolve) return deferred.handle @@ -858,7 +934,7 @@ return deferred.handle #### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:551](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L551) *** @@ -1091,7 +1167,7 @@ You may need to call [runtime](QuickJSAsyncContext.md#runtime).[QuickJSRuntime#e #### Source -[packages/quickjs-emscripten-core/src/context.ts:586](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L586) +[packages/quickjs-emscripten-core/src/context.ts:662](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L662) *** @@ -1128,7 +1204,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:650](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L650) +[packages/quickjs-emscripten-core/src/context.ts:726](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L726) *** @@ -1152,7 +1228,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:791](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L791) +[packages/quickjs-emscripten-core/src/context.ts:867](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L867) *** @@ -1180,7 +1256,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:517](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L517) +[packages/quickjs-emscripten-core/src/context.ts:593](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L593) *** @@ -1211,7 +1287,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:860](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L860) +[packages/quickjs-emscripten-core/src/context.ts:936](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L936) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSContext.md b/doc/quickjs-emscripten-core/classes/QuickJSContext.md index 2184c2a4..44a29670 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSContext.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSContext.md @@ -303,7 +303,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:714](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L714) +[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) *** @@ -332,7 +332,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1005](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1005) +[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) *** @@ -363,7 +363,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:665](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L665) +[packages/quickjs-emscripten-core/src/context.ts:741](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L741) *** @@ -413,7 +413,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:831](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L831) +[packages/quickjs-emscripten-core/src/context.ts:907](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L907) *** @@ -443,7 +443,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:988](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L988) +[packages/quickjs-emscripten-core/src/context.ts:1064](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1064) *** @@ -493,7 +493,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:761](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L761) +[packages/quickjs-emscripten-core/src/context.ts:837](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L837) *** @@ -513,7 +513,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:564](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L564) +[packages/quickjs-emscripten-core/src/context.ts:640](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L640) *** @@ -533,7 +533,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:555](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L555) +[packages/quickjs-emscripten-core/src/context.ts:631](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L631) *** @@ -559,7 +559,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:526](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L526) +[packages/quickjs-emscripten-core/src/context.ts:602](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L602) *** @@ -589,7 +589,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:629](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L629) +[packages/quickjs-emscripten-core/src/context.ts:705](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L705) *** @@ -613,7 +613,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:534](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L534) +[packages/quickjs-emscripten-core/src/context.ts:610](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L610) *** @@ -634,7 +634,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:543](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L543) +[packages/quickjs-emscripten-core/src/context.ts:619](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L619) *** @@ -715,7 +715,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:481](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L481) +[packages/quickjs-emscripten-core/src/context.ts:557](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L557) #### newError(message) @@ -731,7 +731,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:482](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L482) +[packages/quickjs-emscripten-core/src/context.ts:558](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L558) #### newError(undefined) @@ -743,7 +743,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip ##### Source -[packages/quickjs-emscripten-core/src/context.ts:483](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L483) +[packages/quickjs-emscripten-core/src/context.ts:559](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L559) *** @@ -758,11 +758,87 @@ A [VmFunctionImplementation](../exports.md#vmfunctionimplementationvmhandle) sho value. A VmFunctionImplementation should also not retain any references to its return value. +The function argument handles are automatically disposed when the function +returns. If you want to retain a handle beyond the end of the function, you +can call [Lifetime#dup](Lifetime.md#dup) to create a copy of the handle that you own +and must dispose manually. For example, you need to use this API and do some +extra book keeping to implement `setInterval`: + +```typescript +// This won't work because `callbackHandle` expires when the function returns, +// so when the interval fires, the callback handle is already disposed. +const WRONG_setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + const delayMs = context.getNumber(delayHandle) + const intervalId = globalThis.setInterval(() => { + // ERROR: callbackHandle is already disposed here. + context.callFunction(callbackHandle) + }, intervalId) + return context.newNumber(intervalId) +}) + +// This works since we dup the callbackHandle. +// We just need to make sure we clean it up manually when the interval is cleared -- +// so we need to keep track of those interval IDs, and make sure we clean all +// of them up when we dispose the owning context. + +const setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + // Ensure the guest can't overload us by scheduling too many intervals. + if (QuickJSInterval.INTERVALS.size > 100) { + throw new Error(`Too many intervals scheduled already`) + } + + const delayMs = context.getNumber(delayHandle) + const longLivedCallbackHandle = callbackHandle.dup() + const intervalId = globalThis.setInterval(() => { + context.callFunction(longLivedCallbackHandle) + }, intervalId) + const disposable = new QuickJSInterval(longLivedCallbackHandle, context, intervalId) + QuickJSInterval.INTERVALS.set(intervalId, disposable) + return context.newNumber(intervalId) +}) + +const clearIntervalHandle = context.newFunction("clearInterval", (intervalIdHandle) => { + const intervalId = context.getNumber(intervalIdHandle) + const disposable = QuickJSInterval.INTERVALS.get(intervalId) + disposable?.dispose() +}) + +class QuickJSInterval extends UsingDisposable { + static INTERVALS = new Map() + + static disposeContext(context: QuickJSContext) { + for (const interval of QuickJSInterval.INTERVALS.values()) { + if (interval.context === context) { + interval.dispose() + } + } + } + + constructor( + public fnHandle: QuickJSHandle, + public context: QuickJSContext, + public intervalId: number, + ) { + super() + } + + dispose() { + globalThis.clearInterval(this.intervalId) + this.fnHandle.dispose() + QuickJSInterval.INTERVALS.delete(this.fnHandle.value) + } + + get alive() { + return this.fnHandle.alive + } +} +``` + To implement an async function, create a promise with [newPromise](QuickJSContext.md#newpromise), then return the deferred promise handle from `deferred.handle` from your function implementation: -``` +```typescript const deferred = vm.newPromise() someNativeAsyncFunction().then(deferred.resolve) return deferred.handle @@ -784,7 +860,7 @@ return deferred.handle #### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:551](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L551) *** @@ -993,7 +1069,7 @@ You may need to call [runtime](QuickJSContext.md#runtime).[QuickJSRuntime#execut #### Source -[packages/quickjs-emscripten-core/src/context.ts:586](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L586) +[packages/quickjs-emscripten-core/src/context.ts:662](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L662) *** @@ -1030,7 +1106,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:650](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L650) +[packages/quickjs-emscripten-core/src/context.ts:726](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L726) *** @@ -1050,7 +1126,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:791](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L791) +[packages/quickjs-emscripten-core/src/context.ts:867](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L867) *** @@ -1078,7 +1154,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:517](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L517) +[packages/quickjs-emscripten-core/src/context.ts:593](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L593) *** @@ -1105,7 +1181,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:860](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L860) +[packages/quickjs-emscripten-core/src/context.ts:936](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L936) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md b/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md index 44fb1505..574eaba7 100644 --- a/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md +++ b/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md @@ -275,7 +275,7 @@ value. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1034 +packages/quickjs-emscripten-core/dist/index.d.ts:1110 *** @@ -308,7 +308,7 @@ socket.on("data", chunk => { #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1127 +packages/quickjs-emscripten-core/dist/index.d.ts:1203 *** @@ -339,7 +339,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1020 +packages/quickjs-emscripten-core/dist/index.d.ts:1096 *** @@ -389,7 +389,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1080 +packages/quickjs-emscripten-core/dist/index.d.ts:1156 *** @@ -423,7 +423,7 @@ socket.write(dataLifetime?.value) #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1114 +packages/quickjs-emscripten-core/dist/index.d.ts:1190 *** @@ -473,7 +473,7 @@ interrupted, the error will have name `InternalError` and message #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1055 +packages/quickjs-emscripten-core/dist/index.d.ts:1131 *** @@ -523,7 +523,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:982 +packages/quickjs-emscripten-core/dist/index.d.ts:1058 *** @@ -547,7 +547,7 @@ Converts `handle` to a Javascript bigint. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:978 +packages/quickjs-emscripten-core/dist/index.d.ts:1054 *** @@ -573,7 +573,7 @@ Converts `handle` into a Javascript number. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:965 +packages/quickjs-emscripten-core/dist/index.d.ts:1041 *** @@ -603,7 +603,7 @@ Javascript string (which will be converted automatically). #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1001 +packages/quickjs-emscripten-core/dist/index.d.ts:1077 *** @@ -627,7 +627,7 @@ Converts `handle` to a Javascript string. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:969 +packages/quickjs-emscripten-core/dist/index.d.ts:1045 *** @@ -652,7 +652,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:974 +packages/quickjs-emscripten-core/dist/index.d.ts:1050 *** @@ -781,7 +781,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:890 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:948 +packages/quickjs-emscripten-core/dist/index.d.ts:1024 #### newError(message) @@ -801,7 +801,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:948 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:952 +packages/quickjs-emscripten-core/dist/index.d.ts:1028 #### newError(undefined) @@ -817,7 +817,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:952 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:953 +packages/quickjs-emscripten-core/dist/index.d.ts:1029 *** @@ -832,11 +832,87 @@ A [VmFunctionImplementation](../exports.md#vmfunctionimplementationvmhandle) sho value. A VmFunctionImplementation should also not retain any references to its return value. +The function argument handles are automatically disposed when the function +returns. If you want to retain a handle beyond the end of the function, you +can call [Lifetime#dup](Lifetime.md#dup) to create a copy of the handle that you own +and must dispose manually. For example, you need to use this API and do some +extra book keeping to implement `setInterval`: + +```typescript +// This won't work because `callbackHandle` expires when the function returns, +// so when the interval fires, the callback handle is already disposed. +const WRONG_setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + const delayMs = context.getNumber(delayHandle) + const intervalId = globalThis.setInterval(() => { + // ERROR: callbackHandle is already disposed here. + context.callFunction(callbackHandle) + }, intervalId) + return context.newNumber(intervalId) +}) + +// This works since we dup the callbackHandle. +// We just need to make sure we clean it up manually when the interval is cleared -- +// so we need to keep track of those interval IDs, and make sure we clean all +// of them up when we dispose the owning context. + +const setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + // Ensure the guest can't overload us by scheduling too many intervals. + if (QuickJSInterval.INTERVALS.size > 100) { + throw new Error(`Too many intervals scheduled already`) + } + + const delayMs = context.getNumber(delayHandle) + const longLivedCallbackHandle = callbackHandle.dup() + const intervalId = globalThis.setInterval(() => { + context.callFunction(longLivedCallbackHandle) + }, intervalId) + const disposable = new QuickJSInterval(longLivedCallbackHandle, context, intervalId) + QuickJSInterval.INTERVALS.set(intervalId, disposable) + return context.newNumber(intervalId) +}) + +const clearIntervalHandle = context.newFunction("clearInterval", (intervalIdHandle) => { + const intervalId = context.getNumber(intervalIdHandle) + const disposable = QuickJSInterval.INTERVALS.get(intervalId) + disposable?.dispose() +}) + +class QuickJSInterval extends UsingDisposable { + static INTERVALS = new Map() + + static disposeContext(context: QuickJSContext) { + for (const interval of QuickJSInterval.INTERVALS.values()) { + if (interval.context === context) { + interval.dispose() + } + } + } + + constructor( + public fnHandle: QuickJSHandle, + public context: QuickJSContext, + public intervalId: number, + ) { + super() + } + + dispose() { + globalThis.clearInterval(this.intervalId) + this.fnHandle.dispose() + QuickJSInterval.INTERVALS.delete(this.fnHandle.value) + } + + get alive() { + return this.fnHandle.alive + } +} +``` + To implement an async function, create a promise with [newPromise](QuickJSAsyncContext.md#newpromise), then return the deferred promise handle from `deferred.handle` from your function implementation: -``` +```typescript const deferred = vm.newPromise() someNativeAsyncFunction().then(deferred.resolve) return deferred.handle @@ -858,7 +934,7 @@ return deferred.handle #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:947 +packages/quickjs-emscripten-core/dist/index.d.ts:1023 *** @@ -1091,7 +1167,7 @@ You may need to call [runtime](QuickJSAsyncContext.md#runtime).[QuickJSRuntime#e #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:993 +packages/quickjs-emscripten-core/dist/index.d.ts:1069 *** @@ -1128,7 +1204,7 @@ properties. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1013 +packages/quickjs-emscripten-core/dist/index.d.ts:1089 *** @@ -1152,7 +1228,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1067 +packages/quickjs-emscripten-core/dist/index.d.ts:1143 *** @@ -1180,7 +1256,7 @@ Does not support BigInt values correctly. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:960 +packages/quickjs-emscripten-core/dist/index.d.ts:1036 *** @@ -1211,7 +1287,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1087 +packages/quickjs-emscripten-core/dist/index.d.ts:1163 *** diff --git a/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md b/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md index 13d21822..80125d72 100644 --- a/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md +++ b/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md @@ -49,7 +49,7 @@ Synchronous evalCode is not supported. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1300 +packages/quickjs-emscripten-core/dist/index.d.ts:1376 *** @@ -79,7 +79,7 @@ See the documentation for [QuickJSWASMModule#evalCode](QuickJSWASMModule.md#eval #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1312 +packages/quickjs-emscripten-core/dist/index.d.ts:1388 *** @@ -105,7 +105,7 @@ be disposed when the context is disposed. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1298 +packages/quickjs-emscripten-core/dist/index.d.ts:1374 *** @@ -131,7 +131,7 @@ concurrent async actions, create multiple WebAssembly modules. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1292 +packages/quickjs-emscripten-core/dist/index.d.ts:1368 *** diff --git a/doc/quickjs-emscripten/classes/QuickJSContext.md b/doc/quickjs-emscripten/classes/QuickJSContext.md index f1c28a58..dc182b41 100644 --- a/doc/quickjs-emscripten/classes/QuickJSContext.md +++ b/doc/quickjs-emscripten/classes/QuickJSContext.md @@ -303,7 +303,7 @@ value. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1034 +packages/quickjs-emscripten-core/dist/index.d.ts:1110 *** @@ -332,7 +332,7 @@ socket.on("data", chunk => { #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1127 +packages/quickjs-emscripten-core/dist/index.d.ts:1203 *** @@ -363,7 +363,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1020 +packages/quickjs-emscripten-core/dist/index.d.ts:1096 *** @@ -413,7 +413,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1080 +packages/quickjs-emscripten-core/dist/index.d.ts:1156 *** @@ -443,7 +443,7 @@ socket.write(dataLifetime?.value) #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1114 +packages/quickjs-emscripten-core/dist/index.d.ts:1190 *** @@ -493,7 +493,7 @@ interrupted, the error will have name `InternalError` and message #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1055 +packages/quickjs-emscripten-core/dist/index.d.ts:1131 *** @@ -513,7 +513,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:982 +packages/quickjs-emscripten-core/dist/index.d.ts:1058 *** @@ -533,7 +533,7 @@ Converts `handle` to a Javascript bigint. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:978 +packages/quickjs-emscripten-core/dist/index.d.ts:1054 *** @@ -559,7 +559,7 @@ Converts `handle` into a Javascript number. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:965 +packages/quickjs-emscripten-core/dist/index.d.ts:1041 *** @@ -589,7 +589,7 @@ Javascript string (which will be converted automatically). #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1001 +packages/quickjs-emscripten-core/dist/index.d.ts:1077 *** @@ -613,7 +613,7 @@ Converts `handle` to a Javascript string. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:969 +packages/quickjs-emscripten-core/dist/index.d.ts:1045 *** @@ -634,7 +634,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:974 +packages/quickjs-emscripten-core/dist/index.d.ts:1050 *** @@ -715,7 +715,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:890 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:948 +packages/quickjs-emscripten-core/dist/index.d.ts:1024 #### newError(message) @@ -731,7 +731,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:948 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:952 +packages/quickjs-emscripten-core/dist/index.d.ts:1028 #### newError(undefined) @@ -743,7 +743,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:952 ##### Source -packages/quickjs-emscripten-core/dist/index.d.ts:953 +packages/quickjs-emscripten-core/dist/index.d.ts:1029 *** @@ -758,11 +758,87 @@ A [VmFunctionImplementation](../exports.md#vmfunctionimplementationvmhandle) sho value. A VmFunctionImplementation should also not retain any references to its return value. +The function argument handles are automatically disposed when the function +returns. If you want to retain a handle beyond the end of the function, you +can call [Lifetime#dup](Lifetime.md#dup) to create a copy of the handle that you own +and must dispose manually. For example, you need to use this API and do some +extra book keeping to implement `setInterval`: + +```typescript +// This won't work because `callbackHandle` expires when the function returns, +// so when the interval fires, the callback handle is already disposed. +const WRONG_setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + const delayMs = context.getNumber(delayHandle) + const intervalId = globalThis.setInterval(() => { + // ERROR: callbackHandle is already disposed here. + context.callFunction(callbackHandle) + }, intervalId) + return context.newNumber(intervalId) +}) + +// This works since we dup the callbackHandle. +// We just need to make sure we clean it up manually when the interval is cleared -- +// so we need to keep track of those interval IDs, and make sure we clean all +// of them up when we dispose the owning context. + +const setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + // Ensure the guest can't overload us by scheduling too many intervals. + if (QuickJSInterval.INTERVALS.size > 100) { + throw new Error(`Too many intervals scheduled already`) + } + + const delayMs = context.getNumber(delayHandle) + const longLivedCallbackHandle = callbackHandle.dup() + const intervalId = globalThis.setInterval(() => { + context.callFunction(longLivedCallbackHandle) + }, intervalId) + const disposable = new QuickJSInterval(longLivedCallbackHandle, context, intervalId) + QuickJSInterval.INTERVALS.set(intervalId, disposable) + return context.newNumber(intervalId) +}) + +const clearIntervalHandle = context.newFunction("clearInterval", (intervalIdHandle) => { + const intervalId = context.getNumber(intervalIdHandle) + const disposable = QuickJSInterval.INTERVALS.get(intervalId) + disposable?.dispose() +}) + +class QuickJSInterval extends UsingDisposable { + static INTERVALS = new Map() + + static disposeContext(context: QuickJSContext) { + for (const interval of QuickJSInterval.INTERVALS.values()) { + if (interval.context === context) { + interval.dispose() + } + } + } + + constructor( + public fnHandle: QuickJSHandle, + public context: QuickJSContext, + public intervalId: number, + ) { + super() + } + + dispose() { + globalThis.clearInterval(this.intervalId) + this.fnHandle.dispose() + QuickJSInterval.INTERVALS.delete(this.fnHandle.value) + } + + get alive() { + return this.fnHandle.alive + } +} +``` + To implement an async function, create a promise with [newPromise](QuickJSContext.md#newpromise), then return the deferred promise handle from `deferred.handle` from your function implementation: -``` +```typescript const deferred = vm.newPromise() someNativeAsyncFunction().then(deferred.resolve) return deferred.handle @@ -784,7 +860,7 @@ return deferred.handle #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:947 +packages/quickjs-emscripten-core/dist/index.d.ts:1023 *** @@ -993,7 +1069,7 @@ You may need to call [runtime](QuickJSContext.md#runtime).[QuickJSRuntime#execut #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:993 +packages/quickjs-emscripten-core/dist/index.d.ts:1069 *** @@ -1030,7 +1106,7 @@ properties. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1013 +packages/quickjs-emscripten-core/dist/index.d.ts:1089 *** @@ -1050,7 +1126,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1067 +packages/quickjs-emscripten-core/dist/index.d.ts:1143 *** @@ -1078,7 +1154,7 @@ Does not support BigInt values correctly. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:960 +packages/quickjs-emscripten-core/dist/index.d.ts:1036 *** @@ -1105,7 +1181,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1087 +packages/quickjs-emscripten-core/dist/index.d.ts:1163 *** diff --git a/doc/quickjs-emscripten/classes/QuickJSWASMModule.md b/doc/quickjs-emscripten/classes/QuickJSWASMModule.md index 000ef67b..25e286b8 100644 --- a/doc/quickjs-emscripten/classes/QuickJSWASMModule.md +++ b/doc/quickjs-emscripten/classes/QuickJSWASMModule.md @@ -80,7 +80,7 @@ with name `"InternalError"` and message `"interrupted"`. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1256 +packages/quickjs-emscripten-core/dist/index.d.ts:1332 *** @@ -102,7 +102,7 @@ be disposed when the context is disposed. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1230 +packages/quickjs-emscripten-core/dist/index.d.ts:1306 *** @@ -124,7 +124,7 @@ loading for one or more [QuickJSContext](QuickJSContext.md)s inside the runtime. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1224 +packages/quickjs-emscripten-core/dist/index.d.ts:1300 *** diff --git a/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md b/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md index 78c44235..07e99375 100644 --- a/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md +++ b/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md @@ -50,7 +50,7 @@ freed all the memory you've ever allocated. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1495 +packages/quickjs-emscripten-core/dist/index.d.ts:1571 ## Properties @@ -60,7 +60,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1495 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1493 +packages/quickjs-emscripten-core/dist/index.d.ts:1569 *** @@ -70,7 +70,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1493 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1494 +packages/quickjs-emscripten-core/dist/index.d.ts:1570 ## Methods @@ -84,7 +84,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1494 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1500 +packages/quickjs-emscripten-core/dist/index.d.ts:1576 *** @@ -98,7 +98,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1500 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1499 +packages/quickjs-emscripten-core/dist/index.d.ts:1575 *** @@ -122,7 +122,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1499 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1498 +packages/quickjs-emscripten-core/dist/index.d.ts:1574 *** @@ -144,7 +144,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1498 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1497 +packages/quickjs-emscripten-core/dist/index.d.ts:1573 *** @@ -166,7 +166,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1497 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1496 +packages/quickjs-emscripten-core/dist/index.d.ts:1572 *** diff --git a/doc/quickjs-emscripten/exports.md b/doc/quickjs-emscripten/exports.md index fbfbbdd9..b44e599b 100644 --- a/doc/quickjs-emscripten/exports.md +++ b/doc/quickjs-emscripten/exports.md @@ -540,7 +540,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:80 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1374 +packages/quickjs-emscripten-core/dist/index.d.ts:1450 *** @@ -593,7 +593,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:523 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1315 +packages/quickjs-emscripten-core/dist/index.d.ts:1391 *** @@ -1206,7 +1206,7 @@ const getDebugModule = memoizePromiseFactory(() => newQuickJSWASMModule(DEBUG_SY #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1373 +packages/quickjs-emscripten-core/dist/index.d.ts:1449 *** @@ -1335,7 +1335,7 @@ const quickjs = new newQuickJSAsyncWASMModuleFromVariant( #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1362 +packages/quickjs-emscripten-core/dist/index.d.ts:1438 *** @@ -1397,7 +1397,7 @@ const quickjs = new newQuickJSWASMModuleFromVariant( #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1337 +packages/quickjs-emscripten-core/dist/index.d.ts:1413 *** @@ -1424,7 +1424,7 @@ This may be necessary in Cloudflare Workers, which can't compile WebAssembly mod #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1415 +packages/quickjs-emscripten-core/dist/index.d.ts:1491 *** @@ -1447,7 +1447,7 @@ Interrupt execution if it's still running after this time. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1423 +packages/quickjs-emscripten-core/dist/index.d.ts:1499 *** diff --git a/doc/quickjs-emscripten/interfaces/CustomizeVariantOptions.md b/doc/quickjs-emscripten/interfaces/CustomizeVariantOptions.md index d328e524..2e718031 100644 --- a/doc/quickjs-emscripten/interfaces/CustomizeVariantOptions.md +++ b/doc/quickjs-emscripten/interfaces/CustomizeVariantOptions.md @@ -28,7 +28,7 @@ The enumerable properties of this object will be passed verbatim, although they #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1407 +packages/quickjs-emscripten-core/dist/index.d.ts:1483 *** @@ -68,7 +68,7 @@ Often `''` (empty string) #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1405 +packages/quickjs-emscripten-core/dist/index.d.ts:1481 *** @@ -100,7 +100,7 @@ Debug logger #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1409 +packages/quickjs-emscripten-core/dist/index.d.ts:1485 *** @@ -112,7 +112,7 @@ If given, Emscripten will compile the WebAssembly.Module from these bytes. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1379 +packages/quickjs-emscripten-core/dist/index.d.ts:1455 *** @@ -124,7 +124,7 @@ If given, Emscripten will try to load the WebAssembly module data from this loca #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1377 +packages/quickjs-emscripten-core/dist/index.d.ts:1453 *** @@ -136,7 +136,7 @@ If given, Emscripten will instantiate the WebAssembly.Instance from this existin #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1381 +packages/quickjs-emscripten-core/dist/index.d.ts:1457 *** @@ -148,7 +148,7 @@ If given, we will provide the source map to Emscripten directly. This may only b #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1385 +packages/quickjs-emscripten-core/dist/index.d.ts:1461 *** @@ -160,7 +160,7 @@ If given, Emscripten will try to load the source map for the WebAssembly module #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1383 +packages/quickjs-emscripten-core/dist/index.d.ts:1459 *** diff --git a/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md b/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md index b64b7a4d..c94b9613 100644 --- a/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md +++ b/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md @@ -27,7 +27,7 @@ To remove the limit, set to `0`. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1166 +packages/quickjs-emscripten-core/dist/index.d.ts:1242 *** @@ -39,7 +39,7 @@ Memory limit, in bytes, of WebAssembly heap memory used by the QuickJS VM. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1161 +packages/quickjs-emscripten-core/dist/index.d.ts:1237 *** @@ -51,7 +51,7 @@ Module loader for any `import` statements or expressions. #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1170 +packages/quickjs-emscripten-core/dist/index.d.ts:1246 *** @@ -64,7 +64,7 @@ See [shouldInterruptAfterDeadline](../exports.md#shouldinterruptafterdeadline). #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1157 +packages/quickjs-emscripten-core/dist/index.d.ts:1233 *** diff --git a/doc/quickjs-emscripten/namespaces/errors/README.md b/doc/quickjs-emscripten/namespaces/errors/README.md index fbf67b13..a52c7a1d 100644 --- a/doc/quickjs-emscripten/namespaces/errors/README.md +++ b/doc/quickjs-emscripten/namespaces/errors/README.md @@ -37,7 +37,7 @@ #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1459 +packages/quickjs-emscripten-core/dist/index.d.ts:1535 *** @@ -47,7 +47,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1459 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1461 +packages/quickjs-emscripten-core/dist/index.d.ts:1537 *** @@ -57,7 +57,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1461 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1463 +packages/quickjs-emscripten-core/dist/index.d.ts:1539 *** @@ -67,7 +67,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1463 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1465 +packages/quickjs-emscripten-core/dist/index.d.ts:1541 *** @@ -77,7 +77,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1465 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1467 +packages/quickjs-emscripten-core/dist/index.d.ts:1543 *** @@ -87,7 +87,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1467 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1469 +packages/quickjs-emscripten-core/dist/index.d.ts:1545 *** @@ -97,7 +97,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1469 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1471 +packages/quickjs-emscripten-core/dist/index.d.ts:1547 *** @@ -107,7 +107,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1471 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1473 +packages/quickjs-emscripten-core/dist/index.d.ts:1549 *** @@ -117,7 +117,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1473 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1475 +packages/quickjs-emscripten-core/dist/index.d.ts:1551 ## Variables @@ -127,7 +127,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1475 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1459 +packages/quickjs-emscripten-core/dist/index.d.ts:1535 *** @@ -137,7 +137,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1459 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1461 +packages/quickjs-emscripten-core/dist/index.d.ts:1537 *** @@ -147,7 +147,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1461 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1463 +packages/quickjs-emscripten-core/dist/index.d.ts:1539 *** @@ -157,7 +157,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1463 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1465 +packages/quickjs-emscripten-core/dist/index.d.ts:1541 *** @@ -167,7 +167,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1465 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1467 +packages/quickjs-emscripten-core/dist/index.d.ts:1543 *** @@ -177,7 +177,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1467 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1469 +packages/quickjs-emscripten-core/dist/index.d.ts:1545 *** @@ -187,7 +187,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1469 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1471 +packages/quickjs-emscripten-core/dist/index.d.ts:1547 *** @@ -197,7 +197,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1471 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1473 +packages/quickjs-emscripten-core/dist/index.d.ts:1549 *** @@ -207,7 +207,7 @@ packages/quickjs-emscripten-core/dist/index.d.ts:1473 #### Source -packages/quickjs-emscripten-core/dist/index.d.ts:1475 +packages/quickjs-emscripten-core/dist/index.d.ts:1551 *** diff --git a/packages/quickjs-emscripten-core/src/context.ts b/packages/quickjs-emscripten-core/src/context.ts index e574e07e..bb1640b4 100644 --- a/packages/quickjs-emscripten-core/src/context.ts +++ b/packages/quickjs-emscripten-core/src/context.ts @@ -462,11 +462,87 @@ export class QuickJSContext * value. A VmFunctionImplementation should also not retain any references to * its return value. * + * The function argument handles are automatically disposed when the function + * returns. If you want to retain a handle beyond the end of the function, you + * can call {@link Lifetime#dup} to create a copy of the handle that you own + * and must dispose manually. For example, you need to use this API and do some + * extra book keeping to implement `setInterval`: + * + * ```typescript + * // This won't work because `callbackHandle` expires when the function returns, + * // so when the interval fires, the callback handle is already disposed. + * const WRONG_setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + * const delayMs = context.getNumber(delayHandle) + * const intervalId = globalThis.setInterval(() => { + * // ERROR: callbackHandle is already disposed here. + * context.callFunction(callbackHandle) + * }, intervalId) + * return context.newNumber(intervalId) + * }) + * + * // This works since we dup the callbackHandle. + * // We just need to make sure we clean it up manually when the interval is cleared -- + * // so we need to keep track of those interval IDs, and make sure we clean all + * // of them up when we dispose the owning context. + * + * const setIntervalHandle = context.newFunction("setInterval", (callbackHandle, delayHandle) => { + * // Ensure the guest can't overload us by scheduling too many intervals. + * if (QuickJSInterval.INTERVALS.size > 100) { + * throw new Error(`Too many intervals scheduled already`) + * } + * + * const delayMs = context.getNumber(delayHandle) + * const longLivedCallbackHandle = callbackHandle.dup() + * const intervalId = globalThis.setInterval(() => { + * context.callFunction(longLivedCallbackHandle) + * }, intervalId) + * const disposable = new QuickJSInterval(longLivedCallbackHandle, context, intervalId) + * QuickJSInterval.INTERVALS.set(intervalId, disposable) + * return context.newNumber(intervalId) + * }) + * + * const clearIntervalHandle = context.newFunction("clearInterval", (intervalIdHandle) => { + * const intervalId = context.getNumber(intervalIdHandle) + * const disposable = QuickJSInterval.INTERVALS.get(intervalId) + * disposable?.dispose() + * }) + * + * class QuickJSInterval extends UsingDisposable { + * static INTERVALS = new Map() + * + * static disposeContext(context: QuickJSContext) { + * for (const interval of QuickJSInterval.INTERVALS.values()) { + * if (interval.context === context) { + * interval.dispose() + * } + * } + * } + * + * constructor( + * public fnHandle: QuickJSHandle, + * public context: QuickJSContext, + * public intervalId: number, + * ) { + * super() + * } + * + * dispose() { + * globalThis.clearInterval(this.intervalId) + * this.fnHandle.dispose() + * QuickJSInterval.INTERVALS.delete(this.fnHandle.value) + * } + * + * get alive() { + * return this.fnHandle.alive + * } + * } + * ``` + * * To implement an async function, create a promise with {@link newPromise}, then * return the deferred promise handle from `deferred.handle` from your * function implementation: * - * ``` + * ```typescript * const deferred = vm.newPromise() * someNativeAsyncFunction().then(deferred.resolve) * return deferred.handle