From dd1bd5270221d603c7e11eb15a353cd7a75a98cc Mon Sep 17 00:00:00 2001 From: ivagin Date: Wed, 10 Sep 2025 18:04:02 +0300 Subject: [PATCH] Improve FinalizationRegistry implementation Issue: ICXEVZ Signed-off-by: ivagin --- .../stdlib/std/core/FinalizationRegistry.ets | 226 +++++++----------- 1 file changed, 92 insertions(+), 134 deletions(-) diff --git a/static_core/plugins/ets/stdlib/std/core/FinalizationRegistry.ets b/static_core/plugins/ets/stdlib/std/core/FinalizationRegistry.ets index 3370119d04..37dcf4207c 100644 --- a/static_core/plugins/ets/stdlib/std/core/FinalizationRegistry.ets +++ b/static_core/plugins/ets/stdlib/std/core/FinalizationRegistry.ets @@ -15,6 +15,20 @@ package std.core; + +final class FinalizableObjectNode { + public constructor(obj: WeakRef, token: WeakRef, cbArg?: T) { + this.object = obj; + this.token = token; + this.cbArg = cbArg; + } + public object: WeakRef; + public token: WeakRef; + public cbArg: T | undefined; + public prev: FinalizableObjectNode | undefined = undefined; + public next: FinalizableObjectNode | undefined = undefined; +} + /** * FinalizationRegistry provides a way to execute the provided callback when a * registered object gets garbage collected. @@ -35,17 +49,10 @@ package std.core; */ export final class FinalizationRegistry { private callback: (value: T) => void; - // Can be changed to local trimplet class when it will be implemented - private obj_token_refs: FixedArray = []; - private callback_arguments: FixedArray = []; - // Contains chains of used and free trimplet indexes, [0] serves as uselist head - private idxarr: FixedArray = []; - private freeidx: int = -1; private static finalizationRegistryLock = new Mutex(); private mutex = new Mutex(); private workerDomain: int; - - private static readonly FIRST_SIZE = 32; + private hashMap: Map> = new Map>(); private static execCleanup(array: FixedArray<(WeakRef> | undefined)>, workerDomain: int): void { FinalizationRegistry.finalizationRegistryLock.lock(); @@ -70,57 +77,16 @@ export final class FinalizationRegistry { } } - private init_arrays(size: int): void { - arktest.assertLT(1, size); - if (size <= 1) { - throw new AssertionError("Size is " + size + " it should be > 1") - } - this.idxarr = new int[size]; - this.idxarr[0] = -1; // first elem reserved for useidx head - this.enlarge_freeidx(1); - - this.obj_token_refs = new Object[2 * size]; - this.callback_arguments = new (T | undefined)[size]; - } - - private enlarge_arrays(newsize: int): void { - let old_idxarr = this.idxarr; - this.idxarr = new int[newsize]; - for (let i = 0; i < old_idxarr.length; ++i) { - this.idxarr[i] = old_idxarr[i]; - } - this.enlarge_freeidx(old_idxarr.length); - - let old_obj_token_refs = this.obj_token_refs; - this.obj_token_refs = new (Object | undefined)[newsize * 2]; - for (let i = 0; i < old_obj_token_refs.length; ++i) { - this.obj_token_refs[i] = old_obj_token_refs[i]; - } - let old_callback_arguments = this.callback_arguments; - this.callback_arguments = new (T | undefined)[newsize]; - for (let i = 0; i < old_callback_arguments.length; ++i) { - this.callback_arguments[i] = old_callback_arguments[i]; - } - } - - private enlarge_freeidx(oldsize: int): void { - // push new idxarr elements to freelist - let last_free = this.idxarr.length - 1; - this.idxarr[last_free] = this.freeidx; - for (let i = oldsize; i < last_free; i++) { - this.idxarr[i] = i + 1; - } - this.freeidx = oldsize; - } - /** * @returns size of the FinalizationRegistry */ public getSize(): int { let count: int = 0; this.mutex.lockGuard(() => { - for (let i = this.idxarr[0]; i != -1; i = this.idxarr[i]) { - ++count; + for (let key of this.hashMap.keys()) { + for (let curr = this.hashMap.get(key); curr != undefined; curr = curr!.next) { + ++count; + } } }); return count; @@ -135,7 +101,6 @@ export final class FinalizationRegistry { * after registered object reclamation. */ public constructor(callback: (value: T) => void) { - this.init_arrays(FinalizationRegistry.FIRST_SIZE); this.callback = callback; FinalizationRegistry.finalizationRegistryLock.lockGuard(() => { this.workerDomain = FinalizationRegistry.registerInstance(new WeakRef(this)); @@ -156,15 +121,22 @@ export final class FinalizationRegistry { * @param token an object used to remove the entry from the registry */ public register(object: Object, callbackArg: T, token?: Object): void { - let refObject = new WeakRef(object); - let refToken: WeakRef | undefined; - if (token != undefined) { - refToken = new WeakRef(token); - } + const refObject = new WeakRef(object); + const refToken = (token != undefined) ? new WeakRef(token) : new WeakRef(object); + let objectData = new FinalizableObjectNode(refObject, refToken, callbackArg); + const hashCode = Runtime.getHashCode(refToken.deref()!); this.mutex.lock(); - let free_index = this.allocTrimplet(); - // NOTE: GC and thus cleanup function can be triggered here after JIT/AOT optimizations - this.setTrimplet(free_index, refObject, callbackArg, refToken); + if (!this.hashMap.has(hashCode)) { + this.hashMap.set(hashCode, objectData); + } else { + let head = this.hashMap.get(hashCode)!; + objectData.next = head.next; + if (head.next != undefined) { + head.next!.prev = objectData; + } + head.next = objectData; + objectData.prev = head; + } this.mutex.unlock(); } @@ -175,12 +147,28 @@ export final class FinalizationRegistry { */ public unregister(token: Object): void { this.mutex.lockGuard(() => { - let prev = 0; - for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { - let refToken = this.getToken(i); - if (refToken != undefined && refToken.deref() == token) { - this.freeNextTrimplet(prev); - i = prev; // iterator rollback + const hashCode = Runtime.getHashCode(token); + if (this.hashMap.has(hashCode)) { + let node = this.hashMap.get(hashCode)!; + while (true) { + if (node.token.deref() == token) { + if (node.next == undefined && node.prev == undefined) { + this.hashMap.delete(hashCode); + } else if (node.prev == undefined) { + node.next!.prev = node.prev; + this.hashMap.set(hashCode, node.next!); + } else if (node.next == undefined) { + node.prev!.next = node.next; + } else { + node.prev!.next = node.next; + node.next!.prev = node.prev; + } + + if (node.next == undefined) { + return; + } + node = node.next!; + } } } }); @@ -189,28 +177,45 @@ export final class FinalizationRegistry { private cleanup(): void { this.mutex.lock(); try { - let prev = 0; - for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { - let object = this.getObject(i); - if (object != undefined && object.deref() == undefined) { - // Remove from trimplet first to prevent - // calling it again in separate thread - let arg: T | undefined = this.getCallbackArg(i); - this.freeNextTrimplet(prev); - i = prev; // iterator rollback - try { - this.mutex.unlock(); - this.callback(arg as T); - } catch (e) { - if (e instanceof Error) { - console.log("Error: " + e.toString()); - if (e.stack) { - console.log(e.stack); + let keys = this.hashMap.keys() + for (const key of keys) { + let objectData = this.hashMap.get(key)!; + while (true) { + let object = objectData.object; + if (object.deref() == undefined) { + // Unlink node + if (objectData.prev == undefined && objectData.next == undefined) { + this.hashMap.delete(key); + } else if (objectData.prev == undefined) { + objectData.next!.prev = objectData.prev; + this.hashMap.set(key, objectData.next!); + } else if (objectData.next == undefined) { + objectData.prev!.next = objectData.next; + } else { + objectData.prev!.next = objectData.next; + objectData.next!.prev = objectData.prev; + } + + // Call callback + let arg = objectData.cbArg; + try { + this.mutex.unlock(); + this.callback(arg as T); + } catch (e) { + if (e instanceof Error) { + console.log("Error: " + e.toString()); + if (e.stack) { + console.log(e.stack); + } } + } finally { + this.mutex.lock(); } - } finally { - this.mutex.lock(); } + if (objectData.next == undefined) { + break; + } + objectData = objectData.next!; } } } finally { @@ -218,53 +223,6 @@ export final class FinalizationRegistry { } } - private allocTrimplet(): int { - if (this.freeidx == -1) { - // The value selected relative to the internal issue - const mult: int = this.idxarr.length < 1024 ? 32 : 2; - this.enlarge_arrays(this.idxarr.length * mult); - } - // pop elem from freelist - let new_idx = this.freeidx; - this.freeidx = this.idxarr[new_idx]; - - // push elem to uselist - this.idxarr[new_idx] = this.idxarr[0]; - this.idxarr[0] = new_idx; - return new_idx; - } - - private freeNextTrimplet(index: int): void { - // remove next elem from uselist - let freed_idx = this.idxarr[index]; - this.idxarr[index] = this.idxarr[freed_idx]; - - // push freed elem to freelist - this.idxarr[freed_idx] = this.freeidx; - this.freeidx = freed_idx; - - // clean data - this.setTrimplet(freed_idx, undefined, undefined, undefined); - } - - private getObject(index: int): WeakRef | undefined { - return this.obj_token_refs[2 * index] as (WeakRef | undefined); - } - - private getCallbackArg(index: int): T | undefined { - return this.callback_arguments[index]; - } - - private getToken(index: int): WeakRef | undefined { - return this.obj_token_refs[2 * index + 1] as (WeakRef | undefined); - } - - private setTrimplet(index: int, obj: WeakRef | undefined, cbArg: T | undefined, token: WeakRef | undefined): void { - this.obj_token_refs[2 * index] = obj; - this.obj_token_refs[2 * index + 1] = token; - this.callback_arguments[index] = cbArg; - } - private static native registerInstance(object: Object): int; private static native finishCleanup(): void; -- Gitee