Skip to content

Commit

Permalink
Add property and non-volatile table-based ivars.
Browse files Browse the repository at this point in the history
This is in prep for officially saying we don't guarantee that
ivarrs will be volatile. Users concerned about this are
recommended to look into concurrent-ruby, which provides better
guarantees and utilities for volatility, atomicity, and more.

Note that there may still be a chance that other volatile accesses
around the variable updates force it to be effectively volatile,
but this will not be the case when reifying variables to be Java
fields, as we hope to do in an upcoming release.

See discussions surrounding ruby-concurrency/concurrent-ruby#422
for more details.

See also #3353 for discussion about get volatility prior to this
upcoming change in policy.
headius committed Jan 8, 2016
1 parent c4596ce commit b8b07a5
Showing 3 changed files with 174 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse 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/epl-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 EPL, 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 EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.runtime.ivars;

import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.util.unsafe.UnsafeHolder;

/**
* A VariableAccessor that directly updates instance variables without an explicit memory fence
* or synchronization.
*
* If the table itself must be created for the first time or grown to accommodate a new variable,
* those operations will be done in a thread-safe (volatile, atomic) way. However updates of an
* entry in an existing table will not have any explicit memory fence or synchronization.
*/
public class NonvolatileVariableAccessor extends VariableAccessor {
/**
* Construct a new NonvolatileVariableAccessor for the given "real" class,
* variable name, variable index, and class ID.
*
* @param realClass the "real" class
* @param name the variable's name
* @param index the variable's index
* @param classId the class's ID
*/
public NonvolatileVariableAccessor(RubyClass realClass, String name, int index, int classId) {
super(realClass, name, index, classId);
}

/**
* Set this variable into the given object using Unsafe to ensure
* safe creation or growth of the variable table.
*
* @param object the object into which to set this variable
* @param value the variable's value
*/
public void set(Object object, Object value) {
((RubyBasicObject)object).ensureInstanceVariablesSettable();
setVariable((RubyBasicObject)object, realClass, index, value);
}

/**
* Set the given variable index into the specified object. The "real" class
* and index are pass in to provide functional access. This version checks
* if self has been frozen before proceeding to set the variable.
*
* @param self the object into which to set the variable
* @param realClass the "real" class for the object
* @param index the index of the variable
* @param value the variable's value
*/
public static void setVariableChecked(RubyBasicObject self, RubyClass realClass, int index, Object value) {
self.ensureInstanceVariablesSettable();
setVariable(self, realClass, index, value);
}

/**
* Set the given variable index into the specified object. The "real" class
* and index are pass in to provide functional access.
*
* @param self the object into which to set the variable
* @param realClass the "real" class for the object
* @param index the index of the variable
* @param value the variable's value
*/
public static void setVariable(RubyBasicObject self, RubyClass realClass, int index, Object value) {
while (true) {
int currentStamp = self.varTableStamp;
// spin-wait if odd
if((currentStamp & 0x01) != 0)
continue;

Object[] currentTable = (Object[]) UnsafeHolder.U.getObjectVolatile(self, RubyBasicObject.VAR_TABLE_OFFSET);

if (currentTable == null || index >= currentTable.length) {
if (!createTableUnsafe(self, currentStamp, realClass, currentTable, index, value)) continue;
} else {
if (!updateTable(self, currentStamp, currentTable, index, value)) continue;
}

break;
}
}

/**
* Create or exapand a table for the given object, using Unsafe CAS and
* ordering operations to ensure visibility.
*
* @param self the object into which to set the variable
* @param currentStamp the current variable table stamp
* @param realClass the "real" class for the object
* @param currentTable the current table
* @param index the index of the variable
* @param value the variable's value
* @return whether the update was successful, for CAS retrying
*/
private static boolean createTableUnsafe(RubyBasicObject self, int currentStamp, RubyClass realClass, Object[] currentTable, int index, Object value) {
// try to acquire exclusive access to the varTable field
if (!UnsafeHolder.U.compareAndSwapInt(self, RubyBasicObject.STAMP_OFFSET, currentStamp, ++currentStamp)) {
return false;
}

Object[] newTable = new Object[realClass.getVariableTableSizeWithExtras()];

if(currentTable != null) {
System.arraycopy(currentTable, 0, newTable, 0, currentTable.length);
}

newTable[index] = value;

UnsafeHolder.U.putOrderedObject(self, RubyBasicObject.VAR_TABLE_OFFSET, newTable);

// release exclusive access
self.varTableStamp = currentStamp + 1;

return true;
}

/**
* Update the given table table directly.
*
* @param self the object into which to set the variable
* @param currentStamp the current variable table stamp
* @param currentTable the current table
* @param index the index of the variable
* @param value the variable's value
* @return whether the update was successful, for CAS retrying
*/
private static boolean updateTable(RubyBasicObject self, int currentStamp, Object[] currentTable, int index, Object value) {
currentTable[index] = value;

// validate stamp. redo on concurrent modification
return self.varTableStamp == currentStamp;
}
}
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
import org.jruby.RubyClass;
import org.jruby.runtime.ObjectSpace;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;
import org.jruby.util.unsafe.UnsafeHolder;
import static org.jruby.util.StringSupport.EMPTY_STRING_ARRAY;

@@ -545,10 +546,19 @@ synchronized final VariableAccessor allocateVariableAccessor(String name) {
String[] newVariableNames = new String[newIndex + 1];

VariableAccessor newVariableAccessor;
if (UnsafeHolder.U == null) {
newVariableAccessor = new SynchronizedVariableAccessor(realClass, name, newIndex, id);
if (Options.VOLATILE_VARIABLES.load()) {
if (UnsafeHolder.U == null) {
newVariableAccessor = new SynchronizedVariableAccessor(realClass, name, newIndex, id);
} else {
newVariableAccessor = new StampedVariableAccessor(realClass, name, newIndex, id);
}
} else {
newVariableAccessor = new StampedVariableAccessor(realClass, name, newIndex, id);
if (UnsafeHolder.U == null) {
newVariableAccessor = new NonvolatileVariableAccessor(realClass, name, newIndex, id);
} else {
// We still need safe updating of the vartable, so we fall back on sync here too.
newVariableAccessor = new StampedVariableAccessor(realClass, name, newIndex, id);
}
}

System.arraycopy(myVariableNames, 0, newVariableNames, 0, newIndex);
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/util/cli/Options.java
Original file line number Diff line number Diff line change
@@ -163,6 +163,7 @@ public class Options {
public static final Option<Boolean> REIFY_VARIABLES = bool(MISCELLANEOUS, "reify.variables", false, "Attempt to expand instance vars into Java fields");
public static final Option<Boolean> PREFER_IPV4 = bool(MISCELLANEOUS, "net.preferIPv4", true, "Prefer IPv4 network stack");
public static final Option<Boolean> FCNTL_LOCKING = bool(MISCELLANEOUS, "file.flock.fcntl", true, "Use fcntl rather than flock for File#flock");
public static final Option<Boolean> VOLATILE_VARIABLES = bool(MISCELLANEOUS, "volatile.variables", true, "Always ensure volatile semantics for instance variables.");

public static final Option<Boolean> DEBUG_LOADSERVICE = bool(DEBUG, "debug.loadService", false, "Log require/load file searches.");
public static final Option<Boolean> DEBUG_LOADSERVICE_TIMING = bool(DEBUG, "debug.loadService.timing", false, "Log require/load parse+evaluate times.");

0 comments on commit b8b07a5

Please sign in to comment.