3131
3232package org .scijava .plugins .scripting .jython ;
3333
34+ import java .lang .ref .PhantomReference ;
35+ import java .lang .ref .ReferenceQueue ;
36+ import java .util .ArrayList ;
37+ import java .util .LinkedList ;
38+ import java .util .List ;
39+
3440import javax .script .ScriptEngine ;
3541
3642import org .python .core .PyNone ;
3743import org .python .core .PyObject ;
3844import org .python .core .PyString ;
45+ import org .python .util .PythonInterpreter ;
46+ import org .scijava .plugin .Parameter ;
3947import org .scijava .plugin .Plugin ;
4048import org .scijava .script .AdaptedScriptLanguage ;
4149import org .scijava .script .ScriptLanguage ;
50+ import org .scijava .thread .ThreadService ;
4251
4352/**
4453 * An adapter of the Jython interpreter to the SciJava scripting interface.
4958@ Plugin (type = ScriptLanguage .class , name = "Python" )
5059public class JythonScriptLanguage extends AdaptedScriptLanguage {
5160
61+ private final LinkedList <JythonEnginePhantomReference > phantomReferences =
62+ new LinkedList <JythonEnginePhantomReference >();
63+ private final ReferenceQueue <JythonScriptEngine > queue =
64+ new ReferenceQueue <JythonScriptEngine >();
65+
66+ @ Parameter
67+ private ThreadService threadService ;
68+
5269 public JythonScriptLanguage () {
5370 super ("jython" );
5471 }
5572
5673 @ Override
5774 public ScriptEngine getScriptEngine () {
5875 // TODO: Consider adapting the wrapped ScriptEngineFactory's ScriptEngine.
59- return new JythonScriptEngine ();
76+ final JythonScriptEngine engine = new JythonScriptEngine ();
77+
78+ synchronized (phantomReferences ) {
79+ phantomReferences .add (new JythonEnginePhantomReference (engine , queue ));
80+
81+ // If we added references to an empty queue, we need to start
82+ // a new polling thread
83+ if (phantomReferences .size () == 1 ) {
84+ threadService .run (new Runnable () {
85+
86+ @ Override
87+ public void run () {
88+ boolean done = false ;
89+
90+ // poll the queue
91+ while (!done ) {
92+ try {
93+ Thread .sleep (100 );
94+
95+ synchronized (phantomReferences ) {
96+ JythonEnginePhantomReference ref =
97+ (JythonEnginePhantomReference ) queue .poll ();
98+
99+ // if we have a ref, clean it up
100+ if (ref != null ) {
101+ ref .cleanup ();
102+ phantomReferences .remove (ref );
103+ }
104+
105+ // Once we're done with our known phantom refs
106+ // we can shut down this thread.
107+ done = phantomReferences .size () == 0 ;
108+ }
109+
110+ }
111+ catch (final Exception ex ) {
112+ // log exception, continue
113+ }
114+ }
115+ }
116+ });
117+
118+ }
119+ }
120+ return engine ;
60121 }
61122
62123 @ Override
@@ -74,4 +135,35 @@ public Object decode(final Object object) {
74135 return object ;
75136 }
76137
138+ private static class JythonEnginePhantomReference extends
139+ PhantomReference <JythonScriptEngine >
140+ {
141+
142+ public PythonInterpreter interpreter ;
143+
144+ public JythonEnginePhantomReference (JythonScriptEngine engine ,
145+ ReferenceQueue <JythonScriptEngine > queue )
146+ {
147+ super (engine , queue );
148+ interpreter = engine .interpreter ;
149+ }
150+
151+ public void cleanup () {
152+ final List <String > scriptLocals = new ArrayList <String >();
153+ PythonInterpreter interp = interpreter ;
154+ if (interp == null ) return ;
155+
156+ final PyObject locals = interp .getLocals ();
157+ for (final PyObject item : locals .__iter__ ().asIterable ()) {
158+ final String localVar = item .toString ();
159+ if (!localVar .contains ("__name__" ) && !localVar .contains ("__doc__" )) {
160+
161+ scriptLocals .add (item .toString ());
162+ }
163+ }
164+ for (final String string : scriptLocals ) {
165+ interp .set (string , null );
166+ }
167+ }
168+ }
77169}
0 commit comments