• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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