1616import java .io .Reader ;
1717import java .io .StringWriter ;
1818import java .io .Writer ;
19+ import java .lang .ref .Reference ;
20+ import java .lang .ref .ReferenceQueue ;
1921import java .lang .reflect .Constructor ;
2022import java .lang .reflect .InvocationTargetException ;
2123import java .lang .reflect .Method ;
@@ -508,7 +510,7 @@ public static void exit() {
508510 }
509511 if (cx .enterCount < 1 ) Kit .codeBug ();
510512 if (--cx .enterCount == 0 ) {
511- releaseContext (cx );
513+ cx . releaseContext ();
512514 }
513515 }
514516
@@ -518,15 +520,17 @@ public void close() {
518520 if (--enterCount == 0 ) {
519521 assert (currentContext .get () == this )
520522 : "currentContext: " + currentContext .get () + ", this: " + this ;
521- releaseContext (this );
523+ releaseContext ();
522524 }
523525 }
524526
525- private static void releaseContext (Context cx ) {
527+ private void releaseContext () {
528+ // Drain the reference queue to prevent memory leaks
529+ cleanUpReferences ();
526530 // do not use contextLocal.remove() here, as this might be much slower, when the same thread
527531 // creates a new context. See ContextThreadLocalBenchmark.
528532 currentContext .set (null );
529- cx . factory .onContextReleased (cx );
533+ factory .onContextReleased (this );
530534 }
531535
532536 /**
@@ -2530,19 +2534,42 @@ public void enqueueMicrotask(Runnable task) {
25302534 * Run all the microtasks for the current context to completion. This is called by the various
25312535 * "evaluate" functions. Frameworks that call Function objects directly should call this
25322536 * function to ensure that everything completes if they want all Promises to eventually resolve.
2533- * This function is idempotent, but the microtask queue is not thread-safe.
2537+ * This function is idempotent, but the microtask queue is not thread-safe. If references are
2538+ * registered using a FinalizationRegistry, finalization callbacks will be called in this method
2539+ * as well.
25342540 *
25352541 * <p>Nothing will happen if suspendMicrotaskProcessing was called.
25362542 *
25372543 * @see #suspendMicrotaskProcessing()
25382544 */
25392545 public void processMicrotasks () {
2546+ if (microtaskSuspendCount > 0 ) {
2547+ return ;
2548+ }
2549+ // Clean up references in a microtask in case a finalization call
2550+ // registers a microtask
2551+ microtasks .add (this ::cleanUpReferences );
25402552 Runnable head ;
2541- while (microtaskSuspendCount == 0 && (head = microtasks .poll ()) != null ) {
2553+ while ((head = microtasks .poll ()) != null ) {
25422554 head .run ();
25432555 }
25442556 }
25452557
2558+ /**
2559+ * Clean up an item that has been removed from the reference queue and check if there are more.
2560+ */
2561+ private void cleanUpReferences () {
2562+ Reference <?> ref ;
2563+ while ((ref = referenceQueue .poll ()) != null ) {
2564+ if (ref instanceof NativeFinalizationRegistry .Registration registration ) {
2565+ var registry = registration .getRegistry ();
2566+ if (registry != null ) {
2567+ registry .cleanup (this , registration );
2568+ }
2569+ }
2570+ }
2571+ }
2572+
25462573 /**
25472574 * Temporarily suspend microtask processing. Tasks will be resumed again when
25482575 * resumeMicrotaskProcessing() is called. Suspensions are cumulative -- each call to this method
@@ -2571,6 +2598,10 @@ public void resumeMicrotaskProcessing() {
25712598 }
25722599 }
25732600
2601+ ReferenceQueue <Object > getReferenceQueue () {
2602+ return referenceQueue ;
2603+ }
2604+
25742605 /**
25752606 * Control whether to track unhandled promise rejections. If "track" is set to true, then the
25762607 * tracker returned by "getUnhandledPromiseTracker" must be periodically used to process the
@@ -2591,6 +2622,23 @@ public UnhandledRejectionTracker getUnhandledPromiseTracker() {
25912622 return unhandledPromises ;
25922623 }
25932624
2625+ /**
2626+ * Control whether finalization callbacks are dispatched when registered targets are garbage
2627+ * collected. Defaults to false. If set to true, then "processMicrotasks" must periodically be
2628+ * called to ensure that finalization callbacks are fired, or a memory leak may result.
2629+ * (Microtasks are always processed before scripts exit and as a normal part of handling
2630+ * Promises.) It set to false (the default) then the FinalizationRegistry API will still work as
2631+ * the spec describes but callbacks will never fire.
2632+ */
2633+ public void setFinalizationEnabled (boolean enabled ) {
2634+ finalizationEnabled = enabled ;
2635+ }
2636+
2637+ /** Returns whether cleanup callbacks will be dispatched at microtask checkpoints. */
2638+ public boolean isFinalizationEnabled () {
2639+ return finalizationEnabled ;
2640+ }
2641+
25942642 /* ******** end of API ********* */
25952643
25962644 /** Internal method that reports an error for missing calls to enter(). */
@@ -3024,8 +3072,10 @@ public static EvaluationMethod forLevel(int level) {
30243072 private ClassLoader applicationClassLoader ;
30253073 private UnaryOperator <Object > javaToJSONConverter ;
30263074 private final ArrayDeque <Runnable > microtasks = new ArrayDeque <>();
3075+ private final ReferenceQueue <Object > referenceQueue = new ReferenceQueue <>();
30273076 private int microtaskSuspendCount ;
30283077 private final UnhandledRejectionTracker unhandledPromises = new UnhandledRejectionTracker ();
3078+ private boolean finalizationEnabled = false ;
30293079
30303080 /** This is the list of names of objects forcing the creation of function activation records. */
30313081 Set <String > activationNames ;
0 commit comments