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;
|
package jep;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -42,7 +43,7 @@ import jep.python.PyObject;
|
||||||
* @author [mrjohnson0 at sourceforge.net] Mike Johnson
|
* @author [mrjohnson0 at sourceforge.net] Mike Johnson
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public final class Jep {
|
public final class Jep implements Closeable {
|
||||||
|
|
||||||
private static final String THREAD_WARN = "JEP WARNING: "
|
private static final String THREAD_WARN = "JEP WARNING: "
|
||||||
+ "Unsafe reuse of thread ";
|
+ "Unsafe reuse of thread ";
|
||||||
|
@ -204,9 +205,12 @@ public final class Jep {
|
||||||
ClassEnquirer ce) throws JepException {
|
ClassEnquirer ce) throws JepException {
|
||||||
if (threadUsed.get()) {
|
if (threadUsed.get()) {
|
||||||
/*
|
/*
|
||||||
* TODO: Consider throwing an exception for this situation instead
|
* TODO: Throw a JepException if this is detected. This is
|
||||||
* of printing a warning, as it can result in very-hard-to-diagnose
|
* inherently unsafe, the thread state information inside
|
||||||
* bugs such as GIL-related freezes or misleading error messages.
|
* 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();
|
Thread current = Thread.currentThread();
|
||||||
String warn = THREAD_WARN + current.getName() + REUSE_WARN;
|
String warn = THREAD_WARN + current.getName() + REUSE_WARN;
|
||||||
|
@ -934,6 +938,7 @@ public final class Jep {
|
||||||
* Shutdown python interpreter. Make sure you call this.
|
* Shutdown python interpreter. Make sure you call this.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public synchronized void close() {
|
public synchronized void close() {
|
||||||
if(this.closed)
|
if(this.closed)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package jep;
|
package jep;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
* Run.java - Execute a Python script.
|
* Run.java - Execute a Python script.
|
||||||
|
@ -50,16 +52,24 @@ public class Run {
|
||||||
public static int run(boolean eventDispatch) {
|
public static int run(boolean eventDispatch) {
|
||||||
Jep jep = null;
|
Jep jep = null;
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jep = new Jep(false, ".");
|
jep = new Jep(false, ".");
|
||||||
|
|
||||||
// "set" by eval'ing it
|
// "set" by eval'ing it
|
||||||
jep.eval("import sys; sys.argv = argv = " + scriptArgv);
|
jep.eval("import sys; sys.argv = argv = " + scriptArgv);
|
||||||
|
if (!file.endsWith("jep" + File.separator + "console.py")) {
|
||||||
jep.runScript(file);
|
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) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
if(jep != null)
|
if (jep != null)
|
||||||
jep.close();
|
jep.close();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -16,6 +16,7 @@ import jep.python.*;
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class Test implements Runnable {
|
public class Test implements Runnable {
|
||||||
|
|
||||||
private Jep jep = null;
|
private Jep jep = null;
|
||||||
private boolean testEval = false;
|
private boolean testEval = false;
|
||||||
|
|
||||||
|
@ -452,6 +453,45 @@ public class Test implements Runnable {
|
||||||
return Thread.currentThread().getClass();
|
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 {
|
public static void main(String argv[]) throws Throwable {
|
||||||
Jep jep = new Jep();
|
Jep jep = new Jep();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,8 +4,7 @@ import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TestNumpy.java. Runs a variety of simple tests to verify numpy interactions
|
* 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
|
* are working correctly.
|
||||||
* java main() method directly for other tests.
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Created: Wed Apr 08 2015
|
* Created: Wed Apr 08 2015
|
||||||
|
@ -13,38 +12,65 @@ import java.io.File;
|
||||||
* @author [ndjensen at gmail.com] Nate Jensen
|
* @author [ndjensen at gmail.com] Nate Jensen
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class TestNumpy implements Runnable {
|
public class TestNumpy {
|
||||||
|
|
||||||
protected Jep jep = null;
|
|
||||||
|
|
||||||
// set to a high number to test for memory leaks
|
// set to a high number to test for memory leaks
|
||||||
private static final int REPEAT = 1; // 0000000;
|
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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Jep jep = null;
|
||||||
try {
|
try {
|
||||||
File pwd = new File(".");
|
jep = new Jep(true);
|
||||||
/*
|
|
||||||
* Anytime you start a new Jep interpreter and import numpy within
|
} catch (Throwable th) {
|
||||||
* it, even if you call close() on the interpreter you will leak
|
t[0] = th;
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
} catch (JepException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
} finally {
|
||||||
if (jep != null)
|
if(jep != null) {
|
||||||
jep.close();
|
jep.close();
|
||||||
}
|
}
|
||||||
|
synchronized (TestNumpy.this) {
|
||||||
|
TestNumpy.this.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
synchronized (TestNumpy.this) {
|
||||||
|
thread.start();
|
||||||
|
try {
|
||||||
|
TestNumpy.this.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSetAndGet() throws JepException {
|
if(t[0] != null) {
|
||||||
|
throw t[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 };
|
int[] dimensions = new int[] { 4 };
|
||||||
|
|
||||||
// test boolean[]
|
// 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) {
|
public NDArray<int[]> testArgAndReturn(NDArray<int[]> array) {
|
||||||
int[] data = array.getData();
|
int[] data = array.getData();
|
||||||
int[] newData = new int[data.length];
|
int[] newData = new int[data.length];
|
||||||
|
@ -188,29 +220,32 @@ public class TestNumpy implements Runnable {
|
||||||
return new NDArray<int[]>(newData, array.getDimensions());
|
return new NDArray<int[]>(newData, array.getDimensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
// should only be called from main(), not from python unittests
|
|
||||||
public void runPythonSide() {
|
/**
|
||||||
try {
|
* Helper method to support running the main() method to run from Java
|
||||||
File pwd = new File("tests");
|
* instead of python
|
||||||
/*
|
* @param jep
|
||||||
* 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());
|
public void runFromJava(Jep jep) {
|
||||||
|
try {
|
||||||
jep.eval("import test_numpy");
|
jep.eval("import test_numpy");
|
||||||
jep.eval("v = test_numpy.TestNumpy('testArgReturn')");
|
jep.eval("v = test_numpy.TestNumpy('testArgReturn')");
|
||||||
jep.eval("v.setUp()");
|
jep.eval("v.setUp()");
|
||||||
|
for (int i = 0; i < REPEAT; i++) {
|
||||||
|
this.testSetAndGet(jep);
|
||||||
|
}
|
||||||
for (int i = 0; i < REPEAT; i++) {
|
for (int i = 0; i < REPEAT; i++) {
|
||||||
jep.eval("v.testArgReturn()");
|
jep.eval("v.testArgReturn()");
|
||||||
}
|
}
|
||||||
|
System.out.println("return NDArray from Java checked out ok");
|
||||||
for (int i = 0; i < REPEAT; i++) {
|
for (int i = 0; i < REPEAT; i++) {
|
||||||
jep.eval("v.testMultiDimensional()");
|
jep.eval("v.testMultiDimensional()");
|
||||||
}
|
}
|
||||||
|
System.out.println("multi dimensional arrays checked out ok");
|
||||||
for (int i = 0; i < REPEAT; i++) {
|
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) {
|
} catch (JepException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} 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) {
|
public boolean callBooleanMethod(boolean[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callByteMethod(byte[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callShortMethod(short[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callIntMethod(int[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callLongMethod(long[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callFloatMethod(float[] array) {
|
||||||
return array != null;
|
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) {
|
public boolean callDoubleMethod(double[] array) {
|
||||||
return array != null;
|
return array != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that an NDArray will not allow bad/dangerous constructor args.
|
||||||
|
*/
|
||||||
public void testNDArraySafety() {
|
public void testNDArraySafety() {
|
||||||
float[][] f = new float[15][];
|
float[][] f = new float[15][];
|
||||||
int[] dims = new int[] { 15, 20 };
|
int[] dims = new int[] { 15, 20 };
|
||||||
|
@ -257,7 +337,7 @@ public class TestNumpy implements Runnable {
|
||||||
"NDArray should have failed instantiation");
|
"NDArray should have failed instantiation");
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
if (PRINT) {
|
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");
|
"NDArray should have failed instantiation");
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
if (PRINT) {
|
if (PRINT) {
|
||||||
e.printStackTrace();
|
System.out.println("NDArray blocked bad dimensions args");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,10 +361,21 @@ public class TestNumpy implements Runnable {
|
||||||
* @param args
|
* @param args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
TestNumpy test = new TestNumpy();
|
File pwd = new File("tests");
|
||||||
test.run();
|
TestNumpy test = null;
|
||||||
test.runPythonSide();
|
Jep jep = null;
|
||||||
|
try {
|
||||||
|
test = new TestNumpy();
|
||||||
|
jep = new Jep(false, pwd.getPath());
|
||||||
|
test.runFromJava(jep);
|
||||||
test.testNDArraySafety();
|
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
|
* A test class that illustrates how numpy's floating point error handling can
|
||||||
* cause python to deadlock trying to acquire the GIL. More specifically,
|
* 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.
|
* 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
|
* Python is confused about the thread state and GIL state due to more than
|
||||||
* interpreter being initialized on the same thread as the sub-interpreter(s).
|
* one Jep interpreter on the same thread.
|
||||||
*
|
*
|
||||||
* Please see the TODO below for how to trigger the freeze.
|
|
||||||
*
|
*
|
||||||
* Created: Thu Apr 09 2015
|
* Created: Thu Apr 09 2015
|
||||||
*
|
*
|
||||||
|
@ -29,20 +28,11 @@ public class TestNumpyGILFreeze {
|
||||||
* @param args
|
* @param args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] 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() {
|
|
||||||
Jep jep = null;
|
Jep jep = null;
|
||||||
try {
|
try {
|
||||||
|
Jep jep0 = new Jep(true);
|
||||||
jep = new Jep(true);
|
jep = new Jep(true);
|
||||||
|
jep0.close();
|
||||||
jep.eval("import numpy");
|
jep.eval("import numpy");
|
||||||
/*
|
/*
|
||||||
* If error conditions are set to ignore, we will not reach the
|
* 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("numpy.seterr(under='print')");
|
||||||
jep.eval(UNDERFLOW);
|
jep.eval(UNDERFLOW);
|
||||||
jep.eval("forceUnderflow()");
|
jep.eval("forceUnderflow()"); // this line will freeze
|
||||||
System.out.println("returned from python interpreter");
|
System.out.println("returned from python interpreter");
|
||||||
} catch (JepException e) {
|
} catch (JepException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -61,6 +51,7 @@ public class TestNumpyGILFreeze {
|
||||||
jep.close();
|
jep.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
System.out.println("java main() finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue