@@ -19,7 +19,7 @@ import kotlin.time.toJavaDuration
19
19
import kotlinx.coroutines.CoroutineScope
20
20
import kotlinx.coroutines.CoroutineStart.LAZY
21
21
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
22
- import kotlinx.coroutines.Dispatchers.Default
22
+ import kotlinx.coroutines.DelicateCoroutinesApi
23
23
import kotlinx.coroutines.ExperimentalCoroutinesApi
24
24
import kotlinx.coroutines.InternalCoroutinesApi
25
25
import kotlinx.coroutines.cancelAndJoin
@@ -44,6 +44,7 @@ import org.slf4j.Logger
44
44
import org.slf4j.LoggerFactory
45
45
import kotlin.coroutines.CoroutineContext
46
46
import kotlin.time.Duration
47
+ import kotlin.time.Duration.Companion.milliseconds
47
48
48
49
private val logger: Logger =
49
50
LoggerFactory .getLogger(EventLoop ::class .java)
@@ -83,7 +84,7 @@ internal class EventLoop<K, V>(
83
84
channel.consumeAsFlow()
84
85
.onStart {
85
86
if (topicNames.isNotEmpty()) subscribe(topicNames)
86
- withContext(scope.coroutineContext) { poll() }
87
+ schedulePoll()
87
88
commitManager.start()
88
89
}.onCompletion {
89
90
commitBatchSignal.close()
@@ -120,8 +121,20 @@ internal class EventLoop<K, V>(
120
121
return pausedNow
121
122
}
122
123
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
+
123
135
@ConsumerThread
124
136
private fun poll () {
137
+ checkConsumerThread(" poll" )
125
138
try {
126
139
runCommitIfRequired(false )
127
140
@@ -164,32 +177,35 @@ internal class EventLoop<K, V>(
164
177
ConsumerRecords .empty()
165
178
}
166
179
167
- if (! records.isEmpty) {
180
+ if (records.isEmpty) {
181
+ schedulePoll()
182
+ } else {
168
183
if (settings.maxDeferredCommits > 0 ) {
169
184
commitBatch.addUncommitted(records)
170
185
}
171
186
logger.debug(" Attempting to send ${records.count()} records to Channel" )
172
187
channel.trySend(records)
173
- .onSuccess { poll () }
188
+ .onSuccess { schedulePoll () }
174
189
.onClosed { error -> logger.error(" Channel closed when trying to send records." , error) }
175
190
.onFailure { error ->
176
191
if (error != null ) {
177
192
logger.error(" Channel send failed when trying to send records." , error)
178
193
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()
190
208
}
191
- isPolling.set(true )
192
- poll()
193
209
}
194
210
}
195
211
}
@@ -567,7 +583,7 @@ private annotation class ConsumerThread
567
583
private const val DEBUG : Boolean = true
568
584
569
585
private fun checkConsumerThread (msg : String ): Unit =
570
- if (DEBUG ) require (
586
+ if (DEBUG ) check (
571
587
Thread .currentThread().name.startsWith(" kotlin-kafka-" )
572
588
) { " $msg => should run on kotlin-kafka thread, but found ${Thread .currentThread().name} " }
573
589
else Unit
0 commit comments