Merging changes from upstream
This commit is contained in:
parent
aa350ee0e7
commit
0104a91828
66 changed files with 1557 additions and 1537 deletions
|
|
@ -26,7 +26,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
||||||
import software.amazon.kinesis.coordinator.Scheduler;
|
import software.amazon.kinesis.coordinator.Scheduler;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorFactory;
|
import software.amazon.kinesis.processor.RecordProcessorFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main app that launches the scheduler that runs the multi-language record processor.
|
* Main app that launches the scheduler that runs the multi-language record processor.
|
||||||
|
|
@ -73,7 +73,7 @@ public class MultiLangDaemon implements Callable<Integer> {
|
||||||
this(buildWorker(recordProcessorFactory, configuration, workerThreadPool));
|
this(buildWorker(recordProcessorFactory, configuration, workerThreadPool));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Scheduler buildWorker(IRecordProcessorFactory recordProcessorFactory,
|
private static Scheduler buildWorker(RecordProcessorFactory recordProcessorFactory,
|
||||||
KinesisClientLibConfiguration configuration, ExecutorService workerThreadPool) {
|
KinesisClientLibConfiguration configuration, ExecutorService workerThreadPool) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
||||||
import software.amazon.kinesis.lifecycle.InitializationInput;
|
import software.amazon.kinesis.lifecycle.InitializationInput;
|
||||||
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
||||||
|
|
@ -39,7 +39,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
* called.
|
* called.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MultiLangRecordProcessor implements IRecordProcessor, IShutdownNotificationAware {
|
public class MultiLangRecordProcessor implements RecordProcessor, ShutdownNotificationAware {
|
||||||
private static final int EXIT_VALUE = 1;
|
private static final int EXIT_VALUE = 1;
|
||||||
|
|
||||||
/** Whether or not record processor initialization is successful. Defaults to false. */
|
/** Whether or not record processor initialization is successful. Defaults to false. */
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ package com.amazonaws.services.kinesis.multilang;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorFactory;
|
import software.amazon.kinesis.processor.RecordProcessorFactory;
|
||||||
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
* Creates {@link MultiLangRecordProcessor}'s.
|
* Creates {@link MultiLangRecordProcessor}'s.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MultiLangRecordProcessorFactory implements IRecordProcessorFactory {
|
public class MultiLangRecordProcessorFactory implements RecordProcessorFactory {
|
||||||
private static final String COMMAND_DELIMETER_REGEX = " +";
|
private static final String COMMAND_DELIMETER_REGEX = " +";
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
|
|
@ -63,7 +63,7 @@ public class MultiLangRecordProcessorFactory implements IRecordProcessorFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IRecordProcessor createProcessor() {
|
public RecordProcessor createProcessor() {
|
||||||
log.debug("Creating new record processor for client executable: {}", command);
|
log.debug("Creating new record processor for client executable: {}", command);
|
||||||
/*
|
/*
|
||||||
* Giving ProcessBuilder the command as an array of Strings allows users to specify command line arguments.
|
* Giving ProcessBuilder the command as an array of Strings allows users to specify command line arguments.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
@ -32,7 +32,7 @@ public class StreamingRecordProcessorFactoryTest {
|
||||||
@Test
|
@Test
|
||||||
public void createProcessorTest() {
|
public void createProcessorTest() {
|
||||||
MultiLangRecordProcessorFactory factory = new MultiLangRecordProcessorFactory("somecommand", null, configuration);
|
MultiLangRecordProcessorFactory factory = new MultiLangRecordProcessorFactory("somecommand", null, configuration);
|
||||||
IRecordProcessor processor = factory.createProcessor();
|
RecordProcessor processor = factory.createProcessor();
|
||||||
|
|
||||||
Assert.assertEquals("Should have constructed a StreamingRecordProcessor", MultiLangRecordProcessor.class,
|
Assert.assertEquals("Should have constructed a StreamingRecordProcessor", MultiLangRecordProcessor.class,
|
||||||
processor.getClass());
|
processor.getClass());
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@
|
||||||
package software.amazon.kinesis.checkpoint;
|
package software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class encapsulating the 2 pieces of state stored in a checkpoint.
|
* A class encapsulating the 2 pieces of state stored in a checkpoint.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@Accessors(fluent = true)
|
||||||
public class Checkpoint {
|
public class Checkpoint {
|
||||||
private final ExtendedSequenceNumber checkpoint;
|
private final ExtendedSequenceNumber checkpoint;
|
||||||
private final ExtendedSequenceNumber pendingCheckpoint;
|
private final ExtendedSequenceNumber pendingCheckpoint;
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,15 @@
|
||||||
package software.amazon.kinesis.checkpoint;
|
package software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
|
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLeaseManager;
|
import software.amazon.kinesis.leases.KinesisClientLeaseManager;
|
||||||
import software.amazon.kinesis.leases.LeaseManagementConfig;
|
import software.amazon.kinesis.leases.KinesisClientLibLeaseCoordinator;
|
||||||
|
import software.amazon.kinesis.leases.LeaseCoordinator;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
||||||
|
|
||||||
|
|
@ -53,7 +55,7 @@ public class CheckpointConfig {
|
||||||
|
|
||||||
private long failoverTimeMillis = 10000L;
|
private long failoverTimeMillis = 10000L;
|
||||||
|
|
||||||
private ILeaseManager leaseManager;
|
private ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
|
|
||||||
private int maxLeasesForWorker = Integer.MAX_VALUE;
|
private int maxLeasesForWorker = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
|
@ -67,7 +69,9 @@ public class CheckpointConfig {
|
||||||
|
|
||||||
private long epsilonMillis = 25L;
|
private long epsilonMillis = 25L;
|
||||||
|
|
||||||
public ILeaseManager leaseManager() {
|
private LeaseCoordinator<KinesisClientLease> leaseCoordinator;
|
||||||
|
|
||||||
|
public ILeaseManager<KinesisClientLease> leaseManager() {
|
||||||
if (leaseManager == null) {
|
if (leaseManager == null) {
|
||||||
leaseManager = new KinesisClientLeaseManager(tableName, amazonDynamoDB, consistentReads);
|
leaseManager = new KinesisClientLeaseManager(tableName, amazonDynamoDB, consistentReads);
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +80,14 @@ public class CheckpointConfig {
|
||||||
|
|
||||||
public CheckpointFactory checkpointFactory() {
|
public CheckpointFactory checkpointFactory() {
|
||||||
if (checkpointFactory == null) {
|
if (checkpointFactory == null) {
|
||||||
checkpointFactory = new DynamoDBCheckpointFactory(leaseManager(),
|
checkpointFactory = new DynamoDBCheckpointFactory(leaseCoordinator(), leaseManager(), metricsFactory());
|
||||||
|
}
|
||||||
|
return checkpointFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LeaseCoordinator<KinesisClientLease> leaseCoordinator() {
|
||||||
|
if (leaseCoordinator == null) {
|
||||||
|
leaseCoordinator = new KinesisClientLibLeaseCoordinator(leaseManager(),
|
||||||
workerIdentifier(),
|
workerIdentifier(),
|
||||||
failoverTimeMillis(),
|
failoverTimeMillis(),
|
||||||
epsilonMillis(),
|
epsilonMillis(),
|
||||||
|
|
@ -85,6 +96,6 @@ public class CheckpointConfig {
|
||||||
maxLeaseRenewalThreads(),
|
maxLeaseRenewalThreads(),
|
||||||
metricsFactory());
|
metricsFactory());
|
||||||
}
|
}
|
||||||
return checkpointFactory;
|
return leaseCoordinator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
package software.amazon.kinesis.checkpoint;
|
package software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
import software.amazon.kinesis.leases.KinesisClientLibLeaseCoordinator;
|
import software.amazon.kinesis.leases.KinesisClientLibLeaseCoordinator;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface CheckpointFactory {
|
public interface CheckpointFactory {
|
||||||
ICheckpoint createCheckpoint(KinesisClientLibLeaseCoordinator leaseCoordinator);
|
Checkpointer createCheckpoint(KinesisClientLibLeaseCoordinator leaseCoordinator);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public class DoesNothingPreparedCheckpointer implements IPreparedCheckpointer {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ExtendedSequenceNumber getPendingCheckpoint() {
|
public ExtendedSequenceNumber pendingCheckpoint() {
|
||||||
return sequenceNumber;
|
return sequenceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ package software.amazon.kinesis.checkpoint;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLibLeaseCoordinator;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
|
import software.amazon.kinesis.leases.LeaseCoordinator;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -28,19 +29,15 @@ import software.amazon.kinesis.processor.ICheckpoint;
|
||||||
@Data
|
@Data
|
||||||
public class DynamoDBCheckpointFactory implements CheckpointFactory {
|
public class DynamoDBCheckpointFactory implements CheckpointFactory {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ILeaseManager leaseManager;
|
private final LeaseCoordinator<KinesisClientLease> leaseLeaseCoordinator;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String workerIdentifier;
|
private final ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
private final long failoverTimeMillis;
|
|
||||||
private final long epsilonMillis;
|
|
||||||
private final int maxLeasesForWorker;
|
|
||||||
private final int maxLeasesToStealAtOneTime;
|
|
||||||
private final int maxLeaseRenewalThreads;
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IMetricsFactory metricsFactory;
|
private final IMetricsFactory metricsFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICheckpoint createCheckpoint(KinesisClientLibLeaseCoordinator leaseCoordinator) {
|
public Checkpointer createCheckpoint() {
|
||||||
return leaseCoordinator;
|
return new DynamoDBCheckpointer(leaseLeaseCoordinator, leaseManager, metricsFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Amazon Software License (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://aws.amazon.com/asl/
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibDependencyException;
|
||||||
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
||||||
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
|
||||||
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingException;
|
||||||
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.internal.KinesisClientLibIOException;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
|
import software.amazon.kinesis.leases.LeaseCoordinator;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class DynamoDBCheckpointer implements Checkpointer {
|
||||||
|
@NonNull
|
||||||
|
private final LeaseCoordinator<KinesisClientLease> leaseCoordinator;
|
||||||
|
@NonNull
|
||||||
|
private final ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
|
@NonNull
|
||||||
|
private final IMetricsFactory metricsFactory;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCheckpoint(final String shardId, final ExtendedSequenceNumber checkpointValue,
|
||||||
|
final String concurrencyToken) throws KinesisClientLibException {
|
||||||
|
try {
|
||||||
|
boolean wasSuccessful = setCheckpoint(shardId, checkpointValue, UUID.fromString(concurrencyToken));
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
throw new ShutdownException("Can't update checkpoint - instance doesn't hold the lease for this shard");
|
||||||
|
}
|
||||||
|
} catch (ProvisionedThroughputException e) {
|
||||||
|
throw new ThrottlingException("Got throttled while updating checkpoint.", e);
|
||||||
|
} catch (InvalidStateException e) {
|
||||||
|
String message = "Unable to save checkpoint for shardId " + shardId;
|
||||||
|
log.error(message, e);
|
||||||
|
throw new com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException(message, e);
|
||||||
|
} catch (DependencyException e) {
|
||||||
|
throw new KinesisClientLibDependencyException("Unable to save checkpoint for shardId " + shardId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtendedSequenceNumber getCheckpoint(final String shardId) throws KinesisClientLibException {
|
||||||
|
try {
|
||||||
|
return leaseManager.getLease(shardId).getCheckpoint();
|
||||||
|
} catch (DependencyException | InvalidStateException | ProvisionedThroughputException e) {
|
||||||
|
String message = "Unable to fetch checkpoint for shardId " + shardId;
|
||||||
|
log.error(message, e);
|
||||||
|
throw new KinesisClientLibIOException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Checkpoint getCheckpointObject(final String shardId) throws KinesisClientLibException {
|
||||||
|
try {
|
||||||
|
KinesisClientLease lease = leaseManager.getLease(shardId);
|
||||||
|
return new Checkpoint(lease.getCheckpoint(), lease.getPendingCheckpoint());
|
||||||
|
} catch (DependencyException | InvalidStateException | ProvisionedThroughputException e) {
|
||||||
|
String message = "Unable to fetch checkpoint for shardId " + shardId;
|
||||||
|
log.error(message, e);
|
||||||
|
throw new KinesisClientLibIOException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareCheckpoint(final String shardId, final ExtendedSequenceNumber pendingCheckpoint,
|
||||||
|
final String concurrencyToken) throws KinesisClientLibException {
|
||||||
|
try {
|
||||||
|
boolean wasSuccessful =
|
||||||
|
prepareCheckpoint(shardId, pendingCheckpoint, UUID.fromString(concurrencyToken));
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
throw new ShutdownException(
|
||||||
|
"Can't prepare checkpoint - instance doesn't hold the lease for this shard");
|
||||||
|
}
|
||||||
|
} catch (ProvisionedThroughputException e) {
|
||||||
|
throw new ThrottlingException("Got throttled while preparing checkpoint.", e);
|
||||||
|
} catch (InvalidStateException e) {
|
||||||
|
String message = "Unable to prepare checkpoint for shardId " + shardId;
|
||||||
|
log.error(message, e);
|
||||||
|
throw new com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException(message, e);
|
||||||
|
} catch (DependencyException e) {
|
||||||
|
throw new KinesisClientLibDependencyException("Unable to prepare checkpoint for shardId " + shardId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public boolean setCheckpoint(String shardId, ExtendedSequenceNumber checkpoint, UUID concurrencyToken)
|
||||||
|
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
|
KinesisClientLease lease = leaseCoordinator.getCurrentlyHeldLease(shardId);
|
||||||
|
if (lease == null) {
|
||||||
|
log.info("Worker {} could not update checkpoint for shard {} because it does not hold the lease",
|
||||||
|
leaseCoordinator.getWorkerIdentifier(), shardId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lease.setCheckpoint(checkpoint);
|
||||||
|
lease.setPendingCheckpoint(null);
|
||||||
|
lease.setOwnerSwitchesSinceCheckpoint(0L);
|
||||||
|
|
||||||
|
return leaseCoordinator.updateLease(lease, concurrencyToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean prepareCheckpoint(String shardId, ExtendedSequenceNumber pendingCheckpoint, UUID concurrencyToken)
|
||||||
|
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
|
KinesisClientLease lease = leaseCoordinator.getCurrentlyHeldLease(shardId);
|
||||||
|
if (lease == null) {
|
||||||
|
log.info("Worker {} could not prepare checkpoint for shard {} because it does not hold the lease",
|
||||||
|
leaseCoordinator.getWorkerIdentifier(), shardId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lease.setPendingCheckpoint(Objects.requireNonNull(pendingCheckpoint, "pendingCheckpoint should not be null"));
|
||||||
|
return leaseCoordinator.updateLease(lease, concurrencyToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ public class PreparedCheckpointer implements IPreparedCheckpointer {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ExtendedSequenceNumber getPendingCheckpoint() {
|
public ExtendedSequenceNumber pendingCheckpoint() {
|
||||||
return pendingCheckpointSequenceNumber;
|
return pendingCheckpointSequenceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
* express or implied. See the License for the specific language governing
|
* express or implied. See the License for the specific language governing
|
||||||
* permissions and limitations under the License.
|
* permissions and limitations under the License.
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.coordinator;
|
package software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException;
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibDependencyException;
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibDependencyException;
|
||||||
|
|
@ -32,7 +32,7 @@ import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.ThreadSafeMetricsDelegatingScope;
|
import software.amazon.kinesis.metrics.ThreadSafeMetricsDelegatingScope;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
@ -49,7 +49,7 @@ public class RecordProcessorCheckpointer implements IRecordProcessorCheckpointer
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ShardInfo shardInfo;
|
private final ShardInfo shardInfo;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ICheckpoint checkpoint;
|
private final Checkpointer checkpoint;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IMetricsFactory metricsFactory;
|
private final IMetricsFactory metricsFactory;
|
||||||
|
|
||||||
|
|
@ -17,10 +17,10 @@ package software.amazon.kinesis.coordinator;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -32,6 +32,6 @@ public interface CoordinatorFactory {
|
||||||
|
|
||||||
WorkerStateChangeListener createWorkerStateChangeListener();
|
WorkerStateChangeListener createWorkerStateChangeListener();
|
||||||
|
|
||||||
RecordProcessorCheckpointer createRecordProcessorCheckpointer(ShardInfo shardInfo, ICheckpoint checkpoint,
|
RecordProcessorCheckpointer createRecordProcessorCheckpointer(ShardInfo shardInfo, Checkpointer checkpoint,
|
||||||
IMetricsFactory metricsFactory);
|
IMetricsFactory metricsFactory);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionIn
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.NoOpShardPrioritization;
|
import software.amazon.kinesis.leases.NoOpShardPrioritization;
|
||||||
import software.amazon.kinesis.leases.ShardPrioritization;
|
import software.amazon.kinesis.leases.ShardPrioritization;
|
||||||
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
||||||
|
|
@ -36,7 +37,7 @@ import software.amazon.kinesis.lifecycle.ShardConsumer;
|
||||||
import software.amazon.kinesis.metrics.IMetricsScope;
|
import software.amazon.kinesis.metrics.IMetricsScope;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.DataFetchingStrategy;
|
import software.amazon.kinesis.retrieval.DataFetchingStrategy;
|
||||||
import software.amazon.kinesis.retrieval.RecordsFetcherFactory;
|
import software.amazon.kinesis.retrieval.RecordsFetcherFactory;
|
||||||
import software.amazon.kinesis.retrieval.SimpleRecordsFetcherFactory;
|
import software.amazon.kinesis.retrieval.SimpleRecordsFetcherFactory;
|
||||||
|
|
@ -1005,7 +1006,7 @@ public class KinesisClientLibConfiguration {
|
||||||
* <p>
|
* <p>
|
||||||
* This value is only used when no records are returned; if records are returned, the {@link ProcessTask} will
|
* This value is only used when no records are returned; if records are returned, the {@link ProcessTask} will
|
||||||
* immediately retrieve the next set of records after the call to
|
* immediately retrieve the next set of records after the call to
|
||||||
* {@link IRecordProcessor#processRecords(ProcessRecordsInput)}
|
* {@link RecordProcessor#processRecords(ProcessRecordsInput)}
|
||||||
* has returned. Setting this value to high may result in the KCL being unable to catch up. If you are changing this
|
* has returned. Setting this value to high may result in the KCL being unable to catch up. If you are changing this
|
||||||
* value it's recommended that you enable {@link #withCallProcessRecordsEvenForEmptyRecordList(boolean)}, and
|
* value it's recommended that you enable {@link #withCallProcessRecordsEvenForEmptyRecordList(boolean)}, and
|
||||||
* monitor how far behind the records retrieved are by inspecting
|
* monitor how far behind the records retrieved are by inspecting
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.MetricsCollectingTaskDecorator;
|
import software.amazon.kinesis.metrics.MetricsCollectingTaskDecorator;
|
||||||
import software.amazon.kinesis.metrics.MetricsConfig;
|
import software.amazon.kinesis.metrics.MetricsConfig;
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
import software.amazon.kinesis.processor.ProcessorConfig;
|
import software.amazon.kinesis.processor.ProcessorConfig;
|
||||||
import software.amazon.kinesis.processor.ProcessorFactory;
|
import software.amazon.kinesis.processor.ProcessorFactory;
|
||||||
import software.amazon.kinesis.retrieval.RetrievalConfig;
|
import software.amazon.kinesis.retrieval.RetrievalConfig;
|
||||||
|
|
@ -84,7 +84,7 @@ public class Scheduler implements Runnable {
|
||||||
private final RetrievalConfig retrievalConfig;
|
private final RetrievalConfig retrievalConfig;
|
||||||
|
|
||||||
private final String applicationName;
|
private final String applicationName;
|
||||||
private final ICheckpoint checkpoint;
|
private final Checkpointer checkpoint;
|
||||||
private final long idleTimeInMilliseconds;
|
private final long idleTimeInMilliseconds;
|
||||||
// Backoff time when polling to check if application has finished processing
|
// Backoff time when polling to check if application has finished processing
|
||||||
// parent shards
|
// parent shards
|
||||||
|
|
@ -320,7 +320,7 @@ public class Scheduler implements Runnable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a graceful shutdown of the worker, notifying record processors, that implement
|
* Requests a graceful shutdown of the worker, notifying record processors, that implement
|
||||||
* {@link IShutdownNotificationAware}, of the impending shutdown. This gives the record processor a final chance to
|
* {@link ShutdownNotificationAware}, of the impending shutdown. This gives the record processor a final chance to
|
||||||
* checkpoint.
|
* checkpoint.
|
||||||
*
|
*
|
||||||
* This will only create a single shutdown future. Additional attempts to start a graceful shutdown will return the
|
* This will only create a single shutdown future. Additional attempts to start a graceful shutdown will return the
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -61,7 +61,7 @@ public class SchedulerCoordinatorFactory implements CoordinatorFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecordProcessorCheckpointer createRecordProcessorCheckpointer(@NonNull final ShardInfo shardInfo,
|
public RecordProcessorCheckpointer createRecordProcessorCheckpointer(@NonNull final ShardInfo shardInfo,
|
||||||
@NonNull final ICheckpoint checkpoint,
|
@NonNull final Checkpointer checkpoint,
|
||||||
@NonNull final IMetricsFactory metricsFactory) {
|
@NonNull final IMetricsFactory metricsFactory) {
|
||||||
return new RecordProcessorCheckpointer(shardInfo, checkpoint, metricsFactory);
|
return new RecordProcessorCheckpointer(shardInfo, checkpoint, metricsFactory);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Amazon Software License (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://aws.amazon.com/asl/
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.leases;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentNavigableMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.amazonaws.services.cloudwatch.model.StandardUnit;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
|
import software.amazon.kinesis.metrics.ThreadSafeMetricsDelegatingScope;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsScope;
|
||||||
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of ILeaseRenewer that uses DynamoDB via LeaseManager.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class LeaseRenewer<T extends Lease> implements ILeaseRenewer<T> {
|
||||||
|
private static final int RENEWAL_RETRIES = 2;
|
||||||
|
|
||||||
|
private final ILeaseManager<T> leaseManager;
|
||||||
|
private final ConcurrentNavigableMap<String, T> ownedLeases = new ConcurrentSkipListMap<String, T>();
|
||||||
|
private final String workerIdentifier;
|
||||||
|
private final long leaseDurationNanos;
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param leaseManager LeaseManager to use
|
||||||
|
* @param workerIdentifier identifier of this worker
|
||||||
|
* @param leaseDurationMillis duration of a lease in milliseconds
|
||||||
|
* @param executorService ExecutorService to use for renewing leases in parallel
|
||||||
|
*/
|
||||||
|
public LeaseRenewer(ILeaseManager<T> leaseManager, String workerIdentifier, long leaseDurationMillis,
|
||||||
|
ExecutorService executorService) {
|
||||||
|
this.leaseManager = leaseManager;
|
||||||
|
this.workerIdentifier = workerIdentifier;
|
||||||
|
this.leaseDurationNanos = TimeUnit.MILLISECONDS.toNanos(leaseDurationMillis);
|
||||||
|
this.executorService = executorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void renewLeases() throws DependencyException, InvalidStateException {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
// Due to the eventually consistent nature of ConcurrentNavigableMap iterators, this log entry may become
|
||||||
|
// inaccurate during iteration.
|
||||||
|
log.debug("Worker {} holding %d leases: {}",
|
||||||
|
workerIdentifier,
|
||||||
|
ownedLeases.size(),
|
||||||
|
ownedLeases);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lease renewals are done in parallel so many leases can be renewed for short lease fail over time
|
||||||
|
* configuration. In this case, metrics scope is also shared across different threads, so scope must be thread
|
||||||
|
* safe.
|
||||||
|
*/
|
||||||
|
IMetricsScope renewLeaseTaskMetricsScope = new ThreadSafeMetricsDelegatingScope(
|
||||||
|
MetricsHelper.getMetricsScope());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We iterate in descending order here so that the synchronized(lease) inside renewLease doesn't "lead" calls
|
||||||
|
* to getCurrentlyHeldLeases. They'll still cross paths, but they won't interleave their executions.
|
||||||
|
*/
|
||||||
|
int lostLeases = 0;
|
||||||
|
List<Future<Boolean>> renewLeaseTasks = new ArrayList<Future<Boolean>>();
|
||||||
|
for (T lease : ownedLeases.descendingMap().values()) {
|
||||||
|
renewLeaseTasks.add(executorService.submit(new RenewLeaseTask(lease, renewLeaseTaskMetricsScope)));
|
||||||
|
}
|
||||||
|
int leasesInUnknownState = 0;
|
||||||
|
Exception lastException = null;
|
||||||
|
for (Future<Boolean> renewLeaseTask : renewLeaseTasks) {
|
||||||
|
try {
|
||||||
|
if (!renewLeaseTask.get()) {
|
||||||
|
lostLeases++;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.info("Interrupted while waiting for a lease to renew.");
|
||||||
|
leasesInUnknownState += 1;
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
log.error("Encountered an exception while renewing a lease.", e.getCause());
|
||||||
|
leasesInUnknownState += 1;
|
||||||
|
lastException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renewLeaseTaskMetricsScope.addData(
|
||||||
|
"LostLeases", lostLeases, StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
renewLeaseTaskMetricsScope.addData(
|
||||||
|
"CurrentLeases", ownedLeases.size(), StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
if (leasesInUnknownState > 0) {
|
||||||
|
throw new DependencyException(String.format("Encountered an exception while renewing leases. "
|
||||||
|
+ "The number of leases which might not have been renewed is %d",
|
||||||
|
leasesInUnknownState),
|
||||||
|
lastException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RenewLeaseTask implements Callable<Boolean> {
|
||||||
|
|
||||||
|
private final T lease;
|
||||||
|
private final IMetricsScope metricsScope;
|
||||||
|
|
||||||
|
public RenewLeaseTask(T lease, IMetricsScope metricsScope) {
|
||||||
|
this.lease = lease;
|
||||||
|
this.metricsScope = metricsScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws Exception {
|
||||||
|
MetricsHelper.setMetricsScope(metricsScope);
|
||||||
|
try {
|
||||||
|
return renewLease(lease);
|
||||||
|
} finally {
|
||||||
|
MetricsHelper.unsetMetricsScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean renewLease(T lease) throws DependencyException, InvalidStateException {
|
||||||
|
return renewLease(lease, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean renewLease(T lease, boolean renewEvenIfExpired) throws DependencyException, InvalidStateException {
|
||||||
|
String leaseKey = lease.getLeaseKey();
|
||||||
|
|
||||||
|
boolean success = false;
|
||||||
|
boolean renewedLease = false;
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
for (int i = 1; i <= RENEWAL_RETRIES; i++) {
|
||||||
|
try {
|
||||||
|
synchronized (lease) {
|
||||||
|
// Don't renew expired lease during regular renewals. getCopyOfHeldLease may have returned null
|
||||||
|
// triggering the application processing to treat this as a lost lease (fail checkpoint with
|
||||||
|
// ShutdownException).
|
||||||
|
boolean isLeaseExpired = lease.isExpired(leaseDurationNanos, System.nanoTime());
|
||||||
|
if (renewEvenIfExpired || !isLeaseExpired) {
|
||||||
|
renewedLease = leaseManager.renewLease(lease);
|
||||||
|
}
|
||||||
|
if (renewedLease) {
|
||||||
|
lease.setLastCounterIncrementNanos(System.nanoTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renewedLease) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Worker {} successfully renewed lease with key {}",
|
||||||
|
workerIdentifier,
|
||||||
|
leaseKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("Worker {} lost lease with key {}", workerIdentifier, leaseKey);
|
||||||
|
ownedLeases.remove(leaseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
} catch (ProvisionedThroughputException e) {
|
||||||
|
log.info("Worker {} could not renew lease with key {} on try {} out of {} due to capacity",
|
||||||
|
workerIdentifier,
|
||||||
|
leaseKey,
|
||||||
|
i,
|
||||||
|
RENEWAL_RETRIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
MetricsHelper.addSuccessAndLatency("RenewLease", startTime, success, MetricsLevel.DETAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renewedLease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, T> getCurrentlyHeldLeases() {
|
||||||
|
Map<String, T> result = new HashMap<String, T>();
|
||||||
|
long now = System.nanoTime();
|
||||||
|
|
||||||
|
for (String leaseKey : ownedLeases.keySet()) {
|
||||||
|
T copy = getCopyOfHeldLease(leaseKey, now);
|
||||||
|
if (copy != null) {
|
||||||
|
result.put(copy.getLeaseKey(), copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T getCurrentlyHeldLease(String leaseKey) {
|
||||||
|
return getCopyOfHeldLease(leaseKey, System.nanoTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to return a lease with a specific lease key only if we currently hold it.
|
||||||
|
*
|
||||||
|
* @param leaseKey key of lease to return
|
||||||
|
* @param now current timestamp for old-ness checking
|
||||||
|
* @return non-authoritative copy of the held lease, or null if we don't currently hold it
|
||||||
|
*/
|
||||||
|
private T getCopyOfHeldLease(String leaseKey, long now) {
|
||||||
|
T authoritativeLease = ownedLeases.get(leaseKey);
|
||||||
|
if (authoritativeLease == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
T copy = null;
|
||||||
|
synchronized (authoritativeLease) {
|
||||||
|
copy = authoritativeLease.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy.isExpired(leaseDurationNanos, now)) {
|
||||||
|
log.info("getCurrentlyHeldLease not returning lease with key {} because it is expired",
|
||||||
|
copy.getLeaseKey());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean updateLease(T lease, UUID concurrencyToken)
|
||||||
|
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
|
verifyNotNull(lease, "lease cannot be null");
|
||||||
|
verifyNotNull(lease.getLeaseKey(), "leaseKey cannot be null");
|
||||||
|
verifyNotNull(concurrencyToken, "concurrencyToken cannot be null");
|
||||||
|
|
||||||
|
String leaseKey = lease.getLeaseKey();
|
||||||
|
T authoritativeLease = ownedLeases.get(leaseKey);
|
||||||
|
|
||||||
|
if (authoritativeLease == null) {
|
||||||
|
log.info("Worker {} could not update lease with key {} because it does not hold it",
|
||||||
|
workerIdentifier,
|
||||||
|
leaseKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the passed-in concurrency token doesn't match the concurrency token of the authoritative lease, it means
|
||||||
|
* the lease was lost and regained between when the caller acquired his concurrency token and when the caller
|
||||||
|
* called update.
|
||||||
|
*/
|
||||||
|
if (!authoritativeLease.getConcurrencyToken().equals(concurrencyToken)) {
|
||||||
|
log.info("Worker {} refusing to update lease with key {} because"
|
||||||
|
+ " concurrency tokens don't match", workerIdentifier, leaseKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
synchronized (authoritativeLease) {
|
||||||
|
authoritativeLease.update(lease);
|
||||||
|
boolean updatedLease = leaseManager.updateLease(authoritativeLease);
|
||||||
|
if (updatedLease) {
|
||||||
|
// Updates increment the counter
|
||||||
|
authoritativeLease.setLastCounterIncrementNanos(System.nanoTime());
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If updateLease returns false, it means someone took the lease from us. Remove the lease
|
||||||
|
* from our set of owned leases pro-actively rather than waiting for a run of renewLeases().
|
||||||
|
*/
|
||||||
|
log.info("Worker {} lost lease with key {} - discovered during update",
|
||||||
|
workerIdentifier,
|
||||||
|
leaseKey);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove only if the value currently in the map is the same as the authoritative lease. We're
|
||||||
|
* guarding against a pause after the concurrency token check above. It plays out like so:
|
||||||
|
*
|
||||||
|
* 1) Concurrency token check passes
|
||||||
|
* 2) Pause. Lose lease, re-acquire lease. This requires at least one lease counter update.
|
||||||
|
* 3) Unpause. leaseManager.updateLease fails conditional write due to counter updates, returns
|
||||||
|
* false.
|
||||||
|
* 4) ownedLeases.remove(key, value) doesn't do anything because authoritativeLease does not
|
||||||
|
* .equals() the re-acquired version in the map on the basis of lease counter. This is what we want.
|
||||||
|
* If we just used ownedLease.remove(key), we would have pro-actively removed a lease incorrectly.
|
||||||
|
*
|
||||||
|
* Note that there is a subtlety here - Lease.equals() deliberately does not check the concurrency
|
||||||
|
* token, but it does check the lease counter, so this scheme works.
|
||||||
|
*/
|
||||||
|
ownedLeases.remove(leaseKey, authoritativeLease);
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
return updatedLease;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
MetricsHelper.addSuccessAndLatency("UpdateLease", startTime, success, MetricsLevel.DETAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addLeasesToRenew(Collection<T> newLeases) {
|
||||||
|
verifyNotNull(newLeases, "newLeases cannot be null");
|
||||||
|
|
||||||
|
for (T lease : newLeases) {
|
||||||
|
if (lease.getLastCounterIncrementNanos() == null) {
|
||||||
|
log.info("addLeasesToRenew ignoring lease with key {} because it does not have lastRenewalNanos set",
|
||||||
|
lease.getLeaseKey());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
T authoritativeLease = lease.copy();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assign a concurrency token when we add this to the set of currently owned leases. This ensures that
|
||||||
|
* every time we acquire a lease, it gets a new concurrency token.
|
||||||
|
*/
|
||||||
|
authoritativeLease.setConcurrencyToken(UUID.randomUUID());
|
||||||
|
ownedLeases.put(authoritativeLease.getLeaseKey(), authoritativeLease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearCurrentlyHeldLeases() {
|
||||||
|
ownedLeases.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @param lease the lease to drop.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dropLease(T lease) {
|
||||||
|
ownedLeases.remove(lease.getLeaseKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize() throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
|
Collection<T> leases = leaseManager.listLeases();
|
||||||
|
List<T> myLeases = new LinkedList<T>();
|
||||||
|
boolean renewEvenIfExpired = true;
|
||||||
|
|
||||||
|
for (T lease : leases) {
|
||||||
|
if (workerIdentifier.equals(lease.getLeaseOwner())) {
|
||||||
|
log.info(" Worker {} found lease {}", workerIdentifier, lease);
|
||||||
|
// Okay to renew even if lease is expired, because we start with an empty list and we add the lease to
|
||||||
|
// our list only after a successful renew. So we don't need to worry about the edge case where we could
|
||||||
|
// continue renewing a lease after signaling a lease loss to the application.
|
||||||
|
if (renewLease(lease, renewEvenIfExpired)) {
|
||||||
|
myLeases.add(lease);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("Worker {} ignoring lease {} ", workerIdentifier, lease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addLeasesToRenew(myLeases);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyNotNull(Object object, String message) {
|
||||||
|
if (object == null) {
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,535 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Amazon Software License (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://aws.amazon.com/asl/
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.leases;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.amazonaws.services.cloudwatch.model.StandardUnit;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsScope;
|
||||||
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of ILeaseTaker that uses DynamoDB via LeaseManager.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DynamoDBLeaseTaker<T extends Lease> implements LeaseTaker<T> {
|
||||||
|
private static final int TAKE_RETRIES = 3;
|
||||||
|
private static final int SCAN_RETRIES = 1;
|
||||||
|
|
||||||
|
// See note on takeLeases(Callable) for why we have this callable.
|
||||||
|
private static final Callable<Long> SYSTEM_CLOCK_CALLABLE = new Callable<Long>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long call() {
|
||||||
|
return System.nanoTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final ILeaseManager<T> leaseManager;
|
||||||
|
private final String workerIdentifier;
|
||||||
|
private final Map<String, T> allLeases = new HashMap<String, T>();
|
||||||
|
private final long leaseDurationNanos;
|
||||||
|
private int maxLeasesForWorker = Integer.MAX_VALUE;
|
||||||
|
private int maxLeasesToStealAtOneTime = 1;
|
||||||
|
|
||||||
|
private long lastScanTimeNanos = 0L;
|
||||||
|
|
||||||
|
public DynamoDBLeaseTaker(ILeaseManager<T> leaseManager, String workerIdentifier, long leaseDurationMillis) {
|
||||||
|
this.leaseManager = leaseManager;
|
||||||
|
this.workerIdentifier = workerIdentifier;
|
||||||
|
this.leaseDurationNanos = TimeUnit.MILLISECONDS.toNanos(leaseDurationMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker will not acquire more than the specified max number of leases even if there are more
|
||||||
|
* shards that need to be processed. This can be used in scenarios where a worker is resource constrained or
|
||||||
|
* to prevent lease thrashing when small number of workers pick up all leases for small amount of time during
|
||||||
|
* deployment.
|
||||||
|
* Note that setting a low value may cause data loss (e.g. if there aren't enough Workers to make progress on all
|
||||||
|
* shards). When setting the value for this property, one must ensure enough workers are present to process
|
||||||
|
* shards and should consider future resharding, child shards that may be blocked on parent shards, some workers
|
||||||
|
* becoming unhealthy, etc.
|
||||||
|
*
|
||||||
|
* @param maxLeasesForWorker Max leases this Worker can handle at a time
|
||||||
|
* @return LeaseTaker
|
||||||
|
*/
|
||||||
|
public DynamoDBLeaseTaker<T> withMaxLeasesForWorker(int maxLeasesForWorker) {
|
||||||
|
if (maxLeasesForWorker <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxLeasesForWorker should be >= 1");
|
||||||
|
}
|
||||||
|
this.maxLeasesForWorker = maxLeasesForWorker;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max leases to steal from a more loaded Worker at one time (for load balancing).
|
||||||
|
* Setting this to a higher number can allow for faster load convergence (e.g. during deployments, cold starts),
|
||||||
|
* but can cause higher churn in the system.
|
||||||
|
*
|
||||||
|
* @param maxLeasesToStealAtOneTime Steal up to this many leases at one time (for load balancing)
|
||||||
|
* @return LeaseTaker
|
||||||
|
*/
|
||||||
|
public DynamoDBLeaseTaker<T> withMaxLeasesToStealAtOneTime(int maxLeasesToStealAtOneTime) {
|
||||||
|
if (maxLeasesToStealAtOneTime <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxLeasesToStealAtOneTime should be >= 1");
|
||||||
|
}
|
||||||
|
this.maxLeasesToStealAtOneTime = maxLeasesToStealAtOneTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, T> takeLeases() throws DependencyException, InvalidStateException {
|
||||||
|
return takeLeases(SYSTEM_CLOCK_CALLABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal implementation of takeLeases. Takes a callable that can provide the time to enable test cases without
|
||||||
|
* Thread.sleep. Takes a callable instead of a raw time value because the time needs to be computed as-of
|
||||||
|
* immediately after the scan.
|
||||||
|
*
|
||||||
|
* @param timeProvider Callable that will supply the time
|
||||||
|
*
|
||||||
|
* @return map of lease key to taken lease
|
||||||
|
*
|
||||||
|
* @throws DependencyException
|
||||||
|
* @throws InvalidStateException
|
||||||
|
*/
|
||||||
|
synchronized Map<String, T> takeLeases(Callable<Long> timeProvider)
|
||||||
|
throws DependencyException, InvalidStateException {
|
||||||
|
// Key is leaseKey
|
||||||
|
Map<String, T> takenLeases = new HashMap<String, T>();
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
boolean success = false;
|
||||||
|
|
||||||
|
ProvisionedThroughputException lastException = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (int i = 1; i <= SCAN_RETRIES; i++) {
|
||||||
|
try {
|
||||||
|
updateAllLeases(timeProvider);
|
||||||
|
success = true;
|
||||||
|
} catch (ProvisionedThroughputException e) {
|
||||||
|
log.info("Worker {} could not find expired leases on try {} out of {}",
|
||||||
|
workerIdentifier,
|
||||||
|
i,
|
||||||
|
TAKE_RETRIES);
|
||||||
|
lastException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
MetricsHelper.addSuccessAndLatency("ListLeases", startTime, success, MetricsLevel.DETAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastException != null) {
|
||||||
|
log.error("Worker {} could not scan leases table, aborting takeLeases. Exception caught by last retry:",
|
||||||
|
workerIdentifier,
|
||||||
|
lastException);
|
||||||
|
return takenLeases;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> expiredLeases = getExpiredLeases();
|
||||||
|
|
||||||
|
Set<T> leasesToTake = computeLeasesToTake(expiredLeases);
|
||||||
|
Set<String> untakenLeaseKeys = new HashSet<String>();
|
||||||
|
|
||||||
|
for (T lease : leasesToTake) {
|
||||||
|
String leaseKey = lease.getLeaseKey();
|
||||||
|
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
success = false;
|
||||||
|
try {
|
||||||
|
for (int i = 1; i <= TAKE_RETRIES; i++) {
|
||||||
|
try {
|
||||||
|
if (leaseManager.takeLease(lease, workerIdentifier)) {
|
||||||
|
lease.setLastCounterIncrementNanos(System.nanoTime());
|
||||||
|
takenLeases.put(leaseKey, lease);
|
||||||
|
} else {
|
||||||
|
untakenLeaseKeys.add(leaseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
} catch (ProvisionedThroughputException e) {
|
||||||
|
log.info("Could not take lease with key {} for worker {} on try {} out of {} due to capacity",
|
||||||
|
leaseKey,
|
||||||
|
workerIdentifier,
|
||||||
|
i,
|
||||||
|
TAKE_RETRIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
MetricsHelper.addSuccessAndLatency("TakeLease", startTime, success, MetricsLevel.DETAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takenLeases.size() > 0) {
|
||||||
|
log.info("Worker {} successfully took {} leases: {}",
|
||||||
|
workerIdentifier,
|
||||||
|
takenLeases.size(),
|
||||||
|
stringJoin(takenLeases.keySet(), ", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (untakenLeaseKeys.size() > 0) {
|
||||||
|
log.info("Worker {} failed to take {} leases: {}",
|
||||||
|
workerIdentifier,
|
||||||
|
untakenLeaseKeys.size(),
|
||||||
|
stringJoin(untakenLeaseKeys, ", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricsHelper.getMetricsScope().addData(
|
||||||
|
"TakenLeases", takenLeases.size(), StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
|
||||||
|
return takenLeases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Package access for testing purposes.
|
||||||
|
*
|
||||||
|
* @param strings
|
||||||
|
* @param delimiter
|
||||||
|
* @return Joined string.
|
||||||
|
*/
|
||||||
|
static String stringJoin(Collection<String> strings, String delimiter) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
boolean needDelimiter = false;
|
||||||
|
for (String string : strings) {
|
||||||
|
if (needDelimiter) {
|
||||||
|
builder.append(delimiter);
|
||||||
|
}
|
||||||
|
builder.append(string);
|
||||||
|
needDelimiter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan all leases and update lastRenewalTime. Add new leases and delete old leases.
|
||||||
|
*
|
||||||
|
* @param timeProvider callable that supplies the current time
|
||||||
|
*
|
||||||
|
* @return list of expired leases, possibly empty, never null.
|
||||||
|
*
|
||||||
|
* @throws ProvisionedThroughputException if listLeases fails due to lack of provisioned throughput
|
||||||
|
* @throws InvalidStateException if the lease table does not exist
|
||||||
|
* @throws DependencyException if listLeases fails in an unexpected way
|
||||||
|
*/
|
||||||
|
private void updateAllLeases(Callable<Long> timeProvider)
|
||||||
|
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
|
List<T> freshList = leaseManager.listLeases();
|
||||||
|
try {
|
||||||
|
lastScanTimeNanos = timeProvider.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DependencyException("Exception caught from timeProvider", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This set will hold the lease keys not updated by the previous listLeases call.
|
||||||
|
Set<String> notUpdated = new HashSet<String>(allLeases.keySet());
|
||||||
|
|
||||||
|
// Iterate over all leases, finding ones to try to acquire that haven't changed since the last iteration
|
||||||
|
for (T lease : freshList) {
|
||||||
|
String leaseKey = lease.getLeaseKey();
|
||||||
|
|
||||||
|
T oldLease = allLeases.get(leaseKey);
|
||||||
|
allLeases.put(leaseKey, lease);
|
||||||
|
notUpdated.remove(leaseKey);
|
||||||
|
|
||||||
|
if (oldLease != null) {
|
||||||
|
// If we've seen this lease before...
|
||||||
|
if (oldLease.getLeaseCounter().equals(lease.getLeaseCounter())) {
|
||||||
|
// ...and the counter hasn't changed, propagate the lastRenewalNanos time from the old lease
|
||||||
|
lease.setLastCounterIncrementNanos(oldLease.getLastCounterIncrementNanos());
|
||||||
|
} else {
|
||||||
|
// ...and the counter has changed, set lastRenewalNanos to the time of the scan.
|
||||||
|
lease.setLastCounterIncrementNanos(lastScanTimeNanos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lease.getLeaseOwner() == null) {
|
||||||
|
// if this new lease is unowned, it's never been renewed.
|
||||||
|
lease.setLastCounterIncrementNanos(0L);
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Treating new lease with key {} as never renewed because it is new and unowned.",
|
||||||
|
leaseKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if this new lease is owned, treat it as renewed as of the scan
|
||||||
|
lease.setLastCounterIncrementNanos(lastScanTimeNanos);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Treating new lease with key {} as recently renewed because it is new and owned.",
|
||||||
|
leaseKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove dead leases from allLeases
|
||||||
|
for (String key : notUpdated) {
|
||||||
|
allLeases.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list of leases that were expired as of our last scan.
|
||||||
|
*/
|
||||||
|
private List<T> getExpiredLeases() {
|
||||||
|
List<T> expiredLeases = new ArrayList<T>();
|
||||||
|
|
||||||
|
for (T lease : allLeases.values()) {
|
||||||
|
if (lease.isExpired(leaseDurationNanos, lastScanTimeNanos)) {
|
||||||
|
expiredLeases.add(lease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiredLeases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the number of leases I should try to take based on the state of the system.
|
||||||
|
*
|
||||||
|
* @param allLeases map of shardId to lease containing all leases
|
||||||
|
* @param expiredLeases list of leases we determined to be expired
|
||||||
|
* @return set of leases to take.
|
||||||
|
*/
|
||||||
|
private Set<T> computeLeasesToTake(List<T> expiredLeases) {
|
||||||
|
Map<String, Integer> leaseCounts = computeLeaseCounts(expiredLeases);
|
||||||
|
Set<T> leasesToTake = new HashSet<T>();
|
||||||
|
IMetricsScope metrics = MetricsHelper.getMetricsScope();
|
||||||
|
|
||||||
|
int numLeases = allLeases.size();
|
||||||
|
int numWorkers = leaseCounts.size();
|
||||||
|
|
||||||
|
if (numLeases == 0) {
|
||||||
|
// If there are no leases, I shouldn't try to take any.
|
||||||
|
return leasesToTake;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target;
|
||||||
|
if (numWorkers >= numLeases) {
|
||||||
|
// If we have n leases and n or more workers, each worker can have up to 1 lease, including myself.
|
||||||
|
target = 1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* numWorkers must be < numLeases.
|
||||||
|
*
|
||||||
|
* Our target for each worker is numLeases / numWorkers (+1 if numWorkers doesn't evenly divide numLeases)
|
||||||
|
*/
|
||||||
|
target = numLeases / numWorkers + (numLeases % numWorkers == 0 ? 0 : 1);
|
||||||
|
|
||||||
|
// Spill over is the number of leases this worker should have claimed, but did not because it would
|
||||||
|
// exceed the max allowed for this worker.
|
||||||
|
int leaseSpillover = Math.max(0, target - maxLeasesForWorker);
|
||||||
|
if (target > maxLeasesForWorker) {
|
||||||
|
log.warn("Worker {} target is {} leases and maxLeasesForWorker is {}."
|
||||||
|
+ " Resetting target to {}, lease spillover is {}. "
|
||||||
|
+ " Note that some shards may not be processed if no other workers are able to pick them up.",
|
||||||
|
workerIdentifier,
|
||||||
|
target,
|
||||||
|
maxLeasesForWorker,
|
||||||
|
maxLeasesForWorker,
|
||||||
|
leaseSpillover);
|
||||||
|
target = maxLeasesForWorker;
|
||||||
|
}
|
||||||
|
metrics.addData("LeaseSpillover", leaseSpillover, StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
int myCount = leaseCounts.get(workerIdentifier);
|
||||||
|
int numLeasesToReachTarget = target - myCount;
|
||||||
|
|
||||||
|
if (numLeasesToReachTarget <= 0) {
|
||||||
|
// If we don't need anything, return the empty set.
|
||||||
|
return leasesToTake;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle expiredLeases so workers don't all try to contend for the same leases.
|
||||||
|
Collections.shuffle(expiredLeases);
|
||||||
|
|
||||||
|
int originalExpiredLeasesSize = expiredLeases.size();
|
||||||
|
if (expiredLeases.size() > 0) {
|
||||||
|
// If we have expired leases, get up to <needed> leases from expiredLeases
|
||||||
|
for (; numLeasesToReachTarget > 0 && expiredLeases.size() > 0; numLeasesToReachTarget--) {
|
||||||
|
leasesToTake.add(expiredLeases.remove(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there are no expired leases and we need a lease, consider stealing.
|
||||||
|
List<T> leasesToSteal = chooseLeasesToSteal(leaseCounts, numLeasesToReachTarget, target);
|
||||||
|
for (T leaseToSteal : leasesToSteal) {
|
||||||
|
log.info("Worker {} needed {} leases but none were expired, so it will steal lease {} from {}",
|
||||||
|
workerIdentifier,
|
||||||
|
numLeasesToReachTarget,
|
||||||
|
leaseToSteal.getLeaseKey(),
|
||||||
|
leaseToSteal.getLeaseOwner());
|
||||||
|
leasesToTake.add(leaseToSteal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!leasesToTake.isEmpty()) {
|
||||||
|
log.info("Worker {} saw {} total leases, {} available leases, {} "
|
||||||
|
+ "workers. Target is {} leases, I have {} leases, I will take {} leases",
|
||||||
|
workerIdentifier,
|
||||||
|
numLeases,
|
||||||
|
originalExpiredLeasesSize,
|
||||||
|
numWorkers,
|
||||||
|
target,
|
||||||
|
myCount,
|
||||||
|
leasesToTake.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.addData("TotalLeases", numLeases, StandardUnit.Count, MetricsLevel.DETAILED);
|
||||||
|
metrics.addData("ExpiredLeases", originalExpiredLeasesSize, StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
metrics.addData("NumWorkers", numWorkers, StandardUnit.Count, MetricsLevel.SUMMARY);
|
||||||
|
metrics.addData("NeededLeases", numLeasesToReachTarget, StandardUnit.Count, MetricsLevel.DETAILED);
|
||||||
|
metrics.addData("LeasesToTake", leasesToTake.size(), StandardUnit.Count, MetricsLevel.DETAILED);
|
||||||
|
|
||||||
|
return leasesToTake;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose leases to steal by randomly selecting one or more (up to max) from the most loaded worker.
|
||||||
|
* Stealing rules:
|
||||||
|
*
|
||||||
|
* Steal up to maxLeasesToStealAtOneTime leases from the most loaded worker if
|
||||||
|
* a) he has > target leases and I need >= 1 leases : steal min(leases needed, maxLeasesToStealAtOneTime)
|
||||||
|
* b) he has == target leases and I need > 1 leases : steal 1
|
||||||
|
*
|
||||||
|
* @param leaseCounts map of workerIdentifier to lease count
|
||||||
|
* @param needed # of leases needed to reach the target leases for the worker
|
||||||
|
* @param target target # of leases per worker
|
||||||
|
* @return Leases to steal, or empty list if we should not steal
|
||||||
|
*/
|
||||||
|
private List<T> chooseLeasesToSteal(Map<String, Integer> leaseCounts, int needed, int target) {
|
||||||
|
List<T> leasesToSteal = new ArrayList<>();
|
||||||
|
|
||||||
|
Entry<String, Integer> mostLoadedWorker = null;
|
||||||
|
// Find the most loaded worker
|
||||||
|
for (Entry<String, Integer> worker : leaseCounts.entrySet()) {
|
||||||
|
if (mostLoadedWorker == null || mostLoadedWorker.getValue() < worker.getValue()) {
|
||||||
|
mostLoadedWorker = worker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int numLeasesToSteal = 0;
|
||||||
|
if ((mostLoadedWorker.getValue() >= target) && (needed > 0)) {
|
||||||
|
int leasesOverTarget = mostLoadedWorker.getValue() - target;
|
||||||
|
numLeasesToSteal = Math.min(needed, leasesOverTarget);
|
||||||
|
// steal 1 if we need > 1 and max loaded worker has target leases.
|
||||||
|
if ((needed > 1) && (numLeasesToSteal == 0)) {
|
||||||
|
numLeasesToSteal = 1;
|
||||||
|
}
|
||||||
|
numLeasesToSteal = Math.min(numLeasesToSteal, maxLeasesToStealAtOneTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numLeasesToSteal <= 0) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(String.format("Worker %s not stealing from most loaded worker %s. He has %d,"
|
||||||
|
+ " target is %d, and I need %d",
|
||||||
|
workerIdentifier,
|
||||||
|
mostLoadedWorker.getKey(),
|
||||||
|
mostLoadedWorker.getValue(),
|
||||||
|
target,
|
||||||
|
needed));
|
||||||
|
}
|
||||||
|
return leasesToSteal;
|
||||||
|
} else {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Worker {} will attempt to steal {} leases from most loaded worker {}. "
|
||||||
|
+ " He has {} leases, target is {}, I need {}, maxLeasesToSteatAtOneTime is {}.",
|
||||||
|
workerIdentifier,
|
||||||
|
numLeasesToSteal,
|
||||||
|
mostLoadedWorker.getKey(),
|
||||||
|
mostLoadedWorker.getValue(),
|
||||||
|
target,
|
||||||
|
needed,
|
||||||
|
maxLeasesToStealAtOneTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String mostLoadedWorkerIdentifier = mostLoadedWorker.getKey();
|
||||||
|
List<T> candidates = new ArrayList<T>();
|
||||||
|
// Collect leases belonging to that worker
|
||||||
|
for (T lease : allLeases.values()) {
|
||||||
|
if (mostLoadedWorkerIdentifier.equals(lease.getLeaseOwner())) {
|
||||||
|
candidates.add(lease);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return random ones
|
||||||
|
Collections.shuffle(candidates);
|
||||||
|
int toIndex = Math.min(candidates.size(), numLeasesToSteal);
|
||||||
|
leasesToSteal.addAll(candidates.subList(0, toIndex));
|
||||||
|
|
||||||
|
return leasesToSteal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count leases by host. Always includes myself, but otherwise only includes hosts that are currently holding
|
||||||
|
* leases.
|
||||||
|
*
|
||||||
|
* @param expiredLeases list of leases that are currently expired
|
||||||
|
* @return map of workerIdentifier to lease count
|
||||||
|
*/
|
||||||
|
private Map<String, Integer> computeLeaseCounts(List<T> expiredLeases) {
|
||||||
|
Map<String, Integer> leaseCounts = new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
// Compute the number of leases per worker by looking through allLeases and ignoring leases that have expired.
|
||||||
|
for (T lease : allLeases.values()) {
|
||||||
|
if (!expiredLeases.contains(lease)) {
|
||||||
|
String leaseOwner = lease.getLeaseOwner();
|
||||||
|
Integer oldCount = leaseCounts.get(leaseOwner);
|
||||||
|
if (oldCount == null) {
|
||||||
|
leaseCounts.put(leaseOwner, 1);
|
||||||
|
} else {
|
||||||
|
leaseCounts.put(leaseOwner, oldCount + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If I have no leases, I wasn't represented in leaseCounts. Let's fix that.
|
||||||
|
Integer myCount = leaseCounts.get(workerIdentifier);
|
||||||
|
if (myCount == null) {
|
||||||
|
myCount = 0;
|
||||||
|
leaseCounts.put(workerIdentifier, myCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return leaseCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getWorkerIdentifier() {
|
||||||
|
return workerIdentifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Amazon Software License (the "License").
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* A copy of the License is located at
|
|
||||||
*
|
|
||||||
* http://aws.amazon.com/asl/
|
|
||||||
*
|
|
||||||
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.leases;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
|
||||||
import software.amazon.kinesis.leases.Lease;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ILeaseRenewer objects are used by LeaseCoordinator to renew leases held by the LeaseCoordinator. Each
|
|
||||||
* LeaseCoordinator instance corresponds to one worker, and uses exactly one ILeaseRenewer to manage lease renewal for
|
|
||||||
* that worker.
|
|
||||||
*/
|
|
||||||
public interface ILeaseRenewer<T extends Lease> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bootstrap initial set of leases from the LeaseManager (e.g. upon process restart, pick up leases we own)
|
|
||||||
* @throws DependencyException on unexpected DynamoDB failures
|
|
||||||
* @throws InvalidStateException if lease table doesn't exist
|
|
||||||
* @throws ProvisionedThroughputException if DynamoDB reads fail due to insufficient capacity
|
|
||||||
*/
|
|
||||||
public void initialize() throws DependencyException, InvalidStateException, ProvisionedThroughputException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to renew all currently held leases.
|
|
||||||
*
|
|
||||||
* @throws DependencyException on unexpected DynamoDB failures
|
|
||||||
* @throws InvalidStateException if lease table does not exist
|
|
||||||
*/
|
|
||||||
public void renewLeases() throws DependencyException, InvalidStateException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return currently held leases. Key is shardId, value is corresponding Lease object. A lease is currently held if
|
|
||||||
* we successfully renewed it on the last run of renewLeases(). Lease objects returned are deep copies -
|
|
||||||
* their lease counters will not tick.
|
|
||||||
*/
|
|
||||||
public Map<String, T> getCurrentlyHeldLeases();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param leaseKey key of the lease to retrieve
|
|
||||||
*
|
|
||||||
* @return a deep copy of a currently held lease, or null if we don't hold the lease
|
|
||||||
*/
|
|
||||||
public T getCurrentlyHeldLease(String leaseKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds leases to this LeaseRenewer's set of currently held leases. Leases must have lastRenewalNanos set to the
|
|
||||||
* last time the lease counter was incremented before being passed to this method.
|
|
||||||
*
|
|
||||||
* @param newLeases new leases.
|
|
||||||
*/
|
|
||||||
public void addLeasesToRenew(Collection<T> newLeases);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears this LeaseRenewer's set of currently held leases.
|
|
||||||
*/
|
|
||||||
public void clearCurrentlyHeldLeases();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the lease renewer from continunig to maintain the given lease.
|
|
||||||
*
|
|
||||||
* @param lease the lease to drop.
|
|
||||||
*/
|
|
||||||
void dropLease(T lease);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update application-specific fields in a currently held lease. Cannot be used to update internal fields such as
|
|
||||||
* leaseCounter, leaseOwner, etc. Fails if we do not hold the lease, or if the concurrency token does not match
|
|
||||||
* the concurrency token on the internal authoritative copy of the lease (ie, if we lost and re-acquired the lease).
|
|
||||||
*
|
|
||||||
* @param lease lease object containing updated data
|
|
||||||
* @param concurrencyToken obtained by calling Lease.getConcurrencyToken for a currently held lease
|
|
||||||
*
|
|
||||||
* @return true if update succeeds, false otherwise
|
|
||||||
*
|
|
||||||
* @throws InvalidStateException if lease table does not exist
|
|
||||||
* @throws ProvisionedThroughputException if DynamoDB update fails due to lack of capacity
|
|
||||||
* @throws DependencyException if DynamoDB update fails in an unexpected way
|
|
||||||
*/
|
|
||||||
boolean updateLease(T lease, UUID concurrencyToken)
|
|
||||||
throws DependencyException, InvalidStateException, ProvisionedThroughputException;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Amazon Software License (the "License").
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* A copy of the License is located at
|
|
||||||
*
|
|
||||||
* http://aws.amazon.com/asl/
|
|
||||||
*
|
|
||||||
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.leases;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
|
||||||
import software.amazon.kinesis.leases.Lease;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ILeaseTaker is used by LeaseCoordinator to take new leases, or leases that other workers fail to renew. Each
|
|
||||||
* LeaseCoordinator instance corresponds to one worker and uses exactly one ILeaseTaker to take leases for that worker.
|
|
||||||
*/
|
|
||||||
public interface ILeaseTaker<T extends Lease> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the set of leases available to be taken and attempt to take them. Lease taking rules are:
|
|
||||||
*
|
|
||||||
* 1) If a lease's counter hasn't changed in long enough, try to take it.
|
|
||||||
* 2) If we see a lease we've never seen before, take it only if owner == null. If it's owned, odds are the owner is
|
|
||||||
* holding it. We can't tell until we see it more than once.
|
|
||||||
* 3) For load balancing purposes, you may violate rules 1 and 2 for EXACTLY ONE lease per call of takeLeases().
|
|
||||||
*
|
|
||||||
* @return map of shardId to Lease object for leases we just successfully took.
|
|
||||||
*
|
|
||||||
* @throws DependencyException on unexpected DynamoDB failures
|
|
||||||
* @throws InvalidStateException if lease table does not exist
|
|
||||||
*/
|
|
||||||
public abstract Map<String, T> takeLeases() throws DependencyException, InvalidStateException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return workerIdentifier for this LeaseTaker
|
|
||||||
*/
|
|
||||||
public abstract String getWorkerIdentifier();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -18,41 +18,28 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibDependencyException;
|
import lombok.Data;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
import lombok.EqualsAndHashCode;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingException;
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.internal.KinesisClientLibIOException;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to coordinate/manage leases owned by this worker process and to get/set checkpoints.
|
* This class is used to coordinate/manage leases owned by this worker process and to get/set checkpoints.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisClientLease> implements ICheckpoint {
|
public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisClientLease> {
|
||||||
private static final long DEFAULT_INITIAL_LEASE_TABLE_READ_CAPACITY = 10L;
|
private static final long DEFAULT_INITIAL_LEASE_TABLE_READ_CAPACITY = 10L;
|
||||||
private static final long DEFAULT_INITIAL_LEASE_TABLE_WRITE_CAPACITY = 10L;
|
private static final long DEFAULT_INITIAL_LEASE_TABLE_WRITE_CAPACITY = 10L;
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to get information about leases for Kinesis shards (e.g. sync shards and leases, check on parent shard
|
|
||||||
* completion).
|
|
||||||
*
|
|
||||||
* @return LeaseManager
|
|
||||||
*/
|
|
||||||
@Getter
|
@Getter
|
||||||
@Accessors(fluent = true)
|
@Accessors(fluent = true)
|
||||||
private final ILeaseManager<KinesisClientLease> leaseManager;
|
private final ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
|
|
@ -60,53 +47,14 @@ public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisCl
|
||||||
private long initialLeaseTableReadCapacity = DEFAULT_INITIAL_LEASE_TABLE_READ_CAPACITY;
|
private long initialLeaseTableReadCapacity = DEFAULT_INITIAL_LEASE_TABLE_READ_CAPACITY;
|
||||||
private long initialLeaseTableWriteCapacity = DEFAULT_INITIAL_LEASE_TABLE_WRITE_CAPACITY;
|
private long initialLeaseTableWriteCapacity = DEFAULT_INITIAL_LEASE_TABLE_WRITE_CAPACITY;
|
||||||
|
|
||||||
/**
|
public KinesisClientLibLeaseCoordinator(final ILeaseManager<KinesisClientLease> leaseManager,
|
||||||
* @param leaseManager Lease manager which provides CRUD lease operations.
|
final String workerIdentifier,
|
||||||
* @param workerIdentifier Used to identify this worker process
|
final long leaseDurationMillis,
|
||||||
* @param leaseDurationMillis Duration of a lease in milliseconds
|
final long epsilonMillis,
|
||||||
* @param epsilonMillis Delta for timing operations (e.g. checking lease expiry)
|
final int maxLeasesForWorker,
|
||||||
*/
|
final int maxLeasesToStealAtOneTime,
|
||||||
public KinesisClientLibLeaseCoordinator(ILeaseManager<KinesisClientLease> leaseManager,
|
final int maxLeaseRenewerThreadCount,
|
||||||
String workerIdentifier,
|
final IMetricsFactory metricsFactory) {
|
||||||
long leaseDurationMillis,
|
|
||||||
long epsilonMillis) {
|
|
||||||
super(leaseManager, workerIdentifier, leaseDurationMillis, epsilonMillis);
|
|
||||||
this.leaseManager = leaseManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param leaseManager Lease manager which provides CRUD lease operations.
|
|
||||||
* @param workerIdentifier Used to identify this worker process
|
|
||||||
* @param leaseDurationMillis Duration of a lease in milliseconds
|
|
||||||
* @param epsilonMillis Delta for timing operations (e.g. checking lease expiry)
|
|
||||||
* @param metricsFactory Metrics factory used to emit metrics
|
|
||||||
*/
|
|
||||||
public KinesisClientLibLeaseCoordinator(ILeaseManager<KinesisClientLease> leaseManager,
|
|
||||||
String workerIdentifier,
|
|
||||||
long leaseDurationMillis,
|
|
||||||
long epsilonMillis,
|
|
||||||
IMetricsFactory metricsFactory) {
|
|
||||||
super(leaseManager, workerIdentifier, leaseDurationMillis, epsilonMillis, metricsFactory);
|
|
||||||
this.leaseManager = leaseManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param leaseManager Lease manager which provides CRUD lease operations.
|
|
||||||
* @param workerIdentifier Used to identify this worker process
|
|
||||||
* @param leaseDurationMillis Duration of a lease in milliseconds
|
|
||||||
* @param epsilonMillis Delta for timing operations (e.g. checking lease expiry)
|
|
||||||
* @param maxLeasesForWorker Max leases this worker can handle at a time
|
|
||||||
* @param maxLeasesToStealAtOneTime Steal up to this many leases at a time (for load balancing)
|
|
||||||
* @param metricsFactory Metrics factory used to emit metrics
|
|
||||||
*/
|
|
||||||
public KinesisClientLibLeaseCoordinator(ILeaseManager<KinesisClientLease> leaseManager,
|
|
||||||
String workerIdentifier,
|
|
||||||
long leaseDurationMillis,
|
|
||||||
long epsilonMillis,
|
|
||||||
int maxLeasesForWorker,
|
|
||||||
int maxLeasesToStealAtOneTime,
|
|
||||||
int maxLeaseRenewerThreadCount,
|
|
||||||
IMetricsFactory metricsFactory) {
|
|
||||||
super(leaseManager, workerIdentifier, leaseDurationMillis, epsilonMillis, maxLeasesForWorker,
|
super(leaseManager, workerIdentifier, leaseDurationMillis, epsilonMillis, maxLeasesForWorker,
|
||||||
maxLeasesToStealAtOneTime, maxLeaseRenewerThreadCount, metricsFactory);
|
maxLeasesToStealAtOneTime, maxLeaseRenewerThreadCount, metricsFactory);
|
||||||
this.leaseManager = leaseManager;
|
this.leaseManager = leaseManager;
|
||||||
|
|
@ -117,7 +65,7 @@ public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisCl
|
||||||
* read capacity
|
* read capacity
|
||||||
* @return KinesisClientLibLeaseCoordinator
|
* @return KinesisClientLibLeaseCoordinator
|
||||||
*/
|
*/
|
||||||
public KinesisClientLibLeaseCoordinator withInitialLeaseTableReadCapacity(long readCapacity) {
|
public KinesisClientLibLeaseCoordinator initialLeaseTableReadCapacity(long readCapacity) {
|
||||||
if (readCapacity <= 0) {
|
if (readCapacity <= 0) {
|
||||||
throw new IllegalArgumentException("readCapacity should be >= 1");
|
throw new IllegalArgumentException("readCapacity should be >= 1");
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +78,7 @@ public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisCl
|
||||||
* write capacity
|
* write capacity
|
||||||
* @return KinesisClientLibLeaseCoordinator
|
* @return KinesisClientLibLeaseCoordinator
|
||||||
*/
|
*/
|
||||||
public KinesisClientLibLeaseCoordinator withInitialLeaseTableWriteCapacity(long writeCapacity) {
|
public KinesisClientLibLeaseCoordinator initialLeaseTableWriteCapacity(long writeCapacity) {
|
||||||
if (writeCapacity <= 0) {
|
if (writeCapacity <= 0) {
|
||||||
throw new IllegalArgumentException("writeCapacity should be >= 1");
|
throw new IllegalArgumentException("writeCapacity should be >= 1");
|
||||||
}
|
}
|
||||||
|
|
@ -138,138 +86,6 @@ public class KinesisClientLibLeaseCoordinator extends LeaseCoordinator<KinesisCl
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the checkpoint for a shard and updates ownerSwitchesSinceCheckpoint.
|
|
||||||
*
|
|
||||||
* @param shardId shardId to update the checkpoint for
|
|
||||||
* @param checkpoint checkpoint value to set
|
|
||||||
* @param concurrencyToken obtained by calling Lease.concurrencyToken for a currently held lease
|
|
||||||
*
|
|
||||||
* @return true if checkpoint update succeeded, false otherwise
|
|
||||||
*
|
|
||||||
* @throws InvalidStateException if lease table does not exist
|
|
||||||
* @throws ProvisionedThroughputException if DynamoDB update fails due to lack of capacity
|
|
||||||
* @throws DependencyException if DynamoDB update fails in an unexpected way
|
|
||||||
*/
|
|
||||||
public boolean setCheckpoint(String shardId, ExtendedSequenceNumber checkpoint, UUID concurrencyToken)
|
|
||||||
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
|
||||||
KinesisClientLease lease = getCurrentlyHeldLease(shardId);
|
|
||||||
if (lease == null) {
|
|
||||||
log.info("Worker {} could not update checkpoint for shard {} because it does not hold the lease",
|
|
||||||
getWorkerIdentifier(), shardId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lease.setCheckpoint(checkpoint);
|
|
||||||
lease.setPendingCheckpoint(null);
|
|
||||||
lease.setOwnerSwitchesSinceCheckpoint(0L);
|
|
||||||
|
|
||||||
return updateLease(lease, concurrencyToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setCheckpoint(String shardId, ExtendedSequenceNumber checkpointValue, String concurrencyToken)
|
|
||||||
throws KinesisClientLibException {
|
|
||||||
try {
|
|
||||||
boolean wasSuccessful = setCheckpoint(shardId, checkpointValue, UUID.fromString(concurrencyToken));
|
|
||||||
if (!wasSuccessful) {
|
|
||||||
throw new ShutdownException("Can't update checkpoint - instance doesn't hold the lease for this shard");
|
|
||||||
}
|
|
||||||
} catch (ProvisionedThroughputException e) {
|
|
||||||
throw new ThrottlingException("Got throttled while updating checkpoint.", e);
|
|
||||||
} catch (InvalidStateException e) {
|
|
||||||
String message = "Unable to save checkpoint for shardId " + shardId;
|
|
||||||
log.error(message, e);
|
|
||||||
throw new com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException(message, e);
|
|
||||||
} catch (DependencyException e) {
|
|
||||||
throw new KinesisClientLibDependencyException("Unable to save checkpoint for shardId " + shardId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public ExtendedSequenceNumber getCheckpoint(String shardId) throws KinesisClientLibException {
|
|
||||||
try {
|
|
||||||
return leaseManager.getLease(shardId).getCheckpoint();
|
|
||||||
} catch (DependencyException | InvalidStateException | ProvisionedThroughputException e) {
|
|
||||||
String message = "Unable to fetch checkpoint for shardId " + shardId;
|
|
||||||
log.error(message, e);
|
|
||||||
throw new KinesisClientLibIOException(message, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records pending checkpoint for a shard. Does not modify checkpoint or ownerSwitchesSinceCheckpoint.
|
|
||||||
*
|
|
||||||
* @param shardId shardId to update the checkpoint for
|
|
||||||
* @param pendingCheckpoint pending checkpoint value to set, not null
|
|
||||||
* @param concurrencyToken obtained by calling Lease.concurrencyToken for a currently held lease
|
|
||||||
*
|
|
||||||
* @return true if setting the pending checkpoint succeeded, false otherwise
|
|
||||||
*
|
|
||||||
* @throws InvalidStateException if lease table does not exist
|
|
||||||
* @throws ProvisionedThroughputException if DynamoDB update fails due to lack of capacity
|
|
||||||
* @throws DependencyException if DynamoDB update fails in an unexpected way
|
|
||||||
*/
|
|
||||||
boolean prepareCheckpoint(String shardId, ExtendedSequenceNumber pendingCheckpoint, UUID concurrencyToken)
|
|
||||||
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
|
||||||
KinesisClientLease lease = getCurrentlyHeldLease(shardId);
|
|
||||||
if (lease == null) {
|
|
||||||
log.info("Worker {} could not prepare checkpoint for shard {} because it does not hold the lease",
|
|
||||||
getWorkerIdentifier(), shardId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lease.setPendingCheckpoint(Objects.requireNonNull(pendingCheckpoint, "pendingCheckpoint should not be null"));
|
|
||||||
return updateLease(lease, concurrencyToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void prepareCheckpoint(String shardId,
|
|
||||||
ExtendedSequenceNumber pendingCheckpointValue,
|
|
||||||
String concurrencyToken) throws KinesisClientLibException {
|
|
||||||
try {
|
|
||||||
boolean wasSuccessful =
|
|
||||||
prepareCheckpoint(shardId, pendingCheckpointValue, UUID.fromString(concurrencyToken));
|
|
||||||
if (!wasSuccessful) {
|
|
||||||
throw new ShutdownException(
|
|
||||||
"Can't prepare checkpoint - instance doesn't hold the lease for this shard");
|
|
||||||
}
|
|
||||||
} catch (ProvisionedThroughputException e) {
|
|
||||||
throw new ThrottlingException("Got throttled while preparing checkpoint.", e);
|
|
||||||
} catch (InvalidStateException e) {
|
|
||||||
String message = "Unable to prepare checkpoint for shardId " + shardId;
|
|
||||||
log.error(message, e);
|
|
||||||
throw new com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException(message, e);
|
|
||||||
} catch (DependencyException e) {
|
|
||||||
throw new KinesisClientLibDependencyException("Unable to prepare checkpoint for shardId " + shardId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Checkpoint getCheckpointObject(String shardId) throws KinesisClientLibException {
|
|
||||||
try {
|
|
||||||
KinesisClientLease lease = leaseManager.getLease(shardId);
|
|
||||||
return new Checkpoint(lease.getCheckpoint(), lease.getPendingCheckpoint());
|
|
||||||
} catch (DependencyException | InvalidStateException | ProvisionedThroughputException e) {
|
|
||||||
String message = "Unable to fetch checkpoint for shardId " + shardId;
|
|
||||||
log.error(message, e);
|
|
||||||
throw new KinesisClientLibIOException(message, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Current shard/lease assignments
|
* @return Current shard/lease assignments
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ public class LeaseCoordinator<T extends Lease> {
|
||||||
.setNameFormat("LeaseRenewer-%04d").setDaemon(true).build();
|
.setNameFormat("LeaseRenewer-%04d").setDaemon(true).build();
|
||||||
|
|
||||||
private final ILeaseRenewer<T> leaseRenewer;
|
private final ILeaseRenewer<T> leaseRenewer;
|
||||||
private final ILeaseTaker<T> leaseTaker;
|
private final LeaseTaker<T> leaseTaker;
|
||||||
private final long renewerIntervalMillis;
|
private final long renewerIntervalMillis;
|
||||||
private final long takerIntervalMillis;
|
private final long takerIntervalMillis;
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ public class LeaseCoordinator<T extends Lease> {
|
||||||
int maxLeaseRenewerThreadCount,
|
int maxLeaseRenewerThreadCount,
|
||||||
IMetricsFactory metricsFactory) {
|
IMetricsFactory metricsFactory) {
|
||||||
this.leaseRenewalThreadpool = getLeaseRenewalExecutorService(maxLeaseRenewerThreadCount);
|
this.leaseRenewalThreadpool = getLeaseRenewalExecutorService(maxLeaseRenewerThreadCount);
|
||||||
this.leaseTaker = new LeaseTaker<T>(leaseManager, workerIdentifier, leaseDurationMillis)
|
this.leaseTaker = new DynamoDBLeaseTaker<T>(leaseManager, workerIdentifier, leaseDurationMillis)
|
||||||
.withMaxLeasesForWorker(maxLeasesForWorker)
|
.withMaxLeasesForWorker(maxLeasesForWorker)
|
||||||
.withMaxLeasesToStealAtOneTime(maxLeasesToStealAtOneTime);
|
.withMaxLeasesToStealAtOneTime(maxLeasesToStealAtOneTime);
|
||||||
this.leaseRenewer = new LeaseRenewer<T>(
|
this.leaseRenewer = new LeaseRenewer<T>(
|
||||||
|
|
|
||||||
|
|
@ -14,397 +14,87 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.leases;
|
package software.amazon.kinesis.leases;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ConcurrentNavigableMap;
|
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.amazonaws.services.cloudwatch.model.StandardUnit;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.leases.Lease;
|
||||||
import software.amazon.kinesis.metrics.ThreadSafeMetricsDelegatingScope;
|
|
||||||
import software.amazon.kinesis.metrics.IMetricsScope;
|
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of ILeaseRenewer that uses DynamoDB via LeaseManager.
|
* ILeaseRenewer objects are used by LeaseCoordinator to renew leases held by the LeaseCoordinator. Each
|
||||||
|
* LeaseCoordinator instance corresponds to one worker, and uses exactly one ILeaseRenewer to manage lease renewal for
|
||||||
|
* that worker.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface ILeaseRenewer<T extends Lease> {
|
||||||
public class LeaseRenewer<T extends Lease> implements ILeaseRenewer<T> {
|
|
||||||
private static final int RENEWAL_RETRIES = 2;
|
|
||||||
|
|
||||||
private final ILeaseManager<T> leaseManager;
|
|
||||||
private final ConcurrentNavigableMap<String, T> ownedLeases = new ConcurrentSkipListMap<String, T>();
|
|
||||||
private final String workerIdentifier;
|
|
||||||
private final long leaseDurationNanos;
|
|
||||||
private final ExecutorService executorService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Bootstrap initial set of leases from the LeaseManager (e.g. upon process restart, pick up leases we own)
|
||||||
|
* @throws DependencyException on unexpected DynamoDB failures
|
||||||
|
* @throws InvalidStateException if lease table doesn't exist
|
||||||
|
* @throws ProvisionedThroughputException if DynamoDB reads fail due to insufficient capacity
|
||||||
|
*/
|
||||||
|
public void initialize() throws DependencyException, InvalidStateException, ProvisionedThroughputException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to renew all currently held leases.
|
||||||
*
|
*
|
||||||
* @param leaseManager LeaseManager to use
|
* @throws DependencyException on unexpected DynamoDB failures
|
||||||
* @param workerIdentifier identifier of this worker
|
* @throws InvalidStateException if lease table does not exist
|
||||||
* @param leaseDurationMillis duration of a lease in milliseconds
|
|
||||||
* @param executorService ExecutorService to use for renewing leases in parallel
|
|
||||||
*/
|
*/
|
||||||
public LeaseRenewer(ILeaseManager<T> leaseManager, String workerIdentifier, long leaseDurationMillis,
|
public void renewLeases() throws DependencyException, InvalidStateException;
|
||||||
ExecutorService executorService) {
|
|
||||||
this.leaseManager = leaseManager;
|
|
||||||
this.workerIdentifier = workerIdentifier;
|
|
||||||
this.leaseDurationNanos = TimeUnit.MILLISECONDS.toNanos(leaseDurationMillis);
|
|
||||||
this.executorService = executorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* @return currently held leases. Key is shardId, value is corresponding Lease object. A lease is currently held if
|
||||||
|
* we successfully renewed it on the last run of renewLeases(). Lease objects returned are deep copies -
|
||||||
|
* their lease counters will not tick.
|
||||||
*/
|
*/
|
||||||
@Override
|
public Map<String, T> getCurrentlyHeldLeases();
|
||||||
public void renewLeases() throws DependencyException, InvalidStateException {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
// Due to the eventually consistent nature of ConcurrentNavigableMap iterators, this log entry may become
|
|
||||||
// inaccurate during iteration.
|
|
||||||
log.debug("Worker {} holding %d leases: {}",
|
|
||||||
workerIdentifier,
|
|
||||||
ownedLeases.size(),
|
|
||||||
ownedLeases);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Lease renewals are done in parallel so many leases can be renewed for short lease fail over time
|
|
||||||
* configuration. In this case, metrics scope is also shared across different threads, so scope must be thread
|
|
||||||
* safe.
|
|
||||||
*/
|
|
||||||
IMetricsScope renewLeaseTaskMetricsScope = new ThreadSafeMetricsDelegatingScope(
|
|
||||||
MetricsHelper.getMetricsScope());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We iterate in descending order here so that the synchronized(lease) inside renewLease doesn't "lead" calls
|
|
||||||
* to getCurrentlyHeldLeases. They'll still cross paths, but they won't interleave their executions.
|
|
||||||
*/
|
|
||||||
int lostLeases = 0;
|
|
||||||
List<Future<Boolean>> renewLeaseTasks = new ArrayList<Future<Boolean>>();
|
|
||||||
for (T lease : ownedLeases.descendingMap().values()) {
|
|
||||||
renewLeaseTasks.add(executorService.submit(new RenewLeaseTask(lease, renewLeaseTaskMetricsScope)));
|
|
||||||
}
|
|
||||||
int leasesInUnknownState = 0;
|
|
||||||
Exception lastException = null;
|
|
||||||
for (Future<Boolean> renewLeaseTask : renewLeaseTasks) {
|
|
||||||
try {
|
|
||||||
if (!renewLeaseTask.get()) {
|
|
||||||
lostLeases++;
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.info("Interrupted while waiting for a lease to renew.");
|
|
||||||
leasesInUnknownState += 1;
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
log.error("Encountered an exception while renewing a lease.", e.getCause());
|
|
||||||
leasesInUnknownState += 1;
|
|
||||||
lastException = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renewLeaseTaskMetricsScope.addData(
|
|
||||||
"LostLeases", lostLeases, StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
renewLeaseTaskMetricsScope.addData(
|
|
||||||
"CurrentLeases", ownedLeases.size(), StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
if (leasesInUnknownState > 0) {
|
|
||||||
throw new DependencyException(String.format("Encountered an exception while renewing leases. "
|
|
||||||
+ "The number of leases which might not have been renewed is %d",
|
|
||||||
leasesInUnknownState),
|
|
||||||
lastException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RenewLeaseTask implements Callable<Boolean> {
|
|
||||||
|
|
||||||
private final T lease;
|
|
||||||
private final IMetricsScope metricsScope;
|
|
||||||
|
|
||||||
public RenewLeaseTask(T lease, IMetricsScope metricsScope) {
|
|
||||||
this.lease = lease;
|
|
||||||
this.metricsScope = metricsScope;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean call() throws Exception {
|
|
||||||
MetricsHelper.setMetricsScope(metricsScope);
|
|
||||||
try {
|
|
||||||
return renewLease(lease);
|
|
||||||
} finally {
|
|
||||||
MetricsHelper.unsetMetricsScope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean renewLease(T lease) throws DependencyException, InvalidStateException {
|
|
||||||
return renewLease(lease, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean renewLease(T lease, boolean renewEvenIfExpired) throws DependencyException, InvalidStateException {
|
|
||||||
String leaseKey = lease.getLeaseKey();
|
|
||||||
|
|
||||||
boolean success = false;
|
|
||||||
boolean renewedLease = false;
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
try {
|
|
||||||
for (int i = 1; i <= RENEWAL_RETRIES; i++) {
|
|
||||||
try {
|
|
||||||
synchronized (lease) {
|
|
||||||
// Don't renew expired lease during regular renewals. getCopyOfHeldLease may have returned null
|
|
||||||
// triggering the application processing to treat this as a lost lease (fail checkpoint with
|
|
||||||
// ShutdownException).
|
|
||||||
boolean isLeaseExpired = lease.isExpired(leaseDurationNanos, System.nanoTime());
|
|
||||||
if (renewEvenIfExpired || !isLeaseExpired) {
|
|
||||||
renewedLease = leaseManager.renewLease(lease);
|
|
||||||
}
|
|
||||||
if (renewedLease) {
|
|
||||||
lease.setLastCounterIncrementNanos(System.nanoTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (renewedLease) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Worker {} successfully renewed lease with key {}",
|
|
||||||
workerIdentifier,
|
|
||||||
leaseKey);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info("Worker {} lost lease with key {}", workerIdentifier, leaseKey);
|
|
||||||
ownedLeases.remove(leaseKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
} catch (ProvisionedThroughputException e) {
|
|
||||||
log.info("Worker {} could not renew lease with key {} on try {} out of {} due to capacity",
|
|
||||||
workerIdentifier,
|
|
||||||
leaseKey,
|
|
||||||
i,
|
|
||||||
RENEWAL_RETRIES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
MetricsHelper.addSuccessAndLatency("RenewLease", startTime, success, MetricsLevel.DETAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return renewedLease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* @param leaseKey key of the lease to retrieve
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, T> getCurrentlyHeldLeases() {
|
|
||||||
Map<String, T> result = new HashMap<String, T>();
|
|
||||||
long now = System.nanoTime();
|
|
||||||
|
|
||||||
for (String leaseKey : ownedLeases.keySet()) {
|
|
||||||
T copy = getCopyOfHeldLease(leaseKey, now);
|
|
||||||
if (copy != null) {
|
|
||||||
result.put(copy.getLeaseKey(), copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public T getCurrentlyHeldLease(String leaseKey) {
|
|
||||||
return getCopyOfHeldLease(leaseKey, System.nanoTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method to return a lease with a specific lease key only if we currently hold it.
|
|
||||||
*
|
*
|
||||||
* @param leaseKey key of lease to return
|
* @return a deep copy of a currently held lease, or null if we don't hold the lease
|
||||||
* @param now current timestamp for old-ness checking
|
|
||||||
* @return non-authoritative copy of the held lease, or null if we don't currently hold it
|
|
||||||
*/
|
*/
|
||||||
private T getCopyOfHeldLease(String leaseKey, long now) {
|
public T getCurrentlyHeldLease(String leaseKey);
|
||||||
T authoritativeLease = ownedLeases.get(leaseKey);
|
|
||||||
if (authoritativeLease == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
T copy = null;
|
|
||||||
synchronized (authoritativeLease) {
|
|
||||||
copy = authoritativeLease.copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy.isExpired(leaseDurationNanos, now)) {
|
|
||||||
log.info("getCurrentlyHeldLease not returning lease with key {} because it is expired",
|
|
||||||
copy.getLeaseKey());
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* Adds leases to this LeaseRenewer's set of currently held leases. Leases must have lastRenewalNanos set to the
|
||||||
*/
|
* last time the lease counter was incremented before being passed to this method.
|
||||||
@Override
|
|
||||||
public boolean updateLease(T lease, UUID concurrencyToken)
|
|
||||||
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
|
||||||
verifyNotNull(lease, "lease cannot be null");
|
|
||||||
verifyNotNull(lease.getLeaseKey(), "leaseKey cannot be null");
|
|
||||||
verifyNotNull(concurrencyToken, "concurrencyToken cannot be null");
|
|
||||||
|
|
||||||
String leaseKey = lease.getLeaseKey();
|
|
||||||
T authoritativeLease = ownedLeases.get(leaseKey);
|
|
||||||
|
|
||||||
if (authoritativeLease == null) {
|
|
||||||
log.info("Worker {} could not update lease with key {} because it does not hold it",
|
|
||||||
workerIdentifier,
|
|
||||||
leaseKey);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the passed-in concurrency token doesn't match the concurrency token of the authoritative lease, it means
|
|
||||||
* the lease was lost and regained between when the caller acquired his concurrency token and when the caller
|
|
||||||
* called update.
|
|
||||||
*/
|
|
||||||
if (!authoritativeLease.getConcurrencyToken().equals(concurrencyToken)) {
|
|
||||||
log.info("Worker {} refusing to update lease with key {} because"
|
|
||||||
+ " concurrency tokens don't match", workerIdentifier, leaseKey);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
boolean success = false;
|
|
||||||
try {
|
|
||||||
synchronized (authoritativeLease) {
|
|
||||||
authoritativeLease.update(lease);
|
|
||||||
boolean updatedLease = leaseManager.updateLease(authoritativeLease);
|
|
||||||
if (updatedLease) {
|
|
||||||
// Updates increment the counter
|
|
||||||
authoritativeLease.setLastCounterIncrementNanos(System.nanoTime());
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* If updateLease returns false, it means someone took the lease from us. Remove the lease
|
|
||||||
* from our set of owned leases pro-actively rather than waiting for a run of renewLeases().
|
|
||||||
*/
|
|
||||||
log.info("Worker {} lost lease with key {} - discovered during update",
|
|
||||||
workerIdentifier,
|
|
||||||
leaseKey);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Remove only if the value currently in the map is the same as the authoritative lease. We're
|
|
||||||
* guarding against a pause after the concurrency token check above. It plays out like so:
|
|
||||||
*
|
*
|
||||||
* 1) Concurrency token check passes
|
* @param newLeases new leases.
|
||||||
* 2) Pause. Lose lease, re-acquire lease. This requires at least one lease counter update.
|
*/
|
||||||
* 3) Unpause. leaseManager.updateLease fails conditional write due to counter updates, returns
|
public void addLeasesToRenew(Collection<T> newLeases);
|
||||||
* false.
|
|
||||||
* 4) ownedLeases.remove(key, value) doesn't do anything because authoritativeLease does not
|
/**
|
||||||
* .equals() the re-acquired version in the map on the basis of lease counter. This is what we want.
|
* Clears this LeaseRenewer's set of currently held leases.
|
||||||
* If we just used ownedLease.remove(key), we would have pro-actively removed a lease incorrectly.
|
*/
|
||||||
|
public void clearCurrentlyHeldLeases();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the lease renewer from continunig to maintain the given lease.
|
||||||
*
|
*
|
||||||
* Note that there is a subtlety here - Lease.equals() deliberately does not check the concurrency
|
|
||||||
* token, but it does check the lease counter, so this scheme works.
|
|
||||||
*/
|
|
||||||
ownedLeases.remove(leaseKey, authoritativeLease);
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
return updatedLease;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
MetricsHelper.addSuccessAndLatency("UpdateLease", startTime, success, MetricsLevel.DETAILED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addLeasesToRenew(Collection<T> newLeases) {
|
|
||||||
verifyNotNull(newLeases, "newLeases cannot be null");
|
|
||||||
|
|
||||||
for (T lease : newLeases) {
|
|
||||||
if (lease.getLastCounterIncrementNanos() == null) {
|
|
||||||
log.info("addLeasesToRenew ignoring lease with key {} because it does not have lastRenewalNanos set",
|
|
||||||
lease.getLeaseKey());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
T authoritativeLease = lease.copy();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Assign a concurrency token when we add this to the set of currently owned leases. This ensures that
|
|
||||||
* every time we acquire a lease, it gets a new concurrency token.
|
|
||||||
*/
|
|
||||||
authoritativeLease.setConcurrencyToken(UUID.randomUUID());
|
|
||||||
ownedLeases.put(authoritativeLease.getLeaseKey(), authoritativeLease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void clearCurrentlyHeldLeases() {
|
|
||||||
ownedLeases.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* @param lease the lease to drop.
|
* @param lease the lease to drop.
|
||||||
*/
|
*/
|
||||||
@Override
|
void dropLease(T lease);
|
||||||
public void dropLease(T lease) {
|
|
||||||
ownedLeases.remove(lease.getLeaseKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* Update application-specific fields in a currently held lease. Cannot be used to update internal fields such as
|
||||||
|
* leaseCounter, leaseOwner, etc. Fails if we do not hold the lease, or if the concurrency token does not match
|
||||||
|
* the concurrency token on the internal authoritative copy of the lease (ie, if we lost and re-acquired the lease).
|
||||||
|
*
|
||||||
|
* @param lease lease object containing updated data
|
||||||
|
* @param concurrencyToken obtained by calling Lease.getConcurrencyToken for a currently held lease
|
||||||
|
*
|
||||||
|
* @return true if update succeeds, false otherwise
|
||||||
|
*
|
||||||
|
* @throws InvalidStateException if lease table does not exist
|
||||||
|
* @throws ProvisionedThroughputException if DynamoDB update fails due to lack of capacity
|
||||||
|
* @throws DependencyException if DynamoDB update fails in an unexpected way
|
||||||
*/
|
*/
|
||||||
@Override
|
boolean updateLease(T lease, UUID concurrencyToken)
|
||||||
public void initialize() throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
throws DependencyException, InvalidStateException, ProvisionedThroughputException;
|
||||||
Collection<T> leases = leaseManager.listLeases();
|
|
||||||
List<T> myLeases = new LinkedList<T>();
|
|
||||||
boolean renewEvenIfExpired = true;
|
|
||||||
|
|
||||||
for (T lease : leases) {
|
|
||||||
if (workerIdentifier.equals(lease.getLeaseOwner())) {
|
|
||||||
log.info(" Worker {} found lease {}", workerIdentifier, lease);
|
|
||||||
// Okay to renew even if lease is expired, because we start with an empty list and we add the lease to
|
|
||||||
// our list only after a successful renew. So we don't need to worry about the edge case where we could
|
|
||||||
// continue renewing a lease after signaling a lease loss to the application.
|
|
||||||
if (renewLease(lease, renewEvenIfExpired)) {
|
|
||||||
myLeases.add(lease);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("Worker {} ignoring lease {} ", workerIdentifier, lease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addLeasesToRenew(myLeases);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyNotNull(Object object, String message) {
|
|
||||||
if (object == null) {
|
|
||||||
throw new IllegalArgumentException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,522 +14,36 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.leases;
|
package software.amazon.kinesis.leases;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.amazonaws.services.cloudwatch.model.StandardUnit;
|
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
import software.amazon.kinesis.leases.Lease;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
|
||||||
import software.amazon.kinesis.metrics.IMetricsScope;
|
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of ILeaseTaker that uses DynamoDB via LeaseManager.
|
* ILeaseTaker is used by LeaseCoordinator to take new leases, or leases that other workers fail to renew. Each
|
||||||
|
* LeaseCoordinator instance corresponds to one worker and uses exactly one ILeaseTaker to take leases for that worker.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface LeaseTaker<T extends Lease> {
|
||||||
public class LeaseTaker<T extends Lease> implements ILeaseTaker<T> {
|
|
||||||
private static final int TAKE_RETRIES = 3;
|
|
||||||
private static final int SCAN_RETRIES = 1;
|
|
||||||
|
|
||||||
// See note on takeLeases(Callable) for why we have this callable.
|
|
||||||
private static final Callable<Long> SYSTEM_CLOCK_CALLABLE = new Callable<Long>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long call() {
|
|
||||||
return System.nanoTime();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ILeaseManager<T> leaseManager;
|
|
||||||
private final String workerIdentifier;
|
|
||||||
private final Map<String, T> allLeases = new HashMap<String, T>();
|
|
||||||
private final long leaseDurationNanos;
|
|
||||||
private int maxLeasesForWorker = Integer.MAX_VALUE;
|
|
||||||
private int maxLeasesToStealAtOneTime = 1;
|
|
||||||
|
|
||||||
private long lastScanTimeNanos = 0L;
|
|
||||||
|
|
||||||
public LeaseTaker(ILeaseManager<T> leaseManager, String workerIdentifier, long leaseDurationMillis) {
|
|
||||||
this.leaseManager = leaseManager;
|
|
||||||
this.workerIdentifier = workerIdentifier;
|
|
||||||
this.leaseDurationNanos = TimeUnit.MILLISECONDS.toNanos(leaseDurationMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Worker will not acquire more than the specified max number of leases even if there are more
|
* Compute the set of leases available to be taken and attempt to take them. Lease taking rules are:
|
||||||
* shards that need to be processed. This can be used in scenarios where a worker is resource constrained or
|
|
||||||
* to prevent lease thrashing when small number of workers pick up all leases for small amount of time during
|
|
||||||
* deployment.
|
|
||||||
* Note that setting a low value may cause data loss (e.g. if there aren't enough Workers to make progress on all
|
|
||||||
* shards). When setting the value for this property, one must ensure enough workers are present to process
|
|
||||||
* shards and should consider future resharding, child shards that may be blocked on parent shards, some workers
|
|
||||||
* becoming unhealthy, etc.
|
|
||||||
*
|
*
|
||||||
* @param maxLeasesForWorker Max leases this Worker can handle at a time
|
* 1) If a lease's counter hasn't changed in long enough, try to take it.
|
||||||
* @return LeaseTaker
|
* 2) If we see a lease we've never seen before, take it only if owner == null. If it's owned, odds are the owner is
|
||||||
|
* holding it. We can't tell until we see it more than once.
|
||||||
|
* 3) For load balancing purposes, you may violate rules 1 and 2 for EXACTLY ONE lease per call of takeLeases().
|
||||||
|
*
|
||||||
|
* @return map of shardId to Lease object for leases we just successfully took.
|
||||||
|
*
|
||||||
|
* @throws DependencyException on unexpected DynamoDB failures
|
||||||
|
* @throws InvalidStateException if lease table does not exist
|
||||||
*/
|
*/
|
||||||
public LeaseTaker<T> withMaxLeasesForWorker(int maxLeasesForWorker) {
|
public abstract Map<String, T> takeLeases() throws DependencyException, InvalidStateException;
|
||||||
if (maxLeasesForWorker <= 0) {
|
|
||||||
throw new IllegalArgumentException("maxLeasesForWorker should be >= 1");
|
|
||||||
}
|
|
||||||
this.maxLeasesForWorker = maxLeasesForWorker;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max leases to steal from a more loaded Worker at one time (for load balancing).
|
* @return workerIdentifier for this LeaseTaker
|
||||||
* Setting this to a higher number can allow for faster load convergence (e.g. during deployments, cold starts),
|
|
||||||
* but can cause higher churn in the system.
|
|
||||||
*
|
|
||||||
* @param maxLeasesToStealAtOneTime Steal up to this many leases at one time (for load balancing)
|
|
||||||
* @return LeaseTaker
|
|
||||||
*/
|
*/
|
||||||
public LeaseTaker<T> withMaxLeasesToStealAtOneTime(int maxLeasesToStealAtOneTime) {
|
public abstract String getWorkerIdentifier();
|
||||||
if (maxLeasesToStealAtOneTime <= 0) {
|
|
||||||
throw new IllegalArgumentException("maxLeasesToStealAtOneTime should be >= 1");
|
|
||||||
}
|
|
||||||
this.maxLeasesToStealAtOneTime = maxLeasesToStealAtOneTime;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, T> takeLeases() throws DependencyException, InvalidStateException {
|
|
||||||
return takeLeases(SYSTEM_CLOCK_CALLABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal implementation of takeLeases. Takes a callable that can provide the time to enable test cases without
|
|
||||||
* Thread.sleep. Takes a callable instead of a raw time value because the time needs to be computed as-of
|
|
||||||
* immediately after the scan.
|
|
||||||
*
|
|
||||||
* @param timeProvider Callable that will supply the time
|
|
||||||
*
|
|
||||||
* @return map of lease key to taken lease
|
|
||||||
*
|
|
||||||
* @throws DependencyException
|
|
||||||
* @throws InvalidStateException
|
|
||||||
*/
|
|
||||||
synchronized Map<String, T> takeLeases(Callable<Long> timeProvider)
|
|
||||||
throws DependencyException, InvalidStateException {
|
|
||||||
// Key is leaseKey
|
|
||||||
Map<String, T> takenLeases = new HashMap<String, T>();
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
boolean success = false;
|
|
||||||
|
|
||||||
ProvisionedThroughputException lastException = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (int i = 1; i <= SCAN_RETRIES; i++) {
|
|
||||||
try {
|
|
||||||
updateAllLeases(timeProvider);
|
|
||||||
success = true;
|
|
||||||
} catch (ProvisionedThroughputException e) {
|
|
||||||
log.info("Worker {} could not find expired leases on try {} out of {}",
|
|
||||||
workerIdentifier,
|
|
||||||
i,
|
|
||||||
TAKE_RETRIES);
|
|
||||||
lastException = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
MetricsHelper.addSuccessAndLatency("ListLeases", startTime, success, MetricsLevel.DETAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastException != null) {
|
|
||||||
log.error("Worker {} could not scan leases table, aborting takeLeases. Exception caught by last retry:",
|
|
||||||
workerIdentifier,
|
|
||||||
lastException);
|
|
||||||
return takenLeases;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<T> expiredLeases = getExpiredLeases();
|
|
||||||
|
|
||||||
Set<T> leasesToTake = computeLeasesToTake(expiredLeases);
|
|
||||||
Set<String> untakenLeaseKeys = new HashSet<String>();
|
|
||||||
|
|
||||||
for (T lease : leasesToTake) {
|
|
||||||
String leaseKey = lease.getLeaseKey();
|
|
||||||
|
|
||||||
startTime = System.currentTimeMillis();
|
|
||||||
success = false;
|
|
||||||
try {
|
|
||||||
for (int i = 1; i <= TAKE_RETRIES; i++) {
|
|
||||||
try {
|
|
||||||
if (leaseManager.takeLease(lease, workerIdentifier)) {
|
|
||||||
lease.setLastCounterIncrementNanos(System.nanoTime());
|
|
||||||
takenLeases.put(leaseKey, lease);
|
|
||||||
} else {
|
|
||||||
untakenLeaseKeys.add(leaseKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
} catch (ProvisionedThroughputException e) {
|
|
||||||
log.info("Could not take lease with key {} for worker {} on try {} out of {} due to capacity",
|
|
||||||
leaseKey,
|
|
||||||
workerIdentifier,
|
|
||||||
i,
|
|
||||||
TAKE_RETRIES);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
MetricsHelper.addSuccessAndLatency("TakeLease", startTime, success, MetricsLevel.DETAILED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (takenLeases.size() > 0) {
|
|
||||||
log.info("Worker {} successfully took {} leases: {}",
|
|
||||||
workerIdentifier,
|
|
||||||
takenLeases.size(),
|
|
||||||
stringJoin(takenLeases.keySet(), ", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (untakenLeaseKeys.size() > 0) {
|
|
||||||
log.info("Worker {} failed to take {} leases: {}",
|
|
||||||
workerIdentifier,
|
|
||||||
untakenLeaseKeys.size(),
|
|
||||||
stringJoin(untakenLeaseKeys, ", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
MetricsHelper.getMetricsScope().addData(
|
|
||||||
"TakenLeases", takenLeases.size(), StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
|
|
||||||
return takenLeases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Package access for testing purposes.
|
|
||||||
*
|
|
||||||
* @param strings
|
|
||||||
* @param delimiter
|
|
||||||
* @return Joined string.
|
|
||||||
*/
|
|
||||||
static String stringJoin(Collection<String> strings, String delimiter) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
boolean needDelimiter = false;
|
|
||||||
for (String string : strings) {
|
|
||||||
if (needDelimiter) {
|
|
||||||
builder.append(delimiter);
|
|
||||||
}
|
|
||||||
builder.append(string);
|
|
||||||
needDelimiter = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan all leases and update lastRenewalTime. Add new leases and delete old leases.
|
|
||||||
*
|
|
||||||
* @param timeProvider callable that supplies the current time
|
|
||||||
*
|
|
||||||
* @return list of expired leases, possibly empty, never null.
|
|
||||||
*
|
|
||||||
* @throws ProvisionedThroughputException if listLeases fails due to lack of provisioned throughput
|
|
||||||
* @throws InvalidStateException if the lease table does not exist
|
|
||||||
* @throws DependencyException if listLeases fails in an unexpected way
|
|
||||||
*/
|
|
||||||
private void updateAllLeases(Callable<Long> timeProvider)
|
|
||||||
throws DependencyException, InvalidStateException, ProvisionedThroughputException {
|
|
||||||
List<T> freshList = leaseManager.listLeases();
|
|
||||||
try {
|
|
||||||
lastScanTimeNanos = timeProvider.call();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new DependencyException("Exception caught from timeProvider", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This set will hold the lease keys not updated by the previous listLeases call.
|
|
||||||
Set<String> notUpdated = new HashSet<String>(allLeases.keySet());
|
|
||||||
|
|
||||||
// Iterate over all leases, finding ones to try to acquire that haven't changed since the last iteration
|
|
||||||
for (T lease : freshList) {
|
|
||||||
String leaseKey = lease.getLeaseKey();
|
|
||||||
|
|
||||||
T oldLease = allLeases.get(leaseKey);
|
|
||||||
allLeases.put(leaseKey, lease);
|
|
||||||
notUpdated.remove(leaseKey);
|
|
||||||
|
|
||||||
if (oldLease != null) {
|
|
||||||
// If we've seen this lease before...
|
|
||||||
if (oldLease.getLeaseCounter().equals(lease.getLeaseCounter())) {
|
|
||||||
// ...and the counter hasn't changed, propagate the lastRenewalNanos time from the old lease
|
|
||||||
lease.setLastCounterIncrementNanos(oldLease.getLastCounterIncrementNanos());
|
|
||||||
} else {
|
|
||||||
// ...and the counter has changed, set lastRenewalNanos to the time of the scan.
|
|
||||||
lease.setLastCounterIncrementNanos(lastScanTimeNanos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lease.getLeaseOwner() == null) {
|
|
||||||
// if this new lease is unowned, it's never been renewed.
|
|
||||||
lease.setLastCounterIncrementNanos(0L);
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Treating new lease with key {} as never renewed because it is new and unowned.",
|
|
||||||
leaseKey);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if this new lease is owned, treat it as renewed as of the scan
|
|
||||||
lease.setLastCounterIncrementNanos(lastScanTimeNanos);
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Treating new lease with key {} as recently renewed because it is new and owned.",
|
|
||||||
leaseKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove dead leases from allLeases
|
|
||||||
for (String key : notUpdated) {
|
|
||||||
allLeases.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list of leases that were expired as of our last scan.
|
|
||||||
*/
|
|
||||||
private List<T> getExpiredLeases() {
|
|
||||||
List<T> expiredLeases = new ArrayList<T>();
|
|
||||||
|
|
||||||
for (T lease : allLeases.values()) {
|
|
||||||
if (lease.isExpired(leaseDurationNanos, lastScanTimeNanos)) {
|
|
||||||
expiredLeases.add(lease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expiredLeases;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the number of leases I should try to take based on the state of the system.
|
|
||||||
*
|
|
||||||
* @param allLeases map of shardId to lease containing all leases
|
|
||||||
* @param expiredLeases list of leases we determined to be expired
|
|
||||||
* @return set of leases to take.
|
|
||||||
*/
|
|
||||||
private Set<T> computeLeasesToTake(List<T> expiredLeases) {
|
|
||||||
Map<String, Integer> leaseCounts = computeLeaseCounts(expiredLeases);
|
|
||||||
Set<T> leasesToTake = new HashSet<T>();
|
|
||||||
IMetricsScope metrics = MetricsHelper.getMetricsScope();
|
|
||||||
|
|
||||||
int numLeases = allLeases.size();
|
|
||||||
int numWorkers = leaseCounts.size();
|
|
||||||
|
|
||||||
if (numLeases == 0) {
|
|
||||||
// If there are no leases, I shouldn't try to take any.
|
|
||||||
return leasesToTake;
|
|
||||||
}
|
|
||||||
|
|
||||||
int target;
|
|
||||||
if (numWorkers >= numLeases) {
|
|
||||||
// If we have n leases and n or more workers, each worker can have up to 1 lease, including myself.
|
|
||||||
target = 1;
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* numWorkers must be < numLeases.
|
|
||||||
*
|
|
||||||
* Our target for each worker is numLeases / numWorkers (+1 if numWorkers doesn't evenly divide numLeases)
|
|
||||||
*/
|
|
||||||
target = numLeases / numWorkers + (numLeases % numWorkers == 0 ? 0 : 1);
|
|
||||||
|
|
||||||
// Spill over is the number of leases this worker should have claimed, but did not because it would
|
|
||||||
// exceed the max allowed for this worker.
|
|
||||||
int leaseSpillover = Math.max(0, target - maxLeasesForWorker);
|
|
||||||
if (target > maxLeasesForWorker) {
|
|
||||||
log.warn("Worker {} target is {} leases and maxLeasesForWorker is {}."
|
|
||||||
+ " Resetting target to {}, lease spillover is {}. "
|
|
||||||
+ " Note that some shards may not be processed if no other workers are able to pick them up.",
|
|
||||||
workerIdentifier,
|
|
||||||
target,
|
|
||||||
maxLeasesForWorker,
|
|
||||||
maxLeasesForWorker,
|
|
||||||
leaseSpillover);
|
|
||||||
target = maxLeasesForWorker;
|
|
||||||
}
|
|
||||||
metrics.addData("LeaseSpillover", leaseSpillover, StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
int myCount = leaseCounts.get(workerIdentifier);
|
|
||||||
int numLeasesToReachTarget = target - myCount;
|
|
||||||
|
|
||||||
if (numLeasesToReachTarget <= 0) {
|
|
||||||
// If we don't need anything, return the empty set.
|
|
||||||
return leasesToTake;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle expiredLeases so workers don't all try to contend for the same leases.
|
|
||||||
Collections.shuffle(expiredLeases);
|
|
||||||
|
|
||||||
int originalExpiredLeasesSize = expiredLeases.size();
|
|
||||||
if (expiredLeases.size() > 0) {
|
|
||||||
// If we have expired leases, get up to <needed> leases from expiredLeases
|
|
||||||
for (; numLeasesToReachTarget > 0 && expiredLeases.size() > 0; numLeasesToReachTarget--) {
|
|
||||||
leasesToTake.add(expiredLeases.remove(0));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If there are no expired leases and we need a lease, consider stealing.
|
|
||||||
List<T> leasesToSteal = chooseLeasesToSteal(leaseCounts, numLeasesToReachTarget, target);
|
|
||||||
for (T leaseToSteal : leasesToSteal) {
|
|
||||||
log.info("Worker {} needed {} leases but none were expired, so it will steal lease {} from {}",
|
|
||||||
workerIdentifier,
|
|
||||||
numLeasesToReachTarget,
|
|
||||||
leaseToSteal.getLeaseKey(),
|
|
||||||
leaseToSteal.getLeaseOwner());
|
|
||||||
leasesToTake.add(leaseToSteal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!leasesToTake.isEmpty()) {
|
|
||||||
log.info("Worker {} saw {} total leases, {} available leases, {} "
|
|
||||||
+ "workers. Target is {} leases, I have {} leases, I will take {} leases",
|
|
||||||
workerIdentifier,
|
|
||||||
numLeases,
|
|
||||||
originalExpiredLeasesSize,
|
|
||||||
numWorkers,
|
|
||||||
target,
|
|
||||||
myCount,
|
|
||||||
leasesToTake.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
metrics.addData("TotalLeases", numLeases, StandardUnit.Count, MetricsLevel.DETAILED);
|
|
||||||
metrics.addData("ExpiredLeases", originalExpiredLeasesSize, StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
metrics.addData("NumWorkers", numWorkers, StandardUnit.Count, MetricsLevel.SUMMARY);
|
|
||||||
metrics.addData("NeededLeases", numLeasesToReachTarget, StandardUnit.Count, MetricsLevel.DETAILED);
|
|
||||||
metrics.addData("LeasesToTake", leasesToTake.size(), StandardUnit.Count, MetricsLevel.DETAILED);
|
|
||||||
|
|
||||||
return leasesToTake;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Choose leases to steal by randomly selecting one or more (up to max) from the most loaded worker.
|
|
||||||
* Stealing rules:
|
|
||||||
*
|
|
||||||
* Steal up to maxLeasesToStealAtOneTime leases from the most loaded worker if
|
|
||||||
* a) he has > target leases and I need >= 1 leases : steal min(leases needed, maxLeasesToStealAtOneTime)
|
|
||||||
* b) he has == target leases and I need > 1 leases : steal 1
|
|
||||||
*
|
|
||||||
* @param leaseCounts map of workerIdentifier to lease count
|
|
||||||
* @param needed # of leases needed to reach the target leases for the worker
|
|
||||||
* @param target target # of leases per worker
|
|
||||||
* @return Leases to steal, or empty list if we should not steal
|
|
||||||
*/
|
|
||||||
private List<T> chooseLeasesToSteal(Map<String, Integer> leaseCounts, int needed, int target) {
|
|
||||||
List<T> leasesToSteal = new ArrayList<>();
|
|
||||||
|
|
||||||
Entry<String, Integer> mostLoadedWorker = null;
|
|
||||||
// Find the most loaded worker
|
|
||||||
for (Entry<String, Integer> worker : leaseCounts.entrySet()) {
|
|
||||||
if (mostLoadedWorker == null || mostLoadedWorker.getValue() < worker.getValue()) {
|
|
||||||
mostLoadedWorker = worker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int numLeasesToSteal = 0;
|
|
||||||
if ((mostLoadedWorker.getValue() >= target) && (needed > 0)) {
|
|
||||||
int leasesOverTarget = mostLoadedWorker.getValue() - target;
|
|
||||||
numLeasesToSteal = Math.min(needed, leasesOverTarget);
|
|
||||||
// steal 1 if we need > 1 and max loaded worker has target leases.
|
|
||||||
if ((needed > 1) && (numLeasesToSteal == 0)) {
|
|
||||||
numLeasesToSteal = 1;
|
|
||||||
}
|
|
||||||
numLeasesToSteal = Math.min(numLeasesToSteal, maxLeasesToStealAtOneTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numLeasesToSteal <= 0) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug(String.format("Worker %s not stealing from most loaded worker %s. He has %d,"
|
|
||||||
+ " target is %d, and I need %d",
|
|
||||||
workerIdentifier,
|
|
||||||
mostLoadedWorker.getKey(),
|
|
||||||
mostLoadedWorker.getValue(),
|
|
||||||
target,
|
|
||||||
needed));
|
|
||||||
}
|
|
||||||
return leasesToSteal;
|
|
||||||
} else {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Worker {} will attempt to steal {} leases from most loaded worker {}. "
|
|
||||||
+ " He has {} leases, target is {}, I need {}, maxLeasesToSteatAtOneTime is {}.",
|
|
||||||
workerIdentifier,
|
|
||||||
numLeasesToSteal,
|
|
||||||
mostLoadedWorker.getKey(),
|
|
||||||
mostLoadedWorker.getValue(),
|
|
||||||
target,
|
|
||||||
needed,
|
|
||||||
maxLeasesToStealAtOneTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String mostLoadedWorkerIdentifier = mostLoadedWorker.getKey();
|
|
||||||
List<T> candidates = new ArrayList<T>();
|
|
||||||
// Collect leases belonging to that worker
|
|
||||||
for (T lease : allLeases.values()) {
|
|
||||||
if (mostLoadedWorkerIdentifier.equals(lease.getLeaseOwner())) {
|
|
||||||
candidates.add(lease);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return random ones
|
|
||||||
Collections.shuffle(candidates);
|
|
||||||
int toIndex = Math.min(candidates.size(), numLeasesToSteal);
|
|
||||||
leasesToSteal.addAll(candidates.subList(0, toIndex));
|
|
||||||
|
|
||||||
return leasesToSteal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count leases by host. Always includes myself, but otherwise only includes hosts that are currently holding
|
|
||||||
* leases.
|
|
||||||
*
|
|
||||||
* @param expiredLeases list of leases that are currently expired
|
|
||||||
* @return map of workerIdentifier to lease count
|
|
||||||
*/
|
|
||||||
private Map<String, Integer> computeLeaseCounts(List<T> expiredLeases) {
|
|
||||||
Map<String, Integer> leaseCounts = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
// Compute the number of leases per worker by looking through allLeases and ignoring leases that have expired.
|
|
||||||
for (T lease : allLeases.values()) {
|
|
||||||
if (!expiredLeases.contains(lease)) {
|
|
||||||
String leaseOwner = lease.getLeaseOwner();
|
|
||||||
Integer oldCount = leaseCounts.get(leaseOwner);
|
|
||||||
if (oldCount == null) {
|
|
||||||
leaseCounts.put(leaseOwner, 1);
|
|
||||||
} else {
|
|
||||||
leaseCounts.put(leaseOwner, oldCount + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If I have no leases, I wasn't represented in leaseCounts. Let's fix that.
|
|
||||||
Integer myCount = leaseCounts.get(workerIdentifier);
|
|
||||||
if (myCount == null) {
|
|
||||||
myCount = 0;
|
|
||||||
leaseCounts.put(workerIdentifier, myCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaseCounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getWorkerIdentifier() {
|
|
||||||
return workerIdentifier;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.lifecycle;
|
package software.amazon.kinesis.lifecycle;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for the parameters to the IRecordProcessor's
|
* Container for the parameters to the IRecordProcessor's
|
||||||
* {@link IRecordProcessor#initialize(InitializationInput
|
* {@link RecordProcessor#initialize(InitializationInput
|
||||||
* initializationInput) initialize} method.
|
* initializationInput) initialize} method.
|
||||||
*/
|
*/
|
||||||
public class InitializationInput {
|
public class InitializationInput {
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ package software.amazon.kinesis.lifecycle;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
import software.amazon.kinesis.checkpoint.Checkpoint;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
@ -40,9 +40,9 @@ public class InitializeTask implements ITask {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ShardInfo shardInfo;
|
private final ShardInfo shardInfo;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IRecordProcessor recordProcessor;
|
private final RecordProcessor recordProcessor;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ICheckpoint checkpoint;
|
private final Checkpointer checkpoint;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
@ -69,7 +69,7 @@ public class InitializeTask implements ITask {
|
||||||
try {
|
try {
|
||||||
log.debug("Initializing ShardId {}", shardInfo);
|
log.debug("Initializing ShardId {}", shardInfo);
|
||||||
Checkpoint initialCheckpointObject = checkpoint.getCheckpointObject(shardInfo.shardId());
|
Checkpoint initialCheckpointObject = checkpoint.getCheckpointObject(shardInfo.shardId());
|
||||||
ExtendedSequenceNumber initialCheckpoint = initialCheckpointObject.getCheckpoint();
|
ExtendedSequenceNumber initialCheckpoint = initialCheckpointObject.checkpoint();
|
||||||
|
|
||||||
cache.start(initialCheckpoint, initialPositionInStream);
|
cache.start(initialCheckpoint, initialPositionInStream);
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ public class InitializeTask implements ITask {
|
||||||
final InitializationInput initializationInput = new InitializationInput()
|
final InitializationInput initializationInput = new InitializationInput()
|
||||||
.withShardId(shardInfo.shardId())
|
.withShardId(shardInfo.shardId())
|
||||||
.withExtendedSequenceNumber(initialCheckpoint)
|
.withExtendedSequenceNumber(initialCheckpoint)
|
||||||
.withPendingCheckpointSequenceNumber(initialCheckpointObject.getPendingCheckpoint());
|
.withPendingCheckpointSequenceNumber(initialCheckpointObject.pendingCheckpoint());
|
||||||
final long recordProcessorStartTimeMillis = System.currentTimeMillis();
|
final long recordProcessorStartTimeMillis = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
recordProcessor.initialize(initializationInput);
|
recordProcessor.initialize(initializationInput);
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import com.amazonaws.services.kinesis.model.Record;
|
import com.amazonaws.services.kinesis.model.Record;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for the parameters to the IRecordProcessor's
|
* Container for the parameters to the IRecordProcessor's
|
||||||
* {@link IRecordProcessor#processRecords(
|
* {@link RecordProcessor#processRecords(
|
||||||
* ProcessRecordsInput processRecordsInput) processRecords} method.
|
* ProcessRecordsInput processRecordsInput) processRecords} method.
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ import com.amazonaws.services.kinesis.model.Shard;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsScope;
|
import software.amazon.kinesis.metrics.IMetricsScope;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
import software.amazon.kinesis.retrieval.ThrottlingReporter;
|
import software.amazon.kinesis.retrieval.ThrottlingReporter;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
@ -42,7 +42,7 @@ public class ProcessTask implements ITask {
|
||||||
private static final String RECORD_PROCESSOR_PROCESS_RECORDS_METRIC = "RecordProcessor.processRecords";
|
private static final String RECORD_PROCESSOR_PROCESS_RECORDS_METRIC = "RecordProcessor.processRecords";
|
||||||
|
|
||||||
private final ShardInfo shardInfo;
|
private final ShardInfo shardInfo;
|
||||||
private final IRecordProcessor recordProcessor;
|
private final RecordProcessor recordProcessor;
|
||||||
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
private final TaskType taskType = TaskType.PROCESS;
|
private final TaskType taskType = TaskType.PROCESS;
|
||||||
private final long backoffTimeMillis;
|
private final long backoffTimeMillis;
|
||||||
|
|
@ -73,7 +73,7 @@ public class ProcessTask implements ITask {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProcessTask(@NonNull final ShardInfo shardInfo,
|
public ProcessTask(@NonNull final ShardInfo shardInfo,
|
||||||
@NonNull final IRecordProcessor recordProcessor,
|
@NonNull final RecordProcessor recordProcessor,
|
||||||
@NonNull final RecordProcessorCheckpointer recordProcessorCheckpointer,
|
@NonNull final RecordProcessorCheckpointer recordProcessorCheckpointer,
|
||||||
final long backoffTimeMillis,
|
final long backoffTimeMillis,
|
||||||
final boolean skipShardSyncAtWorkerInitializationIfLeasesExist,
|
final boolean skipShardSyncAtWorkerInitializationIfLeasesExist,
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,14 @@ import software.amazon.kinesis.lifecycle.events.RecordsReceived;
|
||||||
import software.amazon.kinesis.lifecycle.events.ShardCompleted;
|
import software.amazon.kinesis.lifecycle.events.ShardCompleted;
|
||||||
import software.amazon.kinesis.lifecycle.events.ShutdownRequested;
|
import software.amazon.kinesis.lifecycle.events.ShutdownRequested;
|
||||||
import software.amazon.kinesis.lifecycle.events.Started;
|
import software.amazon.kinesis.lifecycle.events.Started;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RecordProcessorShim implements RecordProcessorLifecycle {
|
public class RecordProcessorShim implements RecordProcessorLifecycle {
|
||||||
|
|
||||||
private final IRecordProcessor delegate;
|
private final RecordProcessor delegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void started(Started started) {
|
public void started(Started started) {
|
||||||
|
|
@ -60,8 +60,8 @@ public class RecordProcessorShim implements RecordProcessorLifecycle {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdownRequested(ShutdownRequested shutdownRequested) {
|
public void shutdownRequested(ShutdownRequested shutdownRequested) {
|
||||||
if (delegate instanceof IShutdownNotificationAware) {
|
if (delegate instanceof ShutdownNotificationAware) {
|
||||||
IShutdownNotificationAware aware = (IShutdownNotificationAware)delegate;
|
ShutdownNotificationAware aware = (ShutdownNotificationAware)delegate;
|
||||||
aware.shutdownRequested(shutdownRequested.getCheckpointer());
|
aware.shutdownRequested(shutdownRequested.getCheckpointer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,16 +34,15 @@ import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Synchronized;
|
import lombok.Synchronized;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseManager;
|
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.MetricsCollectingTaskDecorator;
|
import software.amazon.kinesis.metrics.MetricsCollectingTaskDecorator;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -67,9 +66,9 @@ public class ShardConsumer {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final GetRecordsCache getRecordsCache;
|
private final GetRecordsCache getRecordsCache;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IRecordProcessor recordProcessor;
|
private final RecordProcessor recordProcessor;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ICheckpoint checkpoint;
|
private final Checkpointer checkpoint;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
private final long parentShardPollIntervalMillis;
|
private final long parentShardPollIntervalMillis;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ package software.amazon.kinesis.lifecycle;
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseCoordinator;
|
import software.amazon.kinesis.leases.LeaseCoordinator;
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class ShardConsumerShutdownNotification implements ShutdownNotification {
|
||||||
* the lease that this shutdown request will free once initial shutdown is complete
|
* the lease that this shutdown request will free once initial shutdown is complete
|
||||||
* @param notificationCompleteLatch
|
* @param notificationCompleteLatch
|
||||||
* used to inform the caller once the
|
* used to inform the caller once the
|
||||||
* {@link IShutdownNotificationAware} object has been
|
* {@link ShutdownNotificationAware} object has been
|
||||||
* notified of the shutdown request.
|
* notified of the shutdown request.
|
||||||
* @param shutdownCompleteLatch
|
* @param shutdownCompleteLatch
|
||||||
* used to inform the caller once the record processor is fully shutdown
|
* used to inform the caller once the record processor is fully shutdown
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ package software.amazon.kinesis.lifecycle;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for the parameters to the IRecordProcessor's
|
* Container for the parameters to the IRecordProcessor's
|
||||||
* {@link IRecordProcessor#shutdown(ShutdownInput
|
* {@link RecordProcessor#shutdown(ShutdownInput
|
||||||
* shutdownInput) shutdown} method.
|
* shutdownInput) shutdown} method.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.lifecycle;
|
package software.amazon.kinesis.lifecycle;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A shutdown request to the ShardConsumer
|
* A shutdown request to the ShardConsumer
|
||||||
|
|
@ -29,7 +29,7 @@ public interface ShutdownNotification {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to indicate that the record processor has completed the call to
|
* Used to indicate that the record processor has completed the call to
|
||||||
* {@link IRecordProcessor#shutdown(ShutdownInput)} has
|
* {@link RecordProcessor#shutdown(ShutdownInput)} has
|
||||||
* completed.
|
* completed.
|
||||||
*/
|
*/
|
||||||
void shutdownComplete();
|
void shutdownComplete();
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies record processor of incoming shutdown request, and gives them a chance to checkpoint.
|
* Notifies record processor of incoming shutdown request, and gives them a chance to checkpoint.
|
||||||
|
|
@ -28,7 +28,7 @@ import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ShutdownNotificationTask implements ITask {
|
public class ShutdownNotificationTask implements ITask {
|
||||||
private final IRecordProcessor recordProcessor;
|
private final RecordProcessor recordProcessor;
|
||||||
private final IRecordProcessorCheckpointer recordProcessorCheckpointer;
|
private final IRecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
private final ShutdownNotification shutdownNotification;
|
private final ShutdownNotification shutdownNotification;
|
||||||
// TODO: remove if not used
|
// TODO: remove if not used
|
||||||
|
|
@ -38,8 +38,8 @@ public class ShutdownNotificationTask implements ITask {
|
||||||
@Override
|
@Override
|
||||||
public TaskResult call() {
|
public TaskResult call() {
|
||||||
try {
|
try {
|
||||||
if (recordProcessor instanceof IShutdownNotificationAware) {
|
if (recordProcessor instanceof ShutdownNotificationAware) {
|
||||||
IShutdownNotificationAware shutdownNotificationAware = (IShutdownNotificationAware) recordProcessor;
|
ShutdownNotificationAware shutdownNotificationAware = (ShutdownNotificationAware) recordProcessor;
|
||||||
try {
|
try {
|
||||||
shutdownNotificationAware.shutdownRequested(recordProcessorCheckpointer);
|
shutdownNotificationAware.shutdownRequested(recordProcessorCheckpointer);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ package software.amazon.kinesis.lifecycle;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
|
|
||||||
import static software.amazon.kinesis.lifecycle.ConsumerStates.ConsumerState;
|
import static software.amazon.kinesis.lifecycle.ConsumerStates.ConsumerState;
|
||||||
import static software.amazon.kinesis.lifecycle.ConsumerStates.ShardConsumerState;
|
import static software.amazon.kinesis.lifecycle.ConsumerStates.ShardConsumerState;
|
||||||
|
|
@ -50,7 +50,7 @@ public enum ShutdownReason {
|
||||||
/**
|
/**
|
||||||
* Indicates that the entire application is being shutdown, and if desired the record processor will be given a
|
* Indicates that the entire application is being shutdown, and if desired the record processor will be given a
|
||||||
* final chance to checkpoint. This state will not trigger a direct call to
|
* final chance to checkpoint. This state will not trigger a direct call to
|
||||||
* {@link IRecordProcessor#shutdown(ShutdownInput)}, but
|
* {@link RecordProcessor#shutdown(ShutdownInput)}, but
|
||||||
* instead depend on a different interface for backward compatibility.
|
* instead depend on a different interface for backward compatibility.
|
||||||
*/
|
*/
|
||||||
REQUESTED(1, ShardConsumerState.SHUTDOWN_REQUESTED.getConsumerState());
|
REQUESTED(1, ShardConsumerState.SHUTDOWN_REQUESTED.getConsumerState());
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
|
|
@ -28,7 +28,7 @@ import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.leases.ShardSyncer;
|
import software.amazon.kinesis.leases.ShardSyncer;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
import software.amazon.kinesis.metrics.MetricsLevel;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ public class ShutdownTask implements ITask {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final LeaseManagerProxy leaseManagerProxy;
|
private final LeaseManagerProxy leaseManagerProxy;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final IRecordProcessor recordProcessor;
|
private final RecordProcessor recordProcessor;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
private final RecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ public class MetricsConfig {
|
||||||
*
|
*
|
||||||
* @return {@link AmazonCloudWatch}
|
* @return {@link AmazonCloudWatch}
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
private final AmazonCloudWatch amazonCloudWatch;
|
private final AmazonCloudWatch amazonCloudWatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
/**
|
/**
|
||||||
* Interface for checkpoint trackers.
|
* Interface for checkpoint trackers.
|
||||||
*/
|
*/
|
||||||
public interface ICheckpoint {
|
public interface Checkpointer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a checkpoint for a shard (e.g. sequence and subsequence numbers of last record processed
|
* Record a checkpoint for a shard (e.g. sequence and subsequence numbers of last record processed
|
||||||
|
|
@ -30,7 +30,7 @@ public interface IPreparedCheckpointer {
|
||||||
/**
|
/**
|
||||||
* @return sequence number of pending checkpoint
|
* @return sequence number of pending checkpoint
|
||||||
*/
|
*/
|
||||||
ExtendedSequenceNumber getPendingCheckpoint();
|
ExtendedSequenceNumber pendingCheckpoint();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will record a pending checkpoint.
|
* This method will record a pending checkpoint.
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,5 @@ package software.amazon.kinesis.processor;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public interface ProcessorFactory {
|
public interface ProcessorFactory {
|
||||||
IRecordProcessor createRecordProcessor();
|
RecordProcessor createRecordProcessor();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import software.amazon.kinesis.lifecycle.ShutdownReason;
|
||||||
* The Amazon Kinesis Client Library will instantiate record processors to process data records fetched from Amazon
|
* The Amazon Kinesis Client Library will instantiate record processors to process data records fetched from Amazon
|
||||||
* Kinesis.
|
* Kinesis.
|
||||||
*/
|
*/
|
||||||
public interface IRecordProcessor {
|
public interface RecordProcessor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked by the Amazon Kinesis Client Library before data records are delivered to the RecordProcessor instance
|
* Invoked by the Amazon Kinesis Client Library before data records are delivered to the RecordProcessor instance
|
||||||
|
|
@ -19,13 +19,13 @@ package software.amazon.kinesis.processor;
|
||||||
* The Amazon Kinesis Client Library will use this to instantiate a record processor per shard.
|
* The Amazon Kinesis Client Library will use this to instantiate a record processor per shard.
|
||||||
* Clients may choose to create separate instantiations, or re-use instantiations.
|
* Clients may choose to create separate instantiations, or re-use instantiations.
|
||||||
*/
|
*/
|
||||||
public interface IRecordProcessorFactory {
|
public interface RecordProcessorFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a record processor to be used for processing data records for a (assigned) shard.
|
* Returns a record processor to be used for processing data records for a (assigned) shard.
|
||||||
*
|
*
|
||||||
* @return Returns a processor object.
|
* @return Returns a processor object.
|
||||||
*/
|
*/
|
||||||
IRecordProcessor createProcessor();
|
RecordProcessor createProcessor();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
/**
|
/**
|
||||||
* Allows a record processor to indicate it's aware of requested shutdowns, and handle the request.
|
* Allows a record processor to indicate it's aware of requested shutdowns, and handle the request.
|
||||||
*/
|
*/
|
||||||
public interface IShutdownNotificationAware {
|
public interface ShutdownNotificationAware {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the worker has been requested to shutdown, and gives the record processor a chance to checkpoint.
|
* Called when the worker has been requested to shutdown, and gives the record processor a chance to checkpoint.
|
||||||
|
|
@ -26,11 +26,6 @@ import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class SynchronousBlockingRetrievalFactory implements RetrievalFactory {
|
public class SynchronousBlockingRetrievalFactory implements RetrievalFactory {
|
||||||
// Need to remove this. Has no use any longer.
|
|
||||||
private static final long DESCRIBE_STREAM_BACKOFF_TIME_IN_MILLIS = 1500L;
|
|
||||||
// Need to remove this. Has no use any longer.
|
|
||||||
private static final int MAX_DESCRIBE_STREAM_RETRY_ATTEMPTS = 50;
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String streamName;
|
private final String streamName;
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Amazon Software License (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://aws.amazon.com/asl/
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file 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 software.amazon.kinesis.retrieval;
|
||||||
|
|
||||||
|
import com.amazonaws.services.kinesis.AmazonKinesis;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SynchronousPrefetchingRetrievalFactory implements RetrievalFactory {
|
||||||
|
@NonNull
|
||||||
|
private final String streamName;
|
||||||
|
@NonNull
|
||||||
|
private final AmazonKinesis amazonKinesis;
|
||||||
|
private final int maxRecords;
|
||||||
|
private final int maxPendingProcessRecordsInput;
|
||||||
|
private final int maxByteSize;
|
||||||
|
private final int maxRecordsCount;
|
||||||
|
private final int maxRecordsPerCall;
|
||||||
|
@NonNull
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
private final long idleMillisBetweenCalls;
|
||||||
|
@NonNull
|
||||||
|
private final IMetricsFactory metricsFactory;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GetRecordsRetrievalStrategy createGetRecordsRetrievalStrategy(final ShardInfo shardInfo) {
|
||||||
|
return new SynchronousGetRecordsRetrievalStrategy(
|
||||||
|
new KinesisDataFetcher(amazonKinesis, streamName, shardInfo.shardId(), maxRecords));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GetRecordsCache createGetRecordsCache(final ShardInfo shardInfo) {
|
||||||
|
return new PrefetchGetRecordsCache(maxPendingProcessRecordsInput, maxByteSize, maxRecordsCount,
|
||||||
|
maxRecordsPerCall, createGetRecordsRetrievalStrategy(shardInfo), executorService,
|
||||||
|
idleMillisBetweenCalls, metricsFactory, "Prefetching", shardInfo.shardId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
||||||
|
|
@ -34,7 +34,7 @@ public abstract class CheckpointImplTestBase {
|
||||||
|
|
||||||
protected final String startingSequenceNumber = "0001000";
|
protected final String startingSequenceNumber = "0001000";
|
||||||
protected final String testConcurrencyToken = "testToken";
|
protected final String testConcurrencyToken = "testToken";
|
||||||
protected ICheckpoint checkpoint;
|
protected Checkpointer checkpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws java.lang.Exception
|
* @throws java.lang.Exception
|
||||||
|
|
@ -107,8 +107,8 @@ public abstract class CheckpointImplTestBase {
|
||||||
ExtendedSequenceNumber extendedSequenceNumber = new ExtendedSequenceNumber(checkpointValue);
|
ExtendedSequenceNumber extendedSequenceNumber = new ExtendedSequenceNumber(checkpointValue);
|
||||||
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(checkpointValue), concurrencyToken);
|
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(checkpointValue), concurrencyToken);
|
||||||
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -123,8 +123,8 @@ public abstract class CheckpointImplTestBase {
|
||||||
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), testConcurrencyToken);
|
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), testConcurrencyToken);
|
||||||
|
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -139,8 +139,8 @@ public abstract class CheckpointImplTestBase {
|
||||||
ExtendedSequenceNumber extendedSequenceNumber = new ExtendedSequenceNumber(sequenceNumber);
|
ExtendedSequenceNumber extendedSequenceNumber = new ExtendedSequenceNumber(sequenceNumber);
|
||||||
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(sequenceNumber), testConcurrencyToken);
|
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(sequenceNumber), testConcurrencyToken);
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,20 +155,20 @@ public abstract class CheckpointImplTestBase {
|
||||||
ExtendedSequenceNumber extendedCheckpointNumber = new ExtendedSequenceNumber(checkpointValue);
|
ExtendedSequenceNumber extendedCheckpointNumber = new ExtendedSequenceNumber(checkpointValue);
|
||||||
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(checkpointValue), concurrencyToken);
|
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(checkpointValue), concurrencyToken);
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// prepare checkpoint
|
// prepare checkpoint
|
||||||
ExtendedSequenceNumber extendedPendingCheckpointNumber = new ExtendedSequenceNumber(pendingCheckpointValue);
|
ExtendedSequenceNumber extendedPendingCheckpointNumber = new ExtendedSequenceNumber(pendingCheckpointValue);
|
||||||
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), concurrencyToken);
|
checkpoint.prepareCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), concurrencyToken);
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedCheckpointNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// do checkpoint
|
// do checkpoint
|
||||||
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), concurrencyToken);
|
checkpoint.setCheckpoint(shardId, new ExtendedSequenceNumber(pendingCheckpointValue), concurrencyToken);
|
||||||
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpoint(shardId));
|
||||||
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
Assert.assertEquals(extendedPendingCheckpointNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
Assert.assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import java.util.Map;
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
import software.amazon.kinesis.checkpoint.Checkpoint;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
* Everything is stored in memory and there is no fault-tolerance.
|
* Everything is stored in memory and there is no fault-tolerance.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class InMemoryCheckpointImpl implements ICheckpoint {
|
public class InMemoryCheckpointer implements Checkpointer {
|
||||||
private Map<String, ExtendedSequenceNumber> checkpoints = new HashMap<>();
|
private Map<String, ExtendedSequenceNumber> checkpoints = new HashMap<>();
|
||||||
private Map<String, ExtendedSequenceNumber> flushpoints = new HashMap<>();
|
private Map<String, ExtendedSequenceNumber> flushpoints = new HashMap<>();
|
||||||
private Map<String, ExtendedSequenceNumber> pendingCheckpoints = new HashMap<>();
|
private Map<String, ExtendedSequenceNumber> pendingCheckpoints = new HashMap<>();
|
||||||
|
|
@ -39,7 +39,7 @@ public class InMemoryCheckpointImpl implements ICheckpoint {
|
||||||
*
|
*
|
||||||
* @param startingSequenceNumber Initial checkpoint will be set to this sequenceNumber (for all shards).
|
* @param startingSequenceNumber Initial checkpoint will be set to this sequenceNumber (for all shards).
|
||||||
*/
|
*/
|
||||||
public InMemoryCheckpointImpl(String startingSequenceNumber) {
|
public InMemoryCheckpointer(String startingSequenceNumber) {
|
||||||
super();
|
super();
|
||||||
this.startingSequenceNumber = startingSequenceNumber;
|
this.startingSequenceNumber = startingSequenceNumber;
|
||||||
}
|
}
|
||||||
|
|
@ -20,11 +20,11 @@ import org.junit.Before;
|
||||||
/**
|
/**
|
||||||
* Test the InMemoryCheckpointImplTest class.
|
* Test the InMemoryCheckpointImplTest class.
|
||||||
*/
|
*/
|
||||||
public class InMemoryCheckpointImplTest extends CheckpointImplTestBase {
|
public class InMemoryCheckpointerTest extends CheckpointImplTestBase {
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
public InMemoryCheckpointImplTest() {
|
public InMemoryCheckpointerTest() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,7 +32,7 @@ public class InMemoryCheckpointImplTest extends CheckpointImplTestBase {
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
checkpoint = new InMemoryCheckpointImpl(startingSequenceNumber);
|
checkpoint = new InMemoryCheckpointer(startingSequenceNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.checkpoint;
|
package software.amazon.kinesis.checkpoint;
|
||||||
|
|
||||||
import software.amazon.kinesis.checkpoint.DoesNothingPreparedCheckpointer;
|
|
||||||
import software.amazon.kinesis.checkpoint.PreparedCheckpointer;
|
|
||||||
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
@ -26,13 +24,13 @@ import org.mockito.Mockito;
|
||||||
public class PreparedCheckpointerTest {
|
public class PreparedCheckpointerTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test verifies the relationship between the constructor and getPendingCheckpoint.
|
* This test verifies the relationship between the constructor and pendingCheckpoint.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetSequenceNumber() {
|
public void testGetSequenceNumber() {
|
||||||
ExtendedSequenceNumber sn = new ExtendedSequenceNumber("sn");
|
ExtendedSequenceNumber sn = new ExtendedSequenceNumber("sn");
|
||||||
IPreparedCheckpointer checkpointer = new PreparedCheckpointer(sn, null);
|
IPreparedCheckpointer checkpointer = new PreparedCheckpointer(sn, null);
|
||||||
Assert.assertEquals(sn, checkpointer.getPendingCheckpoint());
|
Assert.assertEquals(sn, checkpointer.pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,7 +56,7 @@ public class PreparedCheckpointerTest {
|
||||||
public void testDoesNothingPreparedCheckpoint() throws Exception {
|
public void testDoesNothingPreparedCheckpoint() throws Exception {
|
||||||
ExtendedSequenceNumber sn = new ExtendedSequenceNumber("sn");
|
ExtendedSequenceNumber sn = new ExtendedSequenceNumber("sn");
|
||||||
IPreparedCheckpointer checkpointer = new DoesNothingPreparedCheckpointer(sn);
|
IPreparedCheckpointer checkpointer = new DoesNothingPreparedCheckpointer(sn);
|
||||||
Assert.assertEquals(sn, checkpointer.getPendingCheckpoint());
|
Assert.assertEquals(sn, checkpointer.pendingCheckpoint());
|
||||||
// nothing happens here
|
// nothing happens here
|
||||||
checkpointer.checkpoint();
|
checkpointer.checkpoint();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
@ -36,16 +34,15 @@ import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.checkpoint.InMemoryCheckpointImpl;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.checkpoint.InMemoryCheckpointer;
|
||||||
import com.amazonaws.services.kinesis.model.Record;
|
import com.amazonaws.services.kinesis.model.Record;
|
||||||
|
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.IMetricsScope;
|
import software.amazon.kinesis.metrics.IMetricsScope;
|
||||||
import software.amazon.kinesis.metrics.MetricsHelper;
|
import software.amazon.kinesis.metrics.MetricsHelper;
|
||||||
import software.amazon.kinesis.metrics.NullMetricsScope;
|
import software.amazon.kinesis.metrics.NullMetricsScope;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
import software.amazon.kinesis.processor.IPreparedCheckpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
import software.amazon.kinesis.retrieval.kpl.UserRecord;
|
import software.amazon.kinesis.retrieval.kpl.UserRecord;
|
||||||
|
|
@ -58,7 +55,7 @@ public class RecordProcessorCheckpointerTest {
|
||||||
private String startingSequenceNumber = "13";
|
private String startingSequenceNumber = "13";
|
||||||
private ExtendedSequenceNumber startingExtendedSequenceNumber = new ExtendedSequenceNumber(startingSequenceNumber);
|
private ExtendedSequenceNumber startingExtendedSequenceNumber = new ExtendedSequenceNumber(startingSequenceNumber);
|
||||||
private String testConcurrencyToken = "testToken";
|
private String testConcurrencyToken = "testToken";
|
||||||
private ICheckpoint checkpoint;
|
private Checkpointer checkpoint;
|
||||||
private ShardInfo shardInfo;
|
private ShardInfo shardInfo;
|
||||||
private String streamName = "testStream";
|
private String streamName = "testStream";
|
||||||
private String shardId = "shardId-123";
|
private String shardId = "shardId-123";
|
||||||
|
|
@ -73,7 +70,7 @@ public class RecordProcessorCheckpointerTest {
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
checkpoint = new InMemoryCheckpointImpl(startingSequenceNumber);
|
checkpoint = new InMemoryCheckpointer(startingSequenceNumber);
|
||||||
// A real checkpoint will return a checkpoint value after it is initialized.
|
// A real checkpoint will return a checkpoint value after it is initialized.
|
||||||
checkpoint.setCheckpoint(shardId, startingExtendedSequenceNumber, testConcurrencyToken);
|
checkpoint.setCheckpoint(shardId, startingExtendedSequenceNumber, testConcurrencyToken);
|
||||||
assertEquals(this.startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(this.startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
|
|
@ -194,22 +191,22 @@ public class RecordProcessorCheckpointerTest {
|
||||||
ExtendedSequenceNumber sequenceNumber1 = new ExtendedSequenceNumber("5001");
|
ExtendedSequenceNumber sequenceNumber1 = new ExtendedSequenceNumber("5001");
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(sequenceNumber1);
|
processingCheckpointer.largestPermittedCheckpointValue(sequenceNumber1);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
||||||
assertEquals(sequenceNumber1, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sequenceNumber1, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sequenceNumber1, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sequenceNumber1, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Advance checkpoint
|
// Advance checkpoint
|
||||||
ExtendedSequenceNumber sequenceNumber2 = new ExtendedSequenceNumber("5019");
|
ExtendedSequenceNumber sequenceNumber2 = new ExtendedSequenceNumber("5019");
|
||||||
|
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(sequenceNumber2);
|
processingCheckpointer.largestPermittedCheckpointValue(sequenceNumber2);
|
||||||
preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
||||||
assertEquals(sequenceNumber2, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sequenceNumber2, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sequenceNumber2, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sequenceNumber2, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(sequenceNumber2, checkpoint.getCheckpoint(shardId));
|
assertEquals(sequenceNumber2, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(sequenceNumber2, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(sequenceNumber2, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -226,15 +223,15 @@ public class RecordProcessorCheckpointerTest {
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(record);
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(record);
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(extendedSequenceNumber, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -252,15 +249,15 @@ public class RecordProcessorCheckpointerTest {
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(subRecord);
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(subRecord);
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(extendedSequenceNumber, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -276,15 +273,15 @@ public class RecordProcessorCheckpointerTest {
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint("5035");
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint("5035");
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(extendedSequenceNumber, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -300,15 +297,15 @@ public class RecordProcessorCheckpointerTest {
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint("5040", 0);
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint("5040", 0);
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(extendedSequenceNumber, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -323,15 +320,15 @@ public class RecordProcessorCheckpointerTest {
|
||||||
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
processingCheckpointer.largestPermittedCheckpointValue(extendedSequenceNumber);
|
||||||
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(ExtendedSequenceNumber.SHARD_END.getSequenceNumber());
|
IPreparedCheckpointer preparedCheckpoint = processingCheckpointer.prepareCheckpoint(ExtendedSequenceNumber.SHARD_END.getSequenceNumber());
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(startingExtendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(extendedSequenceNumber, preparedCheckpoint.getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, preparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// Checkpoint using preparedCheckpoint
|
// Checkpoint using preparedCheckpoint
|
||||||
preparedCheckpoint.checkpoint();
|
preparedCheckpoint.checkpoint();
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(extendedSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -347,24 +344,24 @@ public class RecordProcessorCheckpointerTest {
|
||||||
|
|
||||||
ExtendedSequenceNumber sn1 = new ExtendedSequenceNumber("6010");
|
ExtendedSequenceNumber sn1 = new ExtendedSequenceNumber("6010");
|
||||||
IPreparedCheckpointer firstPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("6010", 0);
|
IPreparedCheckpointer firstPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("6010", 0);
|
||||||
assertEquals(sn1, firstPreparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sn1, firstPreparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
ExtendedSequenceNumber sn2 = new ExtendedSequenceNumber("6020");
|
ExtendedSequenceNumber sn2 = new ExtendedSequenceNumber("6020");
|
||||||
IPreparedCheckpointer secondPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("6020", 0);
|
IPreparedCheckpointer secondPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("6020", 0);
|
||||||
assertEquals(sn2, secondPreparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sn2, secondPreparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// checkpoint in order
|
// checkpoint in order
|
||||||
firstPreparedCheckpoint.checkpoint();
|
firstPreparedCheckpoint.checkpoint();
|
||||||
assertEquals(sn1, checkpoint.getCheckpoint(shardId));
|
assertEquals(sn1, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
secondPreparedCheckpoint.checkpoint();
|
secondPreparedCheckpoint.checkpoint();
|
||||||
assertEquals(sn2, checkpoint.getCheckpoint(shardId));
|
assertEquals(sn2, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -379,19 +376,19 @@ public class RecordProcessorCheckpointerTest {
|
||||||
|
|
||||||
ExtendedSequenceNumber sn1 = new ExtendedSequenceNumber("7010");
|
ExtendedSequenceNumber sn1 = new ExtendedSequenceNumber("7010");
|
||||||
IPreparedCheckpointer firstPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("7010", 0);
|
IPreparedCheckpointer firstPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("7010", 0);
|
||||||
assertEquals(sn1, firstPreparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sn1, firstPreparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sn1, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
ExtendedSequenceNumber sn2 = new ExtendedSequenceNumber("7020");
|
ExtendedSequenceNumber sn2 = new ExtendedSequenceNumber("7020");
|
||||||
IPreparedCheckpointer secondPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("7020", 0);
|
IPreparedCheckpointer secondPreparedCheckpoint = processingCheckpointer.prepareCheckpoint("7020", 0);
|
||||||
assertEquals(sn2, secondPreparedCheckpoint.getPendingCheckpoint());
|
assertEquals(sn2, secondPreparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// checkpoint out of order
|
// checkpoint out of order
|
||||||
secondPreparedCheckpoint.checkpoint();
|
secondPreparedCheckpoint.checkpoint();
|
||||||
assertEquals(sn2, checkpoint.getCheckpoint(shardId));
|
assertEquals(sn2, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(sn2, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
firstPreparedCheckpoint.checkpoint();
|
firstPreparedCheckpoint.checkpoint();
|
||||||
|
|
@ -549,23 +546,23 @@ public class RecordProcessorCheckpointerTest {
|
||||||
IPreparedCheckpointer doesNothingPreparedCheckpoint =
|
IPreparedCheckpointer doesNothingPreparedCheckpoint =
|
||||||
processingCheckpointer.prepareCheckpoint(firstSequenceNumber.getSequenceNumber(), firstSequenceNumber.getSubSequenceNumber());
|
processingCheckpointer.prepareCheckpoint(firstSequenceNumber.getSequenceNumber(), firstSequenceNumber.getSubSequenceNumber());
|
||||||
assertTrue(doesNothingPreparedCheckpoint instanceof DoesNothingPreparedCheckpointer);
|
assertTrue(doesNothingPreparedCheckpoint instanceof DoesNothingPreparedCheckpointer);
|
||||||
assertEquals(firstSequenceNumber, doesNothingPreparedCheckpoint.getPendingCheckpoint());
|
assertEquals(firstSequenceNumber, doesNothingPreparedCheckpoint.pendingCheckpoint());
|
||||||
assertEquals(firstSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(firstSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(firstSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(firstSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// nothing happens after checkpointing a doesNothingPreparedCheckpoint
|
// nothing happens after checkpointing a doesNothingPreparedCheckpoint
|
||||||
doesNothingPreparedCheckpoint.checkpoint();
|
doesNothingPreparedCheckpoint.checkpoint();
|
||||||
assertEquals(firstSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(firstSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(firstSequenceNumber, checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
assertEquals(firstSequenceNumber, checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
// advance to second
|
// advance to second
|
||||||
processingCheckpointer.prepareCheckpoint(secondSequenceNumber.getSequenceNumber(), secondSequenceNumber.getSubSequenceNumber());
|
processingCheckpointer.prepareCheckpoint(secondSequenceNumber.getSequenceNumber(), secondSequenceNumber.getSubSequenceNumber());
|
||||||
assertEquals(secondSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(secondSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
processingCheckpointer.checkpoint(secondSequenceNumber.getSequenceNumber(), secondSequenceNumber.getSubSequenceNumber());
|
processingCheckpointer.checkpoint(secondSequenceNumber.getSequenceNumber(), secondSequenceNumber.getSubSequenceNumber());
|
||||||
assertEquals(secondSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(secondSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
ExtendedSequenceNumber[] valuesWeShouldNotBeAbleToCheckpointAt =
|
ExtendedSequenceNumber[] valuesWeShouldNotBeAbleToCheckpointAt =
|
||||||
{ tooSmall, // Shouldn't be able to move before the first value we ever checkpointed
|
{ tooSmall, // Shouldn't be able to move before the first value we ever checkpointed
|
||||||
|
|
@ -596,13 +593,13 @@ public class RecordProcessorCheckpointerTest {
|
||||||
assertEquals("Largest sequence number should not have changed",
|
assertEquals("Largest sequence number should not have changed",
|
||||||
thirdSequenceNumber,
|
thirdSequenceNumber,
|
||||||
processingCheckpointer.largestPermittedCheckpointValue());
|
processingCheckpointer.largestPermittedCheckpointValue());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance to third number
|
// advance to third number
|
||||||
processingCheckpointer.prepareCheckpoint(thirdSequenceNumber.getSequenceNumber(), thirdSequenceNumber.getSubSequenceNumber());
|
processingCheckpointer.prepareCheckpoint(thirdSequenceNumber.getSequenceNumber(), thirdSequenceNumber.getSubSequenceNumber());
|
||||||
assertEquals(thirdSequenceNumber, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(thirdSequenceNumber, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
processingCheckpointer.checkpoint(thirdSequenceNumber.getSequenceNumber(), thirdSequenceNumber.getSubSequenceNumber());
|
processingCheckpointer.checkpoint(thirdSequenceNumber.getSequenceNumber(), thirdSequenceNumber.getSubSequenceNumber());
|
||||||
assertEquals(thirdSequenceNumber, checkpoint.getCheckpoint(shardId));
|
assertEquals(thirdSequenceNumber, checkpoint.getCheckpoint(shardId));
|
||||||
|
|
||||||
|
|
@ -614,7 +611,7 @@ public class RecordProcessorCheckpointerTest {
|
||||||
assertEquals("Preparing a checkpoing at the sequence number at the end of a shard should be the same as "
|
assertEquals("Preparing a checkpoing at the sequence number at the end of a shard should be the same as "
|
||||||
+ "preparing a checkpoint at SHARD_END",
|
+ "preparing a checkpoint at SHARD_END",
|
||||||
ExtendedSequenceNumber.SHARD_END,
|
ExtendedSequenceNumber.SHARD_END,
|
||||||
checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CheckpointAction {
|
private enum CheckpointAction {
|
||||||
|
|
@ -788,8 +785,8 @@ public class RecordProcessorCheckpointerTest {
|
||||||
case PREPARE_THEN_CHECKPOINTER:
|
case PREPARE_THEN_CHECKPOINTER:
|
||||||
preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
preparedCheckpoint = processingCheckpointer.prepareCheckpoint();
|
||||||
processingCheckpointer.checkpoint(
|
processingCheckpointer.checkpoint(
|
||||||
preparedCheckpoint.getPendingCheckpoint().getSequenceNumber(),
|
preparedCheckpoint.pendingCheckpoint().getSequenceNumber(),
|
||||||
preparedCheckpoint.getPendingCheckpoint().getSubSequenceNumber());
|
preparedCheckpoint.pendingCheckpoint().getSubSequenceNumber());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WITH_SEQUENCE_NUMBER:
|
case WITH_SEQUENCE_NUMBER:
|
||||||
|
|
@ -803,8 +800,8 @@ public class RecordProcessorCheckpointerTest {
|
||||||
case PREPARE_THEN_CHECKPOINTER:
|
case PREPARE_THEN_CHECKPOINTER:
|
||||||
preparedCheckpoint = processingCheckpointer.prepareCheckpoint(entry.getKey());
|
preparedCheckpoint = processingCheckpointer.prepareCheckpoint(entry.getKey());
|
||||||
processingCheckpointer.checkpoint(
|
processingCheckpointer.checkpoint(
|
||||||
preparedCheckpoint.getPendingCheckpoint().getSequenceNumber(),
|
preparedCheckpoint.pendingCheckpoint().getSequenceNumber(),
|
||||||
preparedCheckpoint.getPendingCheckpoint().getSubSequenceNumber());
|
preparedCheckpoint.pendingCheckpoint().getSubSequenceNumber());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -818,8 +815,8 @@ public class RecordProcessorCheckpointerTest {
|
||||||
|
|
||||||
assertEquals(new ExtendedSequenceNumber(entry.getKey()), checkpoint.getCheckpoint(shardId));
|
assertEquals(new ExtendedSequenceNumber(entry.getKey()), checkpoint.getCheckpoint(shardId));
|
||||||
assertEquals(new ExtendedSequenceNumber(entry.getKey()),
|
assertEquals(new ExtendedSequenceNumber(entry.getKey()),
|
||||||
checkpoint.getCheckpointObject(shardId).getCheckpoint());
|
checkpoint.getCheckpointObject(shardId).checkpoint());
|
||||||
assertEquals(null, checkpoint.getCheckpointObject(shardId).getPendingCheckpoint());
|
assertEquals(null, checkpoint.getCheckpointObject(shardId).pendingCheckpoint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,6 @@ import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
import com.amazonaws.ClientConfiguration;
|
|
||||||
import com.amazonaws.auth.AWSCredentialsProvider;
|
|
||||||
import com.amazonaws.regions.Region;
|
|
||||||
import com.amazonaws.regions.RegionUtils;
|
|
||||||
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
|
|
||||||
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
|
|
||||||
import com.amazonaws.services.kinesis.AmazonKinesisClient;
|
|
||||||
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorFactory;
|
|
||||||
import software.amazon.kinesis.metrics.MetricsLevel;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
public class KinesisClientLibConfigurationTest {
|
public class KinesisClientLibConfigurationTest {
|
||||||
/*private static final long INVALID_LONG = 0L;
|
/*private static final long INVALID_LONG = 0L;
|
||||||
private static final int INVALID_INT = 0;
|
private static final int INVALID_INT = 0;
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,8 @@ import software.amazon.kinesis.lifecycle.ShutdownInput;
|
||||||
import software.amazon.kinesis.lifecycle.ShutdownReason;
|
import software.amazon.kinesis.lifecycle.ShutdownReason;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.MetricsConfig;
|
import software.amazon.kinesis.metrics.MetricsConfig;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.ProcessorConfig;
|
import software.amazon.kinesis.processor.ProcessorConfig;
|
||||||
import software.amazon.kinesis.processor.ProcessorFactory;
|
import software.amazon.kinesis.processor.ProcessorFactory;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
|
|
@ -114,7 +114,7 @@ public class SchedulerTest {
|
||||||
@Mock
|
@Mock
|
||||||
private LeaseManagerProxy leaseManagerProxy;
|
private LeaseManagerProxy leaseManagerProxy;
|
||||||
@Mock
|
@Mock
|
||||||
private ICheckpoint checkpoint;
|
private Checkpointer checkpoint;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
|
|
@ -399,8 +399,8 @@ public class SchedulerTest {
|
||||||
|
|
||||||
private static class TestRecordProcessorFactory implements ProcessorFactory {
|
private static class TestRecordProcessorFactory implements ProcessorFactory {
|
||||||
@Override
|
@Override
|
||||||
public IRecordProcessor createRecordProcessor() {
|
public RecordProcessor createRecordProcessor() {
|
||||||
return new IRecordProcessor() {
|
return new RecordProcessor() {
|
||||||
@Override
|
@Override
|
||||||
public void initialize(final InitializationInput initializationInput) {
|
public void initialize(final InitializationInput initializationInput) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
|
@ -458,7 +458,7 @@ public class SchedulerTest {
|
||||||
|
|
||||||
private class TestKinesisCheckpointFactory implements CheckpointFactory {
|
private class TestKinesisCheckpointFactory implements CheckpointFactory {
|
||||||
@Override
|
@Override
|
||||||
public ICheckpoint createCheckpoint(KinesisClientLibLeaseCoordinator leaseCoordinator) {
|
public Checkpointer createCheckpoint() {
|
||||||
return checkpoint;
|
return checkpoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,14 @@ import org.junit.Test;
|
||||||
|
|
||||||
import software.amazon.kinesis.leases.exceptions.LeasingException;
|
import software.amazon.kinesis.leases.exceptions.LeasingException;
|
||||||
|
|
||||||
public class LeaseTakerIntegrationTest extends LeaseIntegrationTest {
|
public class DynamoDBLeaseTakerIntegrationTest extends LeaseIntegrationTest {
|
||||||
|
|
||||||
private static final long LEASE_DURATION_MILLIS = 1000L;
|
private static final long LEASE_DURATION_MILLIS = 1000L;
|
||||||
private LeaseTaker<KinesisClientLease> taker;
|
private DynamoDBLeaseTaker<KinesisClientLease> taker;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
taker = new LeaseTaker<KinesisClientLease>(leaseManager, "foo", LEASE_DURATION_MILLIS);
|
taker = new DynamoDBLeaseTaker<KinesisClientLease>(leaseManager, "foo", LEASE_DURATION_MILLIS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -24,12 +24,11 @@ import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import software.amazon.kinesis.leases.LeaseTaker;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LeaseTakerTest {
|
public class DynamoDBLeaseTakerTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws java.lang.Exception
|
* @throws java.lang.Exception
|
||||||
|
|
@ -60,17 +59,17 @@ public class LeaseTakerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test method for {@link LeaseTaker#stringJoin(java.util.Collection, java.lang.String)}.
|
* Test method for {@link DynamoDBLeaseTaker#stringJoin(java.util.Collection, java.lang.String)}.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public final void testStringJoin() {
|
public final void testStringJoin() {
|
||||||
List<String> strings = new ArrayList<>();
|
List<String> strings = new ArrayList<>();
|
||||||
|
|
||||||
strings.add("foo");
|
strings.add("foo");
|
||||||
Assert.assertEquals("foo", LeaseTaker.stringJoin(strings, ", "));
|
Assert.assertEquals("foo", DynamoDBLeaseTaker.stringJoin(strings, ", "));
|
||||||
|
|
||||||
strings.add("bar");
|
strings.add("bar");
|
||||||
Assert.assertEquals("foo, bar", LeaseTaker.stringJoin(strings, ", "));
|
Assert.assertEquals("foo, bar", DynamoDBLeaseTaker.stringJoin(strings, ", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.leases;
|
package software.amazon.kinesis.leases;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -22,31 +27,38 @@ import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
|
||||||
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
|
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
|
||||||
|
import software.amazon.kinesis.checkpoint.DynamoDBCheckpointer;
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
import software.amazon.kinesis.leases.exceptions.LeasingException;
|
import software.amazon.kinesis.leases.exceptions.LeasingException;
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
|
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
||||||
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
|
|
||||||
public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
|
|
||||||
private static KinesisClientLeaseManager leaseManager;
|
|
||||||
private KinesisClientLibLeaseCoordinator coordinator;
|
|
||||||
private static final String TABLE_NAME = KinesisClientLibLeaseCoordinatorIntegrationTest.class.getSimpleName();
|
private static final String TABLE_NAME = KinesisClientLibLeaseCoordinatorIntegrationTest.class.getSimpleName();
|
||||||
private static final String WORKER_ID = UUID.randomUUID().toString();
|
private static final String WORKER_ID = UUID.randomUUID().toString();
|
||||||
private final String leaseKey = "shd-1";
|
private static final long LEASE_DURATION_MILLIS = 5000L;
|
||||||
|
private static final long EPSILON_MILLIS = 25L;
|
||||||
|
private static final int MAX_LEASES_FOR_WORKER = Integer.MAX_VALUE;
|
||||||
|
private static final int MAX_LEASES_TO_STEAL_AT_ONE_TIME = 1;
|
||||||
|
private static final int MAX_LEASE_RENEWER_THREAD_COUNT = 20;
|
||||||
|
private static KinesisClientLeaseManager leaseManager;
|
||||||
|
private static DynamoDBCheckpointer dynamoDBCheckpointer;
|
||||||
|
|
||||||
|
private KinesisClientLibLeaseCoordinator coordinator;
|
||||||
|
private final String leaseKey = "shd-1";
|
||||||
|
private final IMetricsFactory metricsFactory = new NullMetricsFactory();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws ProvisionedThroughputException, DependencyException, InvalidStateException {
|
public void setUp() throws ProvisionedThroughputException, DependencyException, InvalidStateException {
|
||||||
|
|
@ -58,7 +70,11 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
}
|
}
|
||||||
leaseManager.createLeaseTableIfNotExists(10L, 10L);
|
leaseManager.createLeaseTableIfNotExists(10L, 10L);
|
||||||
leaseManager.deleteAll();
|
leaseManager.deleteAll();
|
||||||
coordinator = new KinesisClientLibLeaseCoordinator(leaseManager, WORKER_ID, 5000L, 50L);
|
coordinator = new KinesisClientLibLeaseCoordinator(leaseManager, WORKER_ID, LEASE_DURATION_MILLIS,
|
||||||
|
EPSILON_MILLIS, MAX_LEASES_FOR_WORKER, MAX_LEASES_TO_STEAL_AT_ONE_TIME, MAX_LEASE_RENEWER_THREAD_COUNT,
|
||||||
|
metricsFactory);
|
||||||
|
dynamoDBCheckpointer = new DynamoDBCheckpointer(coordinator, leaseManager, metricsFactory);
|
||||||
|
|
||||||
coordinator.start();
|
coordinator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +82,7 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
* Tests update checkpoint success.
|
* Tests update checkpoint success.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateCheckpoint() throws LeasingException {
|
public void testUpdateCheckpoint() throws Exception {
|
||||||
TestHarnessBuilder builder = new TestHarnessBuilder();
|
TestHarnessBuilder builder = new TestHarnessBuilder();
|
||||||
builder.withLease(leaseKey, null).build();
|
builder.withLease(leaseKey, null).build();
|
||||||
|
|
||||||
|
|
@ -82,17 +98,17 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(lease, notNullValue());
|
assertNotNull(lease);
|
||||||
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
||||||
// lease's leaseCounter is wrong at this point, but it shouldn't matter.
|
// lease's leaseCounter is wrong at this point, but it shouldn't matter.
|
||||||
Assert.assertTrue(coordinator.setCheckpoint(lease.getLeaseKey(), newCheckpoint, lease.getConcurrencyToken()));
|
assertTrue(dynamoDBCheckpointer.setCheckpoint(lease.getLeaseKey(), newCheckpoint, lease.getConcurrencyToken()));
|
||||||
|
|
||||||
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
||||||
|
|
||||||
lease.setLeaseCounter(lease.getLeaseCounter() + 1);
|
lease.setLeaseCounter(lease.getLeaseCounter() + 1);
|
||||||
lease.setCheckpoint(newCheckpoint);
|
lease.setCheckpoint(newCheckpoint);
|
||||||
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
||||||
Assert.assertEquals(lease, fromDynamo);
|
assertEquals(lease, fromDynamo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -107,18 +123,18 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
coordinator.runLeaseRenewer();
|
coordinator.runLeaseRenewer();
|
||||||
KinesisClientLease lease = coordinator.getCurrentlyHeldLease(leaseKey);
|
KinesisClientLease lease = coordinator.getCurrentlyHeldLease(leaseKey);
|
||||||
|
|
||||||
assertThat(lease, notNullValue());
|
assertNotNull(lease);
|
||||||
leaseManager.renewLease(coordinator.getCurrentlyHeldLease(leaseKey));
|
leaseManager.renewLease(coordinator.getCurrentlyHeldLease(leaseKey));
|
||||||
|
|
||||||
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
||||||
Assert.assertFalse(coordinator.setCheckpoint(lease.getLeaseKey(), newCheckpoint, lease.getConcurrencyToken()));
|
assertFalse(dynamoDBCheckpointer.setCheckpoint(lease.getLeaseKey(), newCheckpoint, lease.getConcurrencyToken()));
|
||||||
|
|
||||||
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
||||||
|
|
||||||
lease.setLeaseCounter(lease.getLeaseCounter() + 1);
|
lease.setLeaseCounter(lease.getLeaseCounter() + 1);
|
||||||
// Counter and owner changed, but checkpoint did not.
|
// Counter and owner changed, but checkpoint did not.
|
||||||
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
||||||
Assert.assertEquals(lease, fromDynamo);
|
assertEquals(lease, fromDynamo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,16 +149,16 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
coordinator.runLeaseRenewer();
|
coordinator.runLeaseRenewer();
|
||||||
KinesisClientLease lease = coordinator.getCurrentlyHeldLease(leaseKey);
|
KinesisClientLease lease = coordinator.getCurrentlyHeldLease(leaseKey);
|
||||||
|
|
||||||
assertThat(lease, notNullValue());
|
assertNotNull(lease);
|
||||||
|
|
||||||
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
ExtendedSequenceNumber newCheckpoint = new ExtendedSequenceNumber("newCheckpoint");
|
||||||
Assert.assertFalse(coordinator.setCheckpoint(lease.getLeaseKey(), newCheckpoint, UUID.randomUUID()));
|
assertFalse(dynamoDBCheckpointer.setCheckpoint(lease.getLeaseKey(), newCheckpoint, UUID.randomUUID()));
|
||||||
|
|
||||||
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
Lease fromDynamo = leaseManager.getLease(lease.getLeaseKey());
|
||||||
|
|
||||||
// Owner should be the only thing that changed.
|
// Owner should be the only thing that changed.
|
||||||
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
lease.setLeaseOwner(coordinator.getWorkerIdentifier());
|
||||||
Assert.assertEquals(lease, fromDynamo);
|
assertEquals(lease, fromDynamo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TestHarnessBuilder {
|
public static class TestHarnessBuilder {
|
||||||
|
|
@ -201,7 +217,7 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
}
|
}
|
||||||
original.setLeaseOwner(newWorkerIdentifier);
|
original.setLeaseOwner(newWorkerIdentifier);
|
||||||
|
|
||||||
Assert.assertEquals(original, actual); // Assert the contents of the lease
|
assertEquals(original, actual); // Assert the contents of the lease
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLeasesToRenew(ILeaseRenewer<KinesisClientLease> renewer, String... shardIds)
|
public void addLeasesToRenew(ILeaseRenewer<KinesisClientLease> renewer, String... shardIds)
|
||||||
|
|
@ -210,7 +226,7 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
|
|
||||||
for (String shardId : shardIds) {
|
for (String shardId : shardIds) {
|
||||||
KinesisClientLease lease = leases.get(shardId);
|
KinesisClientLease lease = leases.get(shardId);
|
||||||
Assert.assertNotNull(lease);
|
assertNotNull(lease);
|
||||||
leasesToRenew.add(lease);
|
leasesToRenew.add(lease);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,17 +238,17 @@ public class KinesisClientLibLeaseCoordinatorIntegrationTest {
|
||||||
renewer.renewLeases();
|
renewer.renewLeases();
|
||||||
|
|
||||||
Map<String, KinesisClientLease> heldLeases = renewer.getCurrentlyHeldLeases();
|
Map<String, KinesisClientLease> heldLeases = renewer.getCurrentlyHeldLeases();
|
||||||
Assert.assertEquals(renewedShardIds.length, heldLeases.size());
|
assertEquals(renewedShardIds.length, heldLeases.size());
|
||||||
|
|
||||||
for (String shardId : renewedShardIds) {
|
for (String shardId : renewedShardIds) {
|
||||||
KinesisClientLease original = leases.get(shardId);
|
KinesisClientLease original = leases.get(shardId);
|
||||||
Assert.assertNotNull(original);
|
assertNotNull(original);
|
||||||
|
|
||||||
KinesisClientLease actual = heldLeases.get(shardId);
|
KinesisClientLease actual = heldLeases.get(shardId);
|
||||||
Assert.assertNotNull(actual);
|
assertNotNull(actual);
|
||||||
|
|
||||||
original.setLeaseCounter(original.getLeaseCounter() + 1);
|
original.setLeaseCounter(original.getLeaseCounter() + 1);
|
||||||
Assert.assertEquals(original, actual);
|
assertEquals(original, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
return heldLeases;
|
return heldLeases;
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,28 @@
|
||||||
*/
|
*/
|
||||||
package software.amazon.kinesis.leases;
|
package software.amazon.kinesis.leases;
|
||||||
|
|
||||||
import static org.mockito.Matchers.anyLong;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.KinesisClientLibException;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
|
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLibLeaseCoordinator;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import software.amazon.kinesis.checkpoint.DynamoDBCheckpointer;
|
||||||
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
import software.amazon.kinesis.leases.exceptions.DependencyException;
|
||||||
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
import software.amazon.kinesis.leases.exceptions.InvalidStateException;
|
||||||
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
import software.amazon.kinesis.leases.exceptions.ProvisionedThroughputException;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class KinesisClientLibLeaseCoordinatorTest {
|
public class KinesisClientLibLeaseCoordinatorTest {
|
||||||
private static final String SHARD_ID = "shardId-test";
|
private static final String SHARD_ID = "shardId-test";
|
||||||
private static final String WORK_ID = "workId-test";
|
private static final String WORK_ID = "workId-test";
|
||||||
|
|
@ -42,35 +43,38 @@ public class KinesisClientLibLeaseCoordinatorTest {
|
||||||
private static final ExtendedSequenceNumber TEST_CHKPT = new ExtendedSequenceNumber("string-test");
|
private static final ExtendedSequenceNumber TEST_CHKPT = new ExtendedSequenceNumber("string-test");
|
||||||
private static final UUID TEST_UUID = UUID.randomUUID();
|
private static final UUID TEST_UUID = UUID.randomUUID();
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Mock
|
@Mock
|
||||||
private ILeaseManager mockLeaseManager;
|
private ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
|
@Mock
|
||||||
|
private LeaseCoordinator<KinesisClientLease> leaseCoordinator;
|
||||||
|
@Mock
|
||||||
|
private IMetricsFactory metricsFactory;
|
||||||
|
|
||||||
private KinesisClientLibLeaseCoordinator leaseCoordinator;
|
private DynamoDBCheckpointer dynamoDBCheckpointer;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Before
|
@Before
|
||||||
public void setUpLeaseCoordinator() throws ProvisionedThroughputException, DependencyException {
|
public void setUpLeaseCoordinator() throws ProvisionedThroughputException, DependencyException {
|
||||||
// Initialize the annotation
|
dynamoDBCheckpointer = new DynamoDBCheckpointer(leaseCoordinator, leaseManager, metricsFactory);
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
// Set up lease coordinator
|
|
||||||
doReturn(true).when(mockLeaseManager).createLeaseTableIfNotExists(anyLong(), anyLong());
|
|
||||||
leaseCoordinator = new KinesisClientLibLeaseCoordinator(mockLeaseManager, WORK_ID, TEST_LONG, TEST_LONG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = ShutdownException.class)
|
@Test(expected = ShutdownException.class)
|
||||||
public void testSetCheckpointWithUnownedShardId()
|
public void testSetCheckpointWithUnownedShardId() throws KinesisClientLibException, DependencyException, InvalidStateException, ProvisionedThroughputException {
|
||||||
throws KinesisClientLibException, DependencyException, InvalidStateException, ProvisionedThroughputException {
|
final KinesisClientLease lease = new KinesisClientLease();
|
||||||
final boolean succeess = leaseCoordinator.setCheckpoint(SHARD_ID, TEST_CHKPT, TEST_UUID);
|
when(leaseManager.getLease(eq(SHARD_ID))).thenReturn(lease);
|
||||||
Assert.assertFalse("Set Checkpoint should return failure", succeess);
|
when(leaseCoordinator.updateLease(eq(lease), eq(TEST_UUID))).thenReturn(false);
|
||||||
leaseCoordinator.setCheckpoint(SHARD_ID, TEST_CHKPT, TEST_UUID.toString());
|
|
||||||
|
dynamoDBCheckpointer.setCheckpoint(SHARD_ID, TEST_CHKPT, TEST_UUID.toString());
|
||||||
|
|
||||||
|
verify(leaseManager).getLease(eq(SHARD_ID));
|
||||||
|
verify(leaseCoordinator).updateLease(eq(lease), eq(TEST_UUID));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = DependencyException.class)
|
// @Test(expected = DependencyException.class)
|
||||||
public void testWaitLeaseTableTimeout()
|
// public void testWaitLeaseTableTimeout()
|
||||||
throws DependencyException, ProvisionedThroughputException, IllegalStateException {
|
// throws DependencyException, ProvisionedThroughputException, IllegalStateException {
|
||||||
// Set mock lease manager to return false in waiting
|
// Set mock lease manager to return false in waiting
|
||||||
doReturn(false).when(mockLeaseManager).waitUntilLeaseTableExists(anyLong(), anyLong());
|
// doReturn(false).when(leaseManager).waitUntilLeaseTableExists(anyLong(), anyLong());
|
||||||
leaseCoordinator.initialize();
|
// leaseCoordinator.initialize();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ public class TestHarnessBuilder {
|
||||||
currentTimeNanos += millis * 1000000;
|
currentTimeNanos += millis * 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, KinesisClientLease> takeMutateAssert(LeaseTaker<KinesisClientLease> taker, int numToTake)
|
public Map<String, KinesisClientLease> takeMutateAssert(DynamoDBLeaseTaker<KinesisClientLease> taker, int numToTake)
|
||||||
throws LeasingException {
|
throws LeasingException {
|
||||||
Map<String, KinesisClientLease> result = taker.takeLeases(timeProvider);
|
Map<String, KinesisClientLease> result = taker.takeLeases(timeProvider);
|
||||||
Assert.assertEquals(numToTake, result.size());
|
Assert.assertEquals(numToTake, result.size());
|
||||||
|
|
@ -106,7 +106,7 @@ public class TestHarnessBuilder {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, KinesisClientLease> takeMutateAssert(LeaseTaker<KinesisClientLease> taker, String... takenShardIds)
|
public Map<String, KinesisClientLease> takeMutateAssert(DynamoDBLeaseTaker<KinesisClientLease> taker, String... takenShardIds)
|
||||||
throws LeasingException {
|
throws LeasingException {
|
||||||
Map<String, KinesisClientLease> result = taker.takeLeases(timeProvider);
|
Map<String, KinesisClientLease> result = taker.takeLeases(timeProvider);
|
||||||
Assert.assertEquals(takenShardIds.length, result.size());
|
Assert.assertEquals(takenShardIds.length, result.size());
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,14 @@ import com.amazonaws.services.kinesis.AmazonKinesis;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
||||||
|
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ public class ConsumerStatesTest {
|
||||||
private ShardConsumer consumer;
|
private ShardConsumer consumer;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private IRecordProcessor recordProcessor;
|
private RecordProcessor recordProcessor;
|
||||||
@Mock
|
@Mock
|
||||||
private RecordProcessorCheckpointer recordProcessorCheckpointer;
|
private RecordProcessorCheckpointer recordProcessorCheckpointer;
|
||||||
@Mock
|
@Mock
|
||||||
|
|
@ -74,7 +74,7 @@ public class ConsumerStatesTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ILeaseManager<KinesisClientLease> leaseManager;
|
private ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
@Mock
|
@Mock
|
||||||
private ICheckpoint checkpoint;
|
private Checkpointer checkpoint;
|
||||||
@Mock
|
@Mock
|
||||||
private ShutdownNotification shutdownNotification;
|
private ShutdownNotification shutdownNotification;
|
||||||
@Mock
|
@Mock
|
||||||
|
|
@ -144,8 +144,8 @@ public class ConsumerStatesTest {
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, initTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
assertThat(task, initTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
||||||
assertThat(task, initTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, initTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, initTask(ICheckpoint.class, "checkpoint", equalTo(checkpoint)));
|
assertThat(task, initTask(Checkpointer.class, "checkpoint", equalTo(checkpoint)));
|
||||||
assertThat(task, initTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, initTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, initTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
assertThat(task, initTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
||||||
|
|
@ -171,7 +171,7 @@ public class ConsumerStatesTest {
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
||||||
assertThat(task, procTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, procTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
||||||
|
|
@ -198,7 +198,7 @@ public class ConsumerStatesTest {
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
||||||
assertThat(task, procTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, procTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
||||||
|
|
@ -225,7 +225,7 @@ public class ConsumerStatesTest {
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
assertThat(task, procTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
||||||
assertThat(task, procTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, procTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, procTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
assertThat(task, procTask(Long.class, "backoffTimeMillis", equalTo(taskBackoffTimeMillis)));
|
||||||
|
|
@ -250,7 +250,7 @@ public class ConsumerStatesTest {
|
||||||
consumer.notifyShutdownRequested(shutdownNotification);
|
consumer.notifyShutdownRequested(shutdownNotification);
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, shutdownReqTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, shutdownReqTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, shutdownReqTask(IRecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, shutdownReqTask(IRecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, shutdownReqTask(ShutdownNotification.class, "shutdownNotification", equalTo(shutdownNotification)));
|
assertThat(task, shutdownReqTask(ShutdownNotification.class, "shutdownNotification", equalTo(shutdownNotification)));
|
||||||
|
|
@ -296,7 +296,7 @@ public class ConsumerStatesTest {
|
||||||
ITask task = state.createTask(consumer);
|
ITask task = state.createTask(consumer);
|
||||||
|
|
||||||
assertThat(task, shutdownTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
assertThat(task, shutdownTask(ShardInfo.class, "shardInfo", equalTo(shardInfo)));
|
||||||
assertThat(task, shutdownTask(IRecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
assertThat(task, shutdownTask(RecordProcessor.class, "recordProcessor", equalTo(recordProcessor)));
|
||||||
assertThat(task, shutdownTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
assertThat(task, shutdownTask(RecordProcessorCheckpointer.class, "recordProcessorCheckpointer",
|
||||||
equalTo(recordProcessorCheckpointer)));
|
equalTo(recordProcessorCheckpointer)));
|
||||||
assertThat(task, shutdownTask(ShutdownReason.class, "reason", equalTo(reason)));
|
assertThat(task, shutdownTask(ShutdownReason.class, "reason", equalTo(reason)));
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,10 @@ import com.amazonaws.services.kinesis.model.Record;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.ThrottlingReporter;
|
import software.amazon.kinesis.retrieval.ThrottlingReporter;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
import software.amazon.kinesis.retrieval.kpl.Messages;
|
import software.amazon.kinesis.retrieval.kpl.Messages;
|
||||||
|
|
@ -79,7 +79,7 @@ public class ProcessTaskTest {
|
||||||
private final long taskBackoffTimeMillis = 1L;
|
private final long taskBackoffTimeMillis = 1L;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private IRecordProcessor recordProcessor;
|
private RecordProcessor recordProcessor;
|
||||||
@Mock
|
@Mock
|
||||||
private RecordProcessorCheckpointer checkpointer;
|
private RecordProcessorCheckpointer checkpointer;
|
||||||
@Mock
|
@Mock
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ import org.mockito.Mock;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
import com.amazonaws.services.kinesis.AmazonKinesis;
|
import com.amazonaws.services.kinesis.AmazonKinesis;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.checkpoint.InMemoryCheckpointImpl;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.checkpoint.InMemoryCheckpointer;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.proxies.KinesisLocalFileProxy;
|
import com.amazonaws.services.kinesis.clientlibrary.proxies.KinesisLocalFileProxy;
|
||||||
|
|
@ -74,15 +74,15 @@ import com.amazonaws.services.kinesis.model.ShardIteratorType;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import software.amazon.kinesis.checkpoint.Checkpoint;
|
import software.amazon.kinesis.checkpoint.Checkpoint;
|
||||||
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
import software.amazon.kinesis.coordinator.KinesisClientLibConfiguration;
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.metrics.IMetricsFactory;
|
import software.amazon.kinesis.metrics.IMetricsFactory;
|
||||||
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
import software.amazon.kinesis.metrics.NullMetricsFactory;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.AsynchronousGetRecordsRetrievalStrategy;
|
import software.amazon.kinesis.retrieval.AsynchronousGetRecordsRetrievalStrategy;
|
||||||
import software.amazon.kinesis.retrieval.BlockingGetRecordsCache;
|
import software.amazon.kinesis.retrieval.BlockingGetRecordsCache;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
|
|
@ -135,13 +135,13 @@ public class ShardConsumerTest {
|
||||||
@Mock
|
@Mock
|
||||||
private RecordsFetcherFactory recordsFetcherFactory;
|
private RecordsFetcherFactory recordsFetcherFactory;
|
||||||
@Mock
|
@Mock
|
||||||
private IRecordProcessor recordProcessor;
|
private RecordProcessor recordProcessor;
|
||||||
@Mock
|
@Mock
|
||||||
private KinesisClientLibConfiguration config;
|
private KinesisClientLibConfiguration config;
|
||||||
@Mock
|
@Mock
|
||||||
private ILeaseManager<KinesisClientLease> leaseManager;
|
private ILeaseManager<KinesisClientLease> leaseManager;
|
||||||
@Mock
|
@Mock
|
||||||
private ICheckpoint checkpoint;
|
private Checkpointer checkpoint;
|
||||||
@Mock
|
@Mock
|
||||||
private ShutdownNotification shutdownNotification;
|
private ShutdownNotification shutdownNotification;
|
||||||
@Mock
|
@Mock
|
||||||
|
|
@ -287,7 +287,7 @@ public class ShardConsumerTest {
|
||||||
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
||||||
|
|
||||||
final int maxRecords = 2;
|
final int maxRecords = 2;
|
||||||
ICheckpoint checkpoint = new InMemoryCheckpointImpl(startSeqNum.toString());
|
Checkpointer checkpoint = new InMemoryCheckpointer(startSeqNum.toString());
|
||||||
checkpoint.setCheckpoint(shardId, ExtendedSequenceNumber.TRIM_HORIZON, concurrencyToken);
|
checkpoint.setCheckpoint(shardId, ExtendedSequenceNumber.TRIM_HORIZON, concurrencyToken);
|
||||||
when(leaseManager.getLease(anyString())).thenReturn(null);
|
when(leaseManager.getLease(anyString())).thenReturn(null);
|
||||||
TestStreamlet processor = new TestStreamlet();
|
TestStreamlet processor = new TestStreamlet();
|
||||||
|
|
@ -390,7 +390,7 @@ public class ShardConsumerTest {
|
||||||
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
||||||
|
|
||||||
final int maxRecords = 2;
|
final int maxRecords = 2;
|
||||||
ICheckpoint checkpoint = new InMemoryCheckpointImpl(startSeqNum.toString());
|
Checkpointer checkpoint = new InMemoryCheckpointer(startSeqNum.toString());
|
||||||
checkpoint.setCheckpoint(shardId, ExtendedSequenceNumber.TRIM_HORIZON, concurrencyToken);
|
checkpoint.setCheckpoint(shardId, ExtendedSequenceNumber.TRIM_HORIZON, concurrencyToken);
|
||||||
when(leaseManager.getLease(anyString())).thenReturn(null);
|
when(leaseManager.getLease(anyString())).thenReturn(null);
|
||||||
|
|
||||||
|
|
@ -485,7 +485,7 @@ public class ShardConsumerTest {
|
||||||
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
IKinesisProxy fileBasedProxy = new KinesisLocalFileProxy(file.getAbsolutePath());
|
||||||
|
|
||||||
final int maxRecords = 2;
|
final int maxRecords = 2;
|
||||||
ICheckpoint checkpoint = new InMemoryCheckpointImpl(startSeqNum.toString());
|
Checkpointer checkpoint = new InMemoryCheckpointer(startSeqNum.toString());
|
||||||
checkpoint.setCheckpoint(streamShardId, ExtendedSequenceNumber.AT_TIMESTAMP, testConcurrencyToken);
|
checkpoint.setCheckpoint(streamShardId, ExtendedSequenceNumber.AT_TIMESTAMP, testConcurrencyToken);
|
||||||
when(leaseManager.getLease(anyString())).thenReturn(null);
|
when(leaseManager.getLease(anyString())).thenReturn(null);
|
||||||
TestStreamlet processor = new TestStreamlet();
|
TestStreamlet processor = new TestStreamlet();
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ import com.amazonaws.services.kinesis.clientlibrary.exceptions.internal.KinesisC
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
|
||||||
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStreamExtended;
|
||||||
|
|
||||||
import software.amazon.kinesis.coordinator.RecordProcessorCheckpointer;
|
import software.amazon.kinesis.checkpoint.RecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.leases.ILeaseManager;
|
import software.amazon.kinesis.leases.ILeaseManager;
|
||||||
import software.amazon.kinesis.leases.KinesisClientLease;
|
import software.amazon.kinesis.leases.KinesisClientLease;
|
||||||
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
import software.amazon.kinesis.leases.LeaseManagerProxy;
|
||||||
import software.amazon.kinesis.leases.ShardInfo;
|
import software.amazon.kinesis.leases.ShardInfo;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
import software.amazon.kinesis.retrieval.GetRecordsCache;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
import software.amazon.kinesis.utils.TestStreamlet;
|
import software.amazon.kinesis.utils.TestStreamlet;
|
||||||
|
|
@ -57,7 +57,7 @@ public class ShutdownTaskTest {
|
||||||
private final String shardId = "shardId-0000397840";
|
private final String shardId = "shardId-0000397840";
|
||||||
private boolean cleanupLeasesOfCompletedShards = false;
|
private boolean cleanupLeasesOfCompletedShards = false;
|
||||||
private boolean ignoreUnexpectedChildShards = false;
|
private boolean ignoreUnexpectedChildShards = false;
|
||||||
private IRecordProcessor recordProcessor;
|
private RecordProcessor recordProcessor;
|
||||||
private ShardInfo shardInfo;
|
private ShardInfo shardInfo;
|
||||||
private ShutdownTask task;
|
private ShutdownTask task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ import com.amazonaws.services.kinesis.model.ResourceNotFoundException;
|
||||||
import com.amazonaws.services.kinesis.model.ShardIteratorType;
|
import com.amazonaws.services.kinesis.model.ShardIteratorType;
|
||||||
|
|
||||||
import software.amazon.kinesis.checkpoint.SentinelCheckpoint;
|
import software.amazon.kinesis.checkpoint.SentinelCheckpoint;
|
||||||
import software.amazon.kinesis.processor.ICheckpoint;
|
import software.amazon.kinesis.processor.Checkpointer;
|
||||||
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -134,7 +134,7 @@ public class KinesisDataFetcherTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testadvanceIteratorTo() throws KinesisClientLibException {
|
public void testadvanceIteratorTo() throws KinesisClientLibException {
|
||||||
final ICheckpoint checkpoint = mock(ICheckpoint.class);
|
final Checkpointer checkpoint = mock(Checkpointer.class);
|
||||||
final String iteratorA = "foo";
|
final String iteratorA = "foo";
|
||||||
final String iteratorB = "bar";
|
final String iteratorB = "bar";
|
||||||
final String seqA = "123";
|
final String seqA = "123";
|
||||||
|
|
@ -398,7 +398,7 @@ public class KinesisDataFetcherTest {
|
||||||
when(amazonKinesis.getRecords(recordsCaptor.capture()))
|
when(amazonKinesis.getRecords(recordsCaptor.capture()))
|
||||||
.thenReturn(new GetRecordsResult().withRecords(expectedRecords));
|
.thenReturn(new GetRecordsResult().withRecords(expectedRecords));
|
||||||
|
|
||||||
ICheckpoint checkpoint = mock(ICheckpoint.class);
|
Checkpointer checkpoint = mock(Checkpointer.class);
|
||||||
when(checkpoint.getCheckpoint(SHARD_ID)).thenReturn(new ExtendedSequenceNumber(seqNo));
|
when(checkpoint.getCheckpoint(SHARD_ID)).thenReturn(new ExtendedSequenceNumber(seqNo));
|
||||||
|
|
||||||
final GetRecordsRetrievalStrategy getRecordsRetrievalStrategy =
|
final GetRecordsRetrievalStrategy getRecordsRetrievalStrategy =
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingExcepti
|
||||||
import software.amazon.kinesis.leases.ShardSequenceVerifier;
|
import software.amazon.kinesis.leases.ShardSequenceVerifier;
|
||||||
import software.amazon.kinesis.lifecycle.ShutdownReason;
|
import software.amazon.kinesis.lifecycle.ShutdownReason;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
import software.amazon.kinesis.processor.IRecordProcessorCheckpointer;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IShutdownNotificationAware;
|
import software.amazon.kinesis.processor.ShutdownNotificationAware;
|
||||||
import software.amazon.kinesis.lifecycle.InitializationInput;
|
import software.amazon.kinesis.lifecycle.InitializationInput;
|
||||||
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
import software.amazon.kinesis.lifecycle.ProcessRecordsInput;
|
||||||
import software.amazon.kinesis.lifecycle.ShutdownInput;
|
import software.amazon.kinesis.lifecycle.ShutdownInput;
|
||||||
|
|
@ -42,7 +42,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
* Streamlet that tracks records it's seen - useful for testing.
|
* Streamlet that tracks records it's seen - useful for testing.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class TestStreamlet implements IRecordProcessor, IShutdownNotificationAware {
|
public class TestStreamlet implements RecordProcessor, ShutdownNotificationAware {
|
||||||
private List<Record> records = new ArrayList<Record>();
|
private List<Record> records = new ArrayList<Record>();
|
||||||
|
|
||||||
private Set<String> processedSeqNums = new HashSet<String>(); // used for deduping
|
private Set<String> processedSeqNums = new HashSet<String>(); // used for deduping
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ import java.util.List;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
import software.amazon.kinesis.leases.ShardSequenceVerifier;
|
import software.amazon.kinesis.leases.ShardSequenceVerifier;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessor;
|
import software.amazon.kinesis.processor.RecordProcessor;
|
||||||
import software.amazon.kinesis.processor.IRecordProcessorFactory;
|
import software.amazon.kinesis.processor.RecordProcessorFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for TestStreamlet record processors.
|
* Factory for TestStreamlet record processors.
|
||||||
*/
|
*/
|
||||||
public class TestStreamletFactory implements IRecordProcessorFactory {
|
public class TestStreamletFactory implements RecordProcessorFactory {
|
||||||
|
|
||||||
// Will be passed to the TestStreamlet. Can be used to check if all records have been processed.
|
// Will be passed to the TestStreamlet. Can be used to check if all records have been processed.
|
||||||
private Semaphore semaphore;
|
private Semaphore semaphore;
|
||||||
|
|
@ -41,7 +41,7 @@ public class TestStreamletFactory implements IRecordProcessorFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized IRecordProcessor createProcessor() {
|
public synchronized RecordProcessor createProcessor() {
|
||||||
TestStreamlet processor = new TestStreamlet(semaphore, shardSequenceVerifier);
|
TestStreamlet processor = new TestStreamlet(semaphore, shardSequenceVerifier);
|
||||||
testStreamlets.add(processor);
|
testStreamlets.add(processor);
|
||||||
return processor;
|
return processor;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue