Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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.
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;
//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);
/**
*
* 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<<4);
/**
*
* 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<<5);
/**
* 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<<6);
/**
* 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<<7);
/**
* Enables collection-related verbose-output.
*
* @see #setJythonGCFlags(short)
* @see #getJythonGCFlags()
* @see #addJythonGCFlags(short)
* @see #removeJythonGCFlags(short)
*/
public static final short VERBOSE_COLLECT = (1<<8);
/**
* Enables weakref-related verbose-output.
*
* @see #setJythonGCFlags(short)
* @see #getJythonGCFlags()
* @see #addJythonGCFlags(short)
* @see #removeJythonGCFlags(short)
*/
public static final short VERBOSE_WEAKREF = (1<<9);
/**
* Enables delayed finalization related verbose-output.
*
* @see #setJythonGCFlags(short)
* @see #getJythonGCFlags()
* @see #addJythonGCFlags(short)
* @see #removeJythonGCFlags(short)
*/
public static final short VERBOSE_DELAYED = (1<<10);
/**
* Enables finalization-related verbose-output.
*
* @see #setJythonGCFlags(short)
* @see #getJythonGCFlags()
* @see #addJythonGCFlags(short)
* @see #removeJythonGCFlags(short)
*/
public static final short VERBOSE_FINALIZE = (1<<11);
/**
* 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, resurrectionCritics;
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_CRITICS = 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;
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) {
if (ob instanceof WeakReferenceGC) {
return ((WeakReferenceGC) ob).get().equals(get())
&& ((WeakReferenceGC) ob).hashCode() == hashCode();
} else if (ob instanceof WeakrefGCCompareDummy) {
return ((WeakrefGCCompareDummy) ob).compare != null
&& ((WeakrefGCCompareDummy) ob).compare.equals(get());
} else {
return false;
}
}
}
private static class WeakrefGCCompareDummy {
public static WeakrefGCCompareDummy defaultInstance =
new WeakrefGCCompareDummy();
PyObject compare;
int hashCode;
public void setCompare(PyObject compare) {
this.compare = compare;
hashCode = System.identityHashCode(compare);
}
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-----------------------------------
private static class DelayedFinalizationProcess implements Runnable {
static DelayedFinalizationProcess defaultInstance =
new DelayedFinalizationProcess();
private 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");
}
}
private void restoreFinalizer(PyObject obj, boolean cyclic) {
FinalizeTrigger ft =
(FinalizeTrigger) JyAttribute.getAttr(obj, JyAttribute.FINALIZE_TRIGGER_ATTR);
FinalizeTrigger.ensureFinalizer(obj);
boolean notify = false;
if (ft != null) {
((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+"; cyclic? "+cyclic);
}
CycleMarkAttr cm = (CycleMarkAttr)
JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR);
if (cm != null && cm.monitored) {
monitorObject(obj, true);
}
if (notify) {
if ((gcFlags & VERBOSE_DELAYED) != 0 || (gcFlags & VERBOSE_FINALIZE) != 0) {
writeDebug("gc", "notify finalizer abort.");
}
notifyAbortFinalize(obj, cyclic);
}
}
public void run() {
if ((gcFlags & VERBOSE_DELAYED) != 0) {
writeDebug("gc", "run delayed finalization. Index: "+
gcMonitoredRunCount);
}
Set critics = resurrectionCritics.keySet();
Set cyclicCritics = removeNonCyclic(critics);
cyclicCritics.retainAll(critics);
critics.removeAll(cyclicCritics);
Set criticReachablePool = findReachables(critics);
//to avoid concurrent modification:
ArrayList criticReachables = new ArrayList<>();
FinalizeTrigger fn;
if (delayedFinalizationMode == MARK_REACHABLE_CRITICS) {
for (PyObject obj: criticReachablePool) {
fn = (FinalizeTrigger) JyAttribute.getAttr(obj,
JyAttribute.FINALIZE_TRIGGER_ATTR);
if (fn != null && fn.isActive() && fn.isFinalized()) {
criticReachables.add(obj);
JyAttribute.setAttr(obj,
JyAttribute.GC_DELAYED_FINALIZE_CRITIC_MARK_ATTR,
Integer.valueOf(gcMonitoredRunCount));
}
}
} else {
for (PyObject obj: criticReachablePool) {
fn = (FinalizeTrigger) JyAttribute.getAttr(obj,
JyAttribute.FINALIZE_TRIGGER_ATTR);
if (fn != null && fn.isActive() && fn.isFinalized()) {
criticReachables.add(obj);
}
}
}
critics.removeAll(criticReachables);
if ((gcFlags & PRESERVE_WEAKREFS_ON_RESURRECTION) != 0) {
if ((gcFlags & VERBOSE_DELAYED) != 0) {
writeDebug("gc", "restore potentially resurrected weak references...");
}
GlobalRef toRestore;
for (PyObject rst: criticReachablePool) {
toRestore = (GlobalRef)
JyAttribute.getAttr(rst, JyAttribute.WEAK_REF_ATTR);
if (toRestore != null) {
toRestore.restore(rst);
}
}
GlobalRef.processDelayedCallbacks();
}
criticReachablePool.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 "+criticReachables.size()+
" potentially resurrected finalizers...");
}
for (PyObject obj: criticReachables) {
CycleMarkAttr cm = (CycleMarkAttr)
JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR);
if (cm != null && cm.isUncollectable()) {
restoreFinalizer(obj, true);
} else {
gc.markCyclicObjects(obj, true);
cm = (CycleMarkAttr)
JyAttribute.getAttr(obj, JyAttribute.GC_CYCLE_MARK_ATTR);
restoreFinalizer(obj, cm != null && cm.isUncollectable());
}
}
} else {
if ((gcFlags & VERBOSE_DELAYED) != 0) {
writeDebug("gc", "delayed finalization of "+criticReachables.size()+
" potentially resurrected finalizers...");
}
for (PyObject del: criticReachables) {
performFinalization(del);
}
}
cyclicCritics.removeAll(criticReachables);
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 && !cyclicCritics.isEmpty()) {
writeDebug("gc", "process "+cyclicCritics.size()+" cyclic delayed finalizers...");
}
for (PyObject del: cyclicCritics) {
performFinalization(del);
}
if ((gcFlags & VERBOSE_DELAYED) != 0 && !critics.isEmpty()) {
writeDebug("gc", "calling "+critics.size()+
" critic finalizers not reachable by other critic finalizers...");
}
if (delayedFinalizationMode == MARK_REACHABLE_CRITICS &&
!critics.isEmpty() && !criticReachables.isEmpty()) {
// This means some critic-reachables might be not critic-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: critics) {
if (!notifyRerun) {
Object m = JyAttribute.getAttr(del,
JyAttribute.GC_DELAYED_FINALIZE_CRITIC_MARK_ATTR);
if (m != null && ((Integer) m).intValue() == gcMonitoredRunCount) {
notifyRerun = true;
}
}
performFinalization(del);
}
} else {
for (PyObject del: critics) {
performFinalization(del);
}
}
delayedFinalizables.clear();
resurrectionCritics.clear();
if ((gcFlags & VERBOSE_DELAYED) != 0) {
writeDebug("gc", "delayed finalization run done");
}
}
}
public static boolean delayedFinalizationEnabled() {
return (gcFlags & (PRESERVE_WEAKREFS_ON_RESURRECTION |
DONT_FINALIZE_RESURRECTED_OBJECTS)) != 0;
}
private static void updateDelayedFinalizationState() {
if (delayedFinalizationEnabled()) {
resumeDelayedFinalization();
} else if (indexOfPostFinalizationProcess(
DelayedFinalizationProcess.defaultInstance) != -1) {
suspendDelayedFinalization();
}
if ((gcFlags & PRESERVE_WEAKREFS_ON_RESURRECTION) == 0) {
if (GlobalRef.hasDelayedCallbacks()) {
Thread dlcProcess = new Thread() {
public void run() {
GlobalRef.processDelayedCallbacks();
}
};
dlcProcess.start();
}
}
}
private static void resumeDelayedFinalization() {
if (delayedFinalizables == null) {
delayedFinalizables = new IdentityHashMap<>();
}
if (resurrectionCritics == null) {
resurrectionCritics = 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 isResurrectionCritic(PyObject ob) {
return (isTraversable(ob))
&& FinalizeTrigger.hasActiveTrigger(ob);
}
public static void registerForDelayedFinalization(PyObject ob) {
if (isResurrectionCritic(ob)) {
resurrectionCritics.put(ob, ob);
} else {
delayedFinalizables.put(ob, 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() {
++openFinalizeCount;
if (System.currentTimeMillis() - 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.compare = null;
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.compare = null;
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_CRITICS;
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 extends Object> 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 contradictionary 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 extends Object> 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 extends Object> 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 extends Object> 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---------------------------------------
}