Skip to content

KAFKA-20302: Receive buffers allocated from MemoryPool may not be released if request is invalid#21740

Open
edoardocomar wants to merge 6 commits intoapache:trunkfrom
edoardocomar:KAFKA-20302
Open

KAFKA-20302: Receive buffers allocated from MemoryPool may not be released if request is invalid#21740
edoardocomar wants to merge 6 commits intoapache:trunkfrom
edoardocomar:KAFKA-20302

Conversation

@edoardocomar
Copy link
Contributor

@edoardocomar edoardocomar commented Mar 13, 2026

If an exception is thrown within
kafka.network.Processor#processCompletedReceives close the receive
(return the buffer to the memory pool) if it has not been returned
already. Buffer may have been returned when successfully creating the
RequestChannel.Request if the api did not require DelayedAllocation

…eased if request is invalid

Return the buffer to the pool if Execptions are caught within processCompletedReceives()
taking care of not returning the buffer twice as it can be returned in the
RequestChannel.Request constructor.
@github-actions github-actions bot added triage PRs from the community core Kafka Broker small Small PRs labels Mar 13, 2026
@github-actions github-actions bot added clients and removed small Small PRs labels Mar 13, 2026
@github-actions github-actions bot removed the triage PRs from the community label Mar 14, 2026
@edoardocomar
Copy link
Contributor Author

thanks for your comments @akhileshchg

@showuon showuon self-assigned this Mar 17, 2026
// note that even though we got an exception, we can assume that receive.source is valid.
// Issues with constructing a valid receive object were handled earlier
case e: Throwable =>
if (header == null || req == null || header.apiKey.requiresDelayedAllocation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

  1. Normally, when will the buffer gets released if the header header.apiKey.requiresDelayedAllocation == true?
  2. In this change, what happen if header.apiKey.requiresDelayedAllocation == false?

Copy link
Contributor Author

@edoardocomar edoardocomar Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the buffer gets released if requiresDelayedAllocation is false
after being parsed inside the constructor of RequestChannel.Request
invoked on line 1027 of this PR version of SocketServer.scala
https://github.com/edoardocomar/kafka/blob/KAFKA-20302/core/src/main/scala/kafka/network/SocketServer.scala#L1027-L1028

but it only gets released there if (!header.apiKey.requiresDelayedAllocation)
see comment

    //most request types are parsed entirely into objects at this point. for those we can release the underlying buffer.
    //some (like produce, or any time the schema contains fields of types BYTES or NULLABLE_BYTES) retain a reference
    //to the buffer. for those requests we cannot release the buffer early, but only when request processing is done.

https://github.com/edoardocomar/kafka/blob/KAFKA-20302/core/src/main/scala/kafka/network/RequestChannel.scala#L86-L105

Copy link
Contributor Author

@edoardocomar edoardocomar Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this PR if header.apiKey.requiresDelayedAllocation == false and in line 1054 we called close inconditionally, nothing wrong happens as NetworkReceive.close() is a no-op if invoked a 2nd time.

But checking for the condition in the catch makes the code more informative, IMHO

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. But could you point me where we will release the buffer when header.apiKey.requiresDelayedAllocation == true? It looks like we will close it when requestHandler completes the handling, is it correct? If so, then what will happen if the buffer is released in L1054 here, but then the request got processed with empty buffer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi I see if the request has been successfully enqueued,
https://github.com/edoardocomar/kafka/blob/KAFKA-20302/core/src/main/scala/kafka/network/SocketServer.scala#L1040
then the buffer it will be eventually released at the end of KafkaRequesthandler.run
https://github.com/edoardocomar/kafka/blob/KAFKA-20302/core/src/main/scala/kafka/server/KafkaRequestHandler.scala#L171
but it will be accessed during the handling and it will be null

so we should not release in SocketServer if successfully enqueued into the requestChannel

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @showuon I have restricted the release of the buffer in the catch to if (header == null || req == null)

as if successfully enqueued it will be released by the KafkaRequesthandler.

Copy link
Contributor Author

@edoardocomar edoardocomar Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with a manual test where I was throwing a random simulated IllegalStateException
at the end of handleChannelMuteEvent
and the pool balance was maintained.
I am unable to automate this test though - would need a mock KafkaChannel.handleChannelMuteEvent

i was wondering whether to move channel.channelMetadataRegistry.registerClientInformation
after the enqueuing of the request

but API_VERSIONS's buffer has been deallocated already as it's not a delayed schema (for now)

// be sure to decrease connection count and drop any in-flight responses
debug(s"Disconnecting expired channel: $channel : $header")
close(channel.id)
receive.close() // return buffer to memory pool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check header.apiKey.requiresDelayedAllocation here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no there we should always close, as no RequestChannel.Request has been created there and no release of the buffer has occurred

// }
// }
// return -1;
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep the reason why we use reflection here. But I'm not sure if we need the example code in the comments here. How about we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree I wanted some feedback on this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively we could make the memorypool accessor public with a comment // accessible for testing only ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rewritten the test comment

@edoardocomar
Copy link
Contributor Author

thanks @showuon for your comments. I believe I have addressed them. can you please re-review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clients core Kafka Broker

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants