Greetings,
Does ABCL safely support re-entrant and multi-entrant calls? What I mean by that is the following:
Re-entrant: on a single OS thread - my Java program calls into ABCL, then ABCL calls into my Java application, and then the Java application calls back into ABCL. So the stack has Java, ABCL, JAVA, and then ABCL again.
Multi-entrant: my Java application has many threads. One of my threads calls into ABCL. Then, while one thread is still in ABCL, another thread evokes ABCL. So now we have two calls into ABCL by two independent Java/OS threads running at the same time.
I understand the typical problems associated with application-level shared variables. This is expected. The question revolves around ABCL's internals. I presume ABCL would have some shared data that is internal to ABCL. That's what I am unclear about. ABCL would have had to be designed for these scenarios from the ground up.
This is a little hard to test because if it can't always correctly handle these situations, it may not become clear until certain scenarios arrive. It may be hard for any "test" program I write to cause those scenarios, so I thought this may be a known answer.
Thanks!
Blake
On Apr 20, 2018, at 11:10, Blake McBride blake@mcbride.name wrote:
Greetings,
Does ABCL safely support re-entrant and multi-entrant calls? What I mean by that is the following:
Re-entrant: on a single OS thread - my Java program calls into ABCL, then ABCL calls into my Java application, and then the Java application calls back into ABCL. So the stack has Java, ABCL, JAVA, and then ABCL again.
Multi-entrant: my Java application has many threads. One of my threads calls into ABCL. Then, while one thread is still in ABCL, another thread evokes ABCL. So now we have two calls into ABCL by two independent Java/OS threads running at the same time.
I understand the typical problems associated with application-level shared variables. This is expected. The question revolves around ABCL's internals. I presume ABCL would have some shared data that is internal to ABCL. That's what I am unclear about. ABCL would have had to be designed for these scenarios from the ground up.
This is a little hard to test because if it can't always correctly handle these situations, it may not become clear until certain scenarios arrive. It may be hard for any "test" program I write to cause those scenarios, so I thought this may be a known answer.
As long as one is referencing the org.armedbear.lisp.Interpreter singleton for the calls into ABCL, everything should work fine. Most of the logic can be understood by studying what [LispThread.java][1] does to mark global/local special variables, and how each Java thread is associated with a LispThread call stack.
[1]: https://gitlab.common-lisp.net/abcl/abcl/blob/master/src/org/armedbear/lisp/...
How exactly are you calling into ABCL from Java? A snippet of code would be useful.
Thanks, Mark. Here is a portion of what I am doing (written many years ago):
public class ABCL { private static Interpreter interpreter; private static boolean invertCase = false; private static Function makeWebServiceArgs; private static int lispRelease = 0; private static boolean once = true;
public static void init() { // only called once interpreter = Interpreter.createInstance(); invertCase();
load("com/xxx/lisp/clos-utils"); load("com/xxx/lisp/package-lru"); load("com/xxx/lisp/utils"); load("com/xxx/lisp/mappings"); makeWebServiceArgs = findLispFunction("UTILS", "make-web-service-args"); // this line is repeated in multiple places }
private static void invertCase() { interpreter.eval("(setf (readtable-case *readtable*) :invert)"); // make lisp case sensitive invertCase = true; }
public static String fixCase(String symbol) { if (invertCase) { int ucl = 0, lcl = 0; char[] vec = symbol.toCharArray(); for (int i=0 ; i < vec.length && (ucl == 0 || lcl == 0) ; i++) if (Character.isUpperCase(vec[i])) ucl++; else if (Character.isLowerCase(vec[i])) lcl++; if (ucl != 0 && lcl != 0 || ucl == 0 && lcl == 0) return symbol; else if (ucl != 0) return symbol.toLowerCase(); else return symbol.toUpperCase(); } else return symbol.toUpperCase(); }
public static void reset() { // if (interpreter == null) // return; // try { // interpreter.eval("(delete-package "ARAHANT-UTILS")"); // } catch (Throwable t) { // }
if (interpreter == null) return; try { interpreter.eval("(delete-package "MAPPINGS")"); } catch (Throwable t) { } if (interpreter == null) return; try { interpreter.eval("(delete-package "UTILS")"); } catch (Throwable t) { } if (interpreter == null) return; try { interpreter.eval("(delete-package "PACKAGE-LRU")"); } catch (Throwable t) { } if (interpreter == null) return; try { interpreter.eval("(delete-package "CLOS-UTILS")"); } catch (Throwable t) { } load("com/xxx/lisp/clos-utils"); load("com/xxx/lisp/package-lru"); load("com/xxx/lisp/utils"); load("com/xxx/lisp/mappings"); makeWebServiceArgs = findLispFunction("UTILS", "make-web-service-args"); // this line is repeated in multiple places }
public static LispObject load(String fileName) { return eval("(load "" + FileSystemUtils.getSourcePath() + fileName + "")"); }
public static LispObject compileFile(String fileName) { return eval("(compile-file "" + FileSystemUtils.getSourcePath() + fileName + "")"); }
public static void loadPackage(String lispPackage, String fileName) throws Exception { try { eval("(package-lru:load-package "" + lispPackage + "" "" + FileSystemUtils.getSourcePath() + fileName + "")"); } catch (Throwable t) { // Convert Throwable to Exception throw new Exception("Error loading lisp file " + fileName, t); } }
public static void packageDone(String lispPackage) { if (FileSystemUtils.isUnderIDE()) eval("(package-lru:package-done-unload "" + lispPackage + "")"); else eval("(package-lru:package-done "" + lispPackage + "")"); }
public static LispObject eval(String str) { return interpreter.eval(str); }
public static Function findLispFunction(String packageName, String funName) { if (packageName == null || packageName.isEmpty()) packageName = "CL-USER"; // else // packageName = fixCase(packageName); org.armedbear.lisp.Package lispPackage = Packages.findPackage(packageName); if (lispPackage == null) throw new RuntimeException("Package " + packageName + " not found"); Symbol symbol = lispPackage.findAccessibleSymbol(fixCase(funName)); if (symbol == null) throw new RuntimeException("Symbol " + packageName + ":" + fixCase(funName) + " not found"); Function fun = (Function) symbol.getSymbolFunction(); return fun; }
public static LispObject executeLispFunction(Function fun, Object ... args) { LispObject [] jargs; jargs = new LispObject[args.length]; for (int i=0 ; i < args.length ; i++) jargs[i] = JavaObject.getInstance(args[i], true); return fun.execute(jargs); }
public static LispObject executeLisp(String packageName, String funName, Object ... args) { Function fun = findLispFunction(packageName, funName); if (fun == null) return null; LispObject [] jargs; jargs = new LispObject[args.length]; for (int i=0 ; i < args.length ; i++) jargs[i] = JavaObject.getInstance(args[i], true); return fun.execute(jargs); }
public static LispObject executeLispArray(String packageName, String funName, Object [] args) { Function fun = findLispFunction(packageName, funName); if (fun == null) return null; LispObject [] jargs; jargs = new LispObject[args.length]; for (int i=0 ; i < args.length ; i++) jargs[i] = JavaObject.getInstance(args[i], true); return fun.execute(jargs); }
public static Function getMakeWebServiceArgs() { return makeWebServiceArgs; }
@SuppressWarnings("unchecked") public static Object LispObjectToJavaObject(LispObject obj) { if (obj.atom()) if (obj.characterp()) return obj.princToString().charAt(0); else if (obj.stringp()) return obj.princToString(); else if (obj.integerp()) return obj.intValue(); else if (obj.realp()) return obj.doubleValue(); else if (obj.listp()) return null; else if (obj.constantp()) return true; else return obj.princToString(); else if (obj.listp()) { LinkedList ll = new LinkedList(); while (!obj.endp()) { ll.addLast(LispObjectToJavaObject(obj.car())); obj = obj.cdr(); } return ll; } else if (obj.vectorp()) { int len = obj.length(); Object [] vec = new Object[len]; for (int i=0 ; i < len ; i++) vec[i] = LispObjectToJavaObject(obj.AREF(i)); return vec; } else return null; }
public static LispObject JavaObjectToLispObject(Object jobj) { if (jobj instanceof Boolean) return ((Boolean)jobj) ? Lisp.T : Lisp.NIL; else if (jobj instanceof Character) return LispCharacter.getInstance((Character)jobj); else if (jobj instanceof Short) return LispInteger.getInstance((Short)jobj); else if (jobj instanceof Integer) return LispInteger.getInstance((Integer)jobj); else if (jobj instanceof Long) return LispInteger.getInstance((Long)jobj); else if (jobj instanceof Float) return SingleFloat.getInstance((Float)jobj); else if (jobj instanceof Double) return DoubleFloat.getInstance((Double)jobj); else if (jobj instanceof String) return new SimpleString((String)jobj); else if (jobj instanceof StringBuilder) return new SimpleString((StringBuilder)jobj); else if (jobj instanceof LinkedList) { LispObject lobj = Lisp.NIL; ListIterator it = ((LinkedList) jobj).listIterator(); while (it.hasNext()) lobj = new Cons(JavaObjectToLispObject(it.next()), lobj); return lobj; } else if (jobj instanceof Set) { LispObject lobj = Lisp.NIL; Iterator it = ((Set) jobj).iterator(); while (it.hasNext()) lobj = new Cons(JavaObjectToLispObject(it.next()), lobj); return lobj; } else if (jobj instanceof Array) { Array a = (Array) jobj; int len = Array.getLength(a); SimpleVector vec = new SimpleVector(len); for (int i=0 ; i < len ; i++) vec.setSlotValue(i, JavaObjectToLispObject(Array.get(a, i))); return null; } return null; }
public static void printStackTrace(Throwable e) { try { Function fun = findLispFunction("UTILS", "print-stack-trace"); if (fun != null) { LispObject stackTrace = LispThread.currentThread().backtrace(0); if (stackTrace != null && stackTrace != Lisp.NIL) { System.err.println("Lisp execution error"); fun.execute(stackTrace); } } } catch (Throwable t) { } e.printStackTrace(); }
public static int getLispRelease() { return lispRelease; }
public static void setLispRelease(int lispRelease) { ABCL.lispRelease = lispRelease; }
}
On Fri, Apr 20, 2018 at 5:36 AM, Mark Evenson evenson@panix.com wrote:
On Apr 20, 2018, at 11:10, Blake McBride blake@mcbride.name wrote:
Greetings,
Does ABCL safely support re-entrant and multi-entrant calls? What I
mean by that is the following:
Re-entrant: on a single OS thread - my Java program calls into ABCL,
then ABCL calls into my Java application, and then the Java application calls back into ABCL. So the stack has Java, ABCL, JAVA, and then ABCL again.
Multi-entrant: my Java application has many threads. One of my threads
calls into ABCL. Then, while one thread is still in ABCL, another thread evokes ABCL. So now we have two calls into ABCL by two independent Java/OS threads running at the same time.
I understand the typical problems associated with application-level
shared variables. This is expected. The question revolves around ABCL's internals. I presume ABCL would have some shared data that is internal to ABCL. That's what I am unclear about. ABCL would have had to be designed for these scenarios from the ground up.
This is a little hard to test because if it can't always correctly
handle these situations, it may not become clear until certain scenarios arrive. It may be hard for any "test" program I write to cause those scenarios, so I thought this may be a known answer.
As long as one is referencing the org.armedbear.lisp.Interpreter singleton for the calls into ABCL, everything should work fine. Most of the logic can be understood by studying what [LispThread.java][1] does to mark global/local special variables, and how each Java thread is associated with a LispThread call stack.
org/armedbear/lisp/LispThread.java#L55
How exactly are you calling into ABCL from Java? A snippet of code would be useful.
-- "A screaming comes across the sky. It has happened before but there is nothing to compare to it now."
armedbear-devel@common-lisp.net