1/* 2 * Copyright (c) 2021-2024 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 18export final class FinalizationRegistry<T> { 19 20 private callbackWithArg: (value: T) => void = (value: T) => { return }; 21 private callbackWithoutArg: () => void = () => { return }; 22 private isCallbackWithArg: boolean = true; 23 // Can be changed to local trimplet class when it will be implemented 24 private obj_token_refs: (Object | null)[] = []; 25 private callback_arguments: (T | null)[] = []; 26 // Contains chains of used and free trimplet indexes, [0] serves as uselist head 27 private idxarr: int[] = []; 28 private freeidx: int = -1; 29 30 private static readonly FIRST_SIZE = 32; 31 32 private init_arrays(size: int): void { 33 assert size > 1; 34 this.idxarr = new int[size]; 35 this.idxarr[0] = -1; // first elem reserved for useidx head 36 this.enlarge_freeidx(1); 37 38 this.obj_token_refs = new Object[2 * size]; 39 this.callback_arguments = new T[size]; 40 } 41 42 private enlarge_arrays(newsize: int): void { 43 let old_idxarr = this.idxarr; 44 this.idxarr = new int[newsize]; 45 for (let i = 0; i < old_idxarr.length; ++i) { 46 this.idxarr[i] = old_idxarr[i]; 47 } 48 this.enlarge_freeidx(old_idxarr.length); 49 50 let old_obj_token_refs = this.obj_token_refs; 51 this.obj_token_refs = new Object[newsize * 2]; 52 for (let i = 0; i < old_obj_token_refs.length; ++i) { 53 this.obj_token_refs[i] = old_obj_token_refs[i]; 54 } 55 let old_callback_arguments = this.callback_arguments; 56 this.callback_arguments = new T[newsize]; 57 for (let i = 0; i < old_callback_arguments.length; ++i) { 58 this.callback_arguments[i] = old_callback_arguments[i]; 59 } 60 } 61 62 private enlarge_freeidx(oldsize: int): void { 63 // push new idxarr elements to freelist 64 let last_free = this.idxarr.length - 1; 65 this.idxarr[last_free] = this.freeidx; 66 for (let i = oldsize; i < last_free; i++) { 67 this.idxarr[i] = i + 1; 68 } 69 this.freeidx = oldsize; 70 } 71 72 /** 73 * Returns size of the FinalizationRegistry 74 * 75 * @returns size of the FinalizationRegistry 76 */ 77 public getSize(): int { 78 let count: int; 79 for (let i = this.idxarr[0]; i != -1; i = this.idxarr[i]) { 80 count++; 81 } 82 return count; 83 } 84 85 /** 86 * Creates FinalizationRegistry object with the specific callback function. 87 * One can register an object to this queue and after reclamation of this object 88 * the callback function will be called. 89 * 90 * @param callback_value callback function which will be called 91 * after registered object reclamation. 92 */ 93 public constructor(callback_value: (value: T) => void) { 94 this.init_arrays(FinalizationRegistry.FIRST_SIZE); 95 this.callbackWithArg = callback_value; 96 this.isCallbackWithArg = true; 97 FinalizationRegistry.registerInstance(this); 98 } 99 100 /** 101 * Creates FinalizationRegistry object with the specific callback function, 102 * which doesn't have any argument 103 * One can register an object to this queue and after reclamation of this object 104 * the callback function will be called. 105 * 106 * @param callback_value callback function which will be called 107 * after registered object reclamation. 108 */ 109 public constructor(callback_value: () => void) { 110 this.init_arrays(FinalizationRegistry.FIRST_SIZE); 111 this.callbackWithoutArg = callback_value; 112 this.isCallbackWithArg = false; 113 FinalizationRegistry.registerInstance(this); 114 } 115 116 /** 117 * Registers an object with specific callback argument and 118 * with token object - an object which can be used to remove 119 * this registred callback. @see unregister 120 * After the object will be removed, the callback method 121 * will be called with the specific argument. 122 * 123 * @param object a tracking object 124 * 125 * @param callbackArg callback argument for callback method 126 * 127 * @param token an object which can be used to remove 128 * this registred callback if provided 129 */ 130 public register(object: Object, callbackArg: T, token?: Object): void { 131 let refObject = new WeakRef<Object>(object); 132 let refToken: WeakRef<Object> | null = null; 133 if (token != null) { 134 refToken = new WeakRef<Object>(token); 135 } 136 let free_index = this.allocTrimplet(); 137 // NOTE: GC and thus cleanup function can be triggered here after JIT/AOT optimizations 138 this.setTrimplet(free_index, refObject, callbackArg, refToken); 139 } 140 141 /** 142 * Removes all registered callbacks with the specific token value 143 * 144 * @param token all registered callbacks with this token will be removed 145 */ 146 public unregister(token: Object): void { 147 let prev = 0; 148 for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { 149 let refToken = this.getToken(i); 150 if (refToken != null && refToken.deref() == token) { 151 this.freeNextTrimplet(prev); 152 i = prev; // iterator rollback 153 } 154 } 155 } 156 157 private cleanup(): void { 158 let prev = 0; 159 for (let i = this.idxarr[prev]; i != -1; prev = i, i = this.idxarr[i]) { 160 let object = this.getObject(i); 161 if (object != null && object.deref() == undefined) { 162 // Remove from trimplet first to prevent 163 // calling it again in separate thread 164 let arg: T | null = this.getCallbackArg(i); 165 this.freeNextTrimplet(prev); 166 i = prev; // iterator rollback 167 if (this.isCallbackWithArg) { 168 this.callbackWithArg(arg!); 169 } else { 170 this.callbackWithoutArg(); 171 } 172 } 173 } 174 } 175 176 private allocTrimplet(): int { 177 if (this.freeidx == -1) { 178 this.enlarge_arrays(this.idxarr.length * 2); 179 } 180 // pop elem from freelist 181 let new_idx = this.freeidx; 182 this.freeidx = this.idxarr[new_idx]; 183 184 // push elem to uselist 185 this.idxarr[new_idx] = this.idxarr[0]; 186 this.idxarr[0] = new_idx; 187 return new_idx; 188 } 189 190 private freeNextTrimplet(index: int): void { 191 // remove next elem from uselist 192 let freed_idx = this.idxarr[index]; 193 this.idxarr[index] = this.idxarr[freed_idx]; 194 195 // push freed elem to freelist 196 this.idxarr[freed_idx] = this.freeidx; 197 this.freeidx = freed_idx; 198 199 // clean data 200 this.setTrimplet(freed_idx, null, null, null); 201 } 202 203 private getObject(index: int): WeakRef<Object> | null { 204 return this.obj_token_refs[2 * index] as (WeakRef<Object> | null); 205 } 206 207 private getCallbackArg(index: int): T | null { 208 return this.callback_arguments[index]; 209 } 210 211 private getToken(index: int): WeakRef<Object> | null { 212 return this.obj_token_refs[2 * index + 1] as (WeakRef<Object> | null); 213 } 214 215 private setTrimplet(index: int, obj: WeakRef<Object> | null, cbArg: T | null, token: WeakRef<Object> | null): void { 216 this.obj_token_refs[2 * index] = obj; 217 this.obj_token_refs[2 * index + 1] = token; 218 this.callback_arguments[index] = cbArg; 219 } 220 221 private static native registerInstance(object: Object): void; 222 223} 224