Skip to content

Commit

Permalink
refactor: generalize finalizer second pass callback (nodejs/node#44141)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed Dec 26, 2022
1 parent 6231ddf commit 0b985bf
Show file tree
Hide file tree
Showing 17 changed files with 322 additions and 147 deletions.
9 changes: 3 additions & 6 deletions packages/emnapi/src/emnapi.c
Expand Up @@ -363,6 +363,8 @@ napi_status napi_cancel_async_work(napi_env env, napi_async_work work) {

#if NAPI_VERSION >= 4 && defined(__EMSCRIPTEN_PTHREADS__)

extern void _emnapi_call_finalizer(napi_env env, napi_finalize cb, void* data, void* hint);

static const unsigned char kDispatchIdle = 0;
static const unsigned char kDispatchRunning = 1 << 0;
static const unsigned char kDispatchPending = 1 << 1;
Expand Down Expand Up @@ -538,16 +540,11 @@ static void _emnapi_tsfn_empty_queue_and_delete(napi_threadsafe_function func) {
_emnapi_tsfn_destroy(func);
}

static void _emnapi_tsfn_call_finalize_cb(napi_env env, void* args) {
napi_threadsafe_function func = (napi_threadsafe_function) args;
func->finalize_cb(env, func->finalize_data, func->context);
}

static void _emnapi_tsfn_finalize(napi_threadsafe_function func) {
napi_handle_scope scope;
napi_open_handle_scope(func->env, &scope);
if (func->finalize_cb) {
_emnapi_call_into_module(func->env, _emnapi_tsfn_call_finalize_cb, func);
_emnapi_call_finalizer(func->env, func->finalize_cb, func->finalize_data, func->context);
}
_emnapi_tsfn_empty_queue_and_delete(func);
napi_close_handle_scope(func->env, scope);
Expand Down
4 changes: 2 additions & 2 deletions packages/emnapi/src/env.ts
@@ -1,9 +1,9 @@
function napi_set_instance_data (env: napi_env, data: void_p, finalize_cb: napi_finalize, finalize_hint: void_p): napi_status {
return emnapiCtx.checkEnv(env, (envObject) => {
if (envObject.instanceData) {
emnapiRt.RefBase.doDelete(envObject.instanceData)
envObject.instanceData.dispose()
}
envObject.instanceData = new emnapiRt.RefBase(envObject, 0, true, finalize_cb, data, finalize_hint)
envObject.instanceData = new emnapiRt.RefBase(envObject, 0, emnapiRt.Ownership.kRuntime, finalize_cb, data, finalize_hint)
return envObject.clearLastError()
})
}
Expand Down
11 changes: 8 additions & 3 deletions packages/emnapi/src/internal.ts
Expand Up @@ -158,12 +158,12 @@ function $emnapiWrap (type: WrapType, env: napi_env, js_object: napi_value, nati
let reference: emnapi.Reference
if (result) {
if (!finalize_cb) return envObject.setLastError(napi_status.napi_invalid_arg)
reference = emnapiRt.Reference.create(envObject, value.id, 0, false, finalize_cb, native_object, finalize_hint)
reference = emnapiRt.Reference.create(envObject, value.id, 0, emnapiRt.Ownership.kUserland, finalize_cb, native_object, finalize_hint)
$from64('result')

$makeSetValue('result', 0, 'reference.id', '*')
} else {
reference = emnapiRt.Reference.create(envObject, value.id, 0, true, finalize_cb, native_object, !finalize_cb ? finalize_cb : finalize_hint)
reference = emnapiRt.Reference.create(envObject, value.id, 0, emnapiRt.Ownership.kRuntime, finalize_cb, native_object, !finalize_cb ? finalize_cb : finalize_hint)
}

if (type === WrapType.retrievable) {
Expand Down Expand Up @@ -199,7 +199,12 @@ function $emnapiUnwrap (env: napi_env, js_object: napi_value, result: void_pp, a
}
if (action === UnwrapAction.RemoveWrap) {
value.wrapped = 0
emnapiRt.Reference.doDelete(ref)
if (ref.ownership() === emnapiRt.Ownership.kUserland) {
// When the wrap is been removed, the finalizer should be reset.
ref.resetFinalizer()
} else {
ref.dispose()
}
}
return envObject.getReturnStatus()
})
Expand Down
4 changes: 2 additions & 2 deletions packages/emnapi/src/life.ts
Expand Up @@ -90,7 +90,7 @@ function napi_create_reference (
}
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ref = emnapiRt.Reference.create(envObject, handle.id, initial_refcount >>> 0, false)
const ref = emnapiRt.Reference.create(envObject, handle.id, initial_refcount >>> 0, emnapiRt.Ownership.kUserland)
$from64('result')
$makeSetValue('result', 0, 'ref.id', '*')
return envObject.clearLastError()
Expand All @@ -104,7 +104,7 @@ function napi_delete_reference (
): napi_status {
return emnapiCtx.checkEnv(env, (envObject) => {
return emnapiCtx.checkArgs(envObject, [ref], () => {
emnapiRt.Reference.doDelete(emnapiCtx.refStore.get(ref)!)
emnapiCtx.refStore.get(ref)!.dispose()
return envObject.clearLastError()
})
})
Expand Down
32 changes: 19 additions & 13 deletions packages/emnapi/src/simple-async-operation.ts
Expand Up @@ -6,23 +6,12 @@
// declare function __emnapi_async_send_js (callback: number, data: number): void
declare function __emnapi_set_immediate (callback: number, data: number): void
declare function __emnapi_next_tick (callback: number, data: number): void
declare function emnapiSetImmediate (callback: () => any): any

mergeInto(LibraryManager.library, {
$emnapiSetImmediate: 'typeof setImmediate === "function" ? setImmediate : function (f) {' +
'var channel = new MessageChannel();' +
'channel.port1.onmessage = function () {' +
'channel.port1.onmessage = null;' +
'channel = undefined;' +
'f();' +
'};' +
'channel.port2.postMessage(null);' +
'};',

_emnapi_set_immediate__sig: 'vpp',
_emnapi_set_immediate__deps: ['$emnapiGetDynamicCalls', '$emnapiSetImmediate'],
_emnapi_set_immediate__deps: ['$emnapiGetDynamicCalls', '$emnapiInit', '$emnapiRt'],
_emnapi_set_immediate: function (callback: number, data: number): void {
emnapiSetImmediate(() => {
emnapiRt._setImmediate(() => {
$from64('callback')
emnapiGetDynamicCalls.call_vp(callback, data)
})
Expand Down Expand Up @@ -99,3 +88,20 @@ mergeInto(LibraryManager.library, {
'return r;' +
'};'
})

function _emnapi_call_into_module (env: napi_env, callback: number, data: number): void {
const envObject = emnapiCtx.envStore.get(env)!
const scope = emnapiCtx.openScope(envObject, emnapiRt.HandleScope)
try {
envObject.callIntoModule((_envObject) => {
$from64('callback')
emnapiGetDynamicCalls.call_vpp(callback, env, data)
})
} catch (err) {
emnapiCtx.closeScope(envObject, scope)
throw err
}
emnapiCtx.closeScope(envObject, scope)
}

emnapiImplement('_emnapi_call_into_module', 'vppp', _emnapi_call_into_module, ['$emnapiGetDynamicCalls'])
17 changes: 4 additions & 13 deletions packages/emnapi/src/thread-safe-function.ts
@@ -1,16 +1,7 @@
function _emnapi_call_into_module (env: napi_env, callback: number, data: number): void {
function _emnapi_call_finalizer (env: napi_env, callback: number, data: number, hint: number): void {
const envObject = emnapiCtx.envStore.get(env)!
const scope = emnapiCtx.openScope(envObject, emnapiRt.HandleScope)
try {
envObject.callIntoModule((_envObject) => {
$from64('callback')
emnapiGetDynamicCalls.call_vpp(callback, env, data)
})
} catch (err) {
emnapiCtx.closeScope(envObject, scope)
throw err
}
emnapiCtx.closeScope(envObject, scope)
$from64('callback')
envObject.callFinalizer(callback, data, hint)
}

function _emnapi_tsfn_dispatch_one_js (env: number, ref: number, call_js_cb: number, context: number, data: number): void {
Expand All @@ -31,5 +22,5 @@ function _emnapi_tsfn_dispatch_one_js (env: number, ref: number, call_js_cb: num
emnapiCtx.closeScope(envObject, scope)
}

emnapiImplement('_emnapi_call_into_module', 'vpp', _emnapi_call_into_module, ['$emnapiGetDynamicCalls'])
emnapiImplement('_emnapi_call_finalizer', 'vpppp', _emnapi_call_finalizer)
emnapiImplement('_emnapi_tsfn_dispatch_one_js', 'vppppp', _emnapi_tsfn_dispatch_one_js, ['$emnapiGetDynamicCalls'])
2 changes: 1 addition & 1 deletion packages/emnapi/src/value/create.ts
Expand Up @@ -67,7 +67,7 @@ function napi_create_external (env: napi_env, data: void_p, finalize_cb: napi_fi
const externalHandle = emnapiRt.ExternalHandle.createExternal(envObject, data)
emnapiCtx.getCurrentScope()!.addHandle(externalHandle)
if (finalize_cb) {
emnapiRt.Reference.create(envObject, externalHandle.id, 0, true, finalize_cb, data, finalize_hint)
emnapiRt.Reference.create(envObject, externalHandle.id, 0, emnapiRt.Ownership.kRuntime, finalize_cb, data, finalize_hint)
}
$from64('result')
$makeSetValue('result', 0, 'externalHandle.id', '*')
Expand Down
31 changes: 11 additions & 20 deletions packages/runtime/src/Finalizer.ts
@@ -1,34 +1,25 @@
import type { Env } from './env'

/** @internal */
export enum EnvReferenceMode {
kNoEnvReference,
kKeepEnvReference
}

/** @internal */
export class Finalizer {
protected _hasEnvReference: boolean
protected _finalizeRan: boolean

public constructor (
protected envObject: Env,
protected _finalizeCallback: napi_finalize = 0,
protected _finalizeData: void_p = 0,
protected _finalizeHint: void_p = 0,
refmode: EnvReferenceMode = EnvReferenceMode.kNoEnvReference
) {
this._finalizeRan = false
this._hasEnvReference = refmode === EnvReferenceMode.kKeepEnvReference
if (this._hasEnvReference) {
envObject.ref()
}
protected _finalizeHint: void_p = 0
) {}

public callback (): napi_finalize { return this._finalizeCallback }
public data (): void_p { return this._finalizeData }
public hint (): void_p { return this._finalizeHint }

public resetFinalizer (): void {
this._finalizeCallback = 0
this._finalizeData = 0
this._finalizeHint = 0
}

public dispose (): void {
if (this._hasEnvReference) {
this.envObject.unref()
}
this.envObject = undefined!
}
}
77 changes: 77 additions & 0 deletions packages/runtime/src/Global.ts
@@ -0,0 +1,77 @@
import { supportFinalizer } from './util'

/** @public */
export class Global<T extends object> {
private _ref: T | WeakRef<T> | null
private _param: any
private _callback: ((param: any) => void) | undefined

private static readonly _registry = supportFinalizer
? new FinalizationRegistry((value: Global<any>) => {
value._ref = null
if (typeof value._callback === 'function') {
value._callback(value._param)
}
})
: undefined!

constructor (value: T) {
this._ref = value
}

setWeak<P> (param: any, callback: (param: P) => void): void {
if (this._ref === null) return
if (!supportFinalizer) return
if (this._ref instanceof WeakRef) return
this._param = param
this._callback = callback
Global._registry.register(this._ref, this, this)
this._ref = new WeakRef<T>(this._ref)
}

clearWeak (): void {
if (this._ref === null) return
if (!supportFinalizer) return
if (this._ref instanceof WeakRef) {
try {
Global._registry.unregister(this)
} catch (_) {}
this._param = undefined
this._callback = undefined
this._ref = this._ref.deref() as T
}
}

reset (other?: T | WeakRef<T>): void {
if (supportFinalizer) {
try {
Global._registry.unregister(this)
} catch (_) {}
}
this._param = undefined
this._callback = undefined
if (other) {
this._ref = other
} else {
this._ref = null
}
}

isEmpty (): boolean {
return this._ref === null
}

deref (): T | undefined {
if (!supportFinalizer) {
return (this._ref as T | null) ?? undefined
}

if (this._ref === null) return undefined

if (this._ref instanceof WeakRef) {
return this._ref.deref()
}

return this._ref
}
}
53 changes: 27 additions & 26 deletions packages/runtime/src/RefBase.ts
@@ -1,11 +1,16 @@
import type { Env } from './env'
import { Finalizer } from './Finalizer'
import { RefTracker } from './RefTracker'
import { supportFinalizer } from './util'

/** @internal */
export interface RefBase extends Finalizer, RefTracker {}

/** @public */
export enum Ownership {
kRuntime,
kUserland
}

/** @internal */
export class RefBase extends Finalizer {
public static finalizeAll (list: RefTracker): void {
Expand All @@ -21,12 +26,12 @@ export class RefBase extends Finalizer {
}

private _refcount: uint32_t
private _deleteSelf: boolean
private readonly _ownership: Ownership

constructor (
envObject: Env,
initial_refcount: uint32_t,
delete_self: boolean,
ownership: Ownership,
finalize_callback: napi_finalize,
finalize_data: void_p,
finalize_hint: void_p
Expand All @@ -35,13 +40,14 @@ export class RefBase extends Finalizer {
;(this as any)._next = null
;(this as any)._prev = null
this._refcount = initial_refcount
this._deleteSelf = delete_self
this._ownership = ownership

this.link(!finalize_callback ? envObject.reflist : envObject.finalizing_reflist)
}

public dispose (): void {
this.unlink()
this.envObject.dequeueFinalizer(this)
super.dispose()
}

Expand All @@ -64,39 +70,34 @@ export class RefBase extends Finalizer {
return this._refcount
}

public static doDelete (reference: RefBase): void {
if ((reference.refCount() !== 0) || (reference._deleteSelf) ||
(reference._finalizeRan) || !supportFinalizer) {
reference.dispose()
} else {
// defer until finalizer runs as
// it may already be queued
reference._deleteSelf = true
}
public ownership (): Ownership {
return this._ownership
}

protected finalize (isEnvTeardown = false): void {
if (isEnvTeardown && this.refCount() > 0) this._refcount = 0
public finalize (): void {
const ownership = this._ownership
// Swap out the field finalize_callback so that it can not be accidentally
// called more than once.
const finalize_callback = this._finalizeCallback
const finalize_data = this._finalizeData
const finalize_hint = this._finalizeHint
this.resetFinalizer()

this.unlink()

let error: any
let caught = false
if (this._finalizeCallback) {
const fini = Number(this._finalizeCallback)
this._finalizeCallback = 0
if (finalize_callback) {
const fini = Number(finalize_callback)
try {
this.envObject.callFinalizer(fini, this._finalizeData, this._finalizeHint)
this.envObject.callFinalizer(fini, finalize_data, finalize_hint)
} catch (err) {
caught = true
error = err
}
}
if (this._deleteSelf || isEnvTeardown) {
RefBase.doDelete(this)
} else {
this._finalizeRan = true
// leak if this is a non-self-delete weak reference
// should call napi_delete_referece manually
// Reference.doDelete(this)
if (ownership === Ownership.kRuntime) {
this.dispose()
}
if (caught) {
throw error
Expand Down

0 comments on commit 0b985bf

Please sign in to comment.