mirror of https://github.com/apache/cassandra
Restore resumable hints delivery
Backport of CASSANDRA-11960 patch by Tommy Stendahl, Stefan Podkowinski and Branimir Lambov; reviewed by Aleksey Yeschenko for CASSANDRA-14419
This commit is contained in:
parent
1e478d380b
commit
c4982587bf
|
|
@ -1,4 +1,5 @@
|
||||||
3.0.17
|
3.0.17
|
||||||
|
* Restore resumable hints delivery, backport CASSANDRA-11960 (CASSANDRA-14419)
|
||||||
* Always close RT markers returned by ReadCommand#executeLocally() (CASSANDRA-14515)
|
* Always close RT markers returned by ReadCommand#executeLocally() (CASSANDRA-14515)
|
||||||
* Reverse order queries with range tombstones can cause data loss (CASSANDRA-14513)
|
* Reverse order queries with range tombstones can cause data loss (CASSANDRA-14513)
|
||||||
* Fix regression of lagging commitlog flush log message (CASSANDRA-14451)
|
* Fix regression of lagging commitlog flush log message (CASSANDRA-14451)
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,18 @@ import java.util.zip.CRC32;
|
||||||
import org.apache.cassandra.io.util.ChannelProxy;
|
import org.apache.cassandra.io.util.ChannelProxy;
|
||||||
import org.apache.cassandra.io.util.DataPosition;
|
import org.apache.cassandra.io.util.DataPosition;
|
||||||
import org.apache.cassandra.io.util.RandomAccessReader;
|
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||||
|
import org.apache.cassandra.utils.NativeLibrary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link RandomAccessReader} wrapper that calctulates the CRC in place.
|
* A {@link RandomAccessReader} wrapper that calculates the CRC in place.
|
||||||
*
|
*
|
||||||
* Useful for {@link org.apache.cassandra.hints.HintsReader}, for example, where we must verify the CRC, yet don't want
|
* Useful for {@link org.apache.cassandra.hints.HintsReader}, for example, where we must verify the CRC, yet don't want
|
||||||
* to allocate an extra byte array just that purpose. The CRC can be embedded in the input stream and checked via checkCrc().
|
* to allocate an extra byte array just that purpose. The CRC can be embedded in the input stream and checked via checkCrc().
|
||||||
*
|
*
|
||||||
* In addition to calculating the CRC, it allows to enforce a maximim known size. This is needed
|
* In addition to calculating the CRC, it allows to enforce a maximum known size. This is needed
|
||||||
* so that {@link org.apache.cassandra.db.Mutation.MutationSerializer} doesn't blow up the heap when deserializing a
|
* so that {@link org.apache.cassandra.db.Mutation.MutationSerializer} doesn't blow up the heap when deserializing a
|
||||||
* corrupted sequence by reading a huge corrupted length of bytes via
|
* corrupted sequence by reading a huge corrupted length of bytes via
|
||||||
* via {@link org.apache.cassandra.utils.ByteBufferUtil#readWithLength(java.io.DataInput)}.
|
* {@link org.apache.cassandra.utils.ByteBufferUtil#readWithLength(java.io.DataInput)}.
|
||||||
*/
|
*/
|
||||||
public class ChecksummedDataInput extends RandomAccessReader.RandomAccessReaderWithOwnChannel
|
public class ChecksummedDataInput extends RandomAccessReader.RandomAccessReaderWithOwnChannel
|
||||||
{
|
{
|
||||||
|
|
@ -63,9 +64,37 @@ public class ChecksummedDataInput extends RandomAccessReader.RandomAccessReaderW
|
||||||
return new Builder(new ChannelProxy(file)).build();
|
return new Builder(new ChannelProxy(file)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void releaseBuffer()
|
static class Position implements InputPosition
|
||||||
{
|
{
|
||||||
super.releaseBuffer();
|
final long sourcePosition;
|
||||||
|
|
||||||
|
public Position(long sourcePosition)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
this.sourcePosition = sourcePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long subtract(InputPosition other)
|
||||||
|
{
|
||||||
|
return sourcePosition - ((Position)other).sourcePosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a seekable representation of the current position. For compressed files this is chunk position
|
||||||
|
* in file and offset within chunk.
|
||||||
|
*/
|
||||||
|
public InputPosition getSeekPosition()
|
||||||
|
{
|
||||||
|
return new Position(getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seek(InputPosition pos)
|
||||||
|
{
|
||||||
|
updateCrc();
|
||||||
|
bufferOffset = ((Position) pos).sourcePosition;
|
||||||
|
buffer.position(0).limit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetCrc()
|
public void resetCrc()
|
||||||
|
|
@ -80,6 +109,15 @@ public class ChecksummedDataInput extends RandomAccessReader.RandomAccessReaderW
|
||||||
limitMark = mark();
|
limitMark = mark();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position in the source file, which is different for getPosition() for compressed/encrypted files
|
||||||
|
* and may be imprecise.
|
||||||
|
*/
|
||||||
|
protected long getSourcePosition()
|
||||||
|
{
|
||||||
|
return bufferOffset;
|
||||||
|
}
|
||||||
|
|
||||||
public void resetLimit()
|
public void resetLimit()
|
||||||
{
|
{
|
||||||
limit = Long.MAX_VALUE;
|
limit = Long.MAX_VALUE;
|
||||||
|
|
@ -141,6 +179,11 @@ public class ChecksummedDataInput extends RandomAccessReader.RandomAccessReaderW
|
||||||
crcPosition = buffer.position();
|
crcPosition = buffer.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void tryUncacheRead()
|
||||||
|
{
|
||||||
|
NativeLibrary.trySkipCache(getChannel().getFileDescriptor(), 0, getSourcePosition(), getPath());
|
||||||
|
}
|
||||||
|
|
||||||
private void updateCrc()
|
private void updateCrc()
|
||||||
{
|
{
|
||||||
if (crcPosition == buffer.position() || crcUpdateDisabled)
|
if (crcPosition == buffer.position() || crcUpdateDisabled)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ import org.apache.cassandra.utils.memory.BufferPool;
|
||||||
public final class CompressedChecksummedDataInput extends ChecksummedDataInput
|
public final class CompressedChecksummedDataInput extends ChecksummedDataInput
|
||||||
{
|
{
|
||||||
private final ICompressor compressor;
|
private final ICompressor compressor;
|
||||||
private volatile long filePosition = 0;
|
private volatile long filePosition = 0; // Current position in file, advanced when reading chunk.
|
||||||
|
private volatile long sourcePosition = 0; // Current position in file to report, advanced after consuming chunk.
|
||||||
private volatile ByteBuffer compressedBuffer = null;
|
private volatile ByteBuffer compressedBuffer = null;
|
||||||
private final ByteBuffer metadataBuffer = ByteBuffer.allocate(CompressedHintsWriter.METADATA_SIZE);
|
private final ByteBuffer metadataBuffer = ByteBuffer.allocate(CompressedHintsWriter.METADATA_SIZE);
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ public final class CompressedChecksummedDataInput extends ChecksummedDataInput
|
||||||
assert regions == null; //mmapped regions are not supported
|
assert regions == null; //mmapped regions are not supported
|
||||||
|
|
||||||
compressor = builder.compressor;
|
compressor = builder.compressor;
|
||||||
filePosition = builder.position;
|
sourcePosition = filePosition = builder.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,8 +52,56 @@ public final class CompressedChecksummedDataInput extends ChecksummedDataInput
|
||||||
return filePosition == channel.size() && buffer.remaining() == 0;
|
return filePosition == channel.size() && buffer.remaining() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getSourcePosition()
|
||||||
|
{
|
||||||
|
return sourcePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Position extends ChecksummedDataInput.Position
|
||||||
|
{
|
||||||
|
final long bufferStart;
|
||||||
|
final int bufferPosition;
|
||||||
|
|
||||||
|
public Position(long sourcePosition, long bufferStart, int bufferPosition)
|
||||||
|
{
|
||||||
|
super(sourcePosition);
|
||||||
|
this.bufferStart = bufferStart;
|
||||||
|
this.bufferPosition = bufferPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long subtract(InputPosition o)
|
||||||
|
{
|
||||||
|
Position other = (Position) o;
|
||||||
|
return bufferStart - other.bufferStart + bufferPosition - other.bufferPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPosition getSeekPosition()
|
||||||
|
{
|
||||||
|
return new Position(sourcePosition, bufferOffset, buffer.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seek(InputPosition p)
|
||||||
|
{
|
||||||
|
Position pos = (Position) p;
|
||||||
|
bufferOffset = pos.bufferStart;
|
||||||
|
filePosition = pos.sourcePosition;
|
||||||
|
buffer.position(0).limit(0);
|
||||||
|
resetCrc();
|
||||||
|
reBuffer();
|
||||||
|
buffer.position(pos.bufferPosition);
|
||||||
|
assert sourcePosition == pos.sourcePosition;
|
||||||
|
assert bufferOffset == pos.bufferStart;
|
||||||
|
assert buffer.position() == pos.bufferPosition;
|
||||||
|
}
|
||||||
|
|
||||||
protected void reBufferStandard()
|
protected void reBufferStandard()
|
||||||
{
|
{
|
||||||
|
sourcePosition = filePosition;
|
||||||
|
if (isEOF())
|
||||||
|
return;
|
||||||
|
|
||||||
metadataBuffer.clear();
|
metadataBuffer.clear();
|
||||||
channel.read(metadataBuffer, filePosition);
|
channel.read(metadataBuffer, filePosition);
|
||||||
filePosition += CompressedHintsWriter.METADATA_SIZE;
|
filePosition += CompressedHintsWriter.METADATA_SIZE;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.RateLimiter;
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
|
@ -48,12 +50,14 @@ final class HintsDispatchExecutor
|
||||||
private final File hintsDirectory;
|
private final File hintsDirectory;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final AtomicBoolean isPaused;
|
private final AtomicBoolean isPaused;
|
||||||
|
private final Function<InetAddress, Boolean> isAlive;
|
||||||
private final Map<UUID, Future> scheduledDispatches;
|
private final Map<UUID, Future> scheduledDispatches;
|
||||||
|
|
||||||
HintsDispatchExecutor(File hintsDirectory, int maxThreads, AtomicBoolean isPaused)
|
HintsDispatchExecutor(File hintsDirectory, int maxThreads, AtomicBoolean isPaused, Function<InetAddress, Boolean> isAlive)
|
||||||
{
|
{
|
||||||
this.hintsDirectory = hintsDirectory;
|
this.hintsDirectory = hintsDirectory;
|
||||||
this.isPaused = isPaused;
|
this.isPaused = isPaused;
|
||||||
|
this.isAlive = isAlive;
|
||||||
|
|
||||||
scheduledDispatches = new ConcurrentHashMap<>();
|
scheduledDispatches = new ConcurrentHashMap<>();
|
||||||
executor = new JMXEnabledThreadPoolExecutor(maxThreads, 1, TimeUnit.MINUTES,
|
executor = new JMXEnabledThreadPoolExecutor(maxThreads, 1, TimeUnit.MINUTES,
|
||||||
|
|
@ -69,6 +73,14 @@ final class HintsDispatchExecutor
|
||||||
{
|
{
|
||||||
scheduledDispatches.clear();
|
scheduledDispatches.clear();
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
executor.awaitTermination(1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isScheduled(HintsStore store)
|
boolean isScheduled(HintsStore store)
|
||||||
|
|
@ -255,9 +267,10 @@ final class HintsDispatchExecutor
|
||||||
private boolean deliver(HintsDescriptor descriptor, InetAddress address)
|
private boolean deliver(HintsDescriptor descriptor, InetAddress address)
|
||||||
{
|
{
|
||||||
File file = new File(hintsDirectory, descriptor.fileName());
|
File file = new File(hintsDirectory, descriptor.fileName());
|
||||||
Long offset = store.getDispatchOffset(descriptor).orElse(null);
|
InputPosition offset = store.getDispatchOffset(descriptor);
|
||||||
|
|
||||||
try (HintsDispatcher dispatcher = HintsDispatcher.create(file, rateLimiter, address, descriptor.hostId, isPaused))
|
BooleanSupplier shouldAbort = () -> !isAlive.apply(address) || isPaused.get();
|
||||||
|
try (HintsDispatcher dispatcher = HintsDispatcher.create(file, rateLimiter, address, descriptor.hostId, shouldAbort))
|
||||||
{
|
{
|
||||||
if (offset != null)
|
if (offset != null)
|
||||||
dispatcher.seek(offset);
|
dispatcher.seek(offset);
|
||||||
|
|
@ -271,7 +284,7 @@ final class HintsDispatchExecutor
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
store.markDispatchOffset(descriptor, dispatcher.dispatchOffset());
|
store.markDispatchOffset(descriptor, dispatcher.dispatchPosition());
|
||||||
store.offerFirst(descriptor);
|
store.offerFirst(descriptor);
|
||||||
logger.info("Finished hinted handoff of file {} to endpoint {}: {}, partially", descriptor.fileName(), address, hostId);
|
logger.info("Finished hinted handoff of file {} to endpoint {}: {}, partially", descriptor.fileName(), address, hostId);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import java.net.InetAddress;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.RateLimiter;
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
|
|
@ -30,7 +30,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.gms.FailureDetector;
|
|
||||||
import org.apache.cassandra.net.IAsyncCallbackWithFailure;
|
import org.apache.cassandra.net.IAsyncCallbackWithFailure;
|
||||||
import org.apache.cassandra.net.MessageIn;
|
import org.apache.cassandra.net.MessageIn;
|
||||||
import org.apache.cassandra.net.MessagingService;
|
import org.apache.cassandra.net.MessagingService;
|
||||||
|
|
@ -52,25 +51,25 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
private final UUID hostId;
|
private final UUID hostId;
|
||||||
private final InetAddress address;
|
private final InetAddress address;
|
||||||
private final int messagingVersion;
|
private final int messagingVersion;
|
||||||
private final AtomicBoolean isPaused;
|
private final BooleanSupplier abortRequested;
|
||||||
|
|
||||||
private long currentPageOffset;
|
private InputPosition currentPagePosition;
|
||||||
|
|
||||||
private HintsDispatcher(HintsReader reader, UUID hostId, InetAddress address, int messagingVersion, AtomicBoolean isPaused)
|
private HintsDispatcher(HintsReader reader, UUID hostId, InetAddress address, int messagingVersion, BooleanSupplier abortRequested)
|
||||||
{
|
{
|
||||||
currentPageOffset = 0L;
|
currentPagePosition = null;
|
||||||
|
|
||||||
this.reader = reader;
|
this.reader = reader;
|
||||||
this.hostId = hostId;
|
this.hostId = hostId;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.messagingVersion = messagingVersion;
|
this.messagingVersion = messagingVersion;
|
||||||
this.isPaused = isPaused;
|
this.abortRequested = abortRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
static HintsDispatcher create(File file, RateLimiter rateLimiter, InetAddress address, UUID hostId, AtomicBoolean isPaused)
|
static HintsDispatcher create(File file, RateLimiter rateLimiter, InetAddress address, UUID hostId, BooleanSupplier abortRequested)
|
||||||
{
|
{
|
||||||
int messagingVersion = MessagingService.instance().getVersion(address);
|
int messagingVersion = MessagingService.instance().getVersion(address);
|
||||||
return new HintsDispatcher(HintsReader.open(file, rateLimiter), hostId, address, messagingVersion, isPaused);
|
return new HintsDispatcher(HintsReader.open(file, rateLimiter), hostId, address, messagingVersion, abortRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close()
|
public void close()
|
||||||
|
|
@ -78,10 +77,9 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
reader.close();
|
reader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void seek(long bytes)
|
void seek(InputPosition position)
|
||||||
{
|
{
|
||||||
reader.seek(bytes);
|
reader.seek(position);
|
||||||
currentPageOffset = 0L;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -91,7 +89,7 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
{
|
{
|
||||||
for (HintsReader.Page page : reader)
|
for (HintsReader.Page page : reader)
|
||||||
{
|
{
|
||||||
currentPageOffset = page.offset;
|
currentPagePosition = page.position;
|
||||||
if (dispatch(page) != Action.CONTINUE)
|
if (dispatch(page) != Action.CONTINUE)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -102,20 +100,11 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
/**
|
/**
|
||||||
* @return offset of the first non-delivered page
|
* @return offset of the first non-delivered page
|
||||||
*/
|
*/
|
||||||
long dispatchOffset()
|
InputPosition dispatchPosition()
|
||||||
{
|
{
|
||||||
return currentPageOffset;
|
return currentPagePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHostAlive()
|
|
||||||
{
|
|
||||||
return FailureDetector.instance.isAlive(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPaused()
|
|
||||||
{
|
|
||||||
return isPaused.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry in case of a timeout; stop in case of a failure, host going down, or delivery paused
|
// retry in case of a timeout; stop in case of a failure, host going down, or delivery paused
|
||||||
private Action dispatch(HintsReader.Page page)
|
private Action dispatch(HintsReader.Page page)
|
||||||
|
|
@ -156,7 +145,7 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
{
|
{
|
||||||
while (hints.hasNext())
|
while (hints.hasNext())
|
||||||
{
|
{
|
||||||
if (!isHostAlive() || isPaused())
|
if (abortRequested.getAsBoolean())
|
||||||
return Action.ABORT;
|
return Action.ABORT;
|
||||||
callbacks.add(sendFunction.apply(hints.next()));
|
callbacks.add(sendFunction.apply(hints.next()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void seek(long newPosition)
|
void seek(InputPosition newPosition)
|
||||||
{
|
{
|
||||||
input.seek(newPosition);
|
input.seek(newPosition);
|
||||||
}
|
}
|
||||||
|
|
@ -126,21 +126,21 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
|
|
||||||
final class Page
|
final class Page
|
||||||
{
|
{
|
||||||
public final long offset;
|
public final InputPosition position;
|
||||||
|
|
||||||
private Page(long offset)
|
private Page(InputPosition inputPosition)
|
||||||
{
|
{
|
||||||
this.offset = offset;
|
this.position = inputPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<Hint> hintsIterator()
|
Iterator<Hint> hintsIterator()
|
||||||
{
|
{
|
||||||
return new HintsIterator(offset);
|
return new HintsIterator(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<ByteBuffer> buffersIterator()
|
Iterator<ByteBuffer> buffersIterator()
|
||||||
{
|
{
|
||||||
return new BuffersIterator(offset);
|
return new BuffersIterator(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,12 +149,12 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
protected Page computeNext()
|
protected Page computeNext()
|
||||||
{
|
{
|
||||||
NativeLibrary.trySkipCache(input.getChannel().getFileDescriptor(), 0, input.getFilePointer(), input.getPath());
|
input.tryUncacheRead();
|
||||||
|
|
||||||
if (input.isEOF())
|
if (input.isEOF())
|
||||||
return endOfData();
|
return endOfData();
|
||||||
|
|
||||||
return new Page(input.getFilePointer());
|
return new Page(input.getSeekPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,9 +163,9 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
*/
|
*/
|
||||||
final class HintsIterator extends AbstractIterator<Hint>
|
final class HintsIterator extends AbstractIterator<Hint>
|
||||||
{
|
{
|
||||||
private final long offset;
|
private final InputPosition offset;
|
||||||
|
|
||||||
HintsIterator(long offset)
|
HintsIterator(InputPosition offset)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
|
@ -177,12 +177,12 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
long position = input.getFilePointer();
|
InputPosition position = input.getSeekPosition();
|
||||||
|
|
||||||
if (input.isEOF())
|
if (input.isEOF())
|
||||||
return endOfData(); // reached EOF
|
return endOfData(); // reached EOF
|
||||||
|
|
||||||
if (position - offset >= PAGE_SIZE)
|
if (position.subtract(offset) >= PAGE_SIZE)
|
||||||
return endOfData(); // read page size or more bytes
|
return endOfData(); // read page size or more bytes
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -260,9 +260,9 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
*/
|
*/
|
||||||
final class BuffersIterator extends AbstractIterator<ByteBuffer>
|
final class BuffersIterator extends AbstractIterator<ByteBuffer>
|
||||||
{
|
{
|
||||||
private final long offset;
|
private final InputPosition offset;
|
||||||
|
|
||||||
BuffersIterator(long offset)
|
BuffersIterator(InputPosition offset)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
|
|
@ -274,12 +274,12 @@ class HintsReader implements AutoCloseable, Iterable<HintsReader.Page>
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
long position = input.getFilePointer();
|
InputPosition position = input.getSeekPosition();
|
||||||
|
|
||||||
if (input.isEOF())
|
if (input.isEOF())
|
||||||
return endOfData(); // reached EOF
|
return endOfData(); // reached EOF
|
||||||
|
|
||||||
if (position - offset >= PAGE_SIZE)
|
if (position.subtract(offset) >= PAGE_SIZE)
|
||||||
return endOfData(); // read page size or more bytes
|
return endOfData(); // read page size or more bytes
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import java.util.function.Supplier;
|
||||||
import javax.management.MBeanServer;
|
import javax.management.MBeanServer;
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -37,6 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.config.ParameterizedClass;
|
import org.apache.cassandra.config.ParameterizedClass;
|
||||||
|
import org.apache.cassandra.gms.FailureDetector;
|
||||||
|
import org.apache.cassandra.gms.IFailureDetector;
|
||||||
import org.apache.cassandra.metrics.HintedHandoffMetrics;
|
import org.apache.cassandra.metrics.HintedHandoffMetrics;
|
||||||
import org.apache.cassandra.metrics.StorageMetrics;
|
import org.apache.cassandra.metrics.StorageMetrics;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
|
|
@ -60,7 +63,7 @@ public final class HintsService implements HintsServiceMBean
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HintsService.class);
|
private static final Logger logger = LoggerFactory.getLogger(HintsService.class);
|
||||||
|
|
||||||
public static final HintsService instance = new HintsService();
|
public static HintsService instance = new HintsService();
|
||||||
|
|
||||||
private static final String MBEAN_NAME = "org.apache.cassandra.hints:type=HintsService";
|
private static final String MBEAN_NAME = "org.apache.cassandra.hints:type=HintsService";
|
||||||
|
|
||||||
|
|
@ -81,6 +84,12 @@ public final class HintsService implements HintsServiceMBean
|
||||||
public final HintedHandoffMetrics metrics;
|
public final HintedHandoffMetrics metrics;
|
||||||
|
|
||||||
private HintsService()
|
private HintsService()
|
||||||
|
{
|
||||||
|
this(FailureDetector.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
HintsService(IFailureDetector failureDetector)
|
||||||
{
|
{
|
||||||
File hintsDirectory = DatabaseDescriptor.getHintsDirectory();
|
File hintsDirectory = DatabaseDescriptor.getHintsDirectory();
|
||||||
int maxDeliveryThreads = DatabaseDescriptor.getMaxHintsDeliveryThreads();
|
int maxDeliveryThreads = DatabaseDescriptor.getMaxHintsDeliveryThreads();
|
||||||
|
|
@ -92,7 +101,7 @@ public final class HintsService implements HintsServiceMBean
|
||||||
bufferPool = new HintsBufferPool(bufferSize, writeExecutor::flushBuffer);
|
bufferPool = new HintsBufferPool(bufferSize, writeExecutor::flushBuffer);
|
||||||
|
|
||||||
isDispatchPaused = new AtomicBoolean(true);
|
isDispatchPaused = new AtomicBoolean(true);
|
||||||
dispatchExecutor = new HintsDispatchExecutor(hintsDirectory, maxDeliveryThreads, isDispatchPaused);
|
dispatchExecutor = new HintsDispatchExecutor(hintsDirectory, maxDeliveryThreads, isDispatchPaused, failureDetector::isAlive);
|
||||||
|
|
||||||
// periodically empty the current content of the buffers
|
// periodically empty the current content of the buffers
|
||||||
int flushPeriod = DatabaseDescriptor.getHintsFlushPeriodInMS();
|
int flushPeriod = DatabaseDescriptor.getHintsFlushPeriodInMS();
|
||||||
|
|
@ -225,7 +234,7 @@ public final class HintsService implements HintsServiceMBean
|
||||||
* Will abort dispatch sessions that are currently in progress (which is okay, it's idempotent),
|
* Will abort dispatch sessions that are currently in progress (which is okay, it's idempotent),
|
||||||
* and make sure the buffers are flushed, hints files written and fsynced.
|
* and make sure the buffers are flushed, hints files written and fsynced.
|
||||||
*/
|
*/
|
||||||
public synchronized void shutdownBlocking()
|
public synchronized void shutdownBlocking() throws ExecutionException, InterruptedException
|
||||||
{
|
{
|
||||||
if (isShutDown)
|
if (isShutDown)
|
||||||
throw new IllegalStateException("HintsService has already been shut down");
|
throw new IllegalStateException("HintsService has already been shut down");
|
||||||
|
|
@ -237,8 +246,8 @@ public final class HintsService implements HintsServiceMBean
|
||||||
|
|
||||||
triggerFlushingFuture.cancel(false);
|
triggerFlushingFuture.cancel(false);
|
||||||
|
|
||||||
writeExecutor.flushBufferPool(bufferPool);
|
writeExecutor.flushBufferPool(bufferPool).get();
|
||||||
writeExecutor.closeAllWriters();
|
writeExecutor.closeAllWriters().get();
|
||||||
|
|
||||||
dispatchExecutor.shutdownBlocking();
|
dispatchExecutor.shutdownBlocking();
|
||||||
writeExecutor.shutdownBlocking();
|
writeExecutor.shutdownBlocking();
|
||||||
|
|
@ -370,4 +379,12 @@ public final class HintsService implements HintsServiceMBean
|
||||||
{
|
{
|
||||||
return catalog;
|
return catalog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true in case service is shut down.
|
||||||
|
*/
|
||||||
|
public boolean isShutDown()
|
||||||
|
{
|
||||||
|
return isShutDown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ final class HintsStore
|
||||||
private final File hintsDirectory;
|
private final File hintsDirectory;
|
||||||
private final ImmutableMap<String, Object> writerParams;
|
private final ImmutableMap<String, Object> writerParams;
|
||||||
|
|
||||||
private final Map<HintsDescriptor, Long> dispatchOffsets;
|
private final Map<HintsDescriptor, InputPosition> dispatchPositions;
|
||||||
private final Deque<HintsDescriptor> dispatchDequeue;
|
private final Deque<HintsDescriptor> dispatchDequeue;
|
||||||
private final Queue<HintsDescriptor> blacklistedFiles;
|
private final Queue<HintsDescriptor> blacklistedFiles;
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ final class HintsStore
|
||||||
this.hintsDirectory = hintsDirectory;
|
this.hintsDirectory = hintsDirectory;
|
||||||
this.writerParams = writerParams;
|
this.writerParams = writerParams;
|
||||||
|
|
||||||
dispatchOffsets = new ConcurrentHashMap<>();
|
dispatchPositions = new ConcurrentHashMap<>();
|
||||||
dispatchDequeue = new ConcurrentLinkedDeque<>(descriptors);
|
dispatchDequeue = new ConcurrentLinkedDeque<>(descriptors);
|
||||||
blacklistedFiles = new ConcurrentLinkedQueue<>();
|
blacklistedFiles = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
|
@ -143,19 +143,19 @@ final class HintsStore
|
||||||
return !dispatchDequeue.isEmpty();
|
return !dispatchDequeue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Long> getDispatchOffset(HintsDescriptor descriptor)
|
InputPosition getDispatchOffset(HintsDescriptor descriptor)
|
||||||
{
|
{
|
||||||
return Optional.ofNullable(dispatchOffsets.get(descriptor));
|
return dispatchPositions.get(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void markDispatchOffset(HintsDescriptor descriptor, long mark)
|
void markDispatchOffset(HintsDescriptor descriptor, InputPosition inputPosition)
|
||||||
{
|
{
|
||||||
dispatchOffsets.put(descriptor, mark);
|
dispatchPositions.put(descriptor, inputPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanUp(HintsDescriptor descriptor)
|
void cleanUp(HintsDescriptor descriptor)
|
||||||
{
|
{
|
||||||
dispatchOffsets.remove(descriptor);
|
dispatchPositions.remove(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void blacklist(HintsDescriptor descriptor)
|
void blacklist(HintsDescriptor descriptor)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.hints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for file positions as provided by the various ChecksummedDataReader implementations.
|
||||||
|
*/
|
||||||
|
public interface InputPosition
|
||||||
|
{
|
||||||
|
long subtract(InputPosition other);
|
||||||
|
}
|
||||||
|
|
@ -152,6 +152,37 @@ public class HintsCatalogTest
|
||||||
assertEquals(0, store.getDispatchQueueSize());
|
assertEquals(0, store.getDispatchQueueSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteHintsTest() throws IOException
|
||||||
|
{
|
||||||
|
File directory = Files.createTempDirectory(null).toFile();
|
||||||
|
UUID hostId1 = UUID.randomUUID();
|
||||||
|
UUID hostId2 = UUID.randomUUID();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
writeDescriptor(directory, new HintsDescriptor(hostId1, now));
|
||||||
|
writeDescriptor(directory, new HintsDescriptor(hostId1, now+1));
|
||||||
|
writeDescriptor(directory, new HintsDescriptor(hostId2, now+2));
|
||||||
|
writeDescriptor(directory, new HintsDescriptor(hostId2, now+3));
|
||||||
|
|
||||||
|
// load catalog containing two stores (one for each host)
|
||||||
|
HintsCatalog catalog = HintsCatalog.load(directory, ImmutableMap.of());
|
||||||
|
assertEquals(2, catalog.stores().count());
|
||||||
|
assertTrue(catalog.hasFiles());
|
||||||
|
|
||||||
|
// delete all hints from store 1
|
||||||
|
assertTrue(catalog.get(hostId1).hasFiles());
|
||||||
|
catalog.deleteAllHints(hostId1);
|
||||||
|
assertFalse(catalog.get(hostId1).hasFiles());
|
||||||
|
// stores are still keepts for each host, even after deleting hints
|
||||||
|
assertEquals(2, catalog.stores().count());
|
||||||
|
assertTrue(catalog.hasFiles());
|
||||||
|
|
||||||
|
// delete all hints from all stores
|
||||||
|
catalog.deleteAllHints();
|
||||||
|
assertEquals(2, catalog.stores().count());
|
||||||
|
assertFalse(catalog.hasFiles());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("EmptyTryBlock")
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
private static void writeDescriptor(File directory, HintsDescriptor descriptor) throws IOException
|
private static void writeDescriptor(File directory, HintsDescriptor descriptor) throws IOException
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,11 @@ public class HintsCompressionTest
|
||||||
try (HintsReader reader = HintsReader.open(new File(dir, descriptor.fileName())))
|
try (HintsReader reader = HintsReader.open(new File(dir, descriptor.fileName())))
|
||||||
{
|
{
|
||||||
List<Hint> deserialized = new ArrayList<>(hintNum);
|
List<Hint> deserialized = new ArrayList<>(hintNum);
|
||||||
|
List<InputPosition> pagePositions = new ArrayList<>(hintNum);
|
||||||
|
|
||||||
for (HintsReader.Page page: reader)
|
for (HintsReader.Page page: reader)
|
||||||
{
|
{
|
||||||
|
pagePositions.add(page.position);
|
||||||
Iterator<Hint> iterator = page.hintsIterator();
|
Iterator<Hint> iterator = page.hintsIterator();
|
||||||
while (iterator.hasNext())
|
while (iterator.hasNext())
|
||||||
{
|
{
|
||||||
|
|
@ -134,6 +136,21 @@ public class HintsCompressionTest
|
||||||
HintsTestUtil.assertHintsEqual(expected, deserialized.get(hintNum));
|
HintsTestUtil.assertHintsEqual(expected, deserialized.get(hintNum));
|
||||||
hintNum++;
|
hintNum++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// explicitely seek to each page by iterating collected page positions and check if hints still match as expected
|
||||||
|
int hintOffset = 0;
|
||||||
|
for (InputPosition pos : pagePositions)
|
||||||
|
{
|
||||||
|
reader.seek(pos);
|
||||||
|
HintsReader.Page page = reader.iterator().next();
|
||||||
|
Iterator<Hint> iterator = page.hintsIterator();
|
||||||
|
while (iterator.hasNext())
|
||||||
|
{
|
||||||
|
Hint seekedHint = iterator.next();
|
||||||
|
HintsTestUtil.assertHintsEqual(hints.get(hintOffset), seekedHint);
|
||||||
|
hintOffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
/*
|
||||||
|
* 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.hints;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.datastax.driver.core.utils.MoreFutures;
|
||||||
|
|
||||||
|
import org.apache.cassandra.SchemaLoader;
|
||||||
|
import org.apache.cassandra.UpdateBuilder;
|
||||||
|
import org.apache.cassandra.config.CFMetaData;
|
||||||
|
import org.apache.cassandra.config.Schema;
|
||||||
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.gms.IFailureDetectionEventListener;
|
||||||
|
import org.apache.cassandra.gms.IFailureDetector;
|
||||||
|
import org.apache.cassandra.metrics.StorageMetrics;
|
||||||
|
import org.apache.cassandra.net.MessageIn;
|
||||||
|
import org.apache.cassandra.net.MessagingService;
|
||||||
|
import org.apache.cassandra.net.MockMessagingService;
|
||||||
|
import org.apache.cassandra.net.MockMessagingSpy;
|
||||||
|
import org.apache.cassandra.schema.KeyspaceParams;
|
||||||
|
import org.apache.cassandra.service.StorageService;
|
||||||
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.Util.dk;
|
||||||
|
import static org.apache.cassandra.net.MockMessagingService.verb;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class HintsServiceTest
|
||||||
|
{
|
||||||
|
private static final String KEYSPACE = "hints_service_test";
|
||||||
|
private static final String TABLE = "table";
|
||||||
|
|
||||||
|
private final MockFailureDetector failureDetector = new MockFailureDetector();
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void defineSchema()
|
||||||
|
{
|
||||||
|
SchemaLoader.prepareServer();
|
||||||
|
StorageService.instance.initServer();
|
||||||
|
SchemaLoader.createKeyspace(KEYSPACE,
|
||||||
|
KeyspaceParams.simple(1),
|
||||||
|
SchemaLoader.standardCFMD(KEYSPACE, TABLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanup()
|
||||||
|
{
|
||||||
|
MockMessagingService.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void reinstanciateService() throws ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
MessagingService.instance().clearMessageSinks();
|
||||||
|
|
||||||
|
if (!HintsService.instance.isShutDown())
|
||||||
|
{
|
||||||
|
HintsService.instance.shutdownBlocking();
|
||||||
|
HintsService.instance.deleteAllHints();
|
||||||
|
}
|
||||||
|
|
||||||
|
failureDetector.isAlive = true;
|
||||||
|
HintsService.instance = new HintsService(failureDetector);
|
||||||
|
HintsService.instance.startDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDispatchHints() throws InterruptedException, ExecutionException
|
||||||
|
{
|
||||||
|
long cnt = StorageMetrics.totalHints.getCount();
|
||||||
|
|
||||||
|
// create spy for hint messages
|
||||||
|
MockMessagingSpy spy = sendHintsAndResponses(100, -1);
|
||||||
|
|
||||||
|
// metrics should have been updated with number of create hints
|
||||||
|
assertEquals(cnt + 100, StorageMetrics.totalHints.getCount());
|
||||||
|
|
||||||
|
// wait until hints have been send
|
||||||
|
spy.interceptMessageOut(100).get();
|
||||||
|
spy.interceptNoMsg(500, TimeUnit.MILLISECONDS).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPauseAndResume() throws InterruptedException, ExecutionException
|
||||||
|
{
|
||||||
|
HintsService.instance.pauseDispatch();
|
||||||
|
|
||||||
|
// create spy for hint messages
|
||||||
|
MockMessagingSpy spy = sendHintsAndResponses(100, -1);
|
||||||
|
|
||||||
|
// we should not send any hints while paused
|
||||||
|
ListenableFuture<Boolean> noMessagesWhilePaused = spy.interceptNoMsg(15, TimeUnit.SECONDS);
|
||||||
|
Futures.addCallback(noMessagesWhilePaused, new MoreFutures.SuccessCallback<Boolean>()
|
||||||
|
{
|
||||||
|
public void onSuccess(@Nullable Boolean aBoolean)
|
||||||
|
{
|
||||||
|
HintsService.instance.resumeDispatch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Futures.allAsList(
|
||||||
|
noMessagesWhilePaused,
|
||||||
|
spy.interceptMessageOut(100),
|
||||||
|
spy.interceptNoMsg(200, TimeUnit.MILLISECONDS)
|
||||||
|
).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPageRetry() throws InterruptedException, ExecutionException, TimeoutException
|
||||||
|
{
|
||||||
|
// create spy for hint messages, but only create responses for 5 hints
|
||||||
|
MockMessagingSpy spy = sendHintsAndResponses(20, 5);
|
||||||
|
|
||||||
|
Futures.allAsList(
|
||||||
|
// the dispatcher will always send all hints within the current page
|
||||||
|
// and only wait for the acks before going to the next page
|
||||||
|
spy.interceptMessageOut(20),
|
||||||
|
spy.interceptNoMsg(200, TimeUnit.MILLISECONDS),
|
||||||
|
|
||||||
|
// next tick will trigger a retry of the same page as we only replied with 5/20 acks
|
||||||
|
spy.interceptMessageOut(20)
|
||||||
|
).get();
|
||||||
|
|
||||||
|
// marking the destination node as dead should stop sending hints
|
||||||
|
failureDetector.isAlive = false;
|
||||||
|
spy.interceptNoMsg(20, TimeUnit.SECONDS).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPageSeek() throws InterruptedException, ExecutionException
|
||||||
|
{
|
||||||
|
// create spy for hint messages, stop replying after 12k (should be on 3rd page)
|
||||||
|
MockMessagingSpy spy = sendHintsAndResponses(20000, 12000);
|
||||||
|
|
||||||
|
// At this point the dispatcher will constantly retry the page we stopped acking,
|
||||||
|
// thus we receive the same hints from the page multiple times and in total more than
|
||||||
|
// all written hints. Lets just consume them for a while and then pause the dispatcher.
|
||||||
|
spy.interceptMessageOut(22000).get();
|
||||||
|
HintsService.instance.pauseDispatch();
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
// verify that we have a dispatch offset set for the page we're currently stuck at
|
||||||
|
HintsStore store = HintsService.instance.getCatalog().get(StorageService.instance.getLocalHostUUID());
|
||||||
|
HintsDescriptor descriptor = store.poll();
|
||||||
|
store.offerFirst(descriptor); // add again for cleanup during re-instanciation
|
||||||
|
InputPosition dispatchOffset = store.getDispatchOffset(descriptor);
|
||||||
|
assertTrue(dispatchOffset != null);
|
||||||
|
assertTrue(((ChecksummedDataInput.Position) dispatchOffset).sourcePosition > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockMessagingSpy sendHintsAndResponses(int noOfHints, int noOfResponses)
|
||||||
|
{
|
||||||
|
// create spy for hint messages, but only create responses for noOfResponses hints
|
||||||
|
MessageIn<HintResponse> messageIn = MessageIn.create(FBUtilities.getBroadcastAddress(),
|
||||||
|
HintResponse.instance,
|
||||||
|
Collections.emptyMap(),
|
||||||
|
MessagingService.Verb.REQUEST_RESPONSE,
|
||||||
|
MessagingService.current_version);
|
||||||
|
|
||||||
|
MockMessagingSpy spy;
|
||||||
|
if (noOfResponses != -1)
|
||||||
|
{
|
||||||
|
spy = MockMessagingService.when(verb(MessagingService.Verb.HINT)).respondN(messageIn, noOfResponses);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spy = MockMessagingService.when(verb(MessagingService.Verb.HINT)).respond(messageIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and write noOfHints using service
|
||||||
|
UUID hostId = StorageService.instance.getLocalHostUUID();
|
||||||
|
for (int i = 0; i < noOfHints; i++)
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
DecoratedKey dkey = dk(String.valueOf(i));
|
||||||
|
CFMetaData cfMetaData = Schema.instance.getCFMetaData(KEYSPACE, TABLE);
|
||||||
|
|
||||||
|
UpdateBuilder builder = UpdateBuilder.create(cfMetaData, dkey)
|
||||||
|
.withTimestamp(now)
|
||||||
|
.newRow("column0")
|
||||||
|
.add("val", "value0");
|
||||||
|
Hint hint = Hint.create((Mutation) builder.makeMutation(), now);
|
||||||
|
|
||||||
|
HintsService.instance.write(hostId, hint);
|
||||||
|
}
|
||||||
|
return spy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MockFailureDetector implements IFailureDetector
|
||||||
|
{
|
||||||
|
private boolean isAlive = true;
|
||||||
|
|
||||||
|
public boolean isAlive(InetAddress ep)
|
||||||
|
{
|
||||||
|
return isAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interpret(InetAddress ep)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void report(InetAddress ep)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerFailureDetectionEventListener(IFailureDetectionEventListener listener)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterFailureDetectionEventListener(IFailureDetectionEventListener listener)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(InetAddress ep)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forceConviction(InetAddress ep)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue