Skip to content

Instantly share code, notes, and snippets.

@the8472
Created January 3, 2013 20:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save the8472/4446959 to your computer and use it in GitHub Desktop.
Save the8472/4446959 to your computer and use it in GitHub Desktop.
stamped ivar updater for jruby
diff --git a/src/org/jruby/RubyBasicObject.java b/src/org/jruby/RubyBasicObject.java
index 2bb34e8..6de4bef 100644
--- a/src/org/jruby/RubyBasicObject.java
+++ b/src/org/jruby/RubyBasicObject.java
@@ -38,6 +38,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.jruby.anno.JRubyMethod;
@@ -67,6 +68,9 @@
import org.jruby.util.TypeConverter;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
+import org.jruby.util.unsafe.UnsafeHolder;
+
+import sun.print.resources.serviceui;
import static org.jruby.javasupport.util.RuntimeHelpers.invokedynamic;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_EQUAL;
@@ -114,7 +118,12 @@
protected int flags;
// variable table, lazily allocated as needed (if needed)
- private transient volatile Object[] varTable;
+ private transient Object[] varTable;
+
+ // locking stamp for Unsafe ops updating the vartable
+ private transient volatile int varTableStamp;
+
+ private static final long VAR_TABLE_OFFSET = UnsafeHolder.fieldOffset(RubyBasicObject.class, "varTable");
/**
* The error message used when some one tries to modify an
@@ -1186,12 +1195,12 @@
return varTable;
}
- private static final AtomicReferenceFieldUpdater VARTABLE_UPDATER;
+ private static final AtomicIntegerFieldUpdater STAMP_UPDATER;
static {
- AtomicReferenceFieldUpdater updater = null;
+ AtomicIntegerFieldUpdater updater = null;
try {
- updater = AtomicReferenceFieldUpdater.newUpdater(RubyBasicObject.class, Object[].class, "varTable");
+ updater = AtomicIntegerFieldUpdater.newUpdater(RubyBasicObject.class, "varTableStamp");
} catch (RuntimeException re) {
if (re.getCause() instanceof AccessControlException) {
// security prevented creation; fall back on synchronized assignment
@@ -1199,78 +1208,10 @@
throw re;
}
}
- VARTABLE_UPDATER = updater;
+ STAMP_UPDATER = updater;
}
- /**
- * Get variable table for write purposes. Initializes if uninitialized, and
- * resizes if necessary.
- */
- protected final Object[] getVariableTableForWrite(int index) {
- if (VARTABLE_UPDATER == null) {
- return getVariableTableForWriteSynchronized(index);
- } else {
- return getVariableTableForWriteAtomic(index);
- }
- }
-
- /**
- * Get the variable table for write. If it is not set or not of the right size,
- * synchronize against the object and prepare it accordingly.
- *
- * @param index the index of the value soon to be set
- * @return the var table, ready for setting
- */
- private Object[] getVariableTableForWriteSynchronized(int index) {
- Object[] myVarTable = varTable;
- if (myVarTable == null || myVarTable.length <= index) {
- synchronized (this) {
- myVarTable = varTable;
-
- if (myVarTable == null) {
- return varTable = new Object[getMetaClass().getRealClass().getVariableTableSizeWithExtras()];
- } else if (myVarTable.length <= index) {
- Object[] newTable = new Object[getMetaClass().getRealClass().getVariableTableSizeWithExtras()];
- System.arraycopy(myVarTable, 0, newTable, 0, myVarTable.length);
- return varTable = newTable;
- } else {
- return myVarTable;
- }
- }
- }
-
- return varTable;
- }
-
-
- /**
- * Get the variable table for write. If it is not set or not of the right size,
- * atomically update it with an appropriate value.
- *
- * @param index the index of the value soon to be set
- * @return the var table, ready for setting
- */
- private Object[] getVariableTableForWriteAtomic(int index) {
- while (true) {
- Object[] myVarTable = varTable;
- Object[] newTable;
-
- if (myVarTable == null) {
- newTable = new Object[metaClass.getRealClass().getVariableTableSizeWithExtras()];
- } else if (myVarTable.length <= index) {
- newTable = new Object[metaClass.getRealClass().getVariableTableSizeWithExtras()];
- System.arraycopy(myVarTable, 0, newTable, 0, myVarTable.length);
- } else {
- return myVarTable;
- }
-
- // proceed with atomic update of table, or retry
- if (VARTABLE_UPDATER.compareAndSet(this, myVarTable, newTable)) {
- return newTable;
- }
- }
- }
public Object getVariable(int index) {
Object[] ivarTable;
@@ -1278,18 +1219,87 @@
if (ivarTable.length > index) return ivarTable[index];
return null;
}
-
+
public void setVariable(int index, Object value) {
ensureInstanceVariablesSettable();
if (index < 0) return;
- Object[] ivarTable = getVariableTableForWrite(index);
- ivarTable[index] = value;
+ setVariableInternal(index, value);
}
+
+ protected final void setVariableInternal(int index, Object value) {
+ if(UnsafeHolder.U == null)
+ setVariableSynchronized(index,value);
+ else
+ setVariableStamped(index,value);
+ }
+
+ private void setVariableSynchronized(int index, Object value) {
+ synchronized (this) {
+ Object[] currentTable = varTable;
+
+ if (currentTable == null) {
+ varTable = currentTable = new Object[getMetaClass().getRealClass().getVariableTableSizeWithExtras()];
+ } else if (currentTable.length <= index) {
+ Object[] newTable = new Object[getMetaClass().getRealClass().getVariableTableSizeWithExtras()];
+ System.arraycopy(currentTable, 0, newTable, 0, currentTable.length);
+ varTable = newTable;
+ }
+
+ varTable[index] = value;
+ }
+ }
+
+ private void setVariableStamped(int index, Object value) {
+
+ for(;;) {
+ int currentStamp = varTableStamp;
+ // spin-wait if odd
+ if((currentStamp & 0x01) == 1)
+ continue;
+
+ Object[] currentTable = (Object[]) UnsafeHolder.U.getObjectVolatile(this, VAR_TABLE_OFFSET);
+
+ if(currentTable == null || index >= currentTable.length)
+ {
+ // try to acquire exclusive access to the varTable field
+ if(!STAMP_UPDATER.compareAndSet(this, currentStamp, ++currentStamp))
+ continue;
+
+ Object[] newTable = new Object[getMetaClass().getRealClass().getVariableTableSizeWithExtras()];
+ if(currentTable != null)
+ System.arraycopy(currentTable, 0, newTable, 0, currentTable.length);
+ newTable[index] = value;
+ UnsafeHolder.U.putOrderedObject(this, VAR_TABLE_OFFSET, newTable);
+
+ // release exclusive access
+ STAMP_UPDATER.set(this, ++currentStamp);
+ } else {
+ // shared access to varTable field.
+
+ if(UnsafeHolder.SUPPORTS_FENCES) {
+ currentTable[index] = value;
+ UnsafeHolder.storeFence();
+ } else {
+ // TODO: maybe optimize by read and checking current value before setting
+ UnsafeHolder.U.putObjectVolatile(currentTable, sun.misc.Unsafe.ARRAY_OBJECT_BASE_OFFSET + sun.misc.Unsafe.ARRAY_OBJECT_INDEX_SCALE * index, value);
+ }
+
+ // validate stamp. redo on concurrent modification
+ if(STAMP_UPDATER.get(this) != currentStamp)
+ continue;
+
+ }
+
+ break;
+ }
+
+
+ }
+
private void setObjectId(int index, long value) {
if (index < 0) return;
- Object[] ivarTable = getVariableTableForWrite(index);
- ivarTable[index] = value;
+ setVariableInternal(index, value);
}
public final Object getNativeHandle() {
@@ -1298,8 +1308,7 @@
public final void setNativeHandle(Object value) {
int index = getMetaClass().getRealClass().getNativeHandleAccessorField().getVariableAccessorForWrite().getIndex();
- Object[] ivarTable = getVariableTableForWrite(index);
- ivarTable[index] = value;
+ setVariableInternal(index, value);
}
public final Object getFFIHandle() {
@@ -1308,8 +1317,7 @@
public final void setFFIHandle(Object value) {
int index = getMetaClass().getRealClass().getFFIHandleAccessorField().getVariableAccessorForWrite().getIndex();
- Object[] ivarTable = getVariableTableForWrite(index);
- ivarTable[index] = value;
+ setVariableInternal(index, value);
}
//
@@ -1460,7 +1468,7 @@
assert !IdUtil.isRubyVariable(name);
return variableTableRemove(name);
}
-
+
/**
* Sync one this object's variables with other's - this is used to make
* rbClone work correctly.
@@ -1471,17 +1479,36 @@
boolean sameTable = otherRealClass == realClass;
if (sameTable) {
- RubyClass.VariableAccessor objIdAccessor = otherRealClass.getObjectIdAccessorField().getVariableAccessorForRead();
+ int idIndex = otherRealClass.getObjectIdAccessorField().getVariableAccessorForRead().getIndex();
+
+
Object[] otherVars = ((RubyBasicObject) other).varTable;
- int otherLength = otherVars.length;
- Object[] myVars = getVariableTableForWrite(otherLength - 1);
- System.arraycopy(otherVars, 0, myVars, 0, otherLength);
+
+ if(UnsafeHolder.U == null)
+ {
+ synchronized (this) {
+ varTable = makeSyncedTable(otherVars, idIndex);
+ }
+ } else {
+ for(;;) {
+ int oldStamp = varTableStamp;
+ // wait for read mode
+ if((oldStamp & 0x01) == 1)
+ continue;
+ // acquire exclusive write mode
+ if(!STAMP_UPDATER.compareAndSet(this, oldStamp, ++oldStamp))
+ continue;
+
+ UnsafeHolder.U.putOrderedObject(this, VAR_TABLE_OFFSET, makeSyncedTable(otherVars, idIndex));
+
+ // release write mode
+ STAMP_UPDATER.set(this, ++oldStamp);
+ break;
+ }
- // null out object ID so we don't share it
- int objIdIndex = objIdAccessor.getIndex();
- if (objIdIndex > 0 && objIdIndex < myVars.length) {
- myVars[objIdIndex] = null;
+
}
+
} else {
for (Map.Entry<String, RubyClass.VariableAccessor> entry : otherRealClass.getVariableAccessorsForRead().entrySet()) {
RubyClass.VariableAccessor accessor = entry.getValue();
@@ -1498,7 +1525,22 @@
}
}
-
+ private Object[] makeSyncedTable(Object[] otherTable, int objectIdIdx) {
+ Object[] currentTable = (Object[]) UnsafeHolder.U.getObjectVolatile(this, VAR_TABLE_OFFSET);
+
+ if(currentTable == null || currentTable.length < otherTable.length)
+ currentTable = otherTable.clone();
+ else
+ System.arraycopy(otherTable, 0, currentTable, 0, otherTable.length);
+
+ // null out object ID so we don't share it
+ if (objectIdIdx > 0 && objectIdIdx < currentTable.length) {
+ currentTable[objectIdIdx] = null;
+ }
+
+ return currentTable;
+ }
+
//
// INSTANCE VARIABLE API METHODS
//
diff --git a/src/org/jruby/util/unsafe/UnsafeHolder.java b/src/org/jruby/util/unsafe/UnsafeHolder.java
new file mode 100644
index 0000000..76f03ec
--- /dev/null
+++ b/src/org/jruby/util/unsafe/UnsafeHolder.java
@@ -0,0 +1,118 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+ * Version: CPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Common Public
+ * License Version 1.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the CPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the CPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.jruby.util.unsafe;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+
+public final class UnsafeHolder {
+
+ private UnsafeHolder(){}
+
+ /**
+ * Holds a reference to Unsafe if available, null otherwise.
+ */
+ public static final sun.misc.Unsafe U = loadUnsafe();
+
+ private static final java.lang.invoke.MethodHandle fullFence = loadFenceHandle("fullFence");
+ private static final java.lang.invoke.MethodHandle loadFence = loadFenceHandle("loadFence");
+ private static final java.lang.invoke.MethodHandle storeFence = loadFenceHandle("storeFence");
+
+ public static final boolean SUPPORTS_FENCES = fullFence != null && loadFence != null && storeFence != null;
+
+ private static sun.misc.Unsafe loadUnsafe() {
+ try {
+ Class unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field f = unsafeClass.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ return (sun.misc.Unsafe) f.get(null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static java.lang.invoke.MethodHandle loadFenceHandle(String name) {
+ if(U == null)
+ return null;
+
+ try {
+ // check if this JRE supports method handles
+ Class clazz = Class.forName("java.lang.invoke.MethodHandles");
+
+ MethodType mt = MethodType.methodType(void.class);
+ MethodHandles.Lookup lookups = MethodHandles.lookup();
+
+ return lookups.findVirtual(U.getClass(), name, mt);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ public static long fieldOffset(Class clazz, String name) {
+ if(U == null)
+ return -1;
+ try {
+ return U.objectFieldOffset(clazz.getDeclaredField(name));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return sun.misc.Unsafe.INVALID_FIELD_OFFSET;
+ }
+ }
+
+ public static void fullFence() {
+ try {
+ fullFence.invokeExact(U);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void loadFence() {
+ try {
+ loadFence.invokeExact(U);
+ } catch (Throwable e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public static void storeFence() {
+ try {
+ storeFence.invokeExact(U);
+ } catch (Throwable e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment