@@ -19,7 +19,7 @@ import kotlin.time.toJavaDuration
1919import kotlinx.coroutines.CoroutineScope
2020import kotlinx.coroutines.CoroutineStart.LAZY
2121import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
22- import kotlinx.coroutines.Dispatchers.Default
22+ import kotlinx.coroutines.DelicateCoroutinesApi
2323import kotlinx.coroutines.ExperimentalCoroutinesApi
2424import kotlinx.coroutines.InternalCoroutinesApi
2525import kotlinx.coroutines.cancelAndJoin
@@ -44,6 +44,7 @@ import org.slf4j.Logger
4444import org.slf4j.LoggerFactory
4545import kotlin.coroutines.CoroutineContext
4646import kotlin.time.Duration
47+ import kotlin.time.Duration.Companion.milliseconds
4748
4849private val logger: Logger =
4950 LoggerFactory .getLogger(EventLoop ::class .java)
@@ -83,7 +84,7 @@ internal class EventLoop<K, V>(
8384 channel.consumeAsFlow()
8485 .onStart {
8586 if (topicNames.isNotEmpty()) subscribe(topicNames)
86- withContext(scope.coroutineContext) { poll() }
87+ schedulePoll()
8788 commitManager.start()
8889 }.onCompletion {
8990 commitBatchSignal.close()
@@ -120,8 +121,20 @@ internal class EventLoop<K, V>(
120121 return pausedNow
121122 }
122123
124+ private val scheduled = AtomicBoolean (false )
125+ private fun schedulePoll () {
126+ if (scheduled.compareAndSet(false , true )) {
127+ scope.launch {
128+ scheduled.set(false )
129+ @OptIn(DelicateCoroutinesApi ::class )
130+ if (! channel.isClosedForSend) poll()
131+ }
132+ }
133+ }
134+
123135 @ConsumerThread
124136 private fun poll () {
137+ checkConsumerThread(" poll" )
125138 try {
126139 runCommitIfRequired(false )
127140
@@ -164,32 +177,35 @@ internal class EventLoop<K, V>(
164177 ConsumerRecords .empty()
165178 }
166179
167- if (! records.isEmpty) {
180+ if (records.isEmpty) {
181+ schedulePoll()
182+ } else {
168183 if (settings.maxDeferredCommits > 0 ) {
169184 commitBatch.addUncommitted(records)
170185 }
171186 logger.debug(" Attempting to send ${records.count()} records to Channel" )
172187 channel.trySend(records)
173- .onSuccess { poll () }
188+ .onSuccess { schedulePoll () }
174189 .onClosed { error -> logger.error(" Channel closed when trying to send records." , error) }
175190 .onFailure { error ->
176191 if (error != null ) {
177192 logger.error(" Channel send failed when trying to send records." , error)
178193 closeChannel(error)
179- } else logger.debug(" Back-pressuring kafka consumer. Might pause KafkaConsumer on next poll tick." )
180-
181- isPolling.set(false )
182-
183- scope.launch(outerContext) {
184- /* Send the records down,
185- * when send returns we attempt to send and empty set of records down to test the backpressure.
186- * If our "backpressure test" returns we start requesting/polling again. */
187- channel.send(records)
188- if (isPaused.get()) {
189- consumer.wakeup()
194+ } else {
195+ logger.debug(" Back-pressuring kafka consumer. Might pause KafkaConsumer on next poll tick." )
196+
197+ isPolling.set(false )
198+ scope.launch(outerContext) {
199+ /* Send the records down,
200+ * when send returns we attempt to send and empty set of records down to test the backpressure.
201+ * If our "backpressure test" returns we start requesting/polling again. */
202+ channel.send(records)
203+ if (isPaused.get()) {
204+ consumer.wakeup()
205+ }
206+ isPolling.set(true )
207+ schedulePoll()
190208 }
191- isPolling.set(true )
192- poll()
193209 }
194210 }
195211 }
@@ -567,7 +583,7 @@ private annotation class ConsumerThread
567583private const val DEBUG : Boolean = true
568584
569585private fun checkConsumerThread (msg : String ): Unit =
570- if (DEBUG ) require (
586+ if (DEBUG ) check (
571587 Thread .currentThread().name.startsWith(" kotlin-kafka-" )
572588 ) { " $msg => should run on kotlin-kafka thread, but found ${Thread .currentThread().name} " }
573589 else Unit
0 commit comments