HyukjinKwon commented on code in PR #59285:
URL: https://github.com/apache/spark/pull/59285#discussion_r4235991793


##########
connector/kafka-0-10-sql/src/test/scala/org/apache/spark/sql/kafka010/consumer/KafkaDataConsumerSuite.scala:
##########
@@ -101,6 +101,39 @@ class KafkaDataConsumerSuite
     assert(e.getCause === cause)
   }
 
+  test("reacquire consumer before recovering from an out-of-range offset") {
+    val testTopic = "out-of-range-recovery-topic-" + Random.nextInt()
+    val testTopicPartition = new TopicPartition(testTopic, 0)
+    testUtils.createTopic(testTopic, 1)
+    testUtils.sendMessages(Seq(new RecordBuilder(testTopic, "value").build()))
+
+    // Closing a consumer as soon as it is returned deterministically 
reproduces an eviction
+    // racing with the out-of-range recovery path.
+    val closingConsumerPool = new InternalKafkaConsumerPool(new SparkConf()) {
+      override def returnObject(consumer: InternalKafkaConsumer): Unit = {
+        super.returnObject(consumer)
+        reset()
+      }
+    }
+    val consumer = new KafkaDataConsumer(
+      testTopicPartition,
+      getKafkaParams(),
+      closingConsumerPool,
+      fetchedDataPool)
+
+    try {
+      val originalConsumer = consumer.getOrRetrieveConsumer()
+      // The only record is at offset 0, so requesting offset 1 triggers 
out-of-range recovery.
+      val record = consumer.get(1, 2, 1000, failOnDataLoss = false)

Review Comment:
   **Nit (P3):** This case only reaches the no-overlap branch. With the single 
record at offset 0, `get(1, 2, ...)` hits `OffsetOutOfRangeException`, 
`getEarliestAvailableOffsetBetween` sees `[0, 1)` and returns `UNKNOWN_OFFSET`, 
and the loop exits without another `fetchRecord`. So nothing checks that the 
fetches after the probe use the reacquired consumer. For example, 
`getEarliestAvailableOffsetBetween(getOrRetrieveConsumer(), ...)` with 
`consumer` left as a `val` would still pass both assertions. It would then hit 
the closed consumer as soon as recovery resumes at `range.earliest`, the usual 
retention case.
   
   Could you add a case where recovery resumes inside `[offset, untilOffset)`? 
For example, produce a few records, delete the earliest ones through the admin 
client so the requested offset falls below the log start offset, then assert 
that `get()` returns the record at the new earliest offset. One catch: the 
suite's `getKafkaParams()` uses `auto.offset.reset=earliest`, which silently 
resets instead of throwing. The new case needs `none`, as 
`kafkaParamsForExecutors` sets it, so the out-of-range error reaches the 
handler.
   
   **Verification:**
   - **Regression:** With a probe-only variant of the fix (a reacquired 
consumer passed only to getEarliestAvailableOffsetBetween while later fetches 
use the released one), the resumable-recovery case fails with the 
closed-consumer error. With the PR's rebinding of the loop's consumer, it 
passes.
   - **Behavior:** The resumable case actually enters the out-of-range handler, 
which releases the consumer to the closing pool, and then returns the record at 
the new earliest available offset rather than null. The existing no-overlap 
case still returns null with a reacquired consumer.



-- 
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