Omaha #4259 fix jep unit test threading unsafety
Change-Id: I13f58d8dcf3e3d074c3586d2682ad3fa9809a3fe Former-commit-id: 24922b47c679227489740da2979d10d9f81088bf
This commit is contained in:
parent
c54c7e6173
commit
eabd52c620
5 changed files with 212 additions and 75 deletions
|
@ -1,5 +1,6 @@
|
|||
package jep;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -42,7 +43,7 @@ import jep.python.PyObject;
|
|||
* @author [mrjohnson0 at sourceforge.net] Mike Johnson
|
||||
* @version $Id$
|
||||
*/
|
||||
public final class Jep {
|
||||
public final class Jep implements Closeable {
|
||||
|
||||
private static final String THREAD_WARN = "JEP WARNING: "
|
||||
+ "Unsafe reuse of thread ";
|
||||
|
@ -204,13 +205,16 @@ public final class Jep {
|
|||
ClassEnquirer ce) throws JepException {
|
||||
if (threadUsed.get()) {
|
||||
/*
|
||||
* TODO: Consider throwing an exception for this situation instead
|
||||
* of printing a warning, as it can result in very-hard-to-diagnose
|
||||
* bugs such as GIL-related freezes or misleading error messages.
|
||||
* TODO: Throw a JepException if this is detected. This is
|
||||
* inherently unsafe, the thread state information inside
|
||||
* Python can get screwed up if there's more than one started/open
|
||||
* Jep on the same thread at any given time. This remains a
|
||||
* warning for the time being to provide time for applications
|
||||
* to be updated to avoid this scenario.
|
||||
*/
|
||||
Thread current = Thread.currentThread();
|
||||
String warn = THREAD_WARN + current.getName() + REUSE_WARN;
|
||||
System.err.println(warn);
|
||||
System.err.println(warn);
|
||||
}
|
||||
|
||||
if(cl == null)
|
||||
|
@ -934,6 +938,7 @@ public final class Jep {
|
|||
* Shutdown python interpreter. Make sure you call this.
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if(this.closed)
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package jep;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Run.java - Execute a Python script.
|
||||
|
@ -50,20 +52,28 @@ public class Run {
|
|||
public static int run(boolean eventDispatch) {
|
||||
Jep jep = null;
|
||||
|
||||
|
||||
try {
|
||||
jep = new Jep(false, ".");
|
||||
|
||||
// "set" by eval'ing it
|
||||
jep.eval("import sys; sys.argv = argv = " + scriptArgv);
|
||||
jep.runScript(file);
|
||||
}
|
||||
catch(Throwable t) {
|
||||
|
||||
// "set" by eval'ing it
|
||||
jep.eval("import sys; sys.argv = argv = " + scriptArgv);
|
||||
if (!file.endsWith("jep" + File.separator + "console.py")) {
|
||||
jep.runScript(file);
|
||||
} else {
|
||||
// don't use console's __main__ so we can reuse the interpreter
|
||||
jep.setInteractive(true);
|
||||
jep.set("jepInstance", jep);
|
||||
jep.eval("from jep import console");
|
||||
jep.eval("console.prompt(jepInstance)");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
if(jep != null)
|
||||
if (jep != null)
|
||||
jep.close();
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if(interactive) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import jep.python.*;
|
|||
* @version $Id$
|
||||
*/
|
||||
public class Test implements Runnable {
|
||||
|
||||
private Jep jep = null;
|
||||
private boolean testEval = false;
|
||||
|
||||
|
@ -452,6 +453,45 @@ public class Test implements Runnable {
|
|||
return Thread.currentThread().getClass();
|
||||
}
|
||||
|
||||
public static void testRestrictedClassLoader() throws Throwable {
|
||||
final Throwable[] t = new Throwable[1];
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Jep jep = null;
|
||||
try {
|
||||
jep = new Jep(true, "", restrictedClassLoader);
|
||||
jep.eval("from java.io import File");
|
||||
} catch (Throwable th) {
|
||||
t[0] = th;
|
||||
} finally {
|
||||
if(jep != null) {
|
||||
jep.close();
|
||||
}
|
||||
synchronized (Test.class) {
|
||||
Test.class.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (Test.class) {
|
||||
thread.start();
|
||||
try {
|
||||
Test.class.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if(t[0] == null) {
|
||||
throw new RuntimeException("Did not throw classloader exception!");
|
||||
} else if(!t[0].getMessage().contains("ImportError")) {
|
||||
throw t[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String argv[]) throws Throwable {
|
||||
Jep jep = new Jep();
|
||||
try {
|
||||
|
|
|
@ -3,9 +3,8 @@ package jep;
|
|||
import java.io.File;
|
||||
|
||||
/**
|
||||
* TestNumpy.java. Runs a variety of simple tests to verify numpy interactions
|
||||
* are working correctly. run() is called by setup.py test, or you can call the
|
||||
* java main() method directly for other tests.
|
||||
* TestNumpy.java. Runs a variety of simple tests to verify numpy interactions
|
||||
* are working correctly.
|
||||
*
|
||||
*
|
||||
* Created: Wed Apr 08 2015
|
||||
|
@ -13,38 +12,65 @@ import java.io.File;
|
|||
* @author [ndjensen at gmail.com] Nate Jensen
|
||||
* @version $Id$
|
||||
*/
|
||||
public class TestNumpy implements Runnable {
|
||||
|
||||
protected Jep jep = null;
|
||||
public class TestNumpy {
|
||||
|
||||
// set to a high number to test for memory leaks
|
||||
private static final int REPEAT = 1; // 0000000;
|
||||
|
||||
private static boolean PRINT = false;
|
||||
private static boolean PRINT = true;
|
||||
|
||||
/**
|
||||
* Calls testSetAndGet(Jep) on a separate thread to avoid threading
|
||||
* issues. Waits for those results before returning.
|
||||
* @throws Throwable
|
||||
*/
|
||||
public void testSetAndGet() throws Throwable
|
||||
{
|
||||
final Throwable[] t = new Throwable[1];
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File pwd = new File(".");
|
||||
/*
|
||||
* Anytime you start a new Jep interpreter and import numpy within
|
||||
* it, even if you call close() on the interpreter you will leak
|
||||
* some native memory. Therefore, for now, do NOT new up the
|
||||
* interpreter and close it within the for loop.
|
||||
*/
|
||||
jep = new Jep(false, pwd.getAbsolutePath());
|
||||
for (int i = 0; i < REPEAT; i++) {
|
||||
testSetAndGet();
|
||||
@Override
|
||||
public void run() {
|
||||
Jep jep = null;
|
||||
try {
|
||||
jep = new Jep(true);
|
||||
|
||||
} catch (Throwable th) {
|
||||
t[0] = th;
|
||||
} finally {
|
||||
if(jep != null) {
|
||||
jep.close();
|
||||
}
|
||||
synchronized (TestNumpy.this) {
|
||||
TestNumpy.this.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (TestNumpy.this) {
|
||||
thread.start();
|
||||
try {
|
||||
TestNumpy.this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (JepException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (jep != null)
|
||||
jep.close();
|
||||
}
|
||||
|
||||
if(t[0] != null) {
|
||||
throw t[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testSetAndGet() throws JepException {
|
||||
/**
|
||||
* Sets NDArrays in a Jep interpreter, then gets them and verifies
|
||||
* the conversion in both directions is safe, ie produces a symmetrical
|
||||
* object despite a different reference/instance.
|
||||
* @param jep
|
||||
* @throws JepException
|
||||
*/
|
||||
public void testSetAndGet(Jep jep) throws JepException {
|
||||
int[] dimensions = new int[] { 4 };
|
||||
|
||||
// test boolean[]
|
||||
|
@ -178,6 +204,12 @@ public class TestNumpy implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from python to verify that a Java method's return type of NDArray
|
||||
* can be auto-converted to a numpy ndarray.
|
||||
* @param array
|
||||
* @return a copy of the data + 5
|
||||
*/
|
||||
public NDArray<int[]> testArgAndReturn(NDArray<int[]> array) {
|
||||
int[] data = array.getData();
|
||||
int[] newData = new int[data.length];
|
||||
|
@ -188,29 +220,32 @@ public class TestNumpy implements Runnable {
|
|||
return new NDArray<int[]>(newData, array.getDimensions());
|
||||
}
|
||||
|
||||
// should only be called from main(), not from python unittests
|
||||
public void runPythonSide() {
|
||||
|
||||
/**
|
||||
* Helper method to support running the main() method to run from Java
|
||||
* instead of python
|
||||
* @param jep
|
||||
*/
|
||||
public void runFromJava(Jep jep) {
|
||||
try {
|
||||
File pwd = new File("tests");
|
||||
/*
|
||||
* Anytime you start a new Jep interpreter and import numpy within
|
||||
* it, even if you call close() on the interpreter you will leak
|
||||
* some native memory. Therefore, for now, do NOT new up the
|
||||
* interpreter and close it within the for loop.
|
||||
*/
|
||||
jep = new Jep(true, pwd.getAbsolutePath());
|
||||
jep.eval("import test_numpy");
|
||||
jep.eval("v = test_numpy.TestNumpy('testArgReturn')");
|
||||
jep.eval("v.setUp()");
|
||||
jep.eval("v = test_numpy.TestNumpy('testArgReturn')");
|
||||
jep.eval("v.setUp()");
|
||||
for (int i = 0; i < REPEAT; i++) {
|
||||
this.testSetAndGet(jep);
|
||||
}
|
||||
for (int i = 0; i < REPEAT; i++) {
|
||||
jep.eval("v.testArgReturn()");
|
||||
}
|
||||
System.out.println("return NDArray from Java checked out ok");
|
||||
for (int i = 0; i < REPEAT; i++) {
|
||||
jep.eval("v.testMultiDimensional()");
|
||||
}
|
||||
System.out.println("multi dimensional arrays checked out ok");
|
||||
for (int i = 0; i < REPEAT; i++) {
|
||||
jep.eval("v.testArrayParams()");
|
||||
jep.eval("v.testArrayParams()");
|
||||
}
|
||||
System.out.println("Passing ndarrays to Java method as Java primitive[] checked out ok");
|
||||
} catch (JepException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
|
@ -219,34 +254,79 @@ public class TestNumpy implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of bool can automatically convert to a method
|
||||
* arg of boolean[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callBooleanMethod(boolean[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of byte can automatically convert to a method
|
||||
* arg of byte[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callByteMethod(byte[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of int16 can automatically convert to a method
|
||||
* arg of short[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callShortMethod(short[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of int32 can automatically convert to a method
|
||||
* arg of int[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callIntMethod(int[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of int64 can automatically convert to a method
|
||||
* arg of long[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callLongMethod(long[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of float32 can automatically convert to a method
|
||||
* arg of float[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callFloatMethod(float[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a numpy.ndarray of float64 can automatically convert to a method
|
||||
* arg of double[]
|
||||
* @param array
|
||||
* @return true on success
|
||||
*/
|
||||
public boolean callDoubleMethod(double[] array) {
|
||||
return array != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an NDArray will not allow bad/dangerous constructor args.
|
||||
*/
|
||||
public void testNDArraySafety() {
|
||||
float[][] f = new float[15][];
|
||||
int[] dims = new int[] { 15, 20 };
|
||||
|
@ -257,7 +337,7 @@ public class TestNumpy implements Runnable {
|
|||
"NDArray should have failed instantiation");
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (PRINT) {
|
||||
e.printStackTrace();
|
||||
System.out.println("NDArray blocked bad type args");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +349,7 @@ public class TestNumpy implements Runnable {
|
|||
"NDArray should have failed instantiation");
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (PRINT) {
|
||||
e.printStackTrace();
|
||||
System.out.println("NDArray blocked bad dimensions args");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,10 +361,21 @@ public class TestNumpy implements Runnable {
|
|||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
TestNumpy test = new TestNumpy();
|
||||
test.run();
|
||||
test.runPythonSide();
|
||||
test.testNDArraySafety();
|
||||
File pwd = new File("tests");
|
||||
TestNumpy test = null;
|
||||
Jep jep = null;
|
||||
try {
|
||||
test = new TestNumpy();
|
||||
jep = new Jep(false, pwd.getPath());
|
||||
test.runFromJava(jep);
|
||||
test.testNDArraySafety();
|
||||
} catch (JepException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (jep != null) {
|
||||
jep.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,9 @@ package jep;
|
|||
* A test class that illustrates how numpy's floating point error handling can
|
||||
* cause python to deadlock trying to acquire the GIL. More specifically,
|
||||
* ufunc_object.c calls NPY_ALLOW_C_API which attempts to acquire the GIL.
|
||||
* Python is confused about the thread state and GIL state due to the top level
|
||||
* interpreter being initialized on the same thread as the sub-interpreter(s).
|
||||
* Python is confused about the thread state and GIL state due to more than
|
||||
* one Jep interpreter on the same thread.
|
||||
*
|
||||
* Please see the TODO below for how to trigger the freeze.
|
||||
*
|
||||
* Created: Thu Apr 09 2015
|
||||
*
|
||||
|
@ -28,21 +27,12 @@ public class TestNumpyGILFreeze {
|
|||
*
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
/*
|
||||
* TODO: To show the freeze, you need to alter Jep.TopInterpreter to
|
||||
* NOT use a separate thread for System.loadLibrary("jep");
|
||||
*/
|
||||
createJepAndUseNumpy();
|
||||
createJepAndUseNumpy();
|
||||
|
||||
System.out.println("java main() finished");
|
||||
}
|
||||
|
||||
public static void createJepAndUseNumpy() {
|
||||
public static void main(String[] args) {
|
||||
Jep jep = null;
|
||||
try {
|
||||
Jep jep0 = new Jep(true);
|
||||
jep = new Jep(true);
|
||||
jep0.close();
|
||||
jep.eval("import numpy");
|
||||
/*
|
||||
* If error conditions are set to ignore, we will not reach the
|
||||
|
@ -51,7 +41,7 @@ public class TestNumpyGILFreeze {
|
|||
*/
|
||||
jep.eval("numpy.seterr(under='print')");
|
||||
jep.eval(UNDERFLOW);
|
||||
jep.eval("forceUnderflow()");
|
||||
jep.eval("forceUnderflow()"); // this line will freeze
|
||||
System.out.println("returned from python interpreter");
|
||||
} catch (JepException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -61,6 +51,7 @@ public class TestNumpyGILFreeze {
|
|||
jep.close();
|
||||
}
|
||||
}
|
||||
System.out.println("java main() finished");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue