Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1181,16 +1181,19 @@ private void ensureValidRecordSize(int size) {
* calls made since the previous {@link #beginTransaction()} are completed before the commit.
* </p>
* <p>
* <b>Important:</b> This method should not be used within the callback provided to
* {@link #send(ProducerRecord, Callback)}. Invoking <code>flush()</code> in this context will cause a deadlock.
* <b>Important:</b> This method must not be called from within the callback provided to
* {@link #send(ProducerRecord, Callback)}. Invoking <code>flush()</code> in this context will result in a
* {@link KafkaException} being thrown, as it will cause a deadlock.
* </p>
*
* @throws InterruptException If the thread is interrupted while blocked
* @throws KafkaException If the method is invoked inside a {@link #send(ProducerRecord, Callback)} callback
*/
@Override
public void flush() {
if (Thread.currentThread() == this.ioThread) {
log.error("KafkaProducer.flush() invocation inside a callback will cause a deadlock.");
log.error("KafkaProducer.flush() invocation inside a callback is not permitted because it may lead to deadlock.");
throw new KafkaException("KafkaProducer.flush() invocation inside a callback is not permitted because it may lead to deadlock.");
}

log.trace("Flushing accumulated records in producer.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,34 @@ public void testCallbackAndInterceptorHandleError() {
}
}

@Test
public void shouldNotInvokeFlushInCallback() {
Map<String, Object> configs = new HashMap<>();
configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9000");
// only test in idempotence disabled producer for simplicity
configs.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this setting relevant? Having it in this test makes me think that the test would only work with it, but I do not believe that to be the case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This setting aims to simplify our test case. Here's my understanding—please correct me if I'm wrong:
Since idempotence is enabled by default, if we don't explicitly set it to false, we would need to prepare additional data to go through the idempotence flow. I believe this is unnecessary for this test case, so I will add a comment to clarify. WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, I see. It prevents needing to do the init producer id stuff. Makes sense to me.


Time time = new MockTime(1);
MetadataResponse initialUpdateResponse = RequestTestUtils.metadataUpdateWith(1, singletonMap("topic", 1));
ProducerMetadata metadata = newMetadata(0, 0, Long.MAX_VALUE);

MockClient client = new MockClient(time, metadata);
client.updateMetadata(initialUpdateResponse);
AtomicReference<KafkaException> kafkaException = new AtomicReference<>();

try (Producer<String, String> producer = kafkaProducer(configs, new StringSerializer(),
new StringSerializer(), metadata, client, null, time)) {
producer.send(
new ProducerRecord<>("topic", "value"),
(recordMetadata, exception) -> kafkaException.set(assertThrows(KafkaException.class, producer::flush))
);
}

assertNotNull(kafkaException.get());
assertEquals("KafkaProducer.flush() invocation inside a callback is not permitted because it may lead to deadlock.",
kafkaException.get().getMessage());
}

@Test
public void negativePartitionShouldThrow() {
Map<String, Object> configs = new HashMap<>();
Expand Down
10 changes: 10 additions & 0 deletions docs/upgrade.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@

<script id="upgrade-template" type="text/x-handlebars-template">

<h4><a id="upgrade_4_1_0" href="#upgrade_4_1_0">Upgrading to 4.1.0 from any version 0.8.x through 4.0.x</a></h4>
<h5><a id="upgrade_410_notable" href="#upgrade_410_notable">Notable changes in 4.1.0</a></h5>
<ul>
<li><b>Producer</b>
<ul>
<li>The <code>flush</code> method now detects potential deadlocks and prohibits its use inside a callback. This change prevents unintended blocking behavior, which was a known risk in earlier versions.
</li>
</ul>
</li>
</ul>
<h4><a id="upgrade_4_0_0" href="#upgrade_4_0_0">Upgrading to 4.0.0 from any version 0.8.x through 3.9.x</a></h4>
<h5><a id="upgrade_400_notable" href="#upgrade_400_notable">Notable changes in 4.0.0</a></h5>
<ul>
Expand Down