Skip to content

Commit 2b2bb89

Browse files
committed
Don't cache tx producers after reset()
Producers were incorrectly returned to the cache after a `reset()`. **I will back-port; conflicts expected**
1 parent e9c5203 commit 2b2bb89

File tree

2 files changed

+66
-16
lines changed

2 files changed

+66
-16
lines changed

spring-kafka/src/main/java/org/springframework/kafka/core/DefaultKafkaProducerFactory.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,6 @@ public class DefaultKafkaProducerFactory<K, V> implements ProducerFactory<K, V>,
127127

128128
private final ThreadLocal<CloseSafeProducer<K, V>> threadBoundProducers = new ThreadLocal<>();
129129

130-
private final ThreadLocal<Integer> threadBoundProducerEpochs = new ThreadLocal<>();
131-
132130
private final AtomicInteger epoch = new AtomicInteger();
133131

134132
private final AtomicInteger clientIdCounter = new AtomicInteger();
@@ -391,25 +389,21 @@ public Producer<K, V> createProducer(@Nullable String txIdPrefixArg) {
391389
}
392390
if (this.producerPerThread) {
393391
CloseSafeProducer<K, V> tlProducer = this.threadBoundProducers.get();
394-
if (this.threadBoundProducerEpochs.get() == null) {
395-
this.threadBoundProducerEpochs.set(this.epoch.get());
396-
}
397-
if (tlProducer != null && this.epoch.get() != this.threadBoundProducerEpochs.get()) {
392+
if (tlProducer != null && this.epoch.get() != tlProducer.epoch) {
398393
closeThreadBoundProducer();
399394
tlProducer = null;
400395
}
401396
if (tlProducer == null) {
402397
tlProducer = new CloseSafeProducer<>(createKafkaProducer(), this::removeProducer,
403-
this.physicalCloseTimeout);
398+
this.physicalCloseTimeout, this.epoch);
404399
this.threadBoundProducers.set(tlProducer);
405-
this.threadBoundProducerEpochs.set(this.epoch.get());
406400
}
407401
return tlProducer;
408402
}
409403
synchronized (this) {
410404
if (this.producer == null) {
411405
this.producer = new CloseSafeProducer<>(createKafkaProducer(), this::removeProducer,
412-
this.physicalCloseTimeout);
406+
this.physicalCloseTimeout, this.epoch);
413407
}
414408
return this.producer;
415409
}
@@ -516,7 +510,8 @@ private CloseSafeProducer<K, V> doCreateTxProducer(String prefix, String suffix,
516510
newProducer = createRawProducer(newProducerConfigs);
517511
newProducer.initTransactions();
518512
return new CloseSafeProducer<>(newProducer, getCache(prefix), remover,
519-
(String) newProducerConfigs.get(ProducerConfig.TRANSACTIONAL_ID_CONFIG), this.physicalCloseTimeout);
513+
(String) newProducerConfigs.get(ProducerConfig.TRANSACTIONAL_ID_CONFIG), this.physicalCloseTimeout,
514+
this.epoch);
520515
}
521516

522517
protected Producer<K, V> createRawProducer(Map<String, Object> configs) {
@@ -585,37 +580,57 @@ protected static class CloseSafeProducer<K, V> implements Producer<K, V> {
585580

586581
private final Duration closeTimeout;
587582

583+
final int epoch; // NOSONAR
584+
585+
private final AtomicInteger factoryEpoch;
586+
588587
private volatile Exception producerFailed;
589588

590589
private volatile boolean closed;
591590

592591
CloseSafeProducer(Producer<K, V> delegate, Consumer<CloseSafeProducer<K, V>> removeProducer,
593592
Duration closeTimeout) {
594593

595-
this(delegate, null, removeProducer, null, closeTimeout);
594+
this(delegate, null, removeProducer, null, closeTimeout, new AtomicInteger());
595+
Assert.isTrue(!(delegate instanceof CloseSafeProducer), "Cannot double-wrap a producer");
596+
}
597+
598+
CloseSafeProducer(Producer<K, V> delegate, Consumer<CloseSafeProducer<K, V>> removeProducer,
599+
Duration closeTimeout, AtomicInteger epoch) {
600+
601+
this(delegate, null, removeProducer, null, closeTimeout, epoch);
596602
Assert.isTrue(!(delegate instanceof CloseSafeProducer), "Cannot double-wrap a producer");
597603
}
598604

599605
CloseSafeProducer(Producer<K, V> delegate, BlockingQueue<CloseSafeProducer<K, V>> cache,
600606
Duration closeTimeout) {
601-
this(delegate, cache, null, closeTimeout);
607+
this(delegate, cache, null, null, closeTimeout, new AtomicInteger());
602608
}
603609

604610
CloseSafeProducer(Producer<K, V> delegate, BlockingQueue<CloseSafeProducer<K, V>> cache,
605611
@Nullable Consumer<CloseSafeProducer<K, V>> removeConsumerProducer, Duration closeTimeout) {
606612

607-
this(delegate, cache, removeConsumerProducer, null, closeTimeout);
613+
this(delegate, cache, removeConsumerProducer, null, closeTimeout, new AtomicInteger());
608614
}
609615

610616
CloseSafeProducer(Producer<K, V> delegate, BlockingQueue<CloseSafeProducer<K, V>> cache,
611617
@Nullable Consumer<CloseSafeProducer<K, V>> removeProducer, @Nullable String txId,
612618
Duration closeTimeout) {
613619

620+
this(delegate, cache, removeProducer, txId, closeTimeout, new AtomicInteger());
621+
}
622+
623+
CloseSafeProducer(Producer<K, V> delegate, BlockingQueue<CloseSafeProducer<K, V>> cache,
624+
@Nullable Consumer<CloseSafeProducer<K, V>> removeProducer, @Nullable String txId,
625+
Duration closeTimeout, AtomicInteger epoch) {
626+
614627
this.delegate = delegate;
615628
this.cache = cache;
616629
this.removeProducer = removeProducer;
617630
this.txId = txId;
618631
this.closeTimeout = closeTimeout;
632+
this.epoch = epoch.get();
633+
this.factoryEpoch = epoch;
619634
LOGGER.debug(() -> "Created new Producer: " + this);
620635
}
621636

@@ -749,8 +764,8 @@ public void close(@Nullable Duration timeout) {
749764
else {
750765
if (this.cache != null && this.removeProducer == null) { // dedicated consumer producers are not cached
751766
synchronized (this) {
752-
if (!this.cache.contains(this)
753-
&& !this.cache.offer(this)) {
767+
if (this.epoch != this.factoryEpoch.get()
768+
|| (!this.cache.contains(this) && !this.cache.offer(this))) {
754769
this.closed = true;
755770
this.delegate.close(timeout);
756771
}

spring-kafka/src/test/java/org/springframework/kafka/core/DefaultKafkaProducerFactoryTests.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,42 @@ protected Producer createTransactionalProducer(String txIdPrefix) {
174174

175175
@Test
176176
@SuppressWarnings({ "rawtypes", "unchecked" })
177-
void testThreadLocal() {
177+
void dontReturnToCacheAfterReset() {
178+
final Producer producer = mock(Producer.class);
179+
ApplicationContext ctx = mock(ApplicationContext.class);
180+
DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory(new HashMap<>()) {
181+
182+
@Override
183+
protected Producer createRawProducer(Map configs) {
184+
return producer;
185+
}
186+
187+
};
188+
pf.setApplicationContext(ctx);
189+
pf.setTransactionIdPrefix("foo");
190+
Producer aProducer = pf.createProducer();
191+
assertThat(aProducer).isNotNull();
192+
aProducer.close();
193+
Producer bProducer = pf.createProducer();
194+
assertThat(bProducer).isSameAs(aProducer);
195+
bProducer.close();
196+
assertThat(KafkaTestUtils.getPropertyValue(pf, "producer")).isNull();
197+
Map<?, ?> cache = KafkaTestUtils.getPropertyValue(pf, "cache", Map.class);
198+
assertThat(cache.size()).isEqualTo(1);
199+
Queue queue = (Queue) cache.get("foo");
200+
assertThat(queue.size()).isEqualTo(1);
201+
bProducer = pf.createProducer();
202+
assertThat(bProducer).isSameAs(aProducer);
203+
assertThat(queue.size()).isEqualTo(0);
204+
pf.reset();
205+
bProducer.close();
206+
assertThat(queue.size()).isEqualTo(0);
207+
pf.destroy();
208+
}
209+
210+
@Test
211+
@SuppressWarnings({ "rawtypes", "unchecked" })
212+
void testThreadLocal() throws InterruptedException {
178213
final Producer producer = mock(Producer.class);
179214
DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory(new HashMap<>()) {
180215

0 commit comments

Comments
 (0)