All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.org.python.modules.gc Maven / Gradle / Ivy

Go to download

Jython is an implementation of the high-level, dynamic, object-oriented language Python written in 100% Pure Java, and seamlessly integrated with the Java platform. It thus allows you to run Python on any Java platform.

There is a newer version: 2.7.4
Show newest version
package org.python.modules;

import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.python.core.JyAttribute;
import org.python.core.Py;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyInstance;
import org.python.core.PyString;
import org.python.core.Traverseproc;
import org.python.core.TraverseprocDerived;
import org.python.core.Visitproc;
import org.python.core.Untraversable;
import org.python.core.finalization.FinalizeTrigger;
import org.python.modules._weakref.GlobalRef;
import org.python.modules._weakref.ReferenceBackend;

//These imports belong to the out-commented section on MXBean-based
//gc-sync far below. That section is kept to document this failed
//approach and allow easy reproduction of this failure.
//import java.lang.management.*;
//import javax.management.*;
//import javax.management.openmbean.*;

/**
 * 

* In Jython, the gc-module notably differs from that in CPython. * This comes from the different ways Jython and CPython perform * garbage-collection. While CPython's garbage collection is based on * * reference-counting, Jython is backed by Java's gc, which is * based on a * * mark-and-sweep-approach. *

*

* This difference becomes most notable if finalizers are involved that perform resurrection. * While the resurrected object itself behaves rather similar between Jython and CPython, * things are more delicate with objects that are reachable (i.e. strongly referenced) * via the resurrected object exclusively. * While in CPython such objects do not get their finalizers called, Jython/Java would * call all their finalizers. That is because Java detects the whole unreachable subgraph * as garbage and thus calls all their finalizers without any chance of direct intervention. * CPython instead detects the unreachable object and calls its finalizer, which makes the * object reachable again. Then all other objects are reachable from it and CPython does not * treat them as garbage and does not call their finalizers at all. * This further means that in Jython weak references to such indirectly resurrected objects * break, while these persist in CPython. *

*

* As of Jython 2.7, the gc-module offers some options to emulate CPython-behavior. * Especially see the flags {@link #PRESERVE_WEAKREFS_ON_RESURRECTION}, * {@link #DONT_FINALIZE_RESURRECTED_OBJECTS} and {@link #DONT_FINALIZE_CYCLIC_GARBAGE} * for this. *

*

* Another difference is that CPython's gc-module offers some debug-features like counting * of collected cyclic trash, which are hard to support by Jython. As of Jython 2.7 the * introduction of a traverseproc-mechanism (c.f. {@link org.python.core.Traverseproc}) * made support of these features feasible. As support of these features comes * with a significant emulation-cost, one must explicitly tell gc to perform this. * To make objects subject to cyclic trash-counting, these objects must be gc-monitored in * Jython. See {@link #monitorObject(PyObject)}, {@link #unmonitorObject(PyObject)}, * {@link #MONITOR_GLOBAL} and {@link #stopMonitoring()} for this.
* If at least one object is gc-monitored, {@link #collect()} works synchronously in the * sense that it blocks until all gc-monitored objects that are garbage actually have been * collected and had their finalizers called and completed. {@link #collect()} will report * the number of collected objects in the same manner as in CPython, i.e. counts only those * that participate in reference cycles. This allows a unified test-implementation across * Jython and CPython (which applies to most tests in test_gc.py). If not any object is * gc-monitored, {@link #collect()} just delegates to {@link java.lang.System.gc()}, runs * asynchronously (i.e. non-blocking) and returns {@link #UNKNOWN_COUNT}. * See also {@link #DEBUG_SAVEALL} for a useful gc-debugging feature that is supported by * Jython from version 2.7 onwards. *

*

* Implementing all these features in Jython involved a lot of synchronization logic. * While care was taken to implement this without using timeouts as far as possible and * rely on locks, states and system/hardware independent synchronization techniques, * this was not entirely feasible.
* The aspects that were only feasible using a timeout are waiting for gc to enqueue all * collected objects (i.e. weak references to monitored objects that were gc'ed) to the * reference queue and waiting for gc to run all PyObject-finalizers. *

*

* Waiting for trash could in theory be strictly synchronized by using {@code MXBean}s, i.e. * GarbageCollectionNotificationInfo and related API. * However, experiments showed that the arising gc-notifications do not reliably indicate * when enqueuing was done for a specific gc-run. We kept the experimental implementation * in source-code comments to allow easy reproducibility of this issue. (Note that out-commented * code contradicts Jython-styleguide, but this one - however - is needed to document this * infeasible approach and is explicitly declared accordingly).
* But how is sync done now? * We insert a sentinel before running gc and wait until this sentinel was collected. * Timestamps are taken to give us an idea at which time-scales the gc of the current JVM * performs. We then wait until twice the measured time (i.e. duration from call to * {@link java.lang.System#gc()} until the sentinel reference was enqueued) has passed after * the last reference was enqueued by gc. While this approach is not entirely safe in theory, * it passes all tests on various systems and machines we had available for testing so far. * We consider it more robust than a fixed-length timeout and regard it the best known feasible * compromise to emulate synchronous gc-runs in Java. *

*

* The other timing-based synchronization issue - waiting for finalizers to run - is solved as * follows. Since PyObject-finalizers are based on * {@link org.python.core.finalization.FinalizeTrigger}s, Jython has full control about * these finalization process from a central point. Before such a finalizer runs, it calls * {@link #notifyPreFinalization()} and when it is done, it calls * {@link #notifyPostFinalization()}. While processing of a finalizer can be of arbitrary * duration, it widely holds that Java's gc-thread calls the next finalizer almost * instantaneously after the former. That means that a timestamp taken in * {@link #notifyPreFinalization()} is usually delayed only few milliseconds * - often even reported as 0 milliseconds - after the last taken timestamp in * {@link #notifyPostFinalization()} (i.e. that was called by the previous finalizer). * Jython's gc-module assumes the end of Java's finalization process if * {@link #postFinalizationTimeOut} milliseconds passed after a call of * {@link #notifyPostFinalization()} without another call to * {@link #notifyPreFinalization()} in that time. The default value of * {@link #postFinalizationTimeOut} is {@code 100}, which is far larger than the * usual almost-zero duration between finalizer calls.
* This process can be disturbed by third-party finalizers of non-PyObjects brought * into the process by external libraries. If these finalizers are of short duration * (which applies to typical finalizers), one can deal with this by adjusting * {@link #postFinalizationTimeOut}, which was declared {@code public} for exactly this * purpose. However if the external framework causing the issue is Jython-aware, a * cleaner solution would be to let its finalizers call {@link #notifyPreFinalization()} * and {@link #notifyPostFinalization()} appropriately. In that case these finalizers * must not terminate by throwing an exception before {@link #notifyPostFinalization()} * was called. This is a strict requirement, since a deadlock can be caused otherwise.
*
* Note that the management API * (c.f. * com.sun.management.GarbageCollectionNotificationInfo) does not emit any * notifications that allow to detect the end of the finalization-phase. So this API * provides no alternative to the described technique. *

*

* Usually Java's gc provides hardly any guarantee about its collection- and finalization- * process. It not even guarantees that finalizers are called at all (c.f. * http://howtodoinjava.com/2012/10/31/why-not-to-use-finalize-method-in-java). * While at least the most common JVM implementations usually do call finalizers * reliably under normal conditions, there still is no specific finalization-order guaranteed * (one might reasonably expect that this would be related to reference-connection graph * topology, but this appears not to be the case). * However Jython now offers some functionality to compensate this * situation. Via {@link #registerPreFinalizationProcess(Runnable)} and * {@link #registerPostFinalizationProcess(Runnable)} and related methods one can now * listen to beginning and end of the finalization process. Note that this functionality * relies on the technique described in the former paragraph (i.e. based on calls to * {@link #notifyPreFinalization()} and {@link #notifyPostFinalization()}) and thus * underlies its unsafety, if third-party finalizers are involved. Such finalizers can * cause false-positive runs of registered (pre/post)-finalization-processes, so this * feature should be used with some care. It is recommended to use it only in such a way * that false-positive runs would not cause serious harm, but only some loss in * performance or so. *

*/ public class gc { /** * A constant that can occur as result of {@link #collect()} and * indicates an unknown number of collected cyclic trash. * It is intentionally not valued -1 as that value is * reserved to indicate an error. */ public static final int UNKNOWN_COUNT = -2; /* Jython-specific gc-flags: */ /** * This flag tells every newly created PyObject to register for * gc-monitoring. This allows {@link #collect()} to report the * number of collected objects. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short MONITOR_GLOBAL = (1<<0); /** * CPython prior to 3.4 does not finalize cyclic garbage * PyObjects, while Jython does this by default. This flag * tells Jython's gc to mimic CPython <3.4 behavior (i.e. * add such objects to {@code gc.garbage} list instead). * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short DONT_FINALIZE_CYCLIC_GARBAGE = (1<<1); /** * If a PyObject is resurrected during its finalization * process and was weakly referenced, Jython breaks the * weak references to the resurrected PyObject by default. * In CPython these persist, if the object was indirectly * resurrected due to resurrection of its owner. * This flag tells Jython's gc to preserve weak references * to such resurrected PyObjects. * It only works if all involved objects implement the * traverseproc mechanism properly (see * {@link org.python.core.Traverseproc}). * Note that this feature comes with some cost as it can * delay garbage collection of some weak referenced objects * for several gc cycles if activated. So we recommend to * use it only for debugging. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short PRESERVE_WEAKREFS_ON_RESURRECTION = (1<<2); /** * If in CPython an object is resurrected via its finalizer * and contained strong references to other objects, these * are also resurrected and not finalized in CPython (as * their reference count never drops to zero). In contrast * to that, Jython calls finalizers for all objects that * were unreachable when gc started (regardless of resurrections * and in unpredictable order). This flag emulates CPython * behavior in Jython. Note that this emulation comes with a * significant cost as it can delay collection of many objects * for several gc-cycles. Its main intention is for debugging * resurrection-sensitive code. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short DONT_FINALIZE_RESURRECTED_OBJECTS = (1<<3); public static final short FORCE_DELAYED_FINALIZATION = (1<<4); public static final short FORCE_DELAYED_WEAKREF_CALLBACKS = (1<<5); /** *

* Reflection-based traversion is an inefficient fallback-method to * traverse PyObject-subtypes that don't implement * {@link org.python.core.Traverseproc} and * are not marked as {@link org.python.core.Untraversable}. * Such a situation indicates that the programmer was not aware of * Jython's traverseproc-mechanism and reflection is used to * compensate this. *

*

* This flag allows to inhibit reflection-based traversion. If it is * activated, objects that don't implement * {@link org.python.core.Traverseproc} * are always treated as if they were marked as * {@link org.python.core.Untraversable}. *

*

* Note that reflection-based traversion fallback is performed by * default. Further note that Jython emits warning-messages if * reflection-based traversion occurs or if an object is encountered * that neither implements {@link org.python.core.Traverseproc} * nor is marked as {@link org.python.core.Untraversable} (even if * reflection-based traversion is inhibited). See * {@link #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING} and * {@link #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING} to control * these warning-messages. *

* * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING */ public static final short DONT_TRAVERSE_BY_REFLECTION = (1<<6); /** *

* If this flag is not set, gc warns whenever an object would be subject to * reflection-based traversion. * Note that if this flag is not set, the warning will occur even if * reflection-based traversion is not active. The purpose of this behavior is * to identify objects that don't properly support the traverseproc-mechanism, * i.e. instances of PyObject-subclasses that neither implement * {@link org.python.core.Traverseproc}, * nor are annotated with the {@link org.python.core.Untraversable}-annotation. *

*

* A SUPPRESS-flag was chosen rather than a WARN-flag, so that warning is the * default behavior - the user must actively set this flag in order to not to * be warned. * This is because in an ideal implementation reflection-based traversion never * occurs; it is only an inefficient fallback. *

* * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING */ public static final short SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING = (1<<7); /** * Makes gc emit reflection-based traversion warning for every traversed * object instead of only once per class. * A potential reflection-based traversion occurs whenever an object is * traversed that neither implements {@link org.python.core.Traverseproc}, * nor is annotated with the {@link org.python.core.Untraversable}-annotation. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING */ public static final short INSTANCE_TRAVERSE_BY_REFLECTION_WARNING = (1<<8); /** * In Jython one usually uses {@code Py.writeDebug} for debugging output. * However that method is only verbose if an appropriate verbose-level * was set. In CPython it is enough to set gc-{@code DEBUG} flags to get * gc-messages, no matter what overall verbose level is selected. * This flag tells Jython to use {@code Py.writeDebug} for debugging output. * If it is not set (default-case), gc-debugging output (if gc-{@code VERBOSE} * or -{@code DEBUG} flags are set) is directly written to {@code System.err}. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short USE_PY_WRITE_DEBUG = (1<<9); /** * Enables collection-related verbose-output. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short VERBOSE_COLLECT = (1<<10); /** * Enables weakref-related verbose-output. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short VERBOSE_WEAKREF = (1<<11); /** * Enables delayed finalization related verbose-output. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short VERBOSE_DELAYED = (1<<12); /** * Enables finalization-related verbose-output. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short VERBOSE_FINALIZE = (1<<13); /** * Bit-combination of the flags {@link #VERBOSE_COLLECT}, * {@link #VERBOSE_WEAKREF}, {@link #VERBOSE_DELAYED}, * {@link #VERBOSE_FINALIZE}. * * @see #setJythonGCFlags(short) * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static final short VERBOSE = VERBOSE_COLLECT | VERBOSE_WEAKREF | VERBOSE_DELAYED | VERBOSE_FINALIZE; /* set for debugging information */ /** * print collection statistics * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_STATS = (1<<0); /** * print collectable objects * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_COLLECTABLE = (1<<1); /** * print uncollectable objects * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_UNCOLLECTABLE = (1<<2); /** * print instances * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_INSTANCES = (1<<3); /** * print other objects * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_OBJECTS = (1<<4); /** * save all garbage in gc.garbage * (in Jython scoped on monitored objects) * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_SAVEALL = (1<<5); /** * Bit-combination of the flags {@link #DEBUG_COLLECTABLE}, * {@link #DEBUG_UNCOLLECTABLE}, {@link #DEBUG_INSTANCES}, * {@link #DEBUG_OBJECTS}, {@link #DEBUG_SAVEALL}. * * @see #set_debug(int) * @see #get_debug() */ public static final int DEBUG_LEAK = DEBUG_COLLECTABLE | DEBUG_UNCOLLECTABLE | DEBUG_INSTANCES | DEBUG_OBJECTS | DEBUG_SAVEALL; private static short gcFlags = 0; private static int debugFlags = 0; private static boolean monitorNonTraversable = false; private static boolean waitingForFinalizers = false; private static AtomicBoolean gcRunning = new AtomicBoolean(false); private static HashSet monitoredObjects; private static HashSet> reflectionWarnedClasses; private static ReferenceQueue gcTrash; private static int finalizeWaitCount = 0; private static int initWaitTime = 10, defaultWaitFactor = 2; private static long lastRemoveTimeStamp = -1, maxWaitTime = initWaitTime; private static int gcMonitoredRunCount = 0; public static long gcRecallTime = 4000; /** * list of uncollectable objects */ public static PyList garbage = new PyList(); /* Finalization preprocess/postprocess-related declarations: */ private static List preFinalizationProcess, postFinalizationProcess; private static List preFinalizationProcessRemove, postFinalizationProcessRemove; private static Thread postFinalizationProcessor; public static long postFinalizationTimeOut = 100; private static long postFinalizationTimestamp = System.currentTimeMillis()-2*postFinalizationTimeOut; private static int openFinalizeCount = 0; private static boolean postFinalizationPending = false; private static boolean lockPostFinalization = false; /* Resurrection-safe finalizer- and weakref-related declarations: */ private static IdentityHashMap delayedFinalizables, resurrectionCriticals; private static int abortedCyclicFinalizers = 0; /* Some modes to control aspects of delayed finalization: */ private static final byte DO_NOTHING_SPECIAL = 0; private static final byte MARK_REACHABLE_CRITICALS = 1; private static final byte NOTIFY_FOR_RERUN = 2; private static byte delayedFinalizationMode = DO_NOTHING_SPECIAL; private static boolean notifyRerun = false; public static final String __doc__ = "This module provides access to the garbage collector for reference cycles.\n" + "\n" + "enable() -- Enable automatic garbage collection (does nothing in Jython).\n" + "disable() -- Disable automatic garbage collection (raises NotImplementedError in Jython).\n" + "isenabled() -- Returns True because Java garbage collection cannot be disabled.\n" + "collect() -- Do a full collection right now (potentially expensive).\n" + "get_count() -- Return the current collection counts (raises NotImplementedError in Jython).\n" + "set_debug() -- Set debugging flags.\n" + "get_debug() -- Get debugging flags.\n" + "set_threshold() -- Set the collection thresholds (raise NotImplementedError in Jython).\n" + "get_threshold() -- Return the current the collection thresholds (raise NotImplementedError in Jython).\n" + "get_objects() -- Return a list of all objects tracked by the collector (raises NotImplementedError in Jython).\n" + "is_tracked() -- Returns true if a given object is tracked (i.e. monitored in Jython).\n" + "get_referrers() -- Return the list of objects that refer to an object (only finds monitored referrers in Jython).\n" + "get_referents() -- Return the list of objects that an object refers to.\n"; public static final String __name__ = "gc"; public static final PyString __doc__enable = new PyString( "enable() -> None\n" + "\n" + "Enable automatic garbage collection.\n" + "(does nothing in Jython)\n"); public static final PyString __doc__disable = new PyString( "disable() -> None\n" + "\n" + "Disable automatic garbage collection.\n" + "(raises NotImplementedError in Jython)\n"); public static final PyString __doc__isenabled = new PyString( "isenabled() -> status\n" + "\n" + "Returns true if automatic garbage collection is enabled.\n"); public static final PyString __doc__collect = new PyString( "collect([generation]) -> n\n" + "\n" + "With no arguments, run a full collection. The optional argument\n" + "may be an integer specifying which generation to collect. A ValueError\n" + "is raised if the generation number is invalid.\n\n" + "The number of unreachable objects is returned.\n" + "(Jython emulates CPython cyclic trash counting if objects are monitored.\n" + "If no objects are monitored, returns -2\n"); public static final PyString __doc__get_count = new PyString( "get_count() -> (count0, count1, count2)\n" + "\n" + "Return the current collection counts\n" + "(raises NotImplementedError in Jython)\n"); public static final PyString __doc__set_debug = new PyString( "set_debug(flags) -> None\n" + "\n" + "Set the garbage collection debugging flags. Debugging information is\n" + "written to sys.stderr.\n" + "\n" + "flags is an integer and can have the following bits turned on:\n" + "\n" + " DEBUG_STATS - Print statistics during collection.\n" + " DEBUG_COLLECTABLE - Print collectable objects found.\n" + " DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects found.\n" + " DEBUG_INSTANCES - Print instance objects.\n" + " DEBUG_OBJECTS - Print objects other than instances.\n" + " DEBUG_SAVEALL - Save objects to gc.garbage rather than freeing them.\n" + " DEBUG_LEAK - Debug leaking programs (everything but STATS).\n"); public static final PyString __doc__get_debug = new PyString( "get_debug() -> flags\n" + "\n" + "Get the garbage collection debugging flags.\n"); public static final PyString __doc__set_thresh = new PyString( "set_threshold(threshold0, [threshold1, threshold2]) -> None\n" + "\n" + "Sets the collection thresholds. Setting threshold0 to zero disables\n" + "collection.\n" + "(raises NotImplementedError in Jython)\n"); public static final PyString __doc__get_thresh = new PyString( "get_threshold() -> (threshold0, threshold1, threshold2)\n" + "\n" + "Return the current collection thresholds\n" + "(raises NotImplementedError in Jython)\n"); public static final PyString __doc__get_objects = new PyString( "get_objects() -> [...]\n" + "\n" + "Return a list of objects tracked by the collector (excluding the list\n" + "returned).\n" + "(raises NotImplementedError in Jython)\n"); public static final PyString __doc__is_tracked = new PyString( "is_tracked(obj) -> bool\n" + "\n" + "Returns true if the object is tracked by the garbage collector.\n" + "(i.e. monitored in Jython)\n"); public static final PyString __doc__get_referrers = new PyString( "get_referrers(*objs) -> list\n" + "Return the list of objects that directly refer to any of objs.\n" + "(only finds monitored referrers in Jython)"); public static final PyString __doc__get_referents = new PyString( "get_referents(*objs) -> list\n" + "Return the list of objects that are directly referred to by objs."); public static class CycleMarkAttr { private boolean cyclic = false; private boolean uncollectable = false; public boolean monitored = false; CycleMarkAttr() { } CycleMarkAttr(boolean cyclic, boolean uncollectable) { this.cyclic = cyclic; this.uncollectable = uncollectable; } public boolean isCyclic() { return cyclic || uncollectable; } public boolean isUncollectable() { return uncollectable; } public void setFlags(boolean cyclic, boolean uncollectable) { this.cyclic = cyclic; this.uncollectable = uncollectable; } } private static class WeakReferenceGC extends WeakReference { int hashCode = 0; public String str = null, inst_str = null; public String cls; boolean isInstance; boolean hasFinalizer = false; CycleMarkAttr cycleMark; WeakReferenceGC(PyObject referent) { super(referent); isInstance = referent instanceof PyInstance; cycleMark = (CycleMarkAttr) JyAttribute.getAttr(referent, JyAttribute.GC_CYCLE_MARK_ATTR); hashCode = System.identityHashCode(referent); cls = referent.getClass().getName(); updateHasFinalizer(); } WeakReferenceGC(PyObject referent, ReferenceQueue q) { super(referent, q); isInstance = referent instanceof PyInstance; cycleMark = (CycleMarkAttr) JyAttribute.getAttr(referent, JyAttribute.GC_CYCLE_MARK_ATTR); hashCode = System.identityHashCode(referent); cls = referent.getClass().getName(); updateHasFinalizer(); } public void updateHasFinalizer() { PyObject gt = get(); Object fn = JyAttribute.getAttr(gt, JyAttribute.FINALIZE_TRIGGER_ATTR); hasFinalizer = fn != null && ((FinalizeTrigger) fn).isActive(); } public void initStr(PyObject referent) { PyObject ref = referent; if (referent == null) { ref = get(); } try { if (ref instanceof PyInstance) { String name = ((PyInstance) ref).fastGetClass().__name__; if (name == null) { name = "?"; } inst_str = String.format("<%.100s instance at %s>", name, Py.idstr(ref)); } str = String.format("<%.100s %s>", ref.getType().getName(), Py.idstr(ref)); } catch (Exception e) { str = "<"+ref.getClass().getSimpleName()+" " +System.identityHashCode(ref)+">"; } } public String toString() { return str; } public int hashCode() { return hashCode; } public boolean equals(Object ob) { Object ownReferent = get(); if (ob instanceof WeakReferenceGC) { Object otherReferent = ((WeakReferenceGC) ob).get(); if (ownReferent == null || otherReferent == null) { return ownReferent == otherReferent && /* We compare the cached hash-codes in order to get an idea * whether in the both-null-case the referent was equal once. */ hashCode == ((WeakReferenceGC) ob).hashCode; } else { return otherReferent.equals(ownReferent) /* Here the hash-codes are only compared as a consistency check. */ && ((WeakReferenceGC) ob).hashCode == hashCode; } } else if (ob instanceof WeakrefGCCompareDummy) { if (ownReferent == null || ((WeakrefGCCompareDummy) ob).compare == null) { return ownReferent == ((WeakrefGCCompareDummy) ob).compare && /* We compare the cached hash-codes in order to get an idea * whether in the both-null-case the referent was equal once. */ hashCode == ((WeakrefGCCompareDummy) ob).hashCode; } else { return ownReferent.equals(((WeakrefGCCompareDummy) ob).compare) /* Here the hash-codes are only compared as a consistency check. */ && hashCode == ((WeakrefGCCompareDummy) ob).hashCode; } } else { return false; } } } private static class WeakrefGCCompareDummy { public static WeakrefGCCompareDummy defaultInstance = new WeakrefGCCompareDummy(); protected PyObject compare; int hashCode = 0; public void setCompare(PyObject compare) { this.compare = compare; hashCode = System.identityHashCode(compare); } public void clearCompare() { compare = null; hashCode = 0; } public int hashCode() { return hashCode; } @SuppressWarnings("rawtypes") public boolean equals(Object ob) { if (ob instanceof Reference) { return compare.equals(((Reference) ob).get()); } else if (ob instanceof WeakrefGCCompareDummy) { return compare.equals(((WeakrefGCCompareDummy) ob).compare); } else { return compare.equals(ob); } } } private static class GCSentinel { Thread waiting; public GCSentinel(Thread notifyOnFinalize) { waiting = notifyOnFinalize; } protected void finalize() throws Throwable { notifyPreFinalization(); if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "Sentinel finalizer called..."); } if (lastRemoveTimeStamp != -1) { long diff = maxWaitTime*defaultWaitFactor-System.currentTimeMillis()+lastRemoveTimeStamp; while (diff > 0) { try { Thread.sleep(diff); } catch (InterruptedException ie) {} diff = maxWaitTime*defaultWaitFactor-System.currentTimeMillis()+lastRemoveTimeStamp; } } if (waiting != null) { waiting.interrupt(); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "Sentinel finalizer done"); } notifyPostFinalization(); } } /** * Works like {@link org.python.core.Py#writeDebug(String, String)}, * but prints to {@link org.python.core.Py#writeDebug(String, String)} * (i.e. subject to Jython's verbose level) * or directly to {@code System.err}, according to * {@link #USE_PY_WRITE_DEBUG}. * * @see #USE_PY_WRITE_DEBUG * @see org.python.core.Py#writeDebug(String, String) */ public static void writeDebug(String type, String msg) { if ((gcFlags & USE_PY_WRITE_DEBUG) != 0) { Py.writeDebug(type, msg); } else { System.err.println(type + ": " + msg); } } //--------------delayed finalization section----------------------------------- /** * In addition to what * {@link org.python.core.finalization.FinalizeTrigger#ensureFinalizer(PyObject)} * does, this method also restores the finalizer's * {@link org.python.core.finalization.FinalizeTrigger}'s flags by taking the * values from the former finalizer. On the other hand - in contrast to * {@link org.python.core.finalization.FinalizeTrigger#ensureFinalizer(PyObject)} - * this method would not create a {@link org.python.core.finalization.FinalizeTrigger} * for an object that did not have one before (i.e. the method checks for an old * (dead) trigger before it creates a new one.

* If a new finalizer is needed due to an * ordinary resurrection (i.e. the object's finalizer was called), * {@link org.python.core.finalization.FinalizeTrigger#ensureFinalizer(PyObject)} * is the right choice. If a finalization was vetoed in context of delayed * finalization (i.e. a resurrection that pretends not to be one and didn't run * the finalizer), this method is the better choice as it helps to make the new * {@link org.python.core.finalization.FinalizeTrigger} look exactly like the * old one regarding flags etc. * E.g. this method is called by {@link #abortDelayedFinalization(PyObject)}. * * @see #abortDelayedFinalization(PyObject) */ public static void restoreFinalizer(PyObject obj) { FinalizeTrigger ft = (FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR); boolean notify = false; if (ft != null) { FinalizeTrigger.ensureFinalizer(obj); /* ensure that the old finalize won't run in any case */ ft.clear(); ((FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR)).flags = ft.flags; notify = (ft.flags & FinalizeTrigger.NOTIFY_GC_FLAG) != 0; } if ((gcFlags & VERBOSE_DELAYED) != 0 || (gcFlags & VERBOSE_FINALIZE) != 0) { writeDebug("gc", "restore finalizer of "+obj); } CycleMarkAttr cm = (CycleMarkAttr) JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR); if (cm != null && cm.monitored) { monitorObject(obj, true); } if (notify) { boolean cyclic; if (cm != null && cm.isUncollectable()) { cyclic = true; } else { markCyclicObjects(obj, true); cm = (CycleMarkAttr) JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR); cyclic = cm != null && cm.isUncollectable(); } if ((gcFlags & VERBOSE_DELAYED) != 0 || (gcFlags & VERBOSE_FINALIZE) != 0) { writeDebug("gc", "notify finalizer abort; cyclic? "+cyclic); } notifyAbortFinalize(obj, cyclic); } } /** * Restores weak references pointing to {@code rst}. Note that * this does not prevent callbacks, unless it is called during * finalization phase (e.g. by a finalizer) and * {@link #delayedWeakrefCallbacksEnabled()} returns {@code true}. * In a manual fashion, one can enforce this by using the gc-flag * {@link #FORCE_DELAYED_WEAKREF_CALLBACKS}. Alternatively, one can * use the automatic way via the gc-flag * {@link #PRESERVE_WEAKREFS_ON_RESURRECTION}, but then one would * not need to call this method anyway. The manual way has better * performance, but also brings more responsibilies. * * @see #delayedWeakrefCallbacksEnabled() * @see #FORCE_DELAYED_WEAKREF_CALLBACKS * @see #PRESERVE_WEAKREFS_ON_RESURRECTION */ public static void restoreWeakReferences(PyObject rst) { ReferenceBackend toRestore = (ReferenceBackend) JyAttribute.getAttr(rst, JyAttribute.WEAK_REF_ATTR); if (toRestore != null) { toRestore.restore(rst); } } private static class DelayedFinalizationProcess implements Runnable { static DelayedFinalizationProcess defaultInstance = new DelayedFinalizationProcess(); private static void performFinalization(PyObject del) { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "delayed finalize of "+del); } FinalizeTrigger ft = (FinalizeTrigger) JyAttribute.getAttr(del, JyAttribute.FINALIZE_TRIGGER_ATTR); if (ft != null) { ft.performFinalization(); } else if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "no FinalizeTrigger"); } } public void run() { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "run delayed finalization. Index: "+ gcMonitoredRunCount); } Set criticals = resurrectionCriticals.keySet(); if (delayedFinalizationMode == DO_NOTHING_SPECIAL && (gcFlags & (PRESERVE_WEAKREFS_ON_RESURRECTION | DONT_FINALIZE_RESURRECTED_OBJECTS)) == 0) { /* In this case we can do a cheap variant... */ if ((gcFlags & FORCE_DELAYED_WEAKREF_CALLBACKS) != 0) { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "process delayed callbacks (force-branch)"); } GlobalRef.processDelayedCallbacks(); } if ((gcFlags & FORCE_DELAYED_FINALIZATION) != 0) { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "process delayed finalizers (force-branch)"); } for (PyObject del: delayedFinalizables.keySet()) { performFinalization(del); } for (PyObject cr: criticals) { performFinalization(cr); } delayedFinalizables.clear(); resurrectionCriticals.clear(); } if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "forced delayed finalization run done"); } return; } Set cyclicCriticals = removeNonCyclic(criticals); cyclicCriticals.retainAll(criticals); criticals.removeAll(cyclicCriticals); Set criticalReachablePool = findReachables(criticals); /* to avoid concurrent modification: */ ArrayList criticalReachables = new ArrayList<>(); FinalizeTrigger fn; if (delayedFinalizationMode == MARK_REACHABLE_CRITICALS) { for (PyObject obj: criticalReachablePool) { fn = (FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR); if (fn != null && fn.isActive() && fn.isFinalized()) { criticalReachables.add(obj); JyAttribute.setAttr(obj, JyAttribute.GC_DELAYED_FINALIZE_CRITICAL_MARK_ATTR, Integer.valueOf(gcMonitoredRunCount)); } } } else { for (PyObject obj: criticalReachablePool) { fn = (FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR); if (fn != null && fn.isActive() && fn.isFinalized()) { criticalReachables.add(obj); } } } criticals.removeAll(criticalReachables); if ((gcFlags & PRESERVE_WEAKREFS_ON_RESURRECTION) != 0) { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "restore potentially resurrected weak references..."); } for (PyObject rst: criticalReachablePool) { restoreWeakReferences(rst); } GlobalRef.processDelayedCallbacks(); } criticalReachablePool.clear(); if ((gcFlags & DONT_FINALIZE_RESURRECTED_OBJECTS) != 0) { /* restore all finalizers that might belong to resurrected objects: */ if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "restore "+criticalReachables.size()+ " potentially resurrected finalizers..."); } for (PyObject obj: criticalReachables) { restoreFinalizer(obj); } } else { if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "delayed finalization of "+criticalReachables.size()+ " potentially resurrected finalizers..."); } for (PyObject del: criticalReachables) { performFinalization(del); } } cyclicCriticals.removeAll(criticalReachables); if ((gcFlags & VERBOSE_DELAYED) != 0 && !delayedFinalizables.isEmpty()) { writeDebug("gc", "process "+delayedFinalizables.size()+ " delayed finalizers..."); } for (PyObject del: delayedFinalizables.keySet()) { performFinalization(del); } if ((gcFlags & VERBOSE_DELAYED) != 0 && !cyclicCriticals.isEmpty()) { writeDebug("gc", "process "+cyclicCriticals.size()+" cyclic delayed finalizers..."); } for (PyObject del: cyclicCriticals) { performFinalization(del); } if ((gcFlags & VERBOSE_DELAYED) != 0 && !criticals.isEmpty()) { writeDebug("gc", "calling "+criticals.size()+ " critical finalizers not reachable by other critical finalizers..."); } if (delayedFinalizationMode == MARK_REACHABLE_CRITICALS && !criticals.isEmpty() && !criticalReachables.isEmpty()) { /* This means some critical-reachables might be not critical-reachable any more. * In a synchronized gc collection approach System.gc should run again while * something like this is found. (Yes, not exactly a cheap task, but since this * is for debugging, correctness counts.) */ notifyRerun = true; } if (delayedFinalizationMode == NOTIFY_FOR_RERUN && !notifyRerun) { for (PyObject del: criticals) { if (!notifyRerun) { Object m = JyAttribute.getAttr(del, JyAttribute.GC_DELAYED_FINALIZE_CRITICAL_MARK_ATTR); if (m != null && ((Integer) m).intValue() == gcMonitoredRunCount) { notifyRerun = true; } } performFinalization(del); } } else { for (PyObject del: criticals) { performFinalization(del); } } delayedFinalizables.clear(); resurrectionCriticals.clear(); if ((gcFlags & VERBOSE_DELAYED) != 0) { writeDebug("gc", "delayed finalization run done"); } } } public static boolean delayedWeakrefCallbacksEnabled() { return (gcFlags & (PRESERVE_WEAKREFS_ON_RESURRECTION | FORCE_DELAYED_WEAKREF_CALLBACKS)) != 0; } public static boolean delayedFinalizationEnabled() { return (gcFlags & (PRESERVE_WEAKREFS_ON_RESURRECTION | DONT_FINALIZE_RESURRECTED_OBJECTS | FORCE_DELAYED_FINALIZATION)) != 0; } private static void updateDelayedFinalizationState() { /* * There might be the case where delayed weakref callbacks are enabled, * but not delayed finalization. We still register a DelayedFinalizationProcess * then. That process detects the situation by checking the flags and only * performs GlobalRef.processDelayedCallbacks() then. */ if (delayedFinalizationEnabled() || delayedWeakrefCallbacksEnabled()) { resumeDelayedFinalization(); } else if (indexOfPostFinalizationProcess( DelayedFinalizationProcess.defaultInstance) != -1) { suspendDelayedFinalization(); } if (!delayedWeakrefCallbacksEnabled() && GlobalRef.hasDelayedCallbacks()) { // If delayed callbacks were turned off, we process remaining // queued callbacks immediately (but in a new thread though): Thread dlcProcess = new Thread() { public void run() { GlobalRef.processDelayedCallbacks(); } }; dlcProcess.start(); } } private static void resumeDelayedFinalization() { if (delayedFinalizables == null) { delayedFinalizables = new IdentityHashMap<>(); } if (resurrectionCriticals == null) { resurrectionCriticals = new IdentityHashMap<>(); } /* add post-finalization process (and cancel pending suspension process if any) */ try { synchronized(postFinalizationProcessRemove) { postFinalizationProcessRemove.remove( DelayedFinalizationProcess.defaultInstance); if (postFinalizationProcessRemove.isEmpty()) { postFinalizationProcessRemove = null; } } } catch (NullPointerException npe) {} if (indexOfPostFinalizationProcess( DelayedFinalizationProcess.defaultInstance) == -1) { registerPostFinalizationProcess( DelayedFinalizationProcess.defaultInstance); } } private static void suspendDelayedFinalization() { unregisterPostFinalizationProcessAfterNextRun( DelayedFinalizationProcess.defaultInstance); } private static boolean isResurrectionCritical(PyObject ob) { return (isTraversable(ob)) && FinalizeTrigger.hasActiveTrigger(ob); } public static void registerForDelayedFinalization(PyObject ob) { if (isResurrectionCritical(ob)) { resurrectionCriticals.put(ob, ob); } else { delayedFinalizables.put(ob, ob); } } public static void abortDelayedFinalization(PyObject ob) { resurrectionCriticals.remove(ob); delayedFinalizables.remove(ob); if ((gcFlags & VERBOSE_DELAYED) != 0 || (gcFlags & VERBOSE_FINALIZE) != 0) { writeDebug("gc", "abort delayed finalization of "+ob); } restoreFinalizer(ob); } //--------------end of delayed finalization section---------------------------- //--------------Finalization preprocess/postprocess section-------------------- private static class PostFinalizationProcessor implements Runnable { protected static PostFinalizationProcessor defaultInstance = new PostFinalizationProcessor(); public void run() { /* We wait until last postFinalizationTimestamp is at least timeOut ago. * This should only be measured when openFinalizeCount is zero. */ long current = System.currentTimeMillis(); while (true) { if (!lockPostFinalization && openFinalizeCount == 0 && current - postFinalizationTimestamp > postFinalizationTimeOut) { break; } try { long time = postFinalizationTimeOut - current + postFinalizationTimestamp; if (openFinalizeCount != 0 || lockPostFinalization || time < 0) { time = gcRecallTime; } Thread.sleep(time); } catch (InterruptedException ie) { } current = System.currentTimeMillis(); } postFinalizationProcessor = null; postFinalizationProcess(); synchronized(PostFinalizationProcessor.class) { postFinalizationPending = false; PostFinalizationProcessor.class.notify(); } } } /** *

* Registers a process that will be called before any finalization during gc-run * takes place ("finalization" refers to Jython-style finalizers ran by * {@link org.python.core.finalization.FinalizeTrigger}s; * to care for other finalizers these must call * {@code gc.notifyPreFinalization()} before anything else is done and * {@code gc.notifyPostFinalization()} afterwards; between these calls the finalizer * must not terminate by throwing an exception). * This works independently from monitoring, which is mainly needed to allow * counting of cyclic garbage in {@link #collect()}. *

*

* This feature compensates that Java's gc does not provide any guarantees about * finalization order. Java not even guarantees that when a weak reference is * added to a reference queue, its finalizer already ran or not yet ran, if any. *

*

* The only guarantee is that {@link java.lang.ref.PhantomReference}s are enqueued * after finalization of their referents, but this happens in another gc-cycle then. *

*

* Actually there are still situations that can cause pre-finalization process to * run again during finalization phase. This can happen if external frameworks use * their own finalizers. This can be cured by letting these finalizers call * {@code gc.notifyPreFinalization()} before anything else is done and * {@code gc.notifyPostFinalization()} right before the finalization method returns. * Between these calls the finalizer must not terminate by throwing an exception. *

*

* We recommend to use this feature in a way such that false-positive runs are * not critically harmful, e.g. use it to enhance performance, but don't let it * cause a crash if preprocess is rerun unexpectedly. *

*/ public static void registerPreFinalizationProcess(Runnable process) { registerPreFinalizationProcess(process, -1); } /** * See doc of {@link #registerPreFinalizationProcess(Runnable)}. */ public static void registerPreFinalizationProcess(Runnable process, int index) { while (true) { try { synchronized (preFinalizationProcess) { preFinalizationProcess.add(index < 0 ? index+preFinalizationProcess.size()+1 : index, process); } return; } catch (NullPointerException npe) { preFinalizationProcess = new ArrayList<>(1); } } } public static int indexOfPreFinalizationProcess(Runnable process) { try { synchronized (preFinalizationProcess) { return preFinalizationProcess.indexOf(process); } } catch (NullPointerException npe) { return -1; } } public static boolean unregisterPreFinalizationProcess(Runnable process) { try { synchronized (preFinalizationProcess) { boolean result = preFinalizationProcess.remove(process); if (result && preFinalizationProcess.isEmpty()) { preFinalizationProcess = null; } return result; } } catch (NullPointerException npe) { return false; } } /** * Useful if a process wants to remove another one or itself during its execution. * This asynchronous unregister method circumvents the synchronized-state on * pre-finalization process list. */ public static void unregisterPreFinalizationProcessAfterNextRun(Runnable process) { while (true) { try { synchronized (preFinalizationProcessRemove) { preFinalizationProcessRemove.add(process); } return; } catch (NullPointerException npe) { preFinalizationProcessRemove = new ArrayList<>(1); } } } /** *

* Registers a process that will be called after all finalization during gc-run * is done ("finalization" refers to Jython-style finalizers ran by * {@link org.python.core.finalization.FinalizeTrigger}s; * to care for other finalizers these must call * {@code gc.notifyPreFinalization()} before anything else is done and * {@code gc.notifyPostFinalization()} afterwards; between these calls the finalizer * must not terminate by throwing an exception). * This works independently from monitoring (which is mainly needed to allow * garbage counting in {@link #collect()}). *

*

* This feature compensates that Java's gc does not provide any guarantees about * finalization order. Java not even guarantees that when a weak reference is * added to a reference queue, its finalizer already ran or not yet ran, if any. *

*

* The only guarantee is that {@link java.lang.ref.PhantomReference}s are * enqueued after finalization of the referents, but this * happens - however - in another gc-cycle then. *

*

* There are situations that can cause post finalization process to run * already during finalization phase. This can happen if external frameworks use * their own finalizers. This can be cured by letting these finalizers call * {@code gc.notifyPreFinalization()} before anything else is done and * {@code gc.notifyPostFinalization()} right before the finalization method returns. * Between these calls the finalizer must not terminate by throwing an exception. *

*

* If it runs too early, we can at least guarantee that it will run again after * finalization was really done. So we recommend to use this feature in a way * such that false-positive runs are not critically harmful. *

*/ public static void registerPostFinalizationProcess(Runnable process) { registerPostFinalizationProcess(process, -1); } /** * See doc of {@link #registerPostFinalizationProcess(Runnable)}. */ public static void registerPostFinalizationProcess(Runnable process, int index) { while (true) { try { synchronized (postFinalizationProcess) { postFinalizationProcess.add(index < 0 ? index+postFinalizationProcess.size()+1 : index, process); } return; } catch (NullPointerException npe) { postFinalizationProcess = new ArrayList<>(1); } } } public static int indexOfPostFinalizationProcess(Runnable process) { try { synchronized (postFinalizationProcess) { return postFinalizationProcess.indexOf(process); } } catch (NullPointerException npe) { return -1; } } public static boolean unregisterPostFinalizationProcess(Runnable process) { try { synchronized (postFinalizationProcess) { boolean result = postFinalizationProcess.remove(process); if (result && postFinalizationProcess.isEmpty()) { postFinalizationProcess = null; } return result; } } catch (NullPointerException npe) { return false; } } /** * Useful if a process wants to remove another one or itself during its execution. * This asynchronous unregister method circumvents the synchronized-state on * post-finalization process list. */ public static void unregisterPostFinalizationProcessAfterNextRun(Runnable process) { while (true) { try { synchronized (postFinalizationProcessRemove) { postFinalizationProcessRemove.add(process); } return; } catch (NullPointerException npe) { postFinalizationProcessRemove = new ArrayList<>(1); } } } public static void notifyPreFinalization() { long callTime = System.currentTimeMillis(); /* * This section is experimental and kept for further investigation. In theory, it can * prevent potential problems in JyNI-gc, if a gc-run overlaps the previous run's * post-finalization phase. However it currently breaks gc-tests, so is out-commented * so far. In practical sense, JyNI's gc-support also works fine without it so far. */ // if (postFinalizationPending) { // if ((gcFlags & VERBOSE_COLLECT) != 0) { // writeDebug("gc", "waiting for pending post-finalization process."); // } // /* It is important to have the while (which is actually an "if" since the // * InterruptedException is very unlikely to occur) *inside* the synchronized // * block. Otherwise the notification might come just between the check and the wait, // * causing an endless waiting. This is no pure academic consideration, but was // * actually observed to happen from time to time, especially on faster systems. // */ // synchronized(PostFinalizationProcessor.class) { // while (postFinalizationPending) { // try { // PostFinalizationProcessor.class.wait(); // } catch (InterruptedException ie3) {} // } // } // if ((gcFlags & VERBOSE_COLLECT) != 0) { // writeDebug("gc", "post-finalization finished."); // } // } // /* // * Increment of openFinalizeCount must not happen before waiting for pending // * post-finalization process is done. Otherwise PostFinalizationProcessor can // * be waiting for a neutral openFinalizeCount, causing a deadlock. // */ ++openFinalizeCount; if (callTime - postFinalizationTimestamp < postFinalizationTimeOut) { return; } try { synchronized(preFinalizationProcess) { for (Runnable r: preFinalizationProcess) { try { r.run(); } catch (Exception preProcessError) { Py.writeError("gc", "Finalization preprocess "+r+" caused error: " +preProcessError); } } try { synchronized (preFinalizationProcessRemove) { preFinalizationProcess.removeAll(preFinalizationProcessRemove); preFinalizationProcessRemove = null; } if (preFinalizationProcess.isEmpty()) { preFinalizationProcess = null; } } catch (NullPointerException npe0) {} } } catch (NullPointerException npe) { preFinalizationProcessRemove = null; } try { synchronized(postFinalizationProcess) { if (!postFinalizationProcess.isEmpty() && postFinalizationProcessor == null) { postFinalizationPending = true; postFinalizationProcessor = new Thread( PostFinalizationProcessor.defaultInstance); postFinalizationProcessor.start(); } } } catch (NullPointerException npe) {} } public static void notifyPostFinalization() { postFinalizationTimestamp = System.currentTimeMillis(); --openFinalizeCount; if (openFinalizeCount == 0 && postFinalizationProcessor != null) { postFinalizationProcessor.interrupt(); } } protected static void postFinalizationProcess() { try { synchronized(postFinalizationProcess) { for (Runnable r: postFinalizationProcess) { try { r.run(); } catch (Exception postProcessError) { System.err.println("Finalization postprocess "+r+" caused error:"); System.err.println(postProcessError); } } try { synchronized (postFinalizationProcessRemove) { postFinalizationProcess.removeAll(postFinalizationProcessRemove); postFinalizationProcessRemove = null; } if (postFinalizationProcess.isEmpty()) { postFinalizationProcess = null; } } catch (NullPointerException npe0) {} } } catch (NullPointerException npe) { postFinalizationProcessRemove = null; } } //--------------end of Finalization preprocess/postprocess section------------- //--------------Monitoring section--------------------------------------------- public static void monitorObject(PyObject ob) { monitorObject(ob, false); } public static void monitorObject(PyObject ob, boolean initString) { /* Already collected garbage should not be monitored, * thus also not the garbage list: */ if (ob == null || ob == garbage) { return; } /* In contrast to isTraversable(ob) we don't look for DONT_TRAVERSE_BY_REFLECTION * here. Objects not explicitly marked as Untraversable get monitored so we are * able to print out warnings in Traverseproc. */ if (!monitorNonTraversable && !(ob instanceof Traverseproc || ob instanceof TraverseprocDerived)) { if (ob.getClass().isAnnotationPresent(Untraversable.class) && !JyAttribute.hasAttr(ob, JyAttribute.FINALIZE_TRIGGER_ATTR)) { return; } } if (gcTrash == null) { gcTrash = new ReferenceQueue<>(); } while (true) { try { synchronized(monitoredObjects) { if (!isMonitored(ob)) { CycleMarkAttr cm = new CycleMarkAttr(); JyAttribute.setAttr(ob, JyAttribute.GC_CYCLE_MARK_ATTR, cm); WeakReferenceGC refPut = new WeakReferenceGC(ob, gcTrash); if (initString) { refPut.initStr(ob); } monitoredObjects.add(refPut); cm.monitored = true; } } return; } catch (NullPointerException npe) { monitoredObjects = new HashSet(); } } } /** * Avoid to use this method. It is inefficient and no intended purpose of the * backing Set of objects. In normal business it should not be needed and only * exists for bare debugging purposes. */ public static WeakReferenceGC getMonitorReference(PyObject ob) { try { synchronized(monitoredObjects) { for (WeakReferenceGC ref: monitoredObjects) { if (ref.equals(ob)) { return ref; } } } } catch (NullPointerException npe) {} return null; } public static boolean isMonitoring() { try { synchronized(monitoredObjects) { return !monitoredObjects.isEmpty(); } } catch (NullPointerException npe) { return false; } } public static boolean isMonitored(PyObject ob) { try { synchronized(monitoredObjects) { WeakrefGCCompareDummy.defaultInstance.setCompare(ob); boolean result = monitoredObjects.contains( WeakrefGCCompareDummy.defaultInstance); WeakrefGCCompareDummy.defaultInstance.clearCompare(); return result; } } catch (NullPointerException npe) { return false; } } public static boolean unmonitorObject(PyObject ob) { try { synchronized(monitoredObjects) { WeakrefGCCompareDummy.defaultInstance.setCompare(ob); WeakReferenceGC rem = getMonitorReference(ob); if (rem != null) { rem.clear(); } boolean result = monitoredObjects.remove( WeakrefGCCompareDummy.defaultInstance); WeakrefGCCompareDummy.defaultInstance.clearCompare(); JyAttribute.delAttr(ob, JyAttribute.GC_CYCLE_MARK_ATTR); FinalizeTrigger ft = (FinalizeTrigger) JyAttribute.getAttr(ob, JyAttribute.FINALIZE_TRIGGER_ATTR); if (ft != null) { ft.flags &= ~FinalizeTrigger.NOTIFY_GC_FLAG; } return result; } } catch (NullPointerException npe) { return false; } } public static void unmonitorAll() { try { synchronized(monitoredObjects) { FinalizeTrigger ft; for (WeakReferenceGC mo: monitoredObjects) { PyObject rfrt = mo.get(); if (rfrt != null) { JyAttribute.delAttr(rfrt, JyAttribute.GC_CYCLE_MARK_ATTR); ft = (FinalizeTrigger) JyAttribute.getAttr(rfrt, JyAttribute.FINALIZE_TRIGGER_ATTR); if (ft != null) { ft.flags &= ~FinalizeTrigger.NOTIFY_GC_FLAG; } } mo.clear(); } monitoredObjects.clear(); } } catch (NullPointerException npe) { } } public static void stopMonitoring() { setMonitorGlobal(false); if (monitoredObjects != null) { unmonitorAll(); monitoredObjects = null; } } public static boolean getMonitorGlobal() { return PyObject.gcMonitorGlobal; } public static void setMonitorGlobal(boolean flag) { if (flag) { gcFlags |= MONITOR_GLOBAL; } else { gcFlags &= ~MONITOR_GLOBAL; } PyObject.gcMonitorGlobal = flag; } //--------------end of Monitoring section-------------------------------------- /** * Gets the current Jython-specific gc-flags. * * @see #MONITOR_GLOBAL * @see #DONT_FINALIZE_CYCLIC_GARBAGE * @see #PRESERVE_WEAKREFS_ON_RESURRECTION * @see #DONT_FINALIZE_RESURRECTED_OBJECTS * @see #DONT_TRAVERSE_BY_REFLECTION * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING * @see #USE_PY_WRITE_DEBUG * @see #VERBOSE_COLLECT * @see #VERBOSE_WEAKREF * @see #VERBOSE_DELAYED * @see #VERBOSE_FINALIZE * @see #VERBOSE * @see #setJythonGCFlags(short) * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static short getJythonGCFlags() { if (((gcFlags & MONITOR_GLOBAL) != 0) != PyObject.gcMonitorGlobal) { if (PyObject.gcMonitorGlobal) { gcFlags |= MONITOR_GLOBAL; } else { gcFlags &= ~MONITOR_GLOBAL; } } return gcFlags; } /** * Sets the current Jython-specific gc-flags. *
* {@code flags} is a {@code short} and can have the following bits turned on:
*
* {@link #MONITOR_GLOBAL} - Automatically monitors all PyObjects created from now on.
* {@link #DONT_FINALIZE_CYCLIC_GARBAGE} - Adds cyclic finalizable PyObjects to {@link #garbage}.
* {@link #PRESERVE_WEAKREFS_ON_RESURRECTION} - Keeps weak references alive if the referent is resurrected.
* {@link #DONT_FINALIZE_RESURRECTED_OBJECTS} - * Emulates CPython behavior regarding resurrected objects and finalization.
* {@link #DONT_TRAVERSE_BY_REFLECTION} - Inhibits reflection-based traversion.
* {@link #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING} - * Suppress warnings for PyObjects that neither implement {@link org.python.core.Traverseproc} nor * are marked as {@link org.python.core.Untraversable}.
* {@link #USE_PY_WRITE_DEBUG} - uses {@link org.python.core.Py#writeDebug(String, String)} for * debugging output instead of directly writing to {@link java.lang.System#err}.
* {@link #VERBOSE_COLLECT} - Enable collection-related verbose output.
* {@link #VERBOSE_WEAKREF} - Enable weakref-related verbose output.
* {@link #VERBOSE_DELAYED} - Enable delayed finalization-related verbose output.
* {@link #VERBOSE_FINALIZE} - Enable finalization-related verbose output.
* {@link #VERBOSE} - All previous verbose-flags combined. * * @see #MONITOR_GLOBAL * @see #DONT_FINALIZE_CYCLIC_GARBAGE * @see #PRESERVE_WEAKREFS_ON_RESURRECTION * @see #DONT_FINALIZE_RESURRECTED_OBJECTS * @see #DONT_TRAVERSE_BY_REFLECTION * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING * @see #USE_PY_WRITE_DEBUG * @see #VERBOSE_COLLECT * @see #VERBOSE_WEAKREF * @see #VERBOSE_DELAYED * @see #VERBOSE_FINALIZE * @see #VERBOSE * @see #getJythonGCFlags() * @see #addJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static void setJythonGCFlags(short flags) { gcFlags = flags; PyObject.gcMonitorGlobal = (gcFlags & MONITOR_GLOBAL) != 0; updateDelayedFinalizationState(); } /** * This is a convenience method to add flags via bitwise or. * * @see #MONITOR_GLOBAL * @see #DONT_FINALIZE_CYCLIC_GARBAGE * @see #PRESERVE_WEAKREFS_ON_RESURRECTION * @see #DONT_FINALIZE_RESURRECTED_OBJECTS * @see #DONT_TRAVERSE_BY_REFLECTION * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING * @see #USE_PY_WRITE_DEBUG * @see #VERBOSE_COLLECT * @see #VERBOSE_WEAKREF * @see #VERBOSE_DELAYED * @see #VERBOSE_FINALIZE * @see #VERBOSE * @see #getJythonGCFlags() * @see #setJythonGCFlags(short) * @see #removeJythonGCFlags(short) */ public static void addJythonGCFlags(short flags) { gcFlags |= flags; PyObject.gcMonitorGlobal = (gcFlags & MONITOR_GLOBAL) != 0; updateDelayedFinalizationState(); } /** * This is a convenience method to remove flags via bitwise and-not. * * @see #MONITOR_GLOBAL * @see #DONT_FINALIZE_CYCLIC_GARBAGE * @see #PRESERVE_WEAKREFS_ON_RESURRECTION * @see #DONT_FINALIZE_RESURRECTED_OBJECTS * @see #DONT_TRAVERSE_BY_REFLECTION * @see #SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING * @see #INSTANCE_TRAVERSE_BY_REFLECTION_WARNING * @see #USE_PY_WRITE_DEBUG * @see #VERBOSE_COLLECT * @see #VERBOSE_WEAKREF * @see #VERBOSE_DELAYED * @see #VERBOSE_FINALIZE * @see #VERBOSE * @see #getJythonGCFlags() * @see #setJythonGCFlags(short) * @see #addJythonGCFlags(short) */ public static void removeJythonGCFlags(short flags) { gcFlags &= ~flags; PyObject.gcMonitorGlobal = (gcFlags & MONITOR_GLOBAL) != 0; updateDelayedFinalizationState(); } /** * Do not call this method manually. * It should only be called by * {@link org.python.core.finalization.FinalizeTrigger}. */ public static void notifyFinalize(PyObject finalized) { if (--finalizeWaitCount == 0 && waitingForFinalizers) { synchronized(GCSentinel.class) { GCSentinel.class.notify(); } } } /** * For now this just calls {@code notifyFinalize}, as the only current * purpose is to decrement the open finalizer count. */ private static void notifyAbortFinalize(PyObject abort, boolean cyclic) { if (cyclic) { ++abortedCyclicFinalizers; } notifyFinalize(abort); } /** * Does nothing in Jython as Java-gc is always enabled. */ public static void enable() {} /** * Not supported by Jython. * Throws {@link org.python.core.Py#NotImplementedError}. * * @throws org.python.core.Py.NotImplementedError */ public static void disable() { throw Py.NotImplementedError("can't disable Java GC"); } /** * Always returns {@code true} in Jython. */ public static boolean isenabled() { return true; } /** * The generation parameter is only for compatibility with * CPython {@link gc.collect()} and is ignored. * @param generation (ignored) * @return Collected monitored cyclic trash-objects or * {@code gc.UNKNOWN_COUNT} if nothing is monitored or -1 if * an error occurred and collection did not complete. * @see #collect() */ public static int collect(int generation) { return collect(); } private static boolean needsTrashPrinting() { return ((debugFlags & DEBUG_COLLECTABLE) != 0 || (debugFlags & DEBUG_UNCOLLECTABLE) != 0) && ((debugFlags & DEBUG_INSTANCES) != 0 || (debugFlags & DEBUG_OBJECTS) != 0); } private static boolean needsCollectBuffer() { return (debugFlags & DEBUG_STATS) != 0 || needsTrashPrinting(); } /** * If no objects are monitored, this just delegates to * {@code System.gc()} and returns {@link #UNKNOWN_COUNT} as a * non-erroneous default value. If objects are monitored, * it emulates a synchronous gc-run in the sense that it waits * until all collected monitored objects were finalized. * * @return Number of collected monitored cyclic trash-objects * or {@link #UNKNOWN_COUNT} if nothing is monitored or -1 * if an error occurred and collection did not complete. * @see #UNKNOWN_COUNT * @see #collect(int) */ public static int collect() { try { return collect_intern(); } catch (java.util.ConcurrentModificationException cme) { cme.printStackTrace(); } catch (NullPointerException npe) { npe.printStackTrace(); } return -1; } private static int collect_intern() { long t1 = 0; int result; if ((debugFlags & DEBUG_STATS) != 0) { t1 = System.currentTimeMillis(); } if (!isMonitoring()) { if ((debugFlags & DEBUG_STATS) != 0) { writeDebug("gc", "collecting generation x..."); writeDebug("gc", "objects in each generation: unknown"); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "no monitoring; perform ordinary async System.gc..."); } System.gc(); result = UNKNOWN_COUNT; /* indicates unknown result (-1 would indicate error) */ } else { if (!gcRunning.compareAndSet(false, true)) { if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "collect already running..."); } /* We must fail fast in this case to avoid deadlocks. * Deadlock would for instance occur if a finalizer calls * gc.collect (like is done in some tests in test_gc). * Former version: throw new IllegalStateException("GC is already running."); */ return -1; /* better not throw exception here, as calling code * is usually not prepared for that */ } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "perform monitored sync gc run..."); } if (needsTrashPrinting() || (gcFlags & VERBOSE) != 0) { /* When the weakrefs are enqueued, the referents won't be available * any more to provide their string representations, so we must * save the string representations in the weak ref objects while * the referents are still alive. * We cannot save the string representations in the moment when the * objects get monitored, because with monitorGlobal activated * the objects get monitored just when they are created and some * of them are in an invalid state then and cannot directly obtain * their string representation (would produce overflow errors and * such bad stuff). So we do it here... */ List lst = new ArrayList<>(); for (WeakReferenceGC wr: monitoredObjects) { if (wr.str == null) { lst.add(wr); } } for (WeakReferenceGC ol: lst) { ol.initStr(null); } lst.clear(); } ++gcMonitoredRunCount; delayedFinalizationMode = MARK_REACHABLE_CRITICALS; notifyRerun = false; int[] stat = {0, 0}; syncCollect(stat, (debugFlags & DEBUG_STATS) != 0); delayedFinalizationMode = NOTIFY_FOR_RERUN; if (notifyRerun) { if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "initial sync collect done."); } while (notifyRerun) { notifyRerun = false; if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "rerun gc..."); } syncCollect(stat, false); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "all sync collect runs done."); } } else if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "sync collect done."); } delayedFinalizationMode = DO_NOTHING_SPECIAL; gcRunning.set(false); result = stat[0]; if ((debugFlags & DEBUG_STATS) != 0) { if (result != UNKNOWN_COUNT) { StringBuilder sb = new StringBuilder("done, "); sb.append(stat[0]); sb.append(" unreachable, "); sb.append(stat[1]); sb.append(" uncollectable"); if (t1 != 0) { sb.append(", "); sb.append((System.currentTimeMillis()-t1)/1000.0); sb.append("s elapsed"); } sb.append("."); writeDebug("gc", sb.toString()); } } } if ((debugFlags & DEBUG_STATS) != 0 && result == UNKNOWN_COUNT) { StringBuilder sb = new StringBuilder("done"); if (t1 != 0) { sb.append(", "); sb.append((System.currentTimeMillis()-t1)/1000.0); sb.append("s elapsed"); } sb.append("."); writeDebug("gc", sb.toString()); } return result; } private static void syncCollect(int[] stat, boolean debugStat) { abortedCyclicFinalizers = 0; lockPostFinalization = true; Reference trash; try { trash = gcTrash.remove(initWaitTime); if (trash != null && (gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "monitored objects from previous gc-run found."); } } catch (InterruptedException ie) { trash = null; } Set cyclic; IdentityHashMap cyclicLookup; synchronized(monitoredObjects) { if (trash != null) { while (trash != null) { monitoredObjects.remove(trash); try { trash = gcTrash.remove(initWaitTime); } catch (InterruptedException ie) { trash = null; } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "cleaned up previous trash."); } } FinalizeTrigger ft; for (WeakReferenceGC wrg: monitoredObjects) { wrg.updateHasFinalizer(); if (wrg.hasFinalizer) { ft = (FinalizeTrigger) JyAttribute.getAttr(wrg.get(), JyAttribute.FINALIZE_TRIGGER_ATTR); ft.flags |= FinalizeTrigger.NOTIFY_GC_FLAG; /* The NOTIFY_GC_FLAG is needed, because monitor state changes during * collection. So the FinalizeTriggers can't use gc.isMonitored to know * whether gc notification is needed. */ } } /* Typically this line causes a gc-run: */ cyclicLookup = removeNonCyclicWeakRefs(monitoredObjects); cyclic = new HashSet<>(cyclicLookup.values()); if (debugStat) { writeDebug("gc", "collecting generation x..."); writeDebug("gc", "objects in each generation: "+cyclic.size()); } if ((debugFlags & DEBUG_SAVEALL) != 0 || (gcFlags & DONT_FINALIZE_RESURRECTED_OBJECTS) != 0) { cyclic.retainAll(monitoredObjects); for (WeakReferenceGC wrg: cyclic) { if (!wrg.hasFinalizer) { PyObject obj = wrg.get(); FinalizeTrigger.ensureFinalizer(obj); wrg.updateHasFinalizer(); ft = (FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR); ft.flags |= FinalizeTrigger.NOT_FINALIZABLE_FLAG; ft.flags |= FinalizeTrigger.NOTIFY_GC_FLAG; } } } } maxWaitTime = initWaitTime; lastRemoveTimeStamp = System.currentTimeMillis(); if (finalizeWaitCount != 0) { System.err.println("Finalize wait count should be initially 0!"); finalizeWaitCount = 0; } /* We tidy up a bit... (Because it is not unlikely that * the preparation-stuff done so far has caused a gc-run.) * This is not entirely safe as gc could interfere with * this process at any time again. Since this is intended * for debugging, this solution is sufficient in practice. * Maybe we will include more mechanisms to ensure safety * in the future. */ try { trash = gcTrash.remove(initWaitTime); if (trash != null && (gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "monitored objects from interferring gc-run found."); } } catch (InterruptedException ie) { trash = null; } if (trash != null) { while (trash != null) { monitoredObjects.remove(trash); if (cyclic.remove(trash) && (gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "cyclic interferring trash: "+trash); } else if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "interferring trash: "+trash); } try { trash = gcTrash.remove(initWaitTime); } catch (InterruptedException ie) { trash = null; } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "cleaned up interferring trash."); } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "call System.gc."); } cyclicLookup = null; List collectBuffer; /* The following out-commented block is a nice idea to sync gc in a more * elegant way. Unfortunately it proved infeasible because MXBean appears * to be no reliable measurement for gc to have finished enqueueing trash. * We leave it here to document this failed approach for future generations, * so nobody needs to waste time on this again/can quickly reproduce how * it fails. // Yes, Errors should not be caught, but in this case we have a very good // reason for it. // collectSyncViaMXBean uses inofficial API, i.e. classes from com.sun.management. // In case that a JVM does not provide this API, we have a less elegant fallback // at hand, which is based on a sentinel- and timeout-technique. try { collectBuffer = collectSyncViaMXBean(stat, cyclic); } catch (NoClassDefFoundError ncdfe) { collectBuffer = collectSyncViaSentinel(stat, cyclic); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "all objects from run enqueud in trash queue."); writeDebug("gc", "pending finalizers: "+finalizeWaitCount); }*/ collectBuffer = collectSyncViaSentinel(stat, cyclic); //lockPostFinalization assures that postFinalization process //only runs once per syncCollect-call. lockPostFinalization = false; if (postFinalizationProcessor != null) { //abort the remaining wait-time if a postFinalizationProcessor is waiting postFinalizationProcessor.interrupt(); } waitingForFinalizers = true; waitForFinalizers(); waitingForFinalizers = false; if (postFinalizationPending) { if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "waiting for pending post-finalization process."); } /* It is important to have the while (which is actually an "if" since the * InterruptedException is very unlikely to occur) *inside* the synchronized * block. Otherwise the notification might come just between the check and the wait, * causing an endless waiting. This is no pure academic consideration, but was * actually observed to happen from time to time, especially on faster systems. */ synchronized(PostFinalizationProcessor.class) { while (postFinalizationPending) { try { PostFinalizationProcessor.class.wait(); } catch (InterruptedException ie3) {} } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "post-finalization finished."); } } if (collectBuffer != null) { /* There is a little discrepancy in CPython's behavior. * The documentation tells that all uncollectable objects * are stored in gc.garbage, but it actually stores only * those uncollectable objects that have finalizers. * In contrast to that the uncollectable counting and * listing related to DEBUG_X-flags also counts/lists * objects that participate in a cycle with uncollectable * finalizable objects. * * Comprehension: * An object is uncollectable if it is in a ref cycle and * has a finalizer. * * CPython * * - counts and prints the whole uncollectable cycle in context * of DEBUG_X-flags. * * - stores only those objects from the cycle that actually have * finalizers in gc.garbage. * * While slightly contradictory to the doc, we reproduce this * behavior here. */ if ((debugFlags & gc.DEBUG_COLLECTABLE) != 0 && ( (debugFlags & gc.DEBUG_OBJECTS) != 0 || (debugFlags & gc.DEBUG_INSTANCES) != 0)) { /* note that all cycleMarks should have been initialized when * objects became monitored. */ for (WeakReferenceGC wrg: collectBuffer) { if (!wrg.cycleMark.isUncollectable()) { if (wrg.isInstance) { writeDebug("gc", "collectable "+ ((debugFlags & gc.DEBUG_INSTANCES) != 0 ? wrg.inst_str : wrg.str)); } else if ((debugFlags & gc.DEBUG_OBJECTS) != 0) { writeDebug("gc", "collectable "+wrg.str); } } else { ++stat[1]; } } } else if ((debugFlags & gc.DEBUG_STATS) != 0) { for (WeakReferenceGC wrg: collectBuffer) { if (wrg.cycleMark.isUncollectable()) { ++stat[1]; } } } if ((debugFlags & gc.DEBUG_UNCOLLECTABLE) != 0 && ( (debugFlags & gc.DEBUG_OBJECTS) != 0 || (debugFlags & gc.DEBUG_INSTANCES) != 0)) { for (WeakReferenceGC wrg: collectBuffer) { if (wrg.cycleMark.isUncollectable()) { if (wrg.isInstance) { writeDebug("gc", "uncollectable "+ ((debugFlags & gc.DEBUG_INSTANCES) != 0 ? wrg.inst_str : wrg.str)); } else if ((debugFlags & gc.DEBUG_OBJECTS) != 0) { writeDebug("gc", "uncollectable "+wrg.str); } } } } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", abortedCyclicFinalizers+ " finalizers aborted."); } stat[0] -= abortedCyclicFinalizers; stat[1] -= abortedCyclicFinalizers; } /* * The following out-commented section belongs to the out-commented * block on MXBean-based GC sync somewhere above. * We keep it here to document this failed approach and to enable * future developers to quickly reproduce and analyse this failure. private static class GCListener implements NotificationListener { private int index; private Thread toNotify; public GCListener(int index, Thread toNotify) { this.index = index; this.toNotify = toNotify; } public void handleNotification(Notification notif, Object handback) { if (waitForGCNotification[index]) { String notifType = notif.getType(); if (notifType.equals( com.sun.management.GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { // retrieve the garbage collection notification information CompositeData cd = (CompositeData) notif.getUserData(); com.sun.management.GarbageCollectionNotificationInfo info = com.sun.management.GarbageCollectionNotificationInfo.from(cd); if (info.getGcCause().equals("System.gc()") && info.getGcAction().startsWith("end of ") && info.getGcAction().endsWith(" GC")) { synchronized (GCListener.class) { --outstandingNotifications; if (toNotify != null && waitingForTrash && outstandingNotifications == 0) { toNotify.interrupt(); toNotify = null; } } } } } } } private static boolean[] waitForGCNotification; private static int outstandingNotifications = 0; private static List currentGCBeans; private static String failFast = null; private static boolean waitingForTrash = false; private static List collectSyncViaMXBean(int[] stat, Set cyclic) { // This step should be done first in order to fail fast, // if com.sun.management is not available. if (failFast == null) { failFast = com.sun.management.GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION; } //Reaching this line successfully means that com.sun.management exists. Reference trash; currentGCBeans = ManagementFactory.getGarbageCollectorMXBeans(); long[] initialGCCounts = new long[currentGCBeans.size()]; GCListener[] listeners = new GCListener[initialGCCounts.length]; int i = 0; for (GarbageCollectorMXBean gcb: currentGCBeans) { listeners[i] = new GCListener(i, Thread.currentThread()); ((NotificationBroadcaster) gcb).addNotificationListener( listeners[i], null, null); initialGCCounts[i++] = gcb.getCollectionCount(); } if (waitForGCNotification == null || waitForGCNotification.length != initialGCCounts.length) { waitForGCNotification = new boolean[initialGCCounts.length]; } synchronized (GCListener.class) { boolean gcRunDetected = false; outstandingNotifications = 0; while (!gcRunDetected) { System.gc(); for (i = 0; i < waitForGCNotification.length; ++i) { waitForGCNotification[i] = currentGCBeans.get(i).getCollectionCount() > initialGCCounts[i]; ++outstandingNotifications; if (waitForGCNotification[i]) { // at least one counter should change if a gc-run occurred. gcRunDetected = true; } } } } List collectBuffer = null; if (needsCollectBuffer()) { collectBuffer = new ArrayList<>(); } while (outstandingNotifications > 0) { try { waitingForTrash = true; Thread.sleep(2000); } catch (InterruptedException ie) { } waitingForTrash = false; } trash = gcTrash.poll(); while (trash != null) { if (trash instanceof WeakReferenceGC) { synchronized(monitoredObjects) { monitoredObjects.remove(trash); } //We avoid counting jython-specific objects in order to //obtain CPython-comparable results. if (cyclic.contains(trash) && !((WeakReferenceGC) trash).cls.contains("Java")) { ++stat[0]; if (collectBuffer != null) { collectBuffer.add((WeakReferenceGC) trash); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "Collected cyclic object: "+trash); } } if (((WeakReferenceGC) trash).hasFinalizer) { ++finalizeWaitCount; if ((gcFlags & VERBOSE_FINALIZE) != 0) { writeDebug("gc", "Collected finalizable object: "+trash); writeDebug("gc", "New finalizeWaitCount: "+finalizeWaitCount); } } } trash = gcTrash.poll(); } try { Thread.sleep(200); } catch (InterruptedException ie) { } trash = gcTrash.poll(); if (trash != null) { //This should not happen, but actually does. //So MXBean-based sync is not reliable! System.out.println("Late trash: "+trash); System.out.println("Late trash: "+trash.getClass()); System.out.println("Late trash: "+System.identityHashCode(trash)); int count = 0; while (trash != null) { System.out.println("Late trash "+count+": "+trash); ++count; trash = gcTrash.poll(); } System.out.println("Late trash count: "+count); } i = 0; for (GarbageCollectorMXBean gcb: currentGCBeans) { try { ((NotificationBroadcaster) gcb).removeNotificationListener( listeners[i++]); } catch (ListenerNotFoundException lnfe) { } } return collectBuffer; } */ private static List collectSyncViaSentinel(int[] stat, Set cyclic) { WeakReference sentRef = new WeakReference<>(new GCSentinel(Thread.currentThread()), gcTrash); Reference trash; System.gc(); List collectBuffer = null; if (needsCollectBuffer()) { collectBuffer = new ArrayList<>(); } long removeTime; try { while(true) { removeTime = System.currentTimeMillis()-lastRemoveTimeStamp; if (removeTime > maxWaitTime) { maxWaitTime = removeTime; } lastRemoveTimeStamp = System.currentTimeMillis(); trash = gcTrash.remove(Math.max(gcRecallTime, maxWaitTime*defaultWaitFactor)); if (trash != null) { if (trash instanceof WeakReferenceGC) { synchronized(monitoredObjects) { monitoredObjects.remove(trash); } /* We avoid counting jython-specific objects in order to * obtain CPython-comparable results. */ if (cyclic.contains(trash) && !((WeakReferenceGC) trash).cls.contains("Java")) { ++stat[0]; if (collectBuffer != null) { collectBuffer.add((WeakReferenceGC) trash); } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "Collected cyclic object: "+trash); } } if (((WeakReferenceGC) trash).hasFinalizer) { ++finalizeWaitCount; if ((gcFlags & VERBOSE_FINALIZE) != 0) { writeDebug("gc", "Collected finalizable object: "+trash); writeDebug("gc", "New finalizeWaitCount: "+finalizeWaitCount); } } } else if (trash == sentRef && (gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "Sentinel collected."); } } else { System.gc(); } } } catch (InterruptedException iex) {} return collectBuffer; } private static void waitForFinalizers() { if (finalizeWaitCount != 0) { if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "waiting for "+finalizeWaitCount+ " pending finalizers."); if (finalizeWaitCount < 0) { /* Maybe even throw exception here? */ Py.writeError("gc", "There should never be "+ "less than zero pending finalizers!"); } } /* It is important to have the while *inside* the synchronized block. * Otherwise the notify might come just between the check and the wait, * causing an endless waiting. */ synchronized(GCSentinel.class) { while (finalizeWaitCount != 0) { try { GCSentinel.class.wait(); } catch (InterruptedException ie2) {} } } if ((gcFlags & VERBOSE_COLLECT) != 0) { writeDebug("gc", "no more finalizers pending."); } } /* Can the following block be skipped if monitor global is active? * No, because there could already be unmonitored finalizable objects! */ while (openFinalizeCount > 0 || System.currentTimeMillis() - postFinalizationTimestamp < postFinalizationTimeOut) { try { Thread.sleep(postFinalizationTimeOut); } catch (InterruptedException ie) {} } } /** * Not supported by Jython. * Throws {@link org.python.core.Py#NotImplementedError}. * * @throws org.python.core.Py.NotImplementedError */ public static PyObject get_count() { throw Py.NotImplementedError("not applicable to Java GC"); } /** * Copied from CPython-doc:
*
* Set the garbage collection debugging flags. Debugging information is * written to {@code System.err}.
*
* {@flags} flags is an {@code int}eger and can have the following bits turned on:
*
* {@link #DEBUG_STATS} - Print statistics during collection.
* {@link #DEBUG_COLLECTABLE} - Print collectable objects found.
* {@link #DEBUG_UNCOLLECTABLE} - Print unreachable but uncollectable objects found.
* {@link #DEBUG_INSTANCES} - Print instance objects.
* {@link #DEBUG_OBJECTS} - Print objects other than instances.
* {@link #DEBUG_SAVEALL} - Save objects to gc.garbage rather than freeing them.
* {@link #DEBUG_LEAK} - Debug leaking programs (everything but STATS). * * @see #DEBUG_STATS * @see #DEBUG_COLLECTABLE * @see #DEBUG_UNCOLLECTABLE * @see #DEBUG_INSTANCES * @see #DEBUG_OBJECTS * @see #DEBUG_SAVEALL * @see #DEBUG_LEAK */ public static void set_debug(int flags) { debugFlags = flags; } /** * Copied from CPython-doc:
*
* Get the garbage collection debugging flags. */ public static int get_debug() { return debugFlags; } /** * Not supported by Jython. * Throws {@link org.python.core.Py#NotImplementedError}. * * @throws org.python.core.Py.NotImplementedError */ public static void set_threshold(PyObject[] args, String[] kwargs) { throw Py.NotImplementedError("not applicable to Java GC"); } /** * Not supported by Jython. * Throws {@link org.python.core.Py#NotImplementedError}. * * @throws org.python.core.Py.NotImplementedError */ public static PyObject get_threshold() { throw Py.NotImplementedError("not applicable to Java GC"); } /** * Not supported by Jython. * Throws {@link org.python.core.Py#NotImplementedError}. * * @throws org.python.core.Py.NotImplementedError */ public static PyObject get_objects() { throw Py.NotImplementedError("not applicable to Java GC"); } /** * Only works reliably if {@code monitorGlobal} is active, as it depends on * monitored objects to search for referrers. It only finds referrers that * properly implement the traverseproc mechanism (unless reflection-based * traversion is activated and works stable). * Further note that the resulting list will contain referrers in no specific * order and may even include duplicates. */ public static PyObject get_referrers(PyObject[] args, String[] kwargs) { if (!isMonitoring()) { throw Py.NotImplementedError( "not applicable in Jython if gc module is not monitoring PyObjects"); } if (args == null) { return Py.None; } PyObject result = new PyList(); PyObject[] coll = {null, result}; PyObject src; synchronized(monitoredObjects) { for (PyObject ob: args) { for (WeakReferenceGC src0: monitoredObjects) { src = (PyObject) src0.get(); /* Sentinels should not be in monitoredObjects */ if (src instanceof Traverseproc) { try { if (((Traverseproc) src).refersDirectlyTo(ob)) { result.__add__(src); } } catch (UnsupportedOperationException uoe) { coll[0] = ob; traverse(ob, ReferrerFinder.defaultInstance, coll); } } else if (isTraversable(src)) { coll[0] = ob; traverse(ob, ReferrerFinder.defaultInstance, coll); } } } } return result; } /** * Only works reliably if all objects in args properly * implement the Traverseproc mechanism (unless reflection-based traversion * is activated and works stable). * Further note that the resulting list will contain referents in no * specific order and may even include duplicates. */ public static PyObject get_referents(PyObject[] args, String[] kwargs) { if (args == null) { return Py.None; } PyObject result = new PyList(); for (PyObject ob: args) { traverse(ob, ReferentsFinder.defaultInstance, result); } return result; } /** * {@code is_tracked} is - in Jython case - interpreted in the sense that * {@code gc.collect} will be able to count the object as collected if it * participates in a cycle. This mimics CPython behavior and passes * the corresponding unit test in {@code test_gc.py}. */ public static PyObject is_tracked(PyObject[] args, String[] kwargs) { if (isTraversable(args[0]) && (monitoredObjects == null || isMonitored(args[0]))) { return Py.True; } else { return Py.False; } } /** * Returns all objects from {@code pool} that are part of reference cycles as a new set. * If a reference-cycle is not entirely contained in {@code pool}, it will be entirely * contained in the resulting set, i.e. missing participants will be added. * This method completely operates on weak references to ensure that the returned * set does not manipulate gc-behavior. * * Note that this method is not thread-safe. Within the gc-module it is only used * by the collect-method which ensures thread-safety by a synchronized block. */ private static IdentityHashMap removeNonCyclicWeakRefs(Iterable pool) { @SuppressWarnings("unchecked") IdentityHashMap[] pools = new IdentityHashMap[2]; pools[0] = new IdentityHashMap(); pools[1] = new IdentityHashMap(); PyObject referent; if (monitorNonTraversable) { /* this means there might be non-traversable objects in * the pool we must filter out now */ for (WeakReferenceGC ref: pool) { referent = ref.get(); if (referent != null && isTraversable(referent)) { pools[0].put(referent, ref); } } } else { /* this means the pool is already entirely traversable */ for (WeakReferenceGC ref: pool) { referent = ref.get(); if (referent != null) pools[0].put(referent, ref); } } IdentityHashMap tmp; IdentityHashMap toProcess = new IdentityHashMap<>(); /* We complete pools[0] with all reachable objects. */ for (WeakReferenceGC ref: pools[0].values()) { traverse((PyObject) ref.get(), ReachableFinderWeakRefs.defaultInstance, pools); } while (!pools[1].isEmpty()) { tmp = pools[1]; pools[1] = toProcess; toProcess = tmp; pools[0].putAll(toProcess); for (WeakReferenceGC ref: toProcess.values()) { traverse((PyObject) ref.get(), ReachableFinderWeakRefs.defaultInstance, pools); } toProcess.clear(); } /* pools[0] should now be a closed set in the sense that it contains all PyObjects * reachable from pools[0]. Now we are going to remove non-cyclic objects: */ boolean done = false; while (!done) { done = true; /* After this loop pools[1] contains all objects from pools[0] * that some object in pools[0] points to. * toRemove will contain all objects from pools[0] that don't * point to any object in pools[0]. Removing toRemove from * pools[1] and repeating this procedure until nothing changes * any more will let only cyclic trash remain. */ for (WeakReferenceGC ref: pools[0].values()) { RefInListFinder.defaultInstance.found = false; referent = ref.get(); traverse(referent , RefInListFinder.defaultInstance, pools); if (!RefInListFinder.defaultInstance.found) { toProcess.put(referent, ref); done = false; } } for (PyObject ref: toProcess.keySet()) { pools[1].remove(ref); } toProcess.clear(); done = done && pools[0].size() == pools[1].size(); tmp = pools[0]; tmp.clear(); pools[0] = pools[1]; pools[1] = tmp; } return pools[0]; } /** * Computes the set of objects reachable from {@code pool}, not necessarily * including {@code pool} itself; only those objects from {@code pool} that are * reachable from at least one other object in {@code pool} will be included * in the result. */ private static Set findReachables(Iterable pool) { @SuppressWarnings("unchecked") IdentityHashMap[] pools = new IdentityHashMap[2]; pools[0] = new IdentityHashMap(); pools[1] = new IdentityHashMap(); IdentityHashMap tmp; IdentityHashMap toProcess = new IdentityHashMap<>(); /* We complete pools[0] with all reachable objects. * Note the difference to the implementation in removeNonCyclic. * There pools[0] was initialized with the contents of pool and * then used here as iteration source. In contrast to that we don't * want to have pool contained in the reachable set in any case here. */ for (PyObject obj: pool) { if (isTraversable(obj)) { traverse(obj, ReachableFinder.defaultInstance, pools); } } while (!pools[1].isEmpty()) { tmp = pools[1]; pools[1] = toProcess; toProcess = tmp; pools[0].putAll(toProcess); for (PyObject obj: toProcess.keySet()) { traverse(obj, ReachableFinder.defaultInstance, pools); } toProcess.clear(); } /* pools[0] should now be a closed set in the sense that it contains * all PyObjects reachable from pools[0]. */ return pools[0].keySet(); } /** * Returns all objects from {@code pool} that are part of reference-cycles as a new set. * If a reference-cycle is not entirely contained in {@code pool}, it will be entirely * contained in the resulting set, i.e. missing participants will be added. * This method completely operates on weak references to ensure that the returned * set does not manipulate gc-behavior. * * Note that this method is not thread-safe. Within the gc-module it is only used * by the collect-method which ensures thread-safety by a synchronized block. */ private static Set removeNonCyclic(Iterable pool) { @SuppressWarnings("unchecked") IdentityHashMap[] pools = new IdentityHashMap[2]; pools[0] = new IdentityHashMap(); pools[1] = new IdentityHashMap(); if (monitorNonTraversable) { /* this means there might be non-traversable objects in * the pool we must filter out now */ for (PyObject obj: pool) { if (isTraversable(obj)) { pools[0].put(obj, obj); } } } else { /* this means the pool is already entirely traversable */ for (PyObject obj: pool) { pools[0].put(obj, obj); } } IdentityHashMap tmp; IdentityHashMap toProcess = new IdentityHashMap<>(); /* We complete pools[0] with all reachable objects. */ for (PyObject obj: pools[0].keySet()) { traverse(obj, ReachableFinder.defaultInstance, pools); } while (!pools[1].isEmpty()) { tmp = pools[1]; pools[1] = toProcess; toProcess = tmp; pools[0].putAll(toProcess); for (PyObject obj: toProcess.keySet()) { traverse(obj, ReachableFinder.defaultInstance, pools); } toProcess.clear(); } /* pools[0] now is a closed set in the sense that it contains all PyObjects * reachable from pools[0]. Now we are going to remove non-cyclic objects: */ boolean done = false; while (!done) { done = true; /* After this loop pools[1] contains all objects from pools[0] * that some object in pools[0] points to. * toRemove will contain all objects from pools[0] that don't * point to any object in pools[0]. Removing toRemove from * pools[1] and repeating this procedure until nothing changes * any more will let only cyclic trash remain. */ for (PyObject obj: pools[0].keySet()) { ObjectInListFinder.defaultInstance.found = false; traverse(obj, ObjectInListFinder.defaultInstance, pools); if (!ObjectInListFinder.defaultInstance.found) { toProcess.put(obj, obj); done = false; } } for (PyObject obj: toProcess.keySet()) { pools[1].remove(obj); } toProcess.clear(); done = done && pools[0].size() == pools[1].size(); tmp = pools[0]; tmp.clear(); pools[0] = pools[1]; pools[1] = tmp; } return pools[0].keySet(); } /** * Mark all objects that are reachable from start AND can reach start, * thus participate in a cycle with start. */ public static void markCyclicObjects(PyObject start, boolean uncollectable) { Set search = findCyclicObjects(start); if (search == null) { return; } /* Search contains the cyclic objects that participate in a cycle with start, * i.e. which are reachable from start AND can reach start. * Mark these... */ CycleMarkAttr cm; for (PyObject obj: search) { cm = (CycleMarkAttr) JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR); if (cm == null) { cm = new CycleMarkAttr(true, uncollectable); JyAttribute.setAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR, cm); } else { cm.setFlags(true, uncollectable); } } } /** * Return objects that are reachable from start AND can reach start, * thus participate in a cycle with start. * Returns {@code null} if start does not participate in any cycle. */ public static Set findCyclicObjects(PyObject start) { IdentityHashMap map = findCyclicObjectsIntern(start); return map == null ? null : map.keySet(); } private static IdentityHashMap findCyclicObjectsIntern(PyObject start) { if (!isTraversable(start)) { return null; } /* first determine the reachable set: */ @SuppressWarnings("unchecked") IdentityHashMap[] reachSearch = (IdentityHashMap[]) new IdentityHashMap[2]; reachSearch[0] = new IdentityHashMap(); reachSearch[1] = new IdentityHashMap(); IdentityHashMap tmp, search = new IdentityHashMap(); traverse(start, ReachableFinder.defaultInstance, reachSearch); tmp = search; search = reachSearch[1]; tmp.clear(); reachSearch[1] = tmp; while (!search.isEmpty()) { reachSearch[0].putAll(search); for (PyObject obj: search.keySet()) { traverse(obj, ReachableFinder.defaultInstance, reachSearch); } tmp = search; search = reachSearch[1]; tmp.clear(); reachSearch[1] = tmp; } /* reachSearch[0] is now the reachable set, but still contains non-cyclic objects */ if (!reachSearch[0].containsKey(start)) { return null; } search.clear(); search.put(start, start); boolean changed = true; while (changed) { changed = false; for (PyObject obj: reachSearch[0].keySet()) { if (traverse(obj, RefersToSetFinder.defaultInstance, search.keySet()) == 1) { changed = true; tmp.put(obj, obj); } } /* move all objects that can reach start from reachSearch[0] to search */ search.putAll(tmp); for (PyObject key: tmp.keySet()) { reachSearch[0].remove(key); } tmp.clear(); } return search; } /** * Does its best to traverse the given {@link org.python.core.PyObject} * {@code ob}. It exploits both * {@link org.python.core.Traverseproc#traverse(Visitproc, Object)} and * {@link org.python.core.TraverseprocDerived#traverseDerived(Visitproc, Object)}. * If {@code ob} neither implements {@link org.python.core.Traverseproc} nor * {@link org.python.core.Traverseproc} and is not annotated with * {@link org.python.core.Untraversable}, reflection-based traversion via * {@link #traverseByReflection(Object, Visitproc, Object)} may be attempted * according to {@link #DONT_TRAVERSE_BY_REFLECTION}. * * @see org.python.core.Traverseproc#traverse(Visitproc, Object) * @see org.python.core.TraverseprocDerived#traverseDerived(Visitproc, Object) * @see #DONT_TRAVERSE_BY_REFLECTION * @see org.python.core.Untraversable * @see #traverseByReflection(Object, Visitproc, Object) */ public static int traverse(PyObject ob, Visitproc visit, Object arg) { int retVal; boolean traversed = false; if (ob instanceof Traverseproc) { retVal = ((Traverseproc) ob).traverse(visit, arg); traversed = true; if (retVal != 0) return retVal; } if (ob instanceof TraverseprocDerived) { retVal = ((TraverseprocDerived) ob).traverseDerived(visit, arg); traversed = true; if (retVal != 0) return retVal; } boolean justAddedWarning = false; if ((gcFlags & SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING) == 0) { if (! (ob instanceof Traverseproc || ob instanceof TraverseprocDerived || ob.getClass() == PyObject.class || ob.getClass().isAnnotationPresent(Untraversable.class)) ) { if (((gcFlags & INSTANCE_TRAVERSE_BY_REFLECTION_WARNING) != 0) || reflectionWarnedClasses == null || !reflectionWarnedClasses.contains(ob.getClass())) { if ((gcFlags & INSTANCE_TRAVERSE_BY_REFLECTION_WARNING) == 0) { if (reflectionWarnedClasses == null) { reflectionWarnedClasses = new HashSet<>(); } reflectionWarnedClasses.add(ob.getClass()); justAddedWarning = true; } Py.writeWarning("gc", "The PyObject-subclass "+ob.getClass().getName()+"\n" + "should either implement Traverseproc or be marked with the\n" + "@Untraversable annotation. See instructions in\n" + "javadoc of org.python.core.Traverseproc.java."); } } } if ((gcFlags & DONT_TRAVERSE_BY_REFLECTION) != 0) { return 0; } Class cls = ob.getClass(); if (traversed || cls == PyObject.class || cls.isAnnotationPresent(Untraversable.class)) { return 0; } if ((gcFlags & SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING) == 0) { if (((gcFlags & INSTANCE_TRAVERSE_BY_REFLECTION_WARNING) != 0) || justAddedWarning || reflectionWarnedClasses == null || !reflectionWarnedClasses.contains(ob.getClass())) { if ((gcFlags & INSTANCE_TRAVERSE_BY_REFLECTION_WARNING) == 0 && !justAddedWarning) { if (reflectionWarnedClasses == null) { reflectionWarnedClasses = new HashSet<>(); } reflectionWarnedClasses.add(ob.getClass()); } Py.writeWarning("gc", "Traverse by reflection: "+ob.getClass().getName()+"\n" + "This is an inefficient procedure. It is recommended to\n" + "implement the traverseproc mechanism properly."); } } return traverseByReflection(ob, visit, arg); } /** *

* This method recursively traverses fields of {@code ob}. * If a field is a PyObject, it is passed to {@code visit}. * and recursion ends in that branch. * If a field is an array, the elements are checked whether * they are PyObjects. {@code PyObject}-elements are passed to * {@code visit}. Elements that are arrays themselves are again * processed elementwise and so on. *

*

* Through the whole search this method fails fast if * {@code visit} returns non-zero. *

*

* Note that we intentionally don't traverse iterables by * iterating them. Since we perform recursion, this should reach * all contained references anyway - in Java every object * can only contain references as fields or arrays. * On the one hand, exploiting iterables would ease the * access to private fields, but on the other hand during * iteration they might change inner state, create * new (Py)Objects or obtain objects from native methods. * Additionally we might run into concurrent modification issues. * So all in all the traversal is cleaner and safer if just * fields and arrays are traversed. *

*/ public static int traverseByReflection(Object ob, Visitproc visit, Object arg) { IdentityHashMap alreadyTraversed = new IdentityHashMap<>(); alreadyTraversed.put(ob, ob); return traverseByReflectionIntern(ob, alreadyTraversed, visit, arg); } private static int traverseByReflectionIntern(Object ob, IdentityHashMap alreadyTraversed, Visitproc visit, Object arg) { Class cls = ob.getClass(); int result = 0; Object element; if (cls.isArray() && canLinkToPyObject(cls.getComponentType(), false)) { for (int i = 0; i < Array.getLength(ob); ++i) { element = Array.get(ob, i); if (element != null) { if (element instanceof PyObject) { result = visit.visit((PyObject) element, arg); } else if (!alreadyTraversed.containsKey(element)) { alreadyTraversed.put(element, element); result = traverseByReflectionIntern(element, alreadyTraversed, visit, arg); } if (result != 0) { return result; } } } } else { while (cls != Object.class && cls != PyObject.class) { Field[] declFields = cls.getDeclaredFields(); for (int i = 0; i < declFields.length; ++i) { if (!Modifier.isStatic(declFields[i].getModifiers()) && !declFields[i].getType().isPrimitive()) { if (!declFields[i].isAccessible()) { declFields[i].setAccessible(true); } if (canLinkToPyObject(declFields[i].getType(), false)) { try { element = declFields[i].get(ob); if (element != null) { if (element instanceof PyObject) { result = visit.visit((PyObject) element, arg); } else if (!alreadyTraversed.containsKey(element)) { alreadyTraversed.put(element, element); result = traverseByReflectionIntern(element, alreadyTraversed, visit, arg); } if (result != 0) { return result; } } } catch (Exception e) {} } } } cls = cls.getSuperclass(); } } return 0; } /** *

* This method checks via type-checking-only, whether an object * of the given class can in principle hold a ref to a {@code PyObject}. * Especially if arrays are involved, this can safe a lot performance. * For now, no generic type-info is exploited. *

*

* If {@code actual} is true, the answer will hold for an object * that is an instance of the given class. * Otherwise it is assumed that cls is the type of a field holding an * object, so cls is considered as upper bound for an objects actual * type. *

*

* One should call with {@code actual == true}, if cls was obtained * by {@code ob.getClass()} and with {@code actual == false}, if cls * was obtained as a field-type or component-type of an array. *

*/ public static boolean canLinkToPyObject(Class cls, boolean actual) { /* At first some quick (fail-fast/succeed-fast)-checks: */ if (quickCheckCannotLinkToPyObject(cls)) { return false; } if (!actual && (!Modifier.isFinal(cls.getModifiers()))) { return true; /* a subclass could contain anything */ } if (quickCheckCanLinkToPyObject(cls)) { return true; } if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers())) { return true; } if (cls.isArray()) { return canLinkToPyObject(cls.getComponentType(), false); } Class cls2 = cls; /* Fail fast if no fields exist in cls: */ int fieldCount = cls2.getDeclaredFields().length; while (fieldCount == 0 && cls2 != Object.class) { cls2 = cls2.getSuperclass(); fieldCount += cls.getDeclaredFields().length; } if (fieldCount == 0) { return false; } IdentityHashMap, Class> alreadyChecked = new IdentityHashMap<>(); alreadyChecked.put(cls, cls); cls2 = cls; Class ft; while (cls2 != Object.class) { for (Field f: cls2.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { ft = f.getType(); if (!ft.isPrimitive() && !alreadyChecked.containsKey(ft)) { alreadyChecked.put(ft, ft); if (canLinkToPyObjectIntern(ft, alreadyChecked)) { return true; } } } } cls2 = cls2.getSuperclass(); } return false; } private static boolean quickCheckCanLinkToPyObject(Class cls) { if (!Modifier.isFinal(cls.getModifiers())) { return true; } if (cls.isAssignableFrom(PyObject.class)) { return true; } if (PyObject.class.isAssignableFrom(cls)) { return true; } if (cls.isArray()) { return quickCheckCanLinkToPyObject(cls.getComponentType()); } return false; } private static boolean quickCheckCannotLinkToPyObject(Class cls) { if (cls.isPrimitive()) { return true; } if (cls == String.class || cls == Class.class || cls == Field.class || cls == java.lang.reflect.Method.class) { return true; } if (cls.isArray()) { return quickCheckCannotLinkToPyObject(cls.getComponentType()); } return false; } private static boolean canLinkToPyObjectIntern(Class cls, IdentityHashMap, Class> alreadyChecked) { if (quickCheckCanLinkToPyObject(cls)) { return true; } if (quickCheckCannotLinkToPyObject(cls)) { return false; } if (cls.isArray()) { return canLinkToPyObjectIntern(cls.getComponentType(), alreadyChecked); } Class cls2 = cls; Class ft; while (cls2 != Object.class) { for (Field f: cls2.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { ft = f.getType(); if (!ft.isPrimitive() && !alreadyChecked.containsKey(ft)) { alreadyChecked.put(ft, ft); if (canLinkToPyObjectIntern(ft, alreadyChecked)) { return true; } } } } cls2 = cls2.getSuperclass(); } return false; } public static boolean isTraversable(PyObject ob) { if (ob == null) { return false; } if (ob instanceof Traverseproc || ob instanceof TraverseprocDerived) { return true; } if ((gcFlags & DONT_TRAVERSE_BY_REFLECTION) != 0) { return false; } Class cls = ob.getClass(); return !(cls == PyObject.class || cls.isAnnotationPresent(Untraversable.class)); } //--------------Visitproc section---------------------------------------------- private static class ReferentsFinder implements Visitproc { public static ReferentsFinder defaultInstance = new ReferentsFinder(); /** * Expects arg to be a list-like {@code PyObject} where the * referents will be inserted. */ public int visit(PyObject object, Object arg) { ((org.python.core.PySequenceList) arg).pyadd(object); return 0; } } /** * Helper to find the reachable set of an object. {@code arg} must be a * 2-component array of type {@code IdentityHashMap[]}. * Although these are maps, the components of {@code arg} are conceptually * used as sets. {@code arg[0]} shall contain all objects already known to * be reachable. The visitproc adds all newly discovered objects to * {@code arg[1]}, so the user can later add these to the reachable set and * knows they need to be explored further. Only traversable objects are * considered by this visitproc. */ private static class ReachableFinder implements Visitproc { public static ReachableFinder defaultInstance = new ReachableFinder(); /** * Expects arg to be a list-like {@code PyObject} where the * referents will be inserted. */ @SuppressWarnings("unchecked") public int visit(PyObject object, Object arg) { IdentityHashMap[] reachSearch = (IdentityHashMap[]) arg; if ((isTraversable(object)) && !reachSearch[0].containsKey(object)) { reachSearch[1].put(object, object); } return 0; } } private static class ReachableFinderWeakRefs implements Visitproc { public static ReachableFinderWeakRefs defaultInstance = new ReachableFinderWeakRefs(); @SuppressWarnings("unchecked") public int visit(PyObject object, Object arg) { if (isTraversable(object)) { IdentityHashMap[] pools = (IdentityHashMap[]) arg; WeakReferenceGC ref = pools[0].get(object); if (ref == null) { ref = new WeakReferenceGC(object); pools[1].put(object, ref); } } return 0; } } private static class ReferrerFinder implements Visitproc { public static ReferrerFinder defaultInstance = new ReferrerFinder(); /** * Expects {@code arg} to be a 2-component array ({@code PyObject[]}) * consisting of the {@code PyObject} to be referred to at * {@code arg[0]} and the destination list (a list-like {@code PyObject} * where the referrers will be inserted) at {@code arg[1]}. */ public int visit(PyObject object, Object arg) { if (((PyObject[]) arg)[0].__eq__(object).__nonzero__()) { ((org.python.core.PySequenceList) ((PyObject[]) arg)[1]).pyadd(object); } return 0; } } /** * Like {@code RefInListFinder} this visitproc looks whether the traversed object * refers to one of the objects in a given set. Here we perform fail-fast * behavior. This method is useful if one is not interested in the referrers, * but only wants to know (quickly) whether a connection exists or not. */ private static class RefersToSetFinder implements Visitproc { public static RefersToSetFinder defaultInstance = new RefersToSetFinder(); @SuppressWarnings("unchecked") public int visit(PyObject object, Object arg) { return ((Set) arg).contains(object) ? 1 : 0; } } /** * This visitproc looks whether an object refers to one of the objects in * a given set.
* {@code arg} must be a 2-component-array of * {@code HashMap}. * These maps are actually used as sets, but resolve the strongref/weakref * views to the objects.
* {@code arg[0]} is the pool we search referrers for. When the traverse method * iterates through the referents of a source object, this visitproc checks * for each referent, whether it is in {@code arg[0]}. If it is, then it is added * to {@code arg[1]} (no double entries here since it is a set-like structure) * and {@code found} is set to {@code true}.
* By repeated use one can collect all objects referring to a given set * of objects in another set. */ private static class RefInListFinder implements Visitproc { public static RefInListFinder defaultInstance = new RefInListFinder(); public boolean found = false; /** * Expects {@code arg} to be a 2-component array of * {@link java.util.Map}s. */ public int visit(PyObject object, Object arg) { @SuppressWarnings("unchecked") IdentityHashMap[] pools = (IdentityHashMap[]) arg; WeakReferenceGC ref = pools[0].get(object); if (ref != null) { pools[1].put(object, ref); found = true; } return 0; } } private static class ObjectInListFinder implements Visitproc { public static ObjectInListFinder defaultInstance = new ObjectInListFinder(); public boolean found = false; /** * Expects {@code arg} to be a 2-component array of * {@link java.util.Map}s. */ public int visit(PyObject object, Object arg) { @SuppressWarnings("unchecked") IdentityHashMap[] pools = (IdentityHashMap[]) arg; if (pools[0].containsKey(object)) { pools[1].put(object, object); found = true; } return 0; } } //--------------end of Visitproc section--------------------------------------- }