@@ -152,6 +152,8 @@ public class DefaultKafkaProducerFactory<K, V> extends KafkaResourceFactory
152152
153153 private String clientIdPrefix ;
154154
155+ private long maxAge ;
156+
155157 private volatile CloseSafeProducer <K , V > producer ;
156158
157159 /**
@@ -349,6 +351,16 @@ public List<ProducerPostProcessor<K, V>> getPostProcessors() {
349351 return Collections .unmodifiableList (this .postProcessors );
350352 }
351353
354+ /**
355+ * Set the maximum age for a producer; useful when using transactions and the broker
356+ * might expire a {@code transactional.id} due to inactivity.
357+ * @param maxAge the maxAge to set
358+ * @since 2.5.8
359+ */
360+ public void setMaxAge (Duration maxAge ) {
361+ this .maxAge = maxAge .toMillis ();
362+ }
363+
352364 /**
353365 * Add a listener.
354366 * @param listener the listener.
@@ -498,7 +510,8 @@ private Producer<K, V> doCreateProducer(@Nullable String txIdPrefix) {
498510 if (this .threadBoundProducerEpochs .get () == null ) {
499511 this .threadBoundProducerEpochs .set (this .epoch .get ());
500512 }
501- if (tlProducer != null && this .epoch .get () != this .threadBoundProducerEpochs .get ()) {
513+ if (tlProducer != null
514+ && (this .epoch .get () != this .threadBoundProducerEpochs .get () || expire (tlProducer ))) {
502515 closeThreadBoundProducer ();
503516 tlProducer = null ;
504517 }
@@ -514,6 +527,9 @@ private Producer<K, V> doCreateProducer(@Nullable String txIdPrefix) {
514527 return tlProducer ;
515528 }
516529 synchronized (this ) {
530+ if (this .producer != null && expire (this .producer )) {
531+ this .producer = null ;
532+ }
517533 if (this .producer == null ) {
518534 this .producer = new CloseSafeProducer <>(createKafkaProducer (), this ::removeProducer ,
519535 this .physicalCloseTimeout , this .beanName );
@@ -552,14 +568,15 @@ protected Producer<K, V> createTransactionalProducerForPartition(String txIdPref
552568 }
553569 else {
554570 synchronized (this .consumerProducers ) {
555- if (!this .consumerProducers .containsKey (suffix )) {
571+ CloseSafeProducer <K , V > consumerProducer = this .consumerProducers .get (suffix );
572+ if (consumerProducer == null || expire (consumerProducer )) {
556573 CloseSafeProducer <K , V > newProducer = doCreateTxProducer (txIdPrefix , suffix ,
557574 this ::removeConsumerProducer );
558575 this .consumerProducers .put (suffix , newProducer );
559576 return newProducer ;
560577 }
561578 else {
562- return this . consumerProducers . get ( suffix ) ;
579+ return consumerProducer ;
563580 }
564581 }
565582 }
@@ -616,7 +633,15 @@ protected Producer<K, V> createTransactionalProducer() {
616633 protected Producer <K , V > createTransactionalProducer (String txIdPrefix ) {
617634 BlockingQueue <CloseSafeProducer <K , V >> queue = getCache (txIdPrefix );
618635 Assert .notNull (queue , () -> "No cache found for " + txIdPrefix );
619- Producer <K , V > cachedProducer = queue .poll ();
636+ CloseSafeProducer <K , V > cachedProducer = queue .poll ();
637+ while (cachedProducer != null ) {
638+ if (expire (cachedProducer )) {
639+ cachedProducer = queue .poll ();
640+ }
641+ else {
642+ break ;
643+ }
644+ }
620645 if (cachedProducer == null ) {
621646 return doCreateTxProducer (txIdPrefix , "" + this .transactionIdSuffix .getAndIncrement (), this ::cacheReturner );
622647 }
@@ -625,6 +650,14 @@ protected Producer<K, V> createTransactionalProducer(String txIdPrefix) {
625650 }
626651 }
627652
653+ private boolean expire (CloseSafeProducer <K , V > producer ) {
654+ boolean expired = this .maxAge > 0 && System .currentTimeMillis () - producer .created > this .maxAge ;
655+ if (expired ) {
656+ producer .closeDelegate (this .physicalCloseTimeout , this .listeners );
657+ }
658+ return expired ;
659+ }
660+
628661 boolean cacheReturner (CloseSafeProducer <K , V > producerToRemove , Duration timeout ) {
629662 if (producerToRemove .closed ) {
630663 producerToRemove .closeDelegate (timeout , this .listeners );
@@ -727,6 +760,8 @@ protected static class CloseSafeProducer<K, V> implements Producer<K, V> {
727760
728761 final String txIdPrefix ; // NOSONAR
729762
763+ final long created ;
764+
730765 private final Duration closeTimeout ;
731766
732767 final String clientId ; // NOSONAR
@@ -761,6 +796,7 @@ protected static class CloseSafeProducer<K, V> implements Producer<K, V> {
761796 id = "unknown" ;
762797 }
763798 this .clientId = factoryName + "." + id ;
799+ this .created = System .currentTimeMillis ();
764800 LOGGER .debug (() -> "Created new Producer: " + this );
765801 }
766802
0 commit comments