5353 * An adapter of the Jython interpreter to the SciJava scripting interface.
5454 *
5555 * @author Johannes Schindelin
56+ * @author Mark Hiner <[email protected] > 5657 * @see ScriptEngine
5758 */
5859@ Plugin (type = ScriptLanguage .class , name = "Python" )
5960public class JythonScriptLanguage extends AdaptedScriptLanguage {
6061
62+ // List of PhantomReferences and corresponding ReferenceQueue to
63+ // facilitate proper PhantomReference use.
64+ // See http://resources.ej-technologies.com/jprofiler/help/doc/index.html
65+
6166 private final LinkedList <JythonEnginePhantomReference > phantomReferences =
6267 new LinkedList <JythonEnginePhantomReference >();
6368 private final ReferenceQueue <JythonScriptEngine > queue =
@@ -76,10 +81,22 @@ public ScriptEngine getScriptEngine() {
7681 final JythonScriptEngine engine = new JythonScriptEngine ();
7782
7883 synchronized (phantomReferences ) {
84+ // NB: This phantom reference is used to clean up any local variables
85+ // created by evaluation of scripts via this ScriptEngine. We need
86+ // to use PhantomReferences because the "scope" of a script extends
87+ // beyond its eval method - a consumer may still want to inspect
88+ // the state of variables after evaluation.
89+ // By using PhantomReferences we are saying that the scope of
90+ // script evaluation equals the lifetime of the ScriptEngine.
7991 phantomReferences .add (new JythonEnginePhantomReference (engine , queue ));
8092
81- // If we added references to an empty queue, we need to start
82- // a new polling thread
93+ // NB: The use of PhantomReferences requires a paired polling thread.
94+ // We poll instead of blocking for input to avoid leaving lingering
95+ // threads that would need to be interrupted - instead only starting
96+ // threads running when there is actually a PhantomReference that
97+ // will eventually be enqueued.
98+ // Here we check if there is already a polling thread in operation -
99+ // if not, we start a new thread.
83100 if (phantomReferences .size () == 1 ) {
84101 threadService .run (new Runnable () {
85102
@@ -93,6 +110,7 @@ public void run() {
93110 Thread .sleep (100 );
94111
95112 synchronized (phantomReferences ) {
113+ // poll the queue
96114 JythonEnginePhantomReference ref =
97115 (JythonEnginePhantomReference ) queue .poll ();
98116
@@ -103,7 +121,7 @@ public void run() {
103121 }
104122
105123 // Once we're done with our known phantom refs
106- // we can shut down this thread.
124+ // we can let this thread shut down
107125 done = phantomReferences .size () == 0 ;
108126 }
109127
@@ -135,6 +153,10 @@ public Object decode(final Object object) {
135153 return object ;
136154 }
137155
156+ /**
157+ * Helper class to clean up {@link PythonInterpreter} local variables when a
158+ * parent {@link JythonScriptEngine} leaves scope.
159+ */
138160 private static class JythonEnginePhantomReference extends
139161 PhantomReference <JythonScriptEngine >
140162 {
@@ -153,14 +175,27 @@ public void cleanup() {
153175 PythonInterpreter interp = interpreter ;
154176 if (interp == null ) return ;
155177
178+ // NB: This method for cleaning up local variables was taken from:
179+ // http://python.6.x6.nabble.com/Cleaning-up-PythonInterpreter-object-td1777184.html
180+ // Because Python is an interpreted language, when a Python script creates new
181+ // variables they stick around in a static org.python.core.PySystemState variable
182+ // (defaultSystemState) the org.python.core.Py class.
183+ // Thus an implicit static state is created by script evaluation, so we must manually
184+ // clean up local variables known to the interpreter when the scope of an executed
185+ // script is over.
186+ // See original bug report for the memory leak that prompted this solution:
187+ // http://fiji.sc/bugzilla/show_bug.cgi?id=1203
156188 final PyObject locals = interp .getLocals ();
157189 for (final PyObject item : locals .__iter__ ().asIterable ()) {
158190 final String localVar = item .toString ();
191+ // Ignore __name__ and __doc__ variables, which are special and known not
192+ // to be local variables created by evaluation.
159193 if (!localVar .contains ("__name__" ) && !localVar .contains ("__doc__" )) {
160-
194+ // Build list of variables to clean
161195 scriptLocals .add (item .toString ());
162196 }
163197 }
198+ // Null out local variables
164199 for (final String string : scriptLocals ) {
165200 interp .set (string , null );
166201 }
0 commit comments