Skip to content

Commit 3daef6e

Browse files
committed
Add lock to guarantee thread safety, don't attempt give up partition when checkpointing fails
Signed-off-by: Taylor Gray <tylgry@amazon.com>
1 parent a188f93 commit 3daef6e

3 files changed

Lines changed: 54 additions & 24 deletions

File tree

data-prepper-plugins/dynamodb-source/src/main/java/org/opensearch/dataprepper/plugins/source/dynamodb/stream/ShardAcknowledgementManager.java

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import java.util.concurrent.ExecutorService;
3838
import java.util.concurrent.Executors;
3939
import java.util.concurrent.TimeUnit;
40+
import java.util.concurrent.locks.Lock;
41+
import java.util.concurrent.locks.ReentrantLock;
4042
import java.util.function.Consumer;
4143

4244
import org.opensearch.dataprepper.plugins.source.dynamodb.model.ShardCheckpointStatus;
@@ -66,6 +68,7 @@ public class ShardAcknowledgementManager {
6668
private boolean shutdownTriggered;
6769

6870
private Instant lastCheckpointTime;
71+
private Lock lock;
6972

7073
ShardAcknowledgementManager(final AcknowledgementSetManager acknowledgementSetManager,
7174
final EnhancedSourceCoordinator sourceCoordinator,
@@ -79,6 +82,7 @@ public class ShardAcknowledgementManager {
7982
this.partitionsToRemove = Collections.synchronizedList(new ArrayList<>());
8083
this.partitionsToGiveUp = Collections.synchronizedList(new ArrayList<>());
8184
this.lastCheckpointTime = Instant.now();
85+
this.lock = new ReentrantLock();
8286

8387
executorService.submit(() -> monitorAcknowledgments(stopWorkerConsumer));
8488
}
@@ -95,6 +99,7 @@ public class ShardAcknowledgementManager {
9599
this.partitionsToRemove = Collections.synchronizedList(new ArrayList<>());
96100
this.partitionsToGiveUp = Collections.synchronizedList(new ArrayList<>());
97101
this.lastCheckpointTime = Instant.now();
102+
this.lock = new ReentrantLock();
98103
}
99104

100105
void monitorAcknowledgments(final Consumer<StreamPartition> stopWorkerConsumer) {
@@ -166,14 +171,21 @@ boolean runMonitorAcknowledgmentLoop(final Consumer<StreamPartition> stopWorkerC
166171

167172
if (latestCheckpointForShard.isFinalAcknowledgmentForPartition()) {
168173
handleCompletedShard(streamPartition);
169-
} else {
174+
} else if (!partitionsToRemove.contains(streamPartition)) {
170175
streamProgressState.setSequenceNumber(Objects.equals(latestCheckpointForShard.getSequenceNumber(), NULL_SEQUENCE_NUMBER) ? null : latestCheckpointForShard.getSequenceNumber());
171-
sourceCoordinator.saveProgressStateForPartition(streamPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
176+
try {
177+
sourceCoordinator.saveProgressStateForPartition(streamPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
178+
} catch (final PartitionUpdateException e) {
179+
LOG.warn("Failed to checkpoint shard {}, stop processing shard. This shard will be processed by another worker.", streamPartition.getPartitionKey());
180+
partitionsToRemove.add(streamPartition);
181+
}
172182
LOG.debug("Checkpointed shard {} with latest sequence number acknowledged {}", streamPartition.getShardId(), latestCheckpointForShard.getSequenceNumber());
173183
}
174184
} catch (final Exception e) {
175185
LOG.error(NOISY, "Received exception while monitoring acknowledgments for stream partition {}, stop processing shard", streamPartition.getPartitionKey(), e);
176-
markPartitionForRemoval(streamPartition);
186+
if (!partitionsToRemove.contains(streamPartition)) {
187+
partitionsToRemove.add(streamPartition);
188+
}
177189
}
178190
}
179191

@@ -190,11 +202,16 @@ public AcknowledgementSet createAcknowledgmentSet(
190202
// Shard should already be in checkpoints map from call to startUpdatingOwnershipForShard, if it is not in the map
191203
// that means that ShardAcknowledgmentManager stopped tracking it due to some error, and another worker will pick it up
192204
// We throw an error in this case to have the ShardConsumer exit and stop reading data from the shard
193-
ConcurrentLinkedQueue<ShardCheckpointStatus> queue = checkpoints.get(streamPartition);
194-
if (queue == null) {
195-
throw new ShardNotTrackedException("The shard {} is not being tracked anymore, stop reading from shard");
205+
lock.lock();
206+
try {
207+
ConcurrentLinkedQueue<ShardCheckpointStatus> queue = checkpoints.get(streamPartition);
208+
if (queue == null) {
209+
throw new ShardNotTrackedException("The shard {} is not being tracked anymore, stop reading from shard");
210+
}
211+
queue.add(shardCheckpointStatus);
212+
} finally {
213+
lock.unlock();
196214
}
197-
queue.add(shardCheckpointStatus);
198215

199216
ackStatuses.computeIfAbsent(streamPartition, segment -> new ConcurrentHashMap<>());
200217
ackStatuses.get(streamPartition).put(sequenceNumberNoNull, shardCheckpointStatus);
@@ -210,7 +227,7 @@ public AcknowledgementSet createAcknowledgmentSet(
210227
streamPartition.getPartitionKey(), sequenceNumberNoNull);
211228
ackCheckpointStatus.setAcknowledged(ShardCheckpointStatus.AcknowledgmentStatus.POSITIVE_ACK);
212229
} else {
213-
LOG.warn("Negative acknowledgment received for partition {} with sequence number {}",
230+
LOG.debug(NOISY, "Negative acknowledgment received for partition {} with sequence number {}",
214231
streamPartition.getPartitionKey(), sequenceNumberNoNull);
215232
ackCheckpointStatus.setAcknowledged(ShardCheckpointStatus.AcknowledgmentStatus.NEGATIVE_ACK);
216233
}
@@ -227,7 +244,7 @@ void updateOwnershipForAllShardPartitions() {
227244
sourceCoordinator.saveProgressStateForPartition(streamPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
228245
} catch (final PartitionUpdateException e) {
229246
LOG.warn(NOISY, "Failed to update progress state for shard {}, will stop tracking this shard as someone else owns it", streamPartition.getShardId());
230-
markPartitionForRemoval(streamPartition);
247+
partitionsToRemove.add(streamPartition);
231248
}
232249
}
233250
}
@@ -268,15 +285,27 @@ public void shutdown() {
268285
}
269286

270287
private void removePartitions() {
271-
partitionsToRemove.forEach(streamPartition -> {
272-
checkpoints.remove(streamPartition);
273-
ackStatuses.remove(streamPartition);
274-
});
275-
276-
partitionsToGiveUp.forEach(sourceCoordinator::giveUpPartition);
288+
lock.lock();
289+
try {
290+
partitionsToRemove.forEach(streamPartition -> {
291+
checkpoints.remove(streamPartition);
292+
ackStatuses.remove(streamPartition);
293+
});
294+
295+
partitionsToGiveUp.forEach(partition -> {
296+
try {
297+
sourceCoordinator.giveUpPartition(partition);
298+
LOG.info("Gave up partition for shard {}", partition.getShardId());
299+
} catch (final PartitionUpdateException e) {
300+
LOG.warn("Received exception giving up shard {}, this shard will be reprocessed once the ownership timeout expires.", partition.getShardId());
301+
}
302+
});
277303

278-
partitionsToRemove.clear();
279-
partitionsToGiveUp.clear();
304+
partitionsToRemove.clear();
305+
partitionsToGiveUp.clear();
306+
} finally {
307+
lock.unlock();
308+
}
280309
}
281310

282311
public void giveUpPartition(final StreamPartition streamPartition) {

data-prepper-plugins/dynamodb-source/src/main/java/org/opensearch/dataprepper/plugins/source/dynamodb/stream/ShardConsumer.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,13 @@ public void run() {
295295

296296
AcknowledgementSet acknowledgementSet = null;
297297
if (shardAcknowledgementManager != null) {
298-
acknowledgementSet = shardAcknowledgementManager.createAcknowledgmentSet(streamPartition, sequenceNumber, shardIterator == null);
298+
try {
299+
acknowledgementSet = shardAcknowledgementManager.createAcknowledgmentSet(streamPartition, sequenceNumber, shardIterator == null);
300+
} catch (final ShardNotTrackedException e) {
301+
LOG.warn(e.getMessage());
302+
break;
303+
}
304+
299305
if (shardIterator == null) {
300306
createdFinalAcknowledgmentSetForShard = true;
301307
}

data-prepper-plugins/dynamodb-source/src/test/java/org/opensearch/dataprepper/plugins/source/dynamodb/stream/ShardAcknowledgementManagerTest.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,6 @@ void failure_to_checkpoint_for_one_partition_stops_tracking_that_partition_and_c
343343
shardAcknowledgementManager.runMonitorAcknowledgmentLoop(stopWorkerConsumer);
344344
assertThat(shardAcknowledgementManager.isStillTrackingShard(secondPartition), equalTo(true));
345345

346-
verify(streamProgressState).setSequenceNumber("seq2");
347-
verify(sourceCoordinator).giveUpPartition(streamPartition);
348346
verify(secondStreamProgressState).setSequenceNumber("seq124");
349347
verify(sourceCoordinator).saveProgressStateForPartition(secondPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
350348
}
@@ -404,7 +402,6 @@ void failure_to_update_state_for_one_partition_stops_tracking_that_partition_and
404402
assertThat(shardAcknowledgementManager.isStillTrackingShard(secondPartition), equalTo(true));
405403
shardAcknowledgementManager.runMonitorAcknowledgmentLoop(stopWorkerConsumer);
406404

407-
verify(streamProgressState).setSequenceNumber("seq2");
408405
verify(secondStreamProgressState).setSequenceNumber("seq123");
409406
callback2_2.accept(true);
410407

@@ -416,9 +413,7 @@ void failure_to_update_state_for_one_partition_stops_tracking_that_partition_and
416413

417414
assertThat(shardAcknowledgementManager.isStillTrackingShard(secondPartition), equalTo(true));
418415

419-
verify(sourceCoordinator).giveUpPartition(streamPartition);
420416
verify(sourceCoordinator, times(3)).saveProgressStateForPartition(secondPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
421-
verify(sourceCoordinator, times(2)).saveProgressStateForPartition(streamPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
422-
417+
verify(sourceCoordinator, times(1)).saveProgressStateForPartition(streamPartition, dynamoDBSourceConfig.getShardAcknowledgmentTimeout());
423418
}
424419
}

0 commit comments

Comments
 (0)