Ensure compatibility with 3.12 and Chronicle Maps created with Integer key, add ChronicleMap3_12IntegerKeyCompatibilityTest

This commit is contained in:
leventov
2017-02-19 17:20:09 -06:00
parent d31d974e2e
commit c90e065e71
11 changed files with 282 additions and 7 deletions
@@ -17,20 +17,25 @@
package net.openhft.chronicle.hash.impl.stage.data.bytes;
import net.openhft.chronicle.algo.hashing.LongHashFunction;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.bytes.RandomDataInput;
import net.openhft.chronicle.hash.AbstractData;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.entry.HashEntryStages;
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
import net.openhft.chronicle.hash.impl.stage.hash.CheckOnEachPublicOperation;
import net.openhft.chronicle.hash.impl.stage.hash.KeyBytesInterop;
import net.openhft.chronicle.hash.serialization.impl.IntegerDataAccess;
import net.openhft.chronicle.hash.serialization.impl.WrongXxHash;
import net.openhft.sg.Stage;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;
@Staged
public class EntryKeyBytesData<K> extends AbstractData<K> {
@StageRef VanillaChronicleHashHolder<K> hh;
@StageRef KeyBytesInterop<K> ki;
@StageRef SegmentStages s;
@StageRef HashEntryStages<K> entry;
@@ -62,6 +67,17 @@ public class EntryKeyBytesData<K> extends AbstractData<K> {
return entry.keySize;
}
@Override
public long hash(LongHashFunction f) {
checkOnEachPublicOperation.checkOnEachPublicOperation();
if (f == LongHashFunction.xx_r39() && entry.keySize == 4 &&
hh.h().keyDataAccess instanceof IntegerDataAccess) {
return WrongXxHash.hashInt(s.segmentBS.readInt(entry.keyOffset));
} else {
return super.hash(f);
}
}
@Override
public K get() {
checkOnEachPublicOperation.checkOnEachPublicOperation();
@@ -18,15 +18,20 @@
package net.openhft.chronicle.hash.impl.stage.iter;
import net.openhft.chronicle.algo.hashing.LongHashFunction;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.entry.HashEntryStages;
import net.openhft.chronicle.hash.impl.stage.entry.KeyHashCode;
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
import net.openhft.chronicle.hash.serialization.impl.IntegerDataAccess;
import net.openhft.chronicle.hash.serialization.impl.WrongXxHash;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;
@Staged
public class IterationKeyHashCode implements KeyHashCode {
@StageRef VanillaChronicleHashHolder<?> hh;
@StageRef SegmentStages s;
@StageRef HashEntryStages<?> e;
@@ -35,7 +40,11 @@ public class IterationKeyHashCode implements KeyHashCode {
void initKeyHash() {
long addr = s.tierBaseAddr + e.keyOffset;
long len = e.keySize;
keyHash = LongHashFunction.xx_r39().hashMemory(addr, len);
if (len == 4 && hh.h().keyDataAccess instanceof IntegerDataAccess) {
keyHash = WrongXxHash.hashInt(OS.memory().readInt(addr));
} else {
keyHash = LongHashFunction.xx_r39().hashMemory(addr, len);
}
}
@Override
@@ -181,11 +181,13 @@ public class TierRecovery {
long entry = hashLookup.readEntry(currentTierBaseAddr, hlPos);
if (!hashLookup.empty(entry)) {
e.readExistingEntry(hashLookup.value(entry));
Data key = (Data) e.key();
Data key = e.key();
try (ExternalMapQueryContext<?, ?, ?> c = m.queryContext(key)) {
MapEntry<?, ?> entry2 = c.entry();
Data<?> key2 = ((MapEntry) c).key();
if (key2.bytes().address(key2.offset()) != key.bytes().address(key.offset())) {
long keyAddress = key.bytes().address(key.offset());
long key2Address = key2.bytes().address(key2.offset());
if (key2Address != keyAddress) {
lh.LOG.error("entries with duplicate key {} in segment {}: " +
"with values {} and {}, removing the latter",
key, c.segmentIndex(),
@@ -29,6 +29,14 @@ import net.openhft.chronicle.wire.WireOut;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @deprecated use {@link IntegerDataAccess_3_13} instead. This class is not removed only because
* users who created a Chronicle Map with older version of the library with this class as the key or
* value data access should be able to read and access the map with the present version of the
* Chronicle Map library.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
public final class IntegerDataAccess extends AbstractData<Integer>
implements DataAccess<Integer>, Data<Integer> {
@@ -78,7 +86,11 @@ public final class IntegerDataAccess extends AbstractData<Integer>
@Override
public long hash(LongHashFunction f) {
return f.hashInt(instance);
if (f == LongHashFunction.xx_r39()) {
return WrongXxHash.hashInt(instance);
} else {
return f.hashInt(instance);
}
}
@Override
@@ -115,6 +127,7 @@ public final class IntegerDataAccess extends AbstractData<Integer>
}
@Override
@SuppressWarnings("deprecation")
public DataAccess<Integer> copy() {
return new IntegerDataAccess();
}
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.serialization.impl;
import net.openhft.chronicle.algo.hashing.LongHashFunction;
import net.openhft.chronicle.bytes.BytesStore;
import net.openhft.chronicle.bytes.RandomDataInput;
import net.openhft.chronicle.bytes.RandomDataOutput;
import net.openhft.chronicle.hash.AbstractData;
import net.openhft.chronicle.hash.Data;
import net.openhft.chronicle.hash.serialization.DataAccess;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class IntegerDataAccess_3_13 extends AbstractData<Integer>
implements DataAccess<Integer>, Data<Integer> {
// Cache fields
private transient boolean bsInit;
private transient BytesStore bs;
/** State field */
private transient Integer instance;
public IntegerDataAccess_3_13() {
initTransients();
}
private void initTransients() {
bs = BytesStore.wrap(new byte[4]);
}
@Override
public RandomDataInput bytes() {
if (!bsInit) {
bs.writeInt(0, instance);
bsInit = true;
}
return bs;
}
@Override
public long offset() {
return 0;
}
@Override
public long size() {
return 4;
}
@Override
public Integer get() {
return instance;
}
@Override
public Integer getUsing(@Nullable Integer using) {
return instance;
}
@Override
public long hash(LongHashFunction f) {
return f.hashInt(instance);
}
@Override
public boolean equivalent(RandomDataInput source, long sourceOffset) {
return source.readInt(sourceOffset) == instance;
}
@Override
public void writeTo(RandomDataOutput target, long targetOffset) {
target.writeInt(targetOffset, instance);
}
@Override
public Data<Integer> getData(@NotNull Integer instance) {
this.instance = instance;
bsInit = false;
return this;
}
@Override
public void uninit() {
instance = null;
}
@Override
public void readMarshallable(@NotNull WireIn wireIn) {
// no config fields to read
initTransients();
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
// no config fields to write
}
@Override
public DataAccess<Integer> copy() {
return new IntegerDataAccess_3_13();
}
}
@@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable;
/**
* As a writer for top-level Chronicle Map's key or value type (Integer), {@code IntegerMarshaller}
* is deprecated in favor of {@link IntegerDataAccess}. As reader and element writer for {@link
* is deprecated in favor of {@link IntegerDataAccess_3_13}. As reader and element writer for {@link
* ListMarshaller} and similar composite marshallers, {@code IntegerMarshaller} is not deprecated.
*/
public final class IntegerMarshaller
@@ -122,7 +122,7 @@ public final class SerializationBuilder<T> implements Cloneable {
sizeMarshaller(constant(8));
} else if (tClass == Integer.class) {
reader((SizedReader<T>) IntegerMarshaller.INSTANCE);
dataAccess((DataAccess<T>) new IntegerDataAccess());
dataAccess((DataAccess<T>) new IntegerDataAccess_3_13());
sizeMarshaller(constant(4));
} else if (tClass == byte[].class) {
reader((SizedReader<T>) ByteArraySizedReader.INSTANCE);
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.serialization.impl;
/**
* @deprecated this class exists for compatibility with old versions of Chronicle Map, which used
* wrong version of xxHash.
*/
@Deprecated
public final class WrongXxHash {
// xxHash constants
private static final long P1 = -7046029288634856825L;
private static final long P2 = -4417276706812531889L;
private static final long P3 = 1609587929392839161L;
private static final long P5 = 2870177450012600261L;
public static long hashInt(int input) {
long hash = P5 + 4;
// Preserve old, wrong behaviour. In the correct version, input is widened to long as
// unsigned here.
hash ^= input * P1;
hash = Long.rotateLeft(hash, 23) * P2 + P3;
return finalize(hash);
}
private static long finalize(long hash) {
hash ^= hash >>> 33;
hash *= P2;
hash ^= hash >>> 29;
hash *= P3;
hash ^= hash >>> 32;
return hash;
}
private WrongXxHash() {}
}