rkhachatryan commented on code in PR #29189:
URL: https://github.com/apache/flink/pull/29189#discussion_r4015931603


##########
flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/channel/ChannelStateFilteringHandler.java:
##########
@@ -133,7 +133,7 @@ public void filterAndRewrite(
                             + ". This gate is not a network input and should 
not have recovered buffers.");
         }
         gateHandler.filterAndRewrite(
-                oldSubtaskIndex, oldChannelIndex, sourceBuffer, 
outputSerializer);
+                oldSubtaskIndex, oldChannelIndex, sourceBuffer.retainBuffer(), 
outputSerializer);

Review Comment:
   **This breaks 
`GateFilterHandlerBufferOwnershipTest#testCloseRecyclesDeserializerHeldBufferAfterError`**
 (not updated in this PR).
   
   That test calls the *dispatcher* 5-arg overload with a buffer at refCnt 1 
and asserts `sourceBuffer.isRecycled()` after the `close()` chain. With the 
added retain: 1 → 2; the serializer throws *after* `vc.setNextBuffer`, so 
`sourceBufferOwnershipTransferred` is true and `GateFilterHandler`'s catch 
correctly skips recycling; then `close() → clear() → deserializer.clear()` 
brings it 2 → 1. `NetworkBuffer.isRecycled()` is `refCnt() == 0`, so the 
assertion fails.
   
   **Related: the two overloads now have opposite ownership contracts.** 
`GateFilterHandler.filterAndRewrite` (inner) *consumes* the caller's reference 
— recycles on pre-transfer throw, hands ownership to the deserializer 
otherwise. After this change the dispatcher *does not*; it adds its own. Same 
method name, same package, inverted semantics, and neither javadoc says so. The 
broken test above is exactly the failure mode this invites.
   
   **Alternative that avoids the asymmetry:** keep `buffer.retainBuffer()` in 
`recover()`, but hoist `segmentSerializerFor(...)` into a local *before* it, 
and make the dispatcher's two early throws recycle `sourceBuffer` — matching 
the inner contract. Both overloads then behave identically and the existing 
ownership test stays green. If you prefer the current shape, the test needs 
updating and both javadocs should state the contract explicitly.



##########
flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/channel/RecoveredChannelStateHandler.java:
##########
@@ -592,7 +592,7 @@ public void recover(
                         channelInfo.getGateIdx(),
                         oldSubtaskIndex,
                         channelInfo.getInputChannelIdx(),
-                        buffer.retainBuffer(),
+                        buffer,

Review Comment:
   Non-blocking, and pre-existing rather than introduced here: the same 
`free()`-under-live-buffer hazard survives on the **post-transfer** throw path.
   
   If `filterAndRewrite` throws after `vc.setNextBuffer` (e.g. a 
`DataOutputSerializer` IOException in `emitAggregated`), the deserializer still 
holds the reference, this `finally` goes 2 → 1, and `preFilterBufferInUse` 
stays `true`. In `SequentialChannelStateReaderImpl#readInputData` the *inner* 
try-with-resources closes `stateHandler` first, so `closeInternal()` calls 
`preFilterSegment.free()` while the deserializer's `NetworkBuffer` is still 
live; the outer `filteringHandler.close()` only clears it afterwards.
   
   Harmless today (the task is already failing and nothing reads the freed 
segment), but it is the same bug class this PR closes. Either swap the close 
order, or have `closeInternal()` refuse to free while `preFilterBufferInUse`. 
Fine as a follow-up.



##########
flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/channel/InputChannelRecoveredStateHandlerTest.java:
##########
@@ -351,6 +351,27 @@ void testPreFilterSegmentFreedOnClose() throws Exception {
         assertThat(filteringHandler.getPreFilterSegmentForTesting()).isNull();
     }
 
+    @Test
+    void testPreFilterBufferRecycledWhenFilterAndRewriteThrows() throws 
Exception {

Review Comment:
   Non-blocking: this covers the one branch that can't occur in production. The 
zero-length `GateFilterHandler[]` hits `gateIndex >= gateHandlers.length`, but 
`createFromContext` always sizes the array to `inputGates.length`, so 
`channelInfo.getGateIdx()` is never out of range in a real job.
   
   The two reachable pre-retain throw sites named in the PR description are 
untested: `segmentSerializerFor(...)` / `getMappedChannels(...)` failing during 
argument evaluation, and `gateHandler == null` (reachable — `createGateHandler` 
returns `null` for a gate with no virtual channels). A non-empty array holding 
a `null` entry would exercise the actual production path.



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

Reply via email to