Hello Kafka Developers, I hope this email finds you well.
This might be a silly question, but I had a question about the Kafka client code (v3.9.0) that is `madeReadProgressLastPoll` field in the `Selector` class and the mechanism described in its comment: "to prevent tight loops when memory is not available to read any more data.". First, let me walk you through my understanding of the code. The `Selector` class has the following field: ```java //indicates if the previous call to poll was able to make progress in reading already-buffered data. //this is used to prevent tight loops when memory is not available to read any more data private boolean madeReadProgressLastPoll = true; ``` At the beginning of `poll(long timeout)*`*, the value of `madeReadProgressLastPoll*`* from the previous poll is stored in a local variable, and the field is then reset to `false*`* within the `clear()` method. ```java @Override public void poll(long timeout) throws IOException { if (timeout < 0) throw new IllegalArgumentException("timeout should be >= 0"); boolean madeReadProgressLastCall = madeReadProgressLastPoll; clear(); // madeReadProgressLastPoll is set to false here // ... } ``` It appears that `madeReadProgressLastPoll` is set back to `true` if any read progress is made, or if there's no work to do. It seems it would only remain `false` if a read was attempted but failed because the `KafkaChannel` was muted(read() on KafkaChannel class), which aligns with "the out of memory scenario" in the comment. ```java // Change madeReadProgressLastPoll to true in poll(long timeout) @Override public void poll(long timeout) throws IOException { // ... if (numReadyKeys > 0 || !immediatelyConnectedKeys.isEmpty() || dataInBuffers) { // ... } else { madeReadProgressLastPoll = true; //no work is also "progress" } // ... } // Other Method in Selector class changing madeReadProgressLastPoll private void attemptRead(KafkaChannel channel, long nowNanos) throws IOException { // ... if (bytesReceived != 0) { madeReadProgressLastPoll = true; } // ... if (channel.isMuted()) { outOfMemory = true; // Indicates we can't read more due to buffer limits } else { // If we are not muted, we are considered to have made progress // since we are ready to read from the channel. madeReadProgressLastPoll = true; } } ``` Now, here is the part that I find confusing. The `timeout` for the NIOSelector's `select()` call is adjusted based on `madeReadProgressLastCall`: ```java @Override public void poll(long timeout) throws IOException { // ... if (!immediatelyConnectedKeys.isEmpty() || (madeReadProgressLastCall && dataInBuffers)) timeout = 0; // ... this.nioSelector.select(timeout); // ... } ``` Based on my reading, if the previous poll failed to make progress due to memory issues (madeReadProgressLastCall is false), the original timeout provided to the poll() method is used for the select() call. When I first saw the "prevent tight-loop" comment, I assumed that if memory were low, the logic might *increase* the poll timeout to slow down the polling frequency and give the consumer a chance to free up memory. However, the logic seems to do the opposite of what I expected for optimization. If the previous poll *did* make progress (madeReadProgressLastCall is true) and there's still data in the buffers (dataInBuffers is true), it sets timeout = 0. This *appears to be an optimization to reduce latency* by immediately processing already-buffered data without blocking in select(). This leads to my core question: *How exactly does this logic "prevent" a tight loop when memory is unavailable?* My analysis suggests it's more about optimizing for fast processing when progress *is* being made, rather than preventing a busy-wait loop when it's not. Am I missing another part of the mechanism? Thank you for your time and for your work on this great project. Best Regards, LEE