1/* 2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16package std.core; 17 18/** 19 * FinalizationRegistry provides a way to execute the provided callback when a 20 * registered object gets garbage collected. 21 * FinalizationRegistry is coroutine-safe class. 22 * - It is safe to pass an instance to another coroutines. 23 * - It is safe to call methods from several coroutines. 24 * 25 * Notes: 26 * - Runtime doesn't provide any guarantees that the registered object will be 27 * garbage collected (even if its dead). 28 * - Even the object is garbage collected runtime doesn't guarantee that 29 * the callback will be called. 30 * - Runtime ignores any exceptions thrown in the callback. 31 * - User should avoid call await in the callback. 32 * - Runtime calls the callback after the object is garbage collected at some point 33 * during execution. 34 * - The callback may be called in any coroutine. 35 */ 36export final class FinalizationRegistry<T> { 37 private callback: (value: T) => void; 38 // Can be changed to local trimplet class when it will be implemented 39 private obj_token_refs: FixedArray<Object | undefined> = []; 40 private callback_arguments: FixedArray<T | undefined> = []; 41 // Contains chains of used and free trimplet indexes, [0] serves as uselist head 42 private idxarr: FixedArray<int> = []; 43 private freeidx: int = -1; 44 private static finalizationRegistryLock = new Mutex(); 45 private mutex = new Mutex(); 46 private launchMode: int; 47 48 private static readonly FIRST_SIZE = 32; 49 50 private static execCleanup(array: FixedArray<(WeakRef<FinalizationRegistry<T>> | undefined)>, launchmode: int): void { 51 FinalizationRegistry.finalizationRegistryLock.lock(); 52 try { 53 for (let i = 0; i < array.length; i++) { 54 if (array[i]?.deref() !== undefined) { 55 if (array[i]!.deref()!.launchMode == launchmode) { 56 try { 57 FinalizationRegistry.finalizationRegistryLock.unlock(); 58 array[i]!.deref()!.cleanup(); 59 } finally { 60 FinalizationRegistry.finalizationRegistryLock.lock(); 61 } 62 } 63 } else { 64 array[i] = undefined; 65 } 66 } 67 } finally { 68 FinalizationRegistry.finalizationRegistryLock.unlock(); 69 FinalizationRegistry.finishCleanup(); 70 } 71 } 72 73 private init_arrays(size: int): void { 74 arktest.assertLT(1, size); 75 if (size <= 1) { 76 throw new AssertionError("Size is " + size + " it should be > 1") 77 } 78 this.idxarr = new int[size]; 79 this.idxarr[0] = -1; // first elem reserved for useidx head 80 this.enlarge_freeidx(1); 81 82 this.obj_token_refs = new Object[2 * size]; 83 this.callback_arguments = new (T | undefined)[size]; 84 } 85 86 private enlarge_arrays(newsize: int): void { 87 let old_idxarr = this.idxarr; 88 this.idxarr = new int[newsize]; 89 for (let i = 0; i < old_idxarr.length; ++i) { 90 this.idxarr[i] = old_idxarr[i]; 91 } 92 this.enlarge_freeidx(old_idxarr.length); 93 94 let old_obj_token_refs = this.obj_token_refs; 95 this.obj_token_refs = new (Object | undefined)[newsize * 2]; 96 for (let i = 0; i < old_obj_token_refs.length; ++i) { 97 this.obj_token_refs[i] = old_obj_token_refs[i]; 98 } 99 let old_callback_arguments = this.callback_arguments; 100 this.callback_arguments = new (T | undefined)[newsize]; 101 for (let i = 0; i < old_callback_arguments.length; ++i) { 102 this.callback_arguments[i] = old_callback_arguments[i]; 103 } 104 } 105 106 private enlarge_freeidx(oldsize: int): void { 107 // push new idxarr elements to freelist 108 let last_free = this.idxarr.length - 1; 109 this.idxarr[last_free] = this.freeidx; 110 for (let i = oldsize; i < last_free; i++) { 111 this.idxarr[i] = i + 1; 112 } 113 this.freeidx = oldsize; 114 } 115 116 /** 117 * @returns size of the FinalizationRegistry 118 */ 119 public getSize(): int { 120 let count: int = 0; 121 this.mutex.lockGuard(() => { 122 for (let i = this.idxarr[0]; i != -1; i = this.idxarr[i]) { 123 ++count; 124 } 125 }); 126 return count; 127 } 128 129 /** 130 * Create FinalizationRegistry object with the specific callback function. 131 * One can register an object and after reclamation of the object 132 * the callback function will be called. 133 * 134 * @param callback_value callback function which will be called 135 * after registered object reclamation. 136 */ 137 public constructor(callback: (value: T) => void) { 138 this.init_arrays(FinalizationRegistry.FIRST_SIZE); 139 this.callback = callback; 140 FinalizationRegistry.finalizationRegistryLock.lockGuard(() => { 141 this.launchMode = FinalizationRegistry.registerInstance(new WeakRef<Object>(this)); 142 }); 143 } 144 145 /** 146 * Registers an object with specific callback's argument and 147 * optional token - an object used to remove the entry from the registry. 148 * @see unregister 149 * After the object is garbage collected, runtime will call the callback 150 * with the specific argument. 151 * 152 * @param object a tracking object 153 * 154 * @param callbackArg callback argument for callback method 155 * 156 * @param token an object used to remove the entry from the registry 157 */ 158 public register(object: Object, callbackArg: T, token?: Object): void { 159 let refObject = new WeakRef<Object>(object); 160 let refToken?: WeakRef<Object> | undefined = undefined; 161 if (token != undefined) { 162 refToken = new WeakRef<Object>(token); 163 } 164 this.mutex.lock(); 165 let free_index = this.allocTrimplet(); 166 // NOTE: GC and thus cleanup function can be triggered here after JIT/AOT optimizations 167 this.setTrimplet(free_index, refObject, callbackArg, refToken); 168 this.mutex.unlock(); 169 } 170 171 /** 172 * Removes all entries with the specific token 173 * 174 * @param token object specified which entries should be removed 175 */ 176 public unregister(token: Object): void { 177 this.mutex.lockGuard(() => { 178 let prev = 0; 179 for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { 180 let refToken = this.getToken(i); 181 if (refToken != undefined && refToken.deref() == token) { 182 this.freeNextTrimplet(prev); 183 i = prev; // iterator rollback 184 } 185 } 186 }); 187 } 188 189 private cleanup(): void { 190 this.mutex.lock(); 191 try { 192 let prev = 0; 193 for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { 194 let object = this.getObject(i); 195 if (object != undefined && object.deref() == undefined) { 196 // Remove from trimplet first to prevent 197 // calling it again in separate thread 198 let arg: T | undefined = this.getCallbackArg(i); 199 this.freeNextTrimplet(prev); 200 i = prev; // iterator rollback 201 try { 202 this.mutex.unlock(); 203 this.callback(arg as T); 204 } catch (e) { 205 if (e instanceof Error) { 206 console.log("Error: " + e.toString()); 207 if (e.stack) { 208 console.log(e.stack); 209 } 210 } 211 } finally { 212 this.mutex.lock(); 213 } 214 } 215 } 216 } finally { 217 this.mutex.unlock(); 218 } 219 } 220 221 private allocTrimplet(): int { 222 if (this.freeidx == -1) { 223 // The value selected relative to the internal issue 224 const mult: int = this.idxarr.length < 1024 ? 32 : 2; 225 this.enlarge_arrays(this.idxarr.length * mult); 226 } 227 // pop elem from freelist 228 let new_idx = this.freeidx; 229 this.freeidx = this.idxarr[new_idx]; 230 231 // push elem to uselist 232 this.idxarr[new_idx] = this.idxarr[0]; 233 this.idxarr[0] = new_idx; 234 return new_idx; 235 } 236 237 private freeNextTrimplet(index: int): void { 238 // remove next elem from uselist 239 let freed_idx = this.idxarr[index]; 240 this.idxarr[index] = this.idxarr[freed_idx]; 241 242 // push freed elem to freelist 243 this.idxarr[freed_idx] = this.freeidx; 244 this.freeidx = freed_idx; 245 246 // clean data 247 this.setTrimplet(freed_idx, undefined, undefined, undefined); 248 } 249 250 private getObject(index: int): WeakRef<Object> | undefined { 251 return this.obj_token_refs[2 * index] as (WeakRef<Object> | undefined); 252 } 253 254 private getCallbackArg(index: int): T | undefined { 255 return this.callback_arguments[index]; 256 } 257 258 private getToken(index: int): WeakRef<Object> | undefined { 259 return this.obj_token_refs[2 * index + 1] as (WeakRef<Object> | undefined); 260 } 261 262 private setTrimplet(index: int, obj: WeakRef<Object> | undefined, cbArg: T | undefined, token: WeakRef<Object> | undefined): void { 263 this.obj_token_refs[2 * index] = obj; 264 this.obj_token_refs[2 * index + 1] = token; 265 this.callback_arguments[index] = cbArg; 266 } 267 268 private static native registerInstance(object: Object): int; 269 private static native finishCleanup(): void; 270 271} 272