Reworked some of the shutdown logic to make the relationships clearer.

This commit is contained in:
Pfifer, Justin 2017-06-13 06:21:27 -07:00
parent 9686d7c3ca
commit 0829378957
5 changed files with 624 additions and 410 deletions

View file

@ -0,0 +1,19 @@
package com.amazonaws.services.kinesis.clientlibrary.lib.worker;
import lombok.Data;
import java.util.concurrent.CountDownLatch;
@Data
class GracefulShutdownContext {
private final CountDownLatch shutdownCompleteLatch;
private final CountDownLatch notificationCompleteLatch;
private final Worker worker;
static GracefulShutdownContext SHUTDOWN_ALREADY_COMPLETED = new GracefulShutdownContext(null, null, null);
boolean isShutdownAlreadyCompleted() {
return shutdownCompleteLatch == null && notificationCompleteLatch == null && worker == null;
}
}

View file

@ -1,7 +1,6 @@
package com.amazonaws.services.kinesis.clientlibrary.lib.worker; package com.amazonaws.services.kinesis.clientlibrary.lib.worker;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -9,9 +8,9 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
class RequestedShutdownCoordinator { class GracefulShutdownCoordinator {
static Future<Boolean> startRequestedShutdown(Callable<Boolean> shutdownCallable) { Future<Boolean> startGracefulShutdown(Callable<Boolean> shutdownCallable) {
FutureTask<Boolean> task = new FutureTask<>(shutdownCallable); FutureTask<Boolean> task = new FutureTask<>(shutdownCallable);
Thread shutdownThread = new Thread(task, "RequestedShutdownThread"); Thread shutdownThread = new Thread(task, "RequestedShutdownThread");
shutdownThread.start(); shutdownThread.start();
@ -19,45 +18,39 @@ class RequestedShutdownCoordinator {
} }
static Callable<Boolean> createRequestedShutdownCallable(CountDownLatch shutdownCompleteLatch, Callable<Boolean> createGracefulShutdownCallable(Callable<GracefulShutdownContext> startWorkerShutdown) {
CountDownLatch notificationCompleteLatch, Worker worker) { return new GracefulShutdownCallable(startWorkerShutdown);
return new RequestedShutdownCallable(shutdownCompleteLatch, notificationCompleteLatch, worker);
} }
static class RequestedShutdownCallable implements Callable<Boolean> { static class GracefulShutdownCallable implements Callable<Boolean> {
private static final Log log = LogFactory.getLog(RequestedShutdownCallable.class); private static final Log log = LogFactory.getLog(GracefulShutdownCallable.class);
private final CountDownLatch shutdownCompleteLatch; private final Callable<GracefulShutdownContext> startWorkerShutdown;
private final CountDownLatch notificationCompleteLatch;
private final Worker worker;
RequestedShutdownCallable(CountDownLatch shutdownCompleteLatch, CountDownLatch notificationCompleteLatch, GracefulShutdownCallable(Callable<GracefulShutdownContext> startWorkerShutdown) {
Worker worker) { this.startWorkerShutdown = startWorkerShutdown;
this.shutdownCompleteLatch = shutdownCompleteLatch;
this.notificationCompleteLatch = notificationCompleteLatch;
this.worker = worker;
} }
private boolean isWorkerShutdownComplete() { private boolean isWorkerShutdownComplete(GracefulShutdownContext context) {
return worker.isShutdownComplete() || worker.getShardInfoShardConsumerMap().isEmpty(); return context.getWorker().isShutdownComplete() || context.getWorker().getShardInfoShardConsumerMap().isEmpty();
} }
private String awaitingLogMessage() { private String awaitingLogMessage(GracefulShutdownContext context) {
long awaitingNotification = notificationCompleteLatch.getCount(); long awaitingNotification = context.getNotificationCompleteLatch().getCount();
long awaitingFinalShutdown = shutdownCompleteLatch.getCount(); long awaitingFinalShutdown = context.getShutdownCompleteLatch().getCount();
return String.format( return String.format(
"Waiting for %d record process to complete shutdown notification, and %d record processor to complete final shutdown ", "Waiting for %d record process to complete shutdown notification, and %d record processor to complete final shutdown ",
awaitingNotification, awaitingFinalShutdown); awaitingNotification, awaitingFinalShutdown);
} }
private String awaitingFinalShutdownMessage() { private String awaitingFinalShutdownMessage(GracefulShutdownContext context) {
long outstanding = shutdownCompleteLatch.getCount(); long outstanding = context.getShutdownCompleteLatch().getCount();
return String.format("Waiting for %d record processors to complete final shutdown", outstanding); return String.format("Waiting for %d record processors to complete final shutdown", outstanding);
} }
private boolean waitForRecordProcessors() { private boolean waitForRecordProcessors(GracefulShutdownContext context) {
// //
// Awaiting for all ShardConsumer/RecordProcessors to be notified that a shutdown has been requested. // Awaiting for all ShardConsumer/RecordProcessors to be notified that a shutdown has been requested.
@ -66,18 +59,18 @@ class RequestedShutdownCoordinator {
// ShardConsumer would start the lease loss shutdown, and may never call the notification methods. // ShardConsumer would start the lease loss shutdown, and may never call the notification methods.
// //
try { try {
while (!notificationCompleteLatch.await(1, TimeUnit.SECONDS)) { while (!context.getNotificationCompleteLatch().await(1, TimeUnit.SECONDS)) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
log.info(awaitingLogMessage()); log.info(awaitingLogMessage(context));
if (workerShutdownWithRemaining(shutdownCompleteLatch.getCount())) { if (workerShutdownWithRemaining(context.getShutdownCompleteLatch().getCount(), context)) {
return false; return false;
} }
} }
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
log.warn("Interrupted while waiting for notification complete, terminating shutdown. " log.warn("Interrupted while waiting for notification complete, terminating shutdown. "
+ awaitingLogMessage()); + awaitingLogMessage(context));
return false; return false;
} }
@ -90,7 +83,7 @@ class RequestedShutdownCoordinator {
// Once all record processors have been notified of the shutdown it is safe to allow the worker to // Once all record processors have been notified of the shutdown it is safe to allow the worker to
// start its shutdown behavior. Once shutdown starts it will stop renewer, and drop any remaining leases. // start its shutdown behavior. Once shutdown starts it will stop renewer, and drop any remaining leases.
// //
worker.shutdown(); context.getWorker().shutdown();
if (Thread.interrupted()) { if (Thread.interrupted()) {
log.warn("Interrupted after worker shutdown, terminating shutdown"); log.warn("Interrupted after worker shutdown, terminating shutdown");
@ -103,18 +96,18 @@ class RequestedShutdownCoordinator {
// ShardConsumer is terminated. // ShardConsumer is terminated.
// //
try { try {
while (!shutdownCompleteLatch.await(1, TimeUnit.SECONDS)) { while (!context.getShutdownCompleteLatch().await(1, TimeUnit.SECONDS)) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
log.info(awaitingFinalShutdownMessage()); log.info(awaitingFinalShutdownMessage(context));
if (workerShutdownWithRemaining(shutdownCompleteLatch.getCount())) { if (workerShutdownWithRemaining(context.getShutdownCompleteLatch().getCount(), context)) {
return false; return false;
} }
} }
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
log.warn("Interrupted while waiting for shutdown completion, terminating shutdown. " log.warn("Interrupted while waiting for shutdown completion, terminating shutdown. "
+ awaitingFinalShutdownMessage()); + awaitingFinalShutdownMessage(context));
return false; return false;
} }
return true; return true;
@ -128,13 +121,13 @@ class RequestedShutdownCoordinator {
* @param outstanding * @param outstanding
* the number of record processor still awaiting shutdown. * the number of record processor still awaiting shutdown.
*/ */
private boolean workerShutdownWithRemaining(long outstanding) { private boolean workerShutdownWithRemaining(long outstanding, GracefulShutdownContext context) {
if (isWorkerShutdownComplete()) { if (isWorkerShutdownComplete(context)) {
if (outstanding != 0) { if (outstanding != 0) {
log.info("Shutdown completed, but shutdownCompleteLatch still had outstanding " + outstanding log.info("Shutdown completed, but shutdownCompleteLatch still had outstanding " + outstanding
+ " with a current value of " + shutdownCompleteLatch.getCount() + ". shutdownComplete: " + " with a current value of " + context.getShutdownCompleteLatch().getCount() + ". shutdownComplete: "
+ worker.isShutdownComplete() + " -- Consumer Map: " + context.getWorker().isShutdownComplete() + " -- Consumer Map: "
+ worker.getShardInfoShardConsumerMap().size()); + context.getWorker().getShardInfoShardConsumerMap().size());
return true; return true;
} }
} }
@ -143,7 +136,14 @@ class RequestedShutdownCoordinator {
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws Exception {
return waitForRecordProcessors(); GracefulShutdownContext context;
try {
context = startWorkerShutdown.call();
} catch (Exception ex) {
log.warn("Caught exception while requesting initial worker shutdown.", ex);
throw ex;
}
return context.isShutdownAlreadyCompleted() || waitForRecordProcessors(context);
} }
} }
} }

View file

@ -1,10 +1,12 @@
package com.amazonaws.services.kinesis.clientlibrary.lib.worker; package com.amazonaws.services.kinesis.clientlibrary.lib.worker;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -18,13 +20,11 @@ import java.util.concurrent.TimeUnit;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.mockito.verification.VerificationMode; import org.mockito.verification.VerificationMode;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class RequestedShutdownCoordinatorTest { public class GracefulShutdownCoordinatorTest {
@Mock @Mock
private CountDownLatch shutdownCompleteLatch; private CountDownLatch shutdownCompleteLatch;
@ -33,6 +33,8 @@ public class RequestedShutdownCoordinatorTest {
@Mock @Mock
private Worker worker; private Worker worker;
@Mock @Mock
private Callable<GracefulShutdownContext> contextCallable;
@Mock
private ConcurrentMap<ShardInfo, ShardConsumer> shardInfoConsumerMap; private ConcurrentMap<ShardInfo, ShardConsumer> shardInfoConsumerMap;
@Test @Test
@ -262,9 +264,18 @@ public class RequestedShutdownCoordinatorTest {
verify(worker).shutdown(); verify(worker).shutdown();
} }
@Test(expected = IllegalStateException.class)
public void testWorkerShutdownCallableThrows() throws Exception {
Callable<Boolean> requestedShutdownCallable = new GracefulShutdownCoordinator().createGracefulShutdownCallable(contextCallable);
when(contextCallable.call()).thenThrow(new IllegalStateException("Bad Shutdown"));
requestedShutdownCallable.call();
}
private void verifyLatchAwait(CountDownLatch latch) throws Exception { private void verifyLatchAwait(CountDownLatch latch) throws Exception {
verifyLatchAwait(latch, times(1)); verifyLatchAwait(latch, times(1));
} }
private void verifyLatchAwait(CountDownLatch latch, int times) throws Exception { private void verifyLatchAwait(CountDownLatch latch, int times) throws Exception {
verifyLatchAwait(latch, times(times)); verifyLatchAwait(latch, times(times));
} }
@ -277,9 +288,11 @@ public class RequestedShutdownCoordinatorTest {
when(latch.await(anyLong(), any(TimeUnit.class))).thenReturn(initial, remaining); when(latch.await(anyLong(), any(TimeUnit.class))).thenReturn(initial, remaining);
} }
private Callable<Boolean> buildRequestedShutdownCallable() { private Callable<Boolean> buildRequestedShutdownCallable() throws Exception {
return RequestedShutdownCoordinator.createRequestedShutdownCallable(shutdownCompleteLatch, GracefulShutdownContext context = new GracefulShutdownContext(shutdownCompleteLatch,
notificationCompleteLatch, worker); notificationCompleteLatch, worker);
when(contextCallable.call()).thenReturn(context);
return new GracefulShutdownCoordinator().createGracefulShutdownCallable(contextCallable);
} }
private void mockShardInfoConsumerMap(Integer initialItemCount, Integer... additionalItemCounts) { private void mockShardInfoConsumerMap(Integer initialItemCount, Integer... additionalItemCounts) {

View file

@ -762,7 +762,7 @@ public class WorkerTest {
verify(executorService, atLeastOnce()).submit(argThat( verify(executorService, atLeastOnce()).submit(argThat(
both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE)))); both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE))));
worker.requestShutdown(); worker.createWorkerShutdownCallable().call();
worker.runProcessLoop(); worker.runProcessLoop();
verify(executorService, atLeastOnce()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class)) verify(executorService, atLeastOnce()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class))
@ -781,6 +781,146 @@ public class WorkerTest {
} }
@Test(expected = IllegalStateException.class)
public void testShutdownCallableNotAllowedTwice() throws Exception {
IRecordProcessorFactory recordProcessorFactory = mock(IRecordProcessorFactory.class);
StreamConfig streamConfig = mock(StreamConfig.class);
IMetricsFactory metricsFactory = mock(IMetricsFactory.class);
ExtendedSequenceNumber checkpoint = new ExtendedSequenceNumber("123", 0L);
KinesisClientLeaseBuilder builder = new KinesisClientLeaseBuilder().withCheckpoint(checkpoint)
.withConcurrencyToken(UUID.randomUUID()).withLastCounterIncrementNanos(0L).withLeaseCounter(0L)
.withOwnerSwitchesSinceCheckpoint(0L).withLeaseOwner("Self");
final List<KinesisClientLease> leases = new ArrayList<>();
final List<ShardInfo> currentAssignments = new ArrayList<>();
KinesisClientLease lease = builder.withLeaseKey(String.format("shardId-%03d", 1)).build();
leases.add(lease);
currentAssignments.add(new ShardInfo(lease.getLeaseKey(), lease.getConcurrencyToken().toString(),
lease.getParentShardIds(), lease.getCheckpoint()));
when(leaseCoordinator.getAssignments()).thenAnswer(new Answer<List<KinesisClientLease>>() {
@Override
public List<KinesisClientLease> answer(InvocationOnMock invocation) throws Throwable {
return leases;
}
});
when(leaseCoordinator.getCurrentAssignments()).thenAnswer(new Answer<List<ShardInfo>>() {
@Override
public List<ShardInfo> answer(InvocationOnMock invocation) throws Throwable {
return currentAssignments;
}
});
IRecordProcessor processor = mock(IRecordProcessor.class);
when(recordProcessorFactory.createProcessor()).thenReturn(processor);
Worker worker = new InjectableWorker("testRequestShutdown", recordProcessorFactory, streamConfig,
INITIAL_POSITION_TRIM_HORIZON, parentShardPollIntervalMillis, shardSyncIntervalMillis,
cleanupLeasesUponShardCompletion, leaseCoordinator, leaseCoordinator, executorService, metricsFactory,
taskBackoffTimeMillis, failoverTimeMillis, false, shardPrioritization) {
@Override
void postConstruct() {
this.gracefuleShutdownStarted = true;
}
};
when(executorService.submit(Matchers.<Callable<TaskResult>> any()))
.thenAnswer(new ShutdownHandlingAnswer(taskFuture));
when(taskFuture.isDone()).thenReturn(true);
when(taskFuture.get()).thenReturn(taskResult);
worker.runProcessLoop();
verify(executorService, atLeastOnce()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class))
.and(TaskTypeMatcher.isOfType(TaskType.BLOCK_ON_PARENT_SHARDS))));
worker.runProcessLoop();
verify(executorService, atLeastOnce()).submit(argThat(
both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE))));
assertThat(worker.hasGracefulShutdownStarted(), equalTo(true));
worker.createWorkerShutdownCallable().call();
}
@Test
public void testGracefulShutdownSingleFuture() throws Exception {
IRecordProcessorFactory recordProcessorFactory = mock(IRecordProcessorFactory.class);
StreamConfig streamConfig = mock(StreamConfig.class);
IMetricsFactory metricsFactory = mock(IMetricsFactory.class);
ExtendedSequenceNumber checkpoint = new ExtendedSequenceNumber("123", 0L);
KinesisClientLeaseBuilder builder = new KinesisClientLeaseBuilder().withCheckpoint(checkpoint)
.withConcurrencyToken(UUID.randomUUID()).withLastCounterIncrementNanos(0L).withLeaseCounter(0L)
.withOwnerSwitchesSinceCheckpoint(0L).withLeaseOwner("Self");
final List<KinesisClientLease> leases = new ArrayList<>();
final List<ShardInfo> currentAssignments = new ArrayList<>();
KinesisClientLease lease = builder.withLeaseKey(String.format("shardId-%03d", 1)).build();
leases.add(lease);
currentAssignments.add(new ShardInfo(lease.getLeaseKey(), lease.getConcurrencyToken().toString(),
lease.getParentShardIds(), lease.getCheckpoint()));
when(leaseCoordinator.getAssignments()).thenAnswer(new Answer<List<KinesisClientLease>>() {
@Override
public List<KinesisClientLease> answer(InvocationOnMock invocation) throws Throwable {
return leases;
}
});
when(leaseCoordinator.getCurrentAssignments()).thenAnswer(new Answer<List<ShardInfo>>() {
@Override
public List<ShardInfo> answer(InvocationOnMock invocation) throws Throwable {
return currentAssignments;
}
});
IRecordProcessor processor = mock(IRecordProcessor.class);
when(recordProcessorFactory.createProcessor()).thenReturn(processor);
GracefulShutdownCoordinator coordinator = mock(GracefulShutdownCoordinator.class);
when(coordinator.createGracefulShutdownCallable(any(Callable.class))).thenReturn(() -> true);
Future<Boolean> gracefulShutdownFuture = mock(Future.class);
when(coordinator.startGracefulShutdown(any(Callable.class))).thenReturn(gracefulShutdownFuture);
Worker worker = new InjectableWorker("testRequestShutdown", recordProcessorFactory, streamConfig,
INITIAL_POSITION_TRIM_HORIZON, parentShardPollIntervalMillis, shardSyncIntervalMillis,
cleanupLeasesUponShardCompletion, leaseCoordinator, leaseCoordinator, executorService, metricsFactory,
taskBackoffTimeMillis, failoverTimeMillis, false, shardPrioritization) {
@Override
void postConstruct() {
this.gracefulShutdownCoordinator = coordinator;
}
};
when(executorService.submit(Matchers.<Callable<TaskResult>> any()))
.thenAnswer(new ShutdownHandlingAnswer(taskFuture));
when(taskFuture.isDone()).thenReturn(true);
when(taskFuture.get()).thenReturn(taskResult);
worker.runProcessLoop();
verify(executorService, atLeastOnce()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class))
.and(TaskTypeMatcher.isOfType(TaskType.BLOCK_ON_PARENT_SHARDS))));
worker.runProcessLoop();
verify(executorService, atLeastOnce()).submit(argThat(
both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE))));
Future<Boolean> firstFuture = worker.startGracefulShutdown();
Future<Boolean> secondFuture = worker.startGracefulShutdown();
assertThat(firstFuture, equalTo(secondFuture));
verify(coordinator).startGracefulShutdown(any(Callable.class));
}
@Test @Test
public void testRequestShutdownNoLeases() throws Exception { public void testRequestShutdownNoLeases() throws Exception {
@ -830,7 +970,7 @@ public class WorkerTest {
verify(executorService, never()).submit(argThat( verify(executorService, never()).submit(argThat(
both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE)))); both(isA(MetricsCollectingTaskDecorator.class)).and(TaskTypeMatcher.isOfType(TaskType.INITIALIZE))));
worker.requestShutdown(); worker.createWorkerShutdownCallable().call();
worker.runProcessLoop(); worker.runProcessLoop();
verify(executorService, never()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class)) verify(executorService, never()).submit(argThat(both(isA(MetricsCollectingTaskDecorator.class))
@ -909,7 +1049,7 @@ public class WorkerTest {
.withField(InitializeTask.class, "shardInfo", equalTo(shardInfo2))))); .withField(InitializeTask.class, "shardInfo", equalTo(shardInfo2)))));
worker.getShardInfoShardConsumerMap().remove(shardInfo2); worker.getShardInfoShardConsumerMap().remove(shardInfo2);
worker.requestShutdown(); worker.createWorkerShutdownCallable().call();
leases.remove(1); leases.remove(1);
currentAssignments.remove(1); currentAssignments.remove(1);
worker.runProcessLoop(); worker.runProcessLoop();
@ -1194,6 +1334,24 @@ public class WorkerTest {
} }
private abstract class InjectableWorker extends Worker {
InjectableWorker(String applicationName, IRecordProcessorFactory recordProcessorFactory,
StreamConfig streamConfig, InitialPositionInStreamExtended initialPositionInStream,
long parentShardPollIntervalMillis, long shardSyncIdleTimeMillis,
boolean cleanupLeasesUponShardCompletion, ICheckpoint checkpoint,
KinesisClientLibLeaseCoordinator leaseCoordinator, ExecutorService execService,
IMetricsFactory metricsFactory, long taskBackoffTimeMillis, long failoverTimeMillis,
boolean skipShardSyncAtWorkerInitializationIfLeasesExist, ShardPrioritization shardPrioritization) {
super(applicationName, recordProcessorFactory, streamConfig, initialPositionInStream,
parentShardPollIntervalMillis, shardSyncIdleTimeMillis, cleanupLeasesUponShardCompletion,
checkpoint, leaseCoordinator, execService, metricsFactory, taskBackoffTimeMillis,
failoverTimeMillis, skipShardSyncAtWorkerInitializationIfLeasesExist, shardPrioritization);
postConstruct();
}
abstract void postConstruct();
}
private KinesisClientLease makeLease(ExtendedSequenceNumber checkpoint, int shardId) { private KinesisClientLease makeLease(ExtendedSequenceNumber checkpoint, int shardId) {
return new KinesisClientLeaseBuilder().withCheckpoint(checkpoint).withConcurrencyToken(UUID.randomUUID()) return new KinesisClientLeaseBuilder().withCheckpoint(checkpoint).withConcurrencyToken(UUID.randomUUID())
.withLastCounterIncrementNanos(0L).withLeaseCounter(0L).withOwnerSwitchesSinceCheckpoint(0L) .withLastCounterIncrementNanos(0L).withLeaseCounter(0L).withOwnerSwitchesSinceCheckpoint(0L)