1
- use std:: task:: { Context , Poll } ;
1
+ use std:: {
2
+ sync:: {
3
+ atomic:: { AtomicUsize , Ordering } ,
4
+ Arc ,
5
+ } ,
6
+ task:: { Context , Poll } ,
7
+ time:: Duration ,
8
+ } ;
2
9
3
10
use bytes:: Bytes ;
4
11
use rdkafka:: {
5
12
error:: KafkaError ,
6
13
message:: OwnedHeaders ,
7
14
producer:: { FutureProducer , FutureRecord } ,
8
- util :: Timeout ,
15
+ types :: RDKafkaErrorCode ,
9
16
} ;
10
17
11
18
use crate :: { kafka:: KafkaStatisticsContext , sinks:: prelude:: * } ;
@@ -59,16 +66,38 @@ impl MetaDescriptive for KafkaRequest {
59
66
}
60
67
}
61
68
69
+ /// BlockedRecordState manages state for a record blocked from being enqueued on the producer.
70
+ struct BlockedRecordState {
71
+ records_blocked : Arc < AtomicUsize > ,
72
+ }
73
+
74
+ impl BlockedRecordState {
75
+ fn new ( records_blocked : Arc < AtomicUsize > ) -> Self {
76
+ records_blocked. fetch_add ( 1 , Ordering :: Relaxed ) ;
77
+ Self { records_blocked }
78
+ }
79
+ }
80
+
81
+ impl Drop for BlockedRecordState {
82
+ fn drop ( & mut self ) {
83
+ self . records_blocked . fetch_sub ( 1 , Ordering :: Relaxed ) ;
84
+ }
85
+ }
86
+
62
87
#[ derive( Clone ) ]
63
88
pub struct KafkaService {
64
89
kafka_producer : FutureProducer < KafkaStatisticsContext > ,
90
+
91
+ /// The number of records blocked from being enqueued on the producer.
92
+ records_blocked : Arc < AtomicUsize > ,
65
93
}
66
94
67
95
impl KafkaService {
68
- pub ( crate ) const fn new (
69
- kafka_producer : FutureProducer < KafkaStatisticsContext > ,
70
- ) -> KafkaService {
71
- KafkaService { kafka_producer }
96
+ pub ( crate ) fn new ( kafka_producer : FutureProducer < KafkaStatisticsContext > ) -> KafkaService {
97
+ KafkaService {
98
+ kafka_producer,
99
+ records_blocked : Arc :: new ( AtomicUsize :: new ( 0 ) ) ,
100
+ }
72
101
}
73
102
}
74
103
@@ -78,13 +107,21 @@ impl Service<KafkaRequest> for KafkaService {
78
107
type Future = BoxFuture < ' static , Result < Self :: Response , Self :: Error > > ;
79
108
80
109
fn poll_ready ( & mut self , _cx : & mut Context < ' _ > ) -> Poll < Result < ( ) , Self :: Error > > {
81
- Poll :: Ready ( Ok ( ( ) ) )
110
+ // The Kafka service is at capacity if any records are currently blocked from being enqueued
111
+ // on the producer.
112
+ if self . records_blocked . load ( Ordering :: Relaxed ) > 0 {
113
+ Poll :: Pending
114
+ } else {
115
+ Poll :: Ready ( Ok ( ( ) ) )
116
+ }
82
117
}
83
118
84
119
fn call ( & mut self , request : KafkaRequest ) -> Self :: Future {
85
120
let this = self . clone ( ) ;
86
121
87
122
Box :: pin ( async move {
123
+ let raw_byte_size =
124
+ request. body . len ( ) + request. metadata . key . as_ref ( ) . map_or ( 0 , |x| x. len ( ) ) ;
88
125
let event_byte_size = request
89
126
. request_metadata
90
127
. into_events_estimated_json_encoded_byte_size ( ) ;
@@ -101,17 +138,39 @@ impl Service<KafkaRequest> for KafkaService {
101
138
record = record. headers ( headers) ;
102
139
}
103
140
104
- // rdkafka will internally retry forever if the queue is full
105
- match this. kafka_producer . send ( record, Timeout :: Never ) . await {
106
- Ok ( ( _partition, _offset) ) => {
107
- let raw_byte_size =
108
- request. body . len ( ) + request. metadata . key . map_or ( 0 , |x| x. len ( ) ) ;
109
- Ok ( KafkaResponse {
110
- event_byte_size,
111
- raw_byte_size,
112
- } )
113
- }
114
- Err ( ( kafka_err, _original_record) ) => Err ( kafka_err) ,
141
+ // Manually poll [FutureProducer::send_result] instead of [FutureProducer::send] to track
142
+ // records that fail to be enqueued on the producer.
143
+ let mut blocked_state: Option < BlockedRecordState > = None ;
144
+ loop {
145
+ match this. kafka_producer . send_result ( record) {
146
+ // Record was successfully enqueued on the producer.
147
+ Ok ( fut) => {
148
+ // Drop the blocked state (if any), as the producer is no longer blocked.
149
+ drop ( blocked_state. take ( ) ) ;
150
+ return fut
151
+ . await
152
+ . expect ( "producer unexpectedly dropped" )
153
+ . map ( |_| KafkaResponse {
154
+ event_byte_size,
155
+ raw_byte_size,
156
+ } )
157
+ . map_err ( |( err, _) | err) ;
158
+ }
159
+ // Producer queue is full.
160
+ Err ( (
161
+ KafkaError :: MessageProduction ( RDKafkaErrorCode :: QueueFull ) ,
162
+ original_record,
163
+ ) ) => {
164
+ if blocked_state. is_none ( ) {
165
+ blocked_state =
166
+ Some ( BlockedRecordState :: new ( Arc :: clone ( & this. records_blocked ) ) ) ;
167
+ }
168
+ record = original_record;
169
+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
170
+ }
171
+ // A different error occurred.
172
+ Err ( ( err, _) ) => return Err ( err) ,
173
+ } ;
115
174
}
116
175
} )
117
176
}
0 commit comments