epugh commented on code in PR #4831:
URL: https://github.com/apache/solr/pull/4831#discussion_r3902931306


##########
solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/PartitionManager.java:
##########
@@ -73,50 +107,74 @@ public PartitionWork getPartitionWork(TopicPartition 
partition) {
         });
   }
 
-  public void checkOffsetUpdates() throws Throwable {
+  public void checkOffsetsAndUpdate() throws Throwable {
     for (TopicPartition partition : partitionWorkMap.keySet()) {
-      checkForOffsetUpdates(partition);
+      checkOffsetsAndUpdate(partition);
     }
   }
 
-  void checkForOffsetUpdates(TopicPartition partition) throws Throwable {
-    synchronized (partition) {
-      PartitionWork work;
-      if ((work = partitionWorkMap.get(partition)) != null) {
-        WorkUnit workUnit = work.partitionQueue.peek();
-        if (workUnit != null) {
-          boolean allFuturesDone = true;
-          for (Future<?> future : workUnit.workItems) {
-            if (!future.isDone()) {
-              if (log.isTraceEnabled()) {
-                log.trace("Future for update is not done topic={}", 
partition.topic());
-              }
-              allFuturesDone = false;
-              break;
-            }
-
-            try {
-              future.get();
-            } catch (InterruptedException e) {
-              log.error("Error updating offset for partition: {}", partition, 
e);
-              throw e;
-            } catch (ExecutionException e) {
-              log.error("Error updating offset for partition: {}", partition, 
e);
-              throw e.getCause();
-            }
-
-            if (log.isTraceEnabled()) {
-              log.trace("Future for update is done topic={}", 
partition.topic());
-            }
+  void checkOffsetsAndUpdate(TopicPartition partition) throws Throwable {
+    // can't synchronize on the argument (equal but distinct object for 
different threads)
+    // sync on the PartitionWork instead, which is unique per partition and 
shared by all threads
+    // that work on that partition.
+    final PartitionWork partitionWork = partitionWorkMap.get(partition);
+    if (partitionWork == null) {
+      // normally impossible because consumer should always call 
#getPartitionWork first

Review Comment:
   is this worth an assert?



##########
solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/PartitionManager.java:
##########
@@ -73,50 +107,74 @@ public PartitionWork getPartitionWork(TopicPartition 
partition) {
         });
   }
 
-  public void checkOffsetUpdates() throws Throwable {
+  public void checkOffsetsAndUpdate() throws Throwable {
     for (TopicPartition partition : partitionWorkMap.keySet()) {
-      checkForOffsetUpdates(partition);
+      checkOffsetsAndUpdate(partition);
     }
   }
 
-  void checkForOffsetUpdates(TopicPartition partition) throws Throwable {
-    synchronized (partition) {
-      PartitionWork work;
-      if ((work = partitionWorkMap.get(partition)) != null) {
-        WorkUnit workUnit = work.partitionQueue.peek();
-        if (workUnit != null) {
-          boolean allFuturesDone = true;
-          for (Future<?> future : workUnit.workItems) {
-            if (!future.isDone()) {
-              if (log.isTraceEnabled()) {
-                log.trace("Future for update is not done topic={}", 
partition.topic());
-              }
-              allFuturesDone = false;
-              break;
-            }
-
-            try {
-              future.get();
-            } catch (InterruptedException e) {
-              log.error("Error updating offset for partition: {}", partition, 
e);
-              throw e;
-            } catch (ExecutionException e) {
-              log.error("Error updating offset for partition: {}", partition, 
e);
-              throw e.getCause();
-            }
-
-            if (log.isTraceEnabled()) {
-              log.trace("Future for update is done topic={}", 
partition.topic());
-            }
+  void checkOffsetsAndUpdate(TopicPartition partition) throws Throwable {
+    // can't synchronize on the argument (equal but distinct object for 
different threads)
+    // sync on the PartitionWork instead, which is unique per partition and 
shared by all threads
+    // that work on that partition.
+    final PartitionWork partitionWork = partitionWorkMap.get(partition);
+    if (partitionWork == null) {
+      // normally impossible because consumer should always call 
#getPartitionWork first
+      // which creates the instance if it doesn't exist.
+      return;
+    }
+    synchronized (partitionWork) {
+      // remove every completed work unit at the head of the queue, stopping 
at the first one
+      // that is still in flight - a work unit's offset may only be committed 
once all of the
+      // work units before it have been committed too.
+      long committableOffset = -1;
+      WorkUnit workUnit;
+      try {
+        while ((workUnit = partitionWork.partitionQueue.peek()) != null) {
+          if (!isComplete(workUnit, partition)) {
+            break;
           }
+          // remove completed unit
+          partitionWork.partitionQueue.poll();
+          committableOffset = workUnit.nextOffset;
+        }
+      } finally {
+        // commit whatever progress was already verified in this drain, even 
if a later

Review Comment:
   I had claude code review, so take this with a grain of salt, but apparently 
you can have a double failure?  IDK -->
   
   This `finally` block can mask the real failure. If an earlier unit in this 
drain already completed (`committableOffset >= 0`) but a *later* unit's 
`isComplete()` throws (propagating a failed work item), the `finally` still 
calls `updateOffset(partition, committableOffset)` → 
`consumer.commitSync(...)`, which isn't guarded by try/catch. Per JLS 14.20.2, 
if that `commitSync` call itself throws (e.g. 
`CommitFailedException`/`TimeoutException` during a rebalance), it discards the 
original work-item exception propagating from the `try` block — only the 
secondary commit failure reaches `KafkaCrossDcConsumer`, and the real root 
cause of the failed work item is lost from the logs.
   >
   > Doesn't reintroduce the data-loss bug this PR fixes (the offset being 
committed here is still legitimately earned), but on a double-failure it 
silently erases the diagnostic trail. Might be worth catching the `commitSync` 
failure here and attaching the original via `addSuppressed`, or logging the 
original before it's discarded.



##########
solr/cross-dc-manager/src/java/org/apache/solr/crossdc/manager/consumer/PartitionManager.java:
##########
@@ -41,16 +39,52 @@ public class PartitionManager {
       new ConcurrentHashMap<>();
   private final KafkaConsumer<String, MirroredSolrRequest<?>> consumer;
 
-  static class PartitionWork {
+  @VisibleForTesting
+  public static class PartitionWork {

Review Comment:
   I think my first thought is shouldn't Kakfa be providing this capability?   
I haven't done a lot with Kafka, so my knowledge is fuzzy, but I wold have 
assumed that consuming this data is soemthing that Kafka client would ensure we 
don't have bad side effects?   Or is kafka more limited and there fofre we have 
to build this logic?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to