Make JRuby classes and modules update their constant transactionally
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/build.xml b/build.xml | |
index 64c66cb..f347790 100644 | |
--- a/build.xml | |
+++ b/build.xml | |
@@ -307,6 +307,7 @@ | |
<zipfileset src="${build.lib.dir}/joda-time-1.6.1.jar"/> | |
<zipfileset src="${build.lib.dir}/yydebug.jar"/> | |
<zipfileset src="${build.lib.dir}/nailgun-0.7.1.jar"/> | |
+ <zipfileset src="${build.lib.dir}/clojure.jar"/> | |
<metainf dir="${base.dir}/spi"> | |
<include name="services/**"/> | |
</metainf> | |
diff --git a/build_lib/clj-ds.jar b/build_lib/clj-ds.jar | |
deleted file mode 100644 | |
index c5f26e4..0000000 | |
Binary files a/build_lib/clj-ds.jar and /dev/null differ | |
diff --git a/build_lib/clojure.jar b/build_lib/clojure.jar | |
new file mode 100644 | |
index 0000000..82b549d | |
Binary files /dev/null and b/build_lib/clojure.jar differ | |
diff --git a/nbproject/project.xml b/nbproject/project.xml | |
index c1f3260..ada8aa1 100644 | |
--- a/nbproject/project.xml | |
+++ b/nbproject/project.xml | |
@@ -225,7 +225,7 @@ | |
<java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/2"> | |
<compilation-unit> | |
<package-root>${src.dir}</package-root> | |
- <classpath mode="compile">build_lib/junit.jar:build_lib/jline-0.9.93.jar:build_lib/jna.jar:build_lib/nailgun-0.7.1.jar:build_lib/joni.jar:build_lib/dynalang-0.3.jar:build_lib/invokedynamic.jar:build_lib/jcodings.jar:build_lib/constantine.jar:build_lib/bytelist.jar:build_lib/jffi.jar:build_lib/yydebug.jar:build_lib/bsf.jar:build_lib/jaffl.jar:build_lib/asm-3.2.jar:build_lib/asm-analysis-3.2.jar:build_lib/asm-commons-3.2.jar:build_lib/asm-tree-3.2.jar:build_lib/asm-util-3.2.jar:build_lib/jsr292-mock.jar:build_lib/jgrapht-jdk1.5.jar:build_lib/jnr-netdb.jar:build_lib/jnr-posix.jar:build_lib/joda-time-1.6.1.jar:build_lib/livetribe-jsr223-2.0.6.jar:build_lib/clj-ds.jar</classpath> | |
+ <classpath mode="compile">build_lib/junit.jar:build_lib/jline-0.9.93.jar:build_lib/jna.jar:build_lib/nailgun-0.7.1.jar:build_lib/joni.jar:build_lib/dynalang-0.3.jar:build_lib/invokedynamic.jar:build_lib/jcodings.jar:build_lib/constantine.jar:build_lib/bytelist.jar:build_lib/jffi.jar:build_lib/yydebug.jar:build_lib/bsf.jar:build_lib/jaffl.jar:build_lib/asm-3.2.jar:build_lib/asm-analysis-3.2.jar:build_lib/asm-commons-3.2.jar:build_lib/asm-tree-3.2.jar:build_lib/asm-util-3.2.jar:build_lib/jsr292-mock.jar:build_lib/jgrapht-jdk1.5.jar:build_lib/jnr-netdb.jar:build_lib/jnr-posix.jar:build_lib/joda-time-1.6.1.jar:build_lib/livetribe-jsr223-2.0.6.jar:build_lib/clojure.jar</classpath> | |
<built-to>${jruby.classes.dir}</built-to> | |
<built-to>${lib.dir}/jruby.jar</built-to> | |
<javadoc-built-to>docs/api</javadoc-built-to> | |
diff --git a/src/org/jruby/Ruby.java b/src/org/jruby/Ruby.java | |
index 815d268..6c67524 100644 | |
--- a/src/org/jruby/Ruby.java | |
+++ b/src/org/jruby/Ruby.java | |
@@ -3206,7 +3206,9 @@ public RaiseException newIllegalSequence(String message) { | |
} | |
public RaiseException newNoMethodError(String message, String name, IRubyObject args) { | |
- return new RaiseException(new RubyNoMethodError(this, getNoMethodError(), message, name, args), true); | |
+ RaiseException exception = new RaiseException(new RubyNoMethodError(this, getNoMethodError(), message, name, args), true); | |
+ exception.preRaise(getCurrentContext()); | |
+ return exception; | |
} | |
public RaiseException newNameError(String message, String name) { | |
@@ -3221,12 +3223,18 @@ public RaiseException newNameError(String message, String name, Throwable origEx | |
if (printWhenVerbose && origException != null && this.isVerbose()) { | |
origException.printStackTrace(getErrorStream()); | |
} | |
- return new RaiseException(new RubyNameError( | |
+ | |
+ RaiseException exception = new RaiseException(new RubyNameError( | |
this, getNameError(), message, name), false); | |
+ exception.preRaise(getCurrentContext()); | |
+ | |
+ return exception; | |
} | |
public RaiseException newLocalJumpError(RubyLocalJumpError.Reason reason, IRubyObject exitValue, String message) { | |
- return new RaiseException(new RubyLocalJumpError(this, getLocalJumpError(), message, reason, exitValue), true); | |
+ RaiseException exception = new RaiseException(new RubyLocalJumpError(this, getLocalJumpError(), message, reason, exitValue), true); | |
+ exception.preRaise(getCurrentContext()); | |
+ return exception; | |
} | |
public RaiseException newLocalJumpErrorNoBlock() { | |
@@ -3234,7 +3242,7 @@ public RaiseException newLocalJumpErrorNoBlock() { | |
} | |
public RaiseException newRedoLocalJumpError() { | |
- return new RaiseException(new RubyLocalJumpError(this, getLocalJumpError(), "unexpected redo", RubyLocalJumpError.Reason.REDO, getNil()), true); | |
+ return newLocalJumpError(RubyLocalJumpError.Reason.REDO, getNil(), "unexpected redo"); | |
} | |
public RaiseException newLoadError(String message) { | |
@@ -3258,7 +3266,9 @@ public RaiseException newSystemStackError(String message, StackOverflowError soe | |
} | |
public RaiseException newSystemExit(int status) { | |
- return new RaiseException(RubySystemExit.newInstance(this, status)); | |
+ RaiseException exception = new RaiseException(RubySystemExit.newInstance(this, status)); | |
+ exception.preRaise(getCurrentContext()); | |
+ return exception; | |
} | |
public RaiseException newIOError(String message) { | |
diff --git a/src/org/jruby/RubyKernel.java b/src/org/jruby/RubyKernel.java | |
index 689c02e..aa45324 100644 | |
--- a/src/org/jruby/RubyKernel.java | |
+++ b/src/org/jruby/RubyKernel.java | |
@@ -267,7 +267,9 @@ private static IRubyObject methodMissing(ThreadContext context, IRubyObject recv | |
exArgs = new IRubyObject[]{msg, symbol}; | |
} | |
- throw new RaiseException((RubyException)exc.newInstance(context, exArgs, Block.NULL_BLOCK)); | |
+ RaiseException exception = new RaiseException((RubyException)exc.newInstance(context, exArgs, Block.NULL_BLOCK)); | |
+ exception.preRaise(context); | |
+ throw exception; | |
} | |
@JRubyMethod(name = "open", required = 1, optional = 2, frame = true, module = true, visibility = PRIVATE) | |
diff --git a/src/org/jruby/RubyModule.java b/src/org/jruby/RubyModule.java | |
index d508014..d7b4df6 100644 | |
--- a/src/org/jruby/RubyModule.java | |
+++ b/src/org/jruby/RubyModule.java | |
@@ -37,6 +37,11 @@ | |
***** END LICENSE BLOCK *****/ | |
package org.jruby; | |
+import clojure.lang.APersistentMap; | |
+import clojure.lang.IPersistentMap; | |
+import clojure.lang.LockingTransaction; | |
+import clojure.lang.PersistentHashMap; | |
+import clojure.lang.Ref; | |
import static org.jruby.anno.FrameField.VISIBILITY; | |
import static org.jruby.runtime.Visibility.MODULE_FUNCTION; | |
import static org.jruby.runtime.Visibility.PRIVATE; | |
@@ -55,6 +60,7 @@ | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
+import java.util.concurrent.Callable; | |
import java.util.concurrent.ConcurrentHashMap; | |
import org.jruby.anno.JRubyClass; | |
@@ -182,11 +188,15 @@ public boolean isInstance(IRubyObject object) { | |
} | |
public Map<String, IRubyObject> getConstantMap() { | |
- return constants; | |
+ return (Map<String, IRubyObject>)constants.deref(); | |
+ } | |
+ | |
+ public IPersistentMap getPersistentConstants() { | |
+ return (IPersistentMap)constants.deref(); | |
} | |
public synchronized Map<String, IRubyObject> getConstantMapForWrite() { | |
- return constants == Collections.EMPTY_MAP ? constants = new ConcurrentHashMap<String, IRubyObject>(4, 0.9f, 1) : constants; | |
+ return (Map<String, IRubyObject>)constants.deref(); | |
} | |
public void addIncludingHierarchy(IncludedModuleWrapper hierarchy) { | |
@@ -207,6 +217,11 @@ protected RubyModule(Ruby runtime, RubyClass metaClass, boolean objectSpace) { | |
// if (parent == null) parent = runtime.getObject(); | |
setFlag(USER7_F, !isClass()); | |
generation = runtime.getNextModuleGeneration(); | |
+ try { | |
+ constants = new Ref(PersistentHashMap.EMPTY); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
+ } | |
} | |
/** used by MODULE_ALLOCATOR and RubyClass constructors | |
@@ -1069,19 +1084,28 @@ public synchronized void defineAliases(List<String> aliases, String oldName) { | |
} | |
invalidateCacheDescendants(); | |
} | |
- | |
+ | |
/** this method should be used only by interpreter or compiler | |
* | |
*/ | |
public RubyClass defineOrGetClassUnder(String name, RubyClass superClazz) { | |
+ return defineOrGetClassUnder(name, superClazz, null); | |
+ } | |
+ | |
+ public interface ModuleCallback { | |
+ public void call(RubyModule cls); | |
+ } | |
+ | |
+ /** this method should be used only by interpreter or compiler | |
+ * | |
+ */ | |
+ public RubyClass defineOrGetClassUnder(final String name, final RubyClass superClazz, final ModuleCallback body) { | |
// This method is intended only for defining new classes in Ruby code, | |
// so it uses the allocator of the specified superclass or default to | |
// the Object allocator. It should NOT be used to define classes that require a native allocator. | |
- | |
- Ruby runtime = getRuntime(); | |
+ final Ruby runtime = getRuntime(); | |
IRubyObject classObj = getConstantAt(name); | |
RubyClass clazz; | |
- | |
if (classObj != null) { | |
if (!(classObj instanceof RubyClass)) throw runtime.newTypeError(name + " is not a class"); | |
clazz = (RubyClass)classObj; | |
@@ -1095,26 +1119,48 @@ public RubyClass defineOrGetClassUnder(String name, RubyClass superClazz) { | |
} | |
if (runtime.getSafeLevel() >= 4) throw runtime.newTypeError("extending class prohibited"); | |
+ return clazz; | |
} else if (classProviders != null && (clazz = searchProvidersForClass(name, superClazz)) != null) { | |
// reopen a java class | |
} else { | |
- if (superClazz == null) superClazz = runtime.getObject(); | |
- if (superClazz == runtime.getObject() && RubyInstanceConfig.REIFY_RUBY_CLASSES) { | |
- clazz = RubyClass.newClass(runtime, superClazz, name, REIFYING_OBJECT_ALLOCATOR, this, true); | |
- } else { | |
- clazz = RubyClass.newClass(runtime, superClazz, name, superClazz.getAllocator(), this, true); | |
+ try { | |
+ clazz = (RubyClass)LockingTransaction.runInTransaction(new Callable() { | |
+ public Object call() throws Exception { | |
+ RubyClass superCls = superClazz; | |
+ RubyClass localClazz; | |
+ if (superCls == null) superCls = runtime.getObject(); | |
+ if (superCls == runtime.getObject() && RubyInstanceConfig.REIFY_RUBY_CLASSES) { | |
+ localClazz = RubyClass.newClass(runtime, superCls, name, REIFYING_OBJECT_ALLOCATOR, RubyModule.this, true); | |
+ } else { | |
+ localClazz = RubyClass.newClass(runtime, superCls, name, superCls.getAllocator(), RubyModule.this, true); | |
+ } | |
+ if (body != null) { | |
+ body.call(localClazz); | |
+ } | |
+ return localClazz; | |
+ } | |
+ }); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
} | |
- } | |
+ } | |
return clazz; | |
} | |
+ /** this method should be used only by interpreter or compiler | |
+ * | |
+ */ | |
+ public RubyModule defineOrGetModuleUnder(String name) { | |
+ return defineOrGetModuleUnder(name, null); | |
+ } | |
+ | |
/** this method should be used only by interpreter or compiler | |
* | |
*/ | |
- public RubyModule defineOrGetModuleUnder(String name) { | |
+ public RubyModule defineOrGetModuleUnder(final String name, final ModuleCallback body) { | |
// This method is intended only for defining new modules in Ruby code | |
- Ruby runtime = getRuntime(); | |
+ final Ruby runtime = getRuntime(); | |
IRubyObject moduleObj = getConstantAt(name); | |
RubyModule module; | |
if (moduleObj != null) { | |
@@ -1124,7 +1170,19 @@ public RubyModule defineOrGetModuleUnder(String name) { | |
} else if (classProviders != null && (module = searchProvidersForModule(name)) != null) { | |
// reopen a java module | |
} else { | |
- module = RubyModule.newModule(runtime, name, this, true); | |
+ try { | |
+ module = (RubyModule)LockingTransaction.runInTransaction(new Callable() { | |
+ public Object call() throws Exception { | |
+ RubyModule module = RubyModule.newModule(runtime, name, RubyModule.this, true); | |
+ if (body != null) { | |
+ body.call(module); | |
+ } | |
+ return module; | |
+ } | |
+ }); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
+ } | |
} | |
return module; | |
} | |
@@ -1447,9 +1505,15 @@ public IRubyObject initialize_copy(IRubyObject original) { | |
return this; | |
} | |
- public void syncConstants(RubyModule other) { | |
- if (other.getConstantMap() != Collections.EMPTY_MAP) { | |
- getConstantMapForWrite().putAll(other.getConstantMap()); | |
+ public void syncConstants(final RubyModule other) { | |
+ try { | |
+ LockingTransaction.runInTransaction(new Callable() { | |
+ public Object call() throws Exception { | |
+ return constants.set(getPersistentConstants().cons(other.getPersistentConstants())); | |
+ } | |
+ }); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
} | |
} | |
@@ -3082,18 +3146,33 @@ protected IRubyObject constantTableFastFetch(String internedName) { | |
return getConstantMap().get(internedName); | |
} | |
- protected IRubyObject constantTableStore(String name, IRubyObject value) { | |
- getConstantMapForWrite().put(name, value); | |
+ protected IRubyObject constantTableStore(final String name, final IRubyObject value) { | |
+ try { | |
+ LockingTransaction.runInTransaction(new Callable() { | |
+ public Object call() throws Exception { | |
+ return constants.set(((IPersistentMap)constants.deref()).assoc(name, value)); | |
+ } | |
+ }); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
+ } | |
return value; | |
} | |
- protected IRubyObject constantTableFastStore(String internedName, IRubyObject value) { | |
- getConstantMapForWrite().put(internedName, value); | |
- return value; | |
+ protected IRubyObject constantTableFastStore(final String internedName, final IRubyObject value) { | |
+ return constantTableStore(internedName, value); | |
} | |
- protected IRubyObject constantTableRemove(String name) { | |
- return getConstantMapForWrite().remove(name); | |
+ protected IRubyObject constantTableRemove(final String name) { | |
+ try { | |
+ return (IRubyObject)LockingTransaction.runInTransaction(new Callable() { | |
+ public Object call() throws Exception { | |
+ return constants.set(((IPersistentMap)constants.deref()).without(name)); | |
+ } | |
+ }); | |
+ } catch (Exception e) { | |
+ throw new RuntimeException(e); | |
+ } | |
} | |
private static void define(RubyModule module, JavaMethodDescriptor desc, DynamicMethod dynamicMethod) { | |
@@ -3182,7 +3261,7 @@ private static void define(RubyModule module, JavaMethodDescriptor desc, Dynamic | |
// If it is null, then it an anonymous class. | |
protected String classId; | |
- private volatile Map<String, IRubyObject> constants = Collections.EMPTY_MAP; | |
+ private final Ref constants; | |
private volatile Map<String, DynamicMethod> methods = Collections.EMPTY_MAP; | |
private Map<String, CacheEntry> cachedMethods = Collections.EMPTY_MAP; | |
protected int generation; | |
diff --git a/src/org/jruby/ast/ClassNode.java b/src/org/jruby/ast/ClassNode.java | |
index 03f2a61..974e8c3 100644 | |
--- a/src/org/jruby/ast/ClassNode.java | |
+++ b/src/org/jruby/ast/ClassNode.java | |
@@ -118,7 +118,7 @@ public Node getSuperNode() { | |
} | |
@Override | |
- public IRubyObject interpret(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) { | |
+ public IRubyObject interpret(final Ruby runtime, final ThreadContext context, final IRubyObject self, final Block aBlock) { | |
RubyModule enclosingClass = cpath.getEnclosingModule(runtime, context, self, aBlock); | |
// TODO: Figure out how this can happen and possibly remove | |
@@ -132,14 +132,15 @@ public IRubyObject interpret(Ruby runtime, ThreadContext context, IRubyObject se | |
superClass = (RubyClass)superObj; | |
} | |
- boolean definedAlready = enclosingClass.isConstantDefined(cpath.getName()); | |
- | |
- RubyClass clazz = enclosingClass.defineOrGetClassUnder(cpath.getName(), superClass); | |
- | |
- scope.setModule(clazz); | |
+ final IRubyObject[] classBodyResult = new IRubyObject[1]; | |
+ RubyClass clazz = enclosingClass.defineOrGetClassUnder(cpath.getName(), superClass, new RubyModule.ModuleCallback() { | |
+ public void call(RubyModule cls) { | |
+ scope.setModule(cls); | |
- IRubyObject classBodyResult = ASTInterpreter.evalClassDefinitionBody(runtime, context, scope, bodyNode, clazz, self, aBlock); | |
+ classBodyResult[0] = ASTInterpreter.evalClassDefinitionBody(runtime, context, scope, bodyNode, cls, self, aBlock); | |
+ } | |
+ }); | |
- return classBodyResult; | |
+ return classBodyResult[0]; | |
} | |
} | |
diff --git a/src/org/jruby/ast/ModuleNode.java b/src/org/jruby/ast/ModuleNode.java | |
index ec3df28..ca6b613 100644 | |
--- a/src/org/jruby/ast/ModuleNode.java | |
+++ b/src/org/jruby/ast/ModuleNode.java | |
@@ -106,17 +106,21 @@ public Colon3Node getCPath() { | |
} | |
@Override | |
- public IRubyObject interpret(Ruby runtime, ThreadContext context, IRubyObject self, Block aBlock) { | |
+ public IRubyObject interpret(final Ruby runtime, final ThreadContext context, final IRubyObject self, final Block aBlock) { | |
RubyModule enclosingModule = cpath.getEnclosingModule(runtime, context, self, aBlock); | |
+ // TODO: Figure out how this can happen and possibly remove | |
if (enclosingModule == null) throw runtime.newTypeError("no outer class/module"); | |
- String name = cpath.getName(); | |
+ final IRubyObject[] moduleBodyResult = new IRubyObject[1]; | |
+ RubyModule module = enclosingModule.defineOrGetModuleUnder(cpath.getName(), new RubyModule.ModuleCallback() { | |
+ public void call(RubyModule module) { | |
+ scope.setModule(module); | |
- RubyModule module = enclosingModule.defineOrGetModuleUnder(name); | |
+ moduleBodyResult[0] = ASTInterpreter.evalClassDefinitionBody(runtime, context, scope, bodyNode, module, self, aBlock); | |
+ } | |
+ }); | |
- scope.setModule(module); | |
- | |
- return ASTInterpreter.evalClassDefinitionBody(runtime, context, scope, bodyNode, module, self, aBlock); | |
+ return moduleBodyResult[0]; | |
} | |
} | |
diff --git a/src/org/jruby/embed/ScriptingContainer.java b/src/org/jruby/embed/ScriptingContainer.java | |
index d218263..ae90bc1 100644 | |
--- a/src/org/jruby/embed/ScriptingContainer.java | |
+++ b/src/org/jruby/embed/ScriptingContainer.java | |
@@ -1463,7 +1463,7 @@ public void setReader(Reader reader) { | |
RubyIO io = new RubyIO(runtime, istream); | |
io.getOpenFile().getMainStream().setSync(true); | |
runtime.defineVariable(new InputGlobalVariable(runtime, "$stdin", io)); | |
- runtime.getObject().getConstantMapForWrite().put("STDIN", io); | |
+ runtime.getObject().setConstant("STDIN", io); | |
} | |
/** | |
@@ -1521,7 +1521,7 @@ private void setOutputStream(PrintStream pstream) { | |
RubyIO io = new RubyIO(runtime, pstream); | |
io.getOpenFile().getMainStream().setSync(true); | |
runtime.defineVariable(new OutputGlobalVariable(runtime, "$stdout", io)); | |
- runtime.getObject().getConstantMapForWrite().put("STDOUT", io); | |
+ runtime.getObject().setConstant("STDOUT", io); | |
runtime.getGlobalVariables().alias("$>", "$stdout"); | |
runtime.getGlobalVariables().alias("$defout", "$stdout"); | |
} | |
@@ -1586,7 +1586,7 @@ private void setErrorStream(PrintStream error) { | |
RubyIO io = new RubyIO(runtime, error); | |
io.getOpenFile().getMainStream().setSync(true); | |
runtime.defineVariable(new OutputGlobalVariable(runtime, "$stderr", io)); | |
- runtime.getObject().getConstantMapForWrite().put("STDERR", io); | |
+ runtime.getObject().setConstant("STDERR", io); | |
runtime.getGlobalVariables().alias("$deferr", "$stderr"); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment