Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consistent hash code values between JVM instances #590

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/org/jruby/Ruby.java
Expand Up @@ -216,8 +216,14 @@ private Ruby(RubyInstanceConfig config) {
myRandom = new Random(); myRandom = new Random();
} }
this.random = myRandom; this.random = myRandom;
this.hashSeedK0 = this.random.nextLong();
this.hashSeedK1 = this.random.nextLong(); if (RubyInstanceConfig.CONSISTENT_HASHING_ENABLED) {
this.hashSeedK0 = -561135208506705104l;
this.hashSeedK1 = 7114160726623585955l;
} else {
this.hashSeedK0 = this.random.nextLong();
this.hashSeedK1 = this.random.nextLong();
}


this.beanManager.register(new Config(this)); this.beanManager.register(new Config(this));
this.beanManager.register(parserStats); this.beanManager.register(parserStats);
Expand Down
24 changes: 23 additions & 1 deletion src/org/jruby/RubyBoolean.java
Expand Up @@ -46,13 +46,23 @@
*/ */
@JRubyClass(name={"TrueClass", "FalseClass"}) @JRubyClass(name={"TrueClass", "FalseClass"})
public class RubyBoolean extends RubyObject { public class RubyBoolean extends RubyObject {


private final int hashCode;

RubyBoolean(Ruby runtime, boolean value) { RubyBoolean(Ruby runtime, boolean value) {
super(runtime, super(runtime,
(value ? runtime.getTrueClass() : runtime.getFalseClass()), (value ? runtime.getTrueClass() : runtime.getFalseClass()),
false); // Don't put in object space false); // Don't put in object space


if (!value) flags = FALSE_F; if (!value) flags = FALSE_F;

if (RubyInstanceConfig.CONSISTENT_HASHING_ENABLED) {
// default to a fixed value
this.hashCode = value ? 155 : -48;
} else {
// save the object id based hash code;
this.hashCode = System.identityHashCode(this);
}
} }


@Override @Override
Expand Down Expand Up @@ -82,6 +92,7 @@ public static RubyClass createFalseClass(Ruby runtime) {
falseClass.setReifiedClass(RubyBoolean.class); falseClass.setReifiedClass(RubyBoolean.class);


falseClass.defineAnnotatedMethods(False.class); falseClass.defineAnnotatedMethods(False.class);
falseClass.defineAnnotatedMethods(RubyBoolean.class);


falseClass.getMetaClass().undefineMethod("new"); falseClass.getMetaClass().undefineMethod("new");


Expand All @@ -95,6 +106,7 @@ public static RubyClass createTrueClass(Ruby runtime) {
trueClass.setReifiedClass(RubyBoolean.class); trueClass.setReifiedClass(RubyBoolean.class);


trueClass.defineAnnotatedMethods(True.class); trueClass.defineAnnotatedMethods(True.class);
trueClass.defineAnnotatedMethods(RubyBoolean.class);


trueClass.getMetaClass().undefineMethod("new"); trueClass.getMetaClass().undefineMethod("new");


Expand Down Expand Up @@ -161,6 +173,16 @@ public static IRubyObject true_to_s(IRubyObject t) {
} }
} }


@JRubyMethod(name = "hash")
public RubyFixnum hash(ThreadContext context) {
return context.runtime.newFixnum(hashCode());
}

@Override
public int hashCode() {
return hashCode;
}

@Override @Override
public RubyFixnum id() { public RubyFixnum id() {
if ((flags & FALSE_F) == 0) { if ((flags & FALSE_F) == 0) {
Expand Down
8 changes: 8 additions & 0 deletions src/org/jruby/RubyInstanceConfig.java
Expand Up @@ -1653,6 +1653,14 @@ public boolean shouldPrecompileAll() {


public static final boolean COROUTINE_FIBERS = Options.FIBER_COROUTINES.load(); public static final boolean COROUTINE_FIBERS = Options.FIBER_COROUTINES.load();


/**
* Whether to calculate consistent hashes across JVM instances, or to ensure
* un-predicatable hash values using SecureRandom.
*
* Set with the <tt>jruby.consistent.hashing.enabled</tt> system property.
*/
public static final boolean CONSISTENT_HASHING_ENABLED = Options.CONSISTENT_HASHING_ENABLED.load();

private static volatile boolean loadedNativeExtensions = false; private static volatile boolean loadedNativeExtensions = false;


//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
Expand Down
23 changes: 22 additions & 1 deletion src/org/jruby/RubyNil.java
Expand Up @@ -45,9 +45,20 @@
*/ */
@JRubyClass(name="NilClass") @JRubyClass(name="NilClass")
public class RubyNil extends RubyObject { public class RubyNil extends RubyObject {

private final int hashCode;

public RubyNil(Ruby runtime) { public RubyNil(Ruby runtime) {
super(runtime, runtime.getNilClass(), false); super(runtime, runtime.getNilClass(), false);
flags |= NIL_F | FALSE_F; flags |= NIL_F | FALSE_F;

if (RubyInstanceConfig.CONSISTENT_HASHING_ENABLED) {
// default to a fixed value
this.hashCode = 34;
} else {
// save the object id based hash code;
this.hashCode = System.identityHashCode(this);
}
} }


public static final ObjectAllocator NIL_ALLOCATOR = new ObjectAllocator() { public static final ObjectAllocator NIL_ALLOCATOR = new ObjectAllocator() {
Expand Down Expand Up @@ -181,7 +192,17 @@ public static RubyBoolean op_xor(IRubyObject recv, IRubyObject obj) {
public IRubyObject nil_p() { public IRubyObject nil_p() {
return getRuntime().getTrue(); return getRuntime().getTrue();
} }


@JRubyMethod(name = "hash")
public RubyFixnum hash(ThreadContext context) {
return context.runtime.newFixnum(hashCode());
}

@Override
public int hashCode() {
return hashCode;
}

@Override @Override
public RubyFixnum id() { public RubyFixnum id() {
return getRuntime().newFixnum(4); return getRuntime().newFixnum(4);
Expand Down
14 changes: 13 additions & 1 deletion src/org/jruby/RubySymbol.java
Expand Up @@ -65,15 +65,20 @@
import org.jruby.runtime.callsite.NormalCachingCallSite; import org.jruby.runtime.callsite.NormalCachingCallSite;
import org.jruby.runtime.marshal.UnmarshalStream; import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList; import org.jruby.util.ByteList;
import org.jruby.util.PerlHash;
import org.jruby.util.SipHashInline;


/** /**
* Represents a Ruby symbol (e.g. :bar) * Represents a Ruby symbol (e.g. :bar)
*/ */
@JRubyClass(name="Symbol") @JRubyClass(name="Symbol")
public class RubySymbol extends RubyObject { public class RubySymbol extends RubyObject {
public static final long symbolHashSeedK0 = 5238926673095087190l;

private final String symbol; private final String symbol;
private final int id; private final int id;
private final ByteList symbolBytes; private final ByteList symbolBytes;
private final int hashCode;


/** /**
* *
Expand All @@ -99,6 +104,13 @@ private RubySymbol(Ruby runtime, String internedSymbol, ByteList symbolBytes) {
this.symbol = internedSymbol; this.symbol = internedSymbol;
this.symbolBytes = symbolBytes; this.symbolBytes = symbolBytes;
this.id = runtime.allocSymbolId(); this.id = runtime.allocSymbolId();

long hash = runtime.isSiphashEnabled() ? SipHashInline.hash24(
symbolHashSeedK0, 0, symbolBytes.getUnsafeBytes(),
symbolBytes.getBegin(), symbolBytes.getRealSize()) :
PerlHash.hash(symbolHashSeedK0, symbolBytes.getUnsafeBytes(),
symbolBytes.getBegin(), symbolBytes.getRealSize());
this.hashCode = (int) hash;
} }


private RubySymbol(Ruby runtime, String internedSymbol) { private RubySymbol(Ruby runtime, String internedSymbol) {
Expand Down Expand Up @@ -296,7 +308,7 @@ public RubyFixnum hash(ThreadContext context) {


@Override @Override
public int hashCode() { public int hashCode() {
return id; return hashCode;
} }


public int getId() { public int getId() {
Expand Down
1 change: 1 addition & 0 deletions src/org/jruby/util/cli/Options.java
Expand Up @@ -213,6 +213,7 @@ public static String dump() {
public static final Option<Boolean> FIBER_COROUTINES = bool(MISCELLANEOUS, "fiber.coroutines", false, "Use JVM coroutines for Fiber."); public static final Option<Boolean> FIBER_COROUTINES = bool(MISCELLANEOUS, "fiber.coroutines", false, "Use JVM coroutines for Fiber.");
public static final Option<Boolean> GLOBAL_REQUIRE_LOCK = bool(MISCELLANEOUS, "global.require.lock", false, "Use a single global lock for requires."); public static final Option<Boolean> GLOBAL_REQUIRE_LOCK = bool(MISCELLANEOUS, "global.require.lock", false, "Use a single global lock for requires.");
public static final Option<Boolean> NATIVE_EXEC = bool(MISCELLANEOUS, "native.exec", true, "Do a true process-obliterating native exec for Kernel#exec."); public static final Option<Boolean> NATIVE_EXEC = bool(MISCELLANEOUS, "native.exec", true, "Do a true process-obliterating native exec for Kernel#exec.");
public static final Option<Boolean> CONSISTENT_HASHING_ENABLED = bool(MISCELLANEOUS, "consistent.hashing.enabled", false, "Generate consistent object hashes across JVMs");


public static final Option<Boolean> DEBUG_LOADSERVICE = bool(DEBUG, "debug.loadService", false, "Log require/load file searches."); 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."); public static final Option<Boolean> DEBUG_LOADSERVICE_TIMING = bool(DEBUG, "debug.loadService.timing", false, "Log require/load parse+evaluate times.");
Expand Down
14 changes: 14 additions & 0 deletions test/org/jruby/test/TestRubyNil.java
Expand Up @@ -38,6 +38,8 @@
import org.jruby.Ruby; import org.jruby.Ruby;
import org.jruby.RubyFixnum; import org.jruby.RubyFixnum;
import org.jruby.RubyNil; import org.jruby.RubyNil;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.builtin.IRubyObject;


/** /**
* @author chadfowler * @author chadfowler
Expand Down Expand Up @@ -98,4 +100,16 @@ public void testOpXOr() {
assertTrue(RubyNil.op_xor(rubyNil, runtime.getTrue()).isTrue()); assertTrue(RubyNil.op_xor(rubyNil, runtime.getTrue()).isTrue());
assertTrue(RubyNil.op_xor(rubyNil, runtime.getFalse()).isFalse()); assertTrue(RubyNil.op_xor(rubyNil, runtime.getFalse()).isFalse());
} }

public void testHash() {
IRubyObject hash = RuntimeHelpers.invoke(
runtime.getCurrentContext(), rubyNil, "hash");
assertEquals(RubyFixnum.newFixnum(
runtime, System.identityHashCode(rubyNil)), hash);
}

public void testHashCode() {
// should be the default Object#hashCode()
assertEquals(System.identityHashCode(rubyNil), rubyNil.hashCode());
}
} }
9 changes: 9 additions & 0 deletions test/org/jruby/test/TestRubySymbol.java
Expand Up @@ -62,4 +62,13 @@ public void testSymbolTable() throws Exception {
assertSame(another, st.getSymbol("another_name")); assertSame(another, st.getSymbol("another_name"));
assertSame(another, st.fastGetSymbol("another_name")); assertSame(another, st.fastGetSymbol("another_name"));
} }

public void testSymbolHashCode() {
RubySymbol sym = RubySymbol.newSymbol(runtime, "somename");
assertTrue(sym.hashCode() != 0);
assertTrue(sym.hashCode() != sym.getId());
if (runtime.isSiphashEnabled()) {
assertEquals(1706472664, sym.hashCode());
}
}
} }