mirror of https://github.com/apache/cassandra
(Accord) NPE while trying to serialize FoundKnownMap as value is null half the time but unexpected while serializing
patch by David Capwell; reviewed by Blake Eggleston for CASSANDRA-19253
This commit is contained in:
parent
cb1a05c5d4
commit
a95f072e35
|
|
@ -1 +1 @@
|
|||
Subproject commit 5523cfefef163efee53c8cc57595f5b50ea4f363
|
||||
Subproject commit 0d8f60f742d443365a50115397ff1f0ab10fc694
|
||||
|
|
@ -671,8 +671,12 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
|||
|
||||
public PartitionUpdateCollector add(PartitionUpdate partitionUpdate)
|
||||
{
|
||||
assert partitionUpdate != null;
|
||||
assert partitionUpdate.partitionKey().getPartitioner() == key.getPartitioner();
|
||||
assert partitionUpdate != null : "Null updates are not allowed";
|
||||
assert partitionUpdate.partitionKey().getPartitioner() == key.getPartitioner(): String.format("Update to key %s with partitioner %s (%s) had an update (%s) with a different partitioner! %s (%s)",
|
||||
key,
|
||||
key.getPartitioner(), key.getPartitioner().getClass(),
|
||||
partitionUpdate,
|
||||
partitionUpdate.partitionKey().getPartitioner(), partitionUpdate.partitionKey().getPartitioner().getClass());
|
||||
// note that ImmutableMap.Builder only allows put:ing the same key once, it will fail during build() below otherwise
|
||||
modifications.put(partitionUpdate.metadata().id, partitionUpdate);
|
||||
empty = false;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public interface IAccordService
|
|||
Set<TableId> allTables = new HashSet<>();
|
||||
Set<TableId> newTables = new HashSet<>();
|
||||
txn.keys().forEach(key -> {
|
||||
TableId table = ((AccordRoutableKey) key).table();
|
||||
TableId table = key instanceof AccordRoutableKey ? ((AccordRoutableKey) key).table() : ((TokenRange) key).table();
|
||||
if (allTables.add(table) && !isAccordManagedTable(table))
|
||||
newTables.add(table);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import org.apache.cassandra.db.TypeSizes;
|
|||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.utils.NullableSerializer;
|
||||
|
||||
import static accord.messages.CheckStatus.SerializationSupport.createOk;
|
||||
|
||||
|
|
@ -78,6 +79,8 @@ public class CheckStatusSerializers
|
|||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<FoundKnown> foundKnownNullable = NullableSerializer.wrap(foundKnown);
|
||||
|
||||
public static final IVersionedSerializer<FoundKnownMap> foundKnownMap = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
|
|
@ -88,7 +91,7 @@ public class CheckStatusSerializers
|
|||
for (int i = 0 ; i <= size ; ++i)
|
||||
KeySerializers.routingKey.serialize(knownMap.startAt(i), out, version);
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
foundKnown.serialize(knownMap.valueAt(i), out, version);
|
||||
foundKnownNullable.serialize(knownMap.valueAt(i), out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -100,7 +103,7 @@ public class CheckStatusSerializers
|
|||
starts[i] = KeySerializers.routingKey.deserialize(in, version);
|
||||
FoundKnown[] values = new FoundKnown[size];
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
values[i] = foundKnown.deserialize(in, version);
|
||||
values[i] = foundKnownNullable.deserialize(in, version);
|
||||
return FoundKnownMap.SerializerSupport.create(true, starts, values);
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +115,7 @@ public class CheckStatusSerializers
|
|||
for (int i = 0 ; i <= size ; ++i)
|
||||
result += KeySerializers.routingKey.serializedSize(knownMap.startAt(i), version);
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
result += foundKnown.serializedSize(knownMap.valueAt(i), version);
|
||||
result += foundKnownNullable.serializedSize(knownMap.valueAt(i), version);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ public final class RemoteProcessor implements Processor
|
|||
{
|
||||
log.waitForHighestConsecutive();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.distributed.test.tcm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Txn;
|
||||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.Feature;
|
||||
import org.apache.cassandra.distributed.api.IInvokableInstance;
|
||||
import org.apache.cassandra.distributed.test.TestBaseImpl;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.TokenRange;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
public class AccordAddTableTest extends TestBaseImpl
|
||||
{
|
||||
@Test
|
||||
public void test() throws IOException
|
||||
{
|
||||
try (Cluster cluster = builder().withNodes(6)
|
||||
.withConfig(c -> c.with(Feature.GOSSIP, Feature.NETWORK))
|
||||
.start())
|
||||
{
|
||||
List<Future<?>> results = new ArrayList<>(cluster.size());
|
||||
for (IInvokableInstance inst : cluster)
|
||||
{
|
||||
Future<?> result = inst.asyncRunsOnInstance(() -> {
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
AccordService.instance().maybeConvertTablesToAccord(fakeTxn(i));
|
||||
if (!ClusterMetadata.current().accordTables.contains(fromNum(i)))
|
||||
throw new AssertionError("Table not found in TCM!");
|
||||
}
|
||||
}).call();
|
||||
results.add(result);
|
||||
}
|
||||
FBUtilities.waitOnFutures(results);
|
||||
}
|
||||
}
|
||||
|
||||
private static Txn fakeTxn(int i)
|
||||
{
|
||||
TableId id = fromNum(i);
|
||||
|
||||
Ranges of = Ranges.of(new TokenRange(AccordRoutingKey.SentinelKey.min(id), AccordRoutingKey.SentinelKey.max(id)));
|
||||
return new Txn.InMemory(of, null, null);
|
||||
}
|
||||
|
||||
private static TableId fromNum(int i)
|
||||
{
|
||||
return TableId.fromUUID(new UUID(i, 0)); // not valid... but do we care?
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.service.accord.serializers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.coordinate.Infer;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.messages.CheckStatus.FoundKnownMap;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.FullKeyRoute;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.utils.AccordGens;
|
||||
import accord.utils.Gen;
|
||||
import accord.utils.Gens;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.dht.Murmur3Partitioner;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||
import org.apache.cassandra.utils.AccordGenerators;
|
||||
import org.apache.cassandra.utils.CassandraGenerators;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import static accord.utils.Property.qt;
|
||||
import static org.apache.cassandra.utils.AccordGenerators.fromQT;
|
||||
|
||||
public class CheckStatusSerializersTest
|
||||
{
|
||||
static
|
||||
{
|
||||
DatabaseDescriptor.clientInitialization();
|
||||
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serde()
|
||||
{
|
||||
DataOutputBuffer buffer = new DataOutputBuffer();
|
||||
qt().forAll(foundKnownMap()).check(map -> Assertions.assertThat(serde(CheckStatusSerializers.foundKnownMap, MessagingService.Version.CURRENT.value, buffer, map)).isEqualTo(map));
|
||||
}
|
||||
|
||||
private static <T> T serde(IVersionedSerializer<T> serializer, int version, DataOutputBuffer buffer, T value) throws IOException
|
||||
{
|
||||
buffer.clear();
|
||||
long expectedSize = serializer.serializedSize(value, version);
|
||||
serializer.serialize(value, buffer, version);
|
||||
Assertions.assertThat(buffer.getLength()).isEqualTo(expectedSize);
|
||||
try (DataInputBuffer in = new DataInputBuffer(buffer.unsafeGetBufferAndFlip(), false))
|
||||
{
|
||||
return serializer.deserialize(in, version);
|
||||
}
|
||||
}
|
||||
|
||||
private static Gen<FoundKnownMap> foundKnownMap()
|
||||
{
|
||||
return rs -> {
|
||||
SaveStatus saveStatus = Gens.pick(SaveStatus.values()).next(rs);
|
||||
Infer.InvalidIfNot invalidIfNot = Gens.pick(Infer.InvalidIfNot.values()).next(rs);
|
||||
Ballot promised = AccordGens.ballot().next(rs);
|
||||
Routable.Domain domain = Gens.pick(Routable.Domain.values()).next(rs);
|
||||
Unseekables<?> keysOrRanges;
|
||||
switch (domain)
|
||||
{
|
||||
case Key:
|
||||
// TODO (coverage): don't hard code murmur
|
||||
Gen<AccordRoutingKey> keyGen = AccordGenerators.routingKeyGen(fromQT(CassandraGenerators.TABLE_ID_GEN), Gens.constant(AccordRoutingKey.RoutingKeyKind.TOKEN), fromQT(CassandraGenerators.murmurToken()));
|
||||
AccordRoutingKey homeKey = keyGen.next(rs);
|
||||
List<AccordRoutingKey> forOrdering = Gens.lists(keyGen).unique().ofSizeBetween(1, 10).next(rs);
|
||||
forOrdering.sort(Comparator.naturalOrder());
|
||||
// TODO (coverage): don't hard code keys type
|
||||
keysOrRanges = new FullKeyRoute(homeKey, forOrdering.contains(homeKey), forOrdering.toArray(RoutingKey[]::new));
|
||||
break;
|
||||
case Range:
|
||||
keysOrRanges = AccordGenerators.ranges(Murmur3Partitioner.instance).next(rs);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown domain");
|
||||
}
|
||||
return FoundKnownMap.create(keysOrRanges, saveStatus, invalidIfNot, promised);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -116,11 +116,24 @@ public class AccordGenerators
|
|||
}
|
||||
|
||||
public static Gen<AccordRoutingKey> routingKeyGen(Gen<TableId> tableIdGen, Gen<Token> tokenGen)
|
||||
{
|
||||
return routingKeyGen(tableIdGen, Gens.enums().all(AccordRoutingKey.RoutingKeyKind.class), tokenGen);
|
||||
}
|
||||
|
||||
public static Gen<AccordRoutingKey> routingKeyGen(Gen<TableId> tableIdGen, Gen<AccordRoutingKey.RoutingKeyKind> kindGen, Gen<Token> tokenGen)
|
||||
{
|
||||
return rs -> {
|
||||
TableId tableId = tableIdGen.next(rs);
|
||||
if (rs.nextBoolean()) return new AccordRoutingKey.TokenKey(tableId, tokenGen.next(rs));
|
||||
else return rs.nextBoolean() ? AccordRoutingKey.SentinelKey.min(tableId) : AccordRoutingKey.SentinelKey.max(tableId);
|
||||
AccordRoutingKey.RoutingKeyKind kind = kindGen.next(rs);
|
||||
switch (kind)
|
||||
{
|
||||
case TOKEN:
|
||||
return new AccordRoutingKey.TokenKey(tableId, tokenGen.next(rs));
|
||||
case SENTINEL:
|
||||
return rs.nextBoolean() ? AccordRoutingKey.SentinelKey.min(tableId) : AccordRoutingKey.SentinelKey.max(tableId);
|
||||
default:
|
||||
throw new AssertionError("Unknown kind: " + kind);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue