@@ -61,6 +61,12 @@ extern "C" {
61
61
62
62
#define INVALID_CLOSED_SLOT -1
63
63
64
+ /*
65
+ TCP poll interval is specified in terms of the TCP coarse timer interval, which is called twice a second
66
+ https://github.com/espressif/esp-lwip/blob/2acf959a2bb559313cd2bf9306c24612ba3d0e19/src/core/tcp.c#L1895
67
+ */
68
+ #define CONFIG_ASYNC_TCP_POLL_TIMER 1
69
+
64
70
/*
65
71
* TCP/IP Event Task
66
72
* */
@@ -139,16 +145,67 @@ static inline bool _init_async_event_queue() {
139
145
return true ;
140
146
}
141
147
142
- static inline bool _send_async_event (lwip_event_packet_t ** e) {
143
- return _async_queue && xQueueSend (_async_queue, e, portMAX_DELAY ) == pdPASS;
148
+ static inline bool _send_async_event (lwip_event_packet_t ** e, TickType_t wait = portMAX_DELAY ) {
149
+ return _async_queue && xQueueSend (_async_queue, e, wait ) == pdPASS;
144
150
}
145
151
146
- static inline bool _prepend_async_event (lwip_event_packet_t ** e) {
147
- return _async_queue && xQueueSendToFront (_async_queue, e, portMAX_DELAY ) == pdPASS;
152
+ static inline bool _prepend_async_event (lwip_event_packet_t ** e, TickType_t wait = portMAX_DELAY ) {
153
+ return _async_queue && xQueueSendToFront (_async_queue, e, wait ) == pdPASS;
148
154
}
149
155
150
156
static inline bool _get_async_event (lwip_event_packet_t ** e) {
151
- return _async_queue && xQueueReceive (_async_queue, e, portMAX_DELAY) == pdPASS;
157
+ if (!_async_queue) {
158
+ return false ;
159
+ }
160
+
161
+ #if CONFIG_ASYNC_TCP_USE_WDT
162
+ // need to return periodically to feed the dog
163
+ if (xQueueReceive (_async_queue, e, pdMS_TO_TICKS (1000 )) != pdPASS)
164
+ return false ;
165
+ #else
166
+ if (xQueueReceive (_async_queue, e, portMAX_DELAY) != pdPASS)
167
+ return false ;
168
+ #endif
169
+
170
+ if ((*e)->event != LWIP_TCP_POLL)
171
+ return true ;
172
+
173
+ /*
174
+ Let's try to coalesce two (or more) consecutive poll events into one
175
+ this usually happens with poor implemented user-callbacks that are runs too long and makes poll events to stack in the queue
176
+ if consecutive user callback for a same connection runs longer that poll time then it will fill the queue with events until it deadlocks.
177
+ This is a workaround to mitigate such poor designs and won't let other events/connections to starve the task time.
178
+ It won't be effective if user would run multiple simultaneous long running callbacks due to message interleaving.
179
+ todo: implement some kind of fair dequeing or (better) simply punish user for a bad designed callbacks by resetting hog connections
180
+ */
181
+ lwip_event_packet_t * next_pkt = NULL ;
182
+ while (xQueuePeek (_async_queue, &next_pkt, 0 ) == pdPASS) {
183
+ if (next_pkt->arg == (*e)->arg && next_pkt->event == LWIP_TCP_POLL) {
184
+ if (xQueueReceive (_async_queue, &next_pkt, 0 ) == pdPASS) {
185
+ free (next_pkt);
186
+ next_pkt = NULL ;
187
+ log_d (" coalescing polls, network congestion or async callbacks might be too slow!" );
188
+ continue ;
189
+ }
190
+ } else {
191
+ /*
192
+ poor designed apps using asynctcp without proper dataflow control could flood the queue with interleaved pool/ack events.
193
+ We can try to mitigate it by discarding poll events when queue grows too much.
194
+ Let's discard poll events using linear probability curve starting from 3/4 of queue length
195
+ Poll events are periodic and connection could get another chance next time
196
+ */
197
+ if (uxQueueMessagesWaiting (_async_queue) > (rand () % CONFIG_ASYNC_TCP_QUEUE_SIZE / 4 + CONFIG_ASYNC_TCP_QUEUE_SIZE * 3 / 4 )) {
198
+ free (next_pkt);
199
+ next_pkt = NULL ;
200
+ log_d (" discarding poll due to queue congestion" );
201
+ // evict next event from a queue
202
+ return _get_async_event (e);
203
+ }
204
+ }
205
+ return true ;
206
+ }
207
+ // last resort return
208
+ return true ;
152
209
}
153
210
154
211
static bool _remove_events_with_arg (void * arg) {
@@ -167,8 +224,11 @@ static bool _remove_events_with_arg(void* arg) {
167
224
if ((int )first_packet->arg == (int )arg) {
168
225
free (first_packet);
169
226
first_packet = NULL ;
170
- // return first packet to the back of the queue
171
- } else if (xQueueSend (_async_queue, &first_packet, portMAX_DELAY) != pdPASS) {
227
+
228
+ // try to return first packet to the back of the queue
229
+ } else if (xQueueSend (_async_queue, &first_packet, 0 ) != pdPASS) {
230
+ // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue
231
+ // otherwise it would deadlock, we have to discard the event
172
232
return false ;
173
233
}
174
234
}
@@ -180,7 +240,9 @@ static bool _remove_events_with_arg(void* arg) {
180
240
if ((int )packet->arg == (int )arg) {
181
241
free (packet);
182
242
packet = NULL ;
183
- } else if (xQueueSend (_async_queue, &packet, portMAX_DELAY) != pdPASS) {
243
+ } else if (xQueueSend (_async_queue, &packet, 0 ) != pdPASS) {
244
+ // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue
245
+ // otherwise it would deadlock, we have to discard the event
184
246
return false ;
185
247
}
186
248
}
@@ -222,22 +284,23 @@ static void _handle_async_event(lwip_event_packet_t* e) {
222
284
}
223
285
224
286
static void _async_service_task (void * pvParameters) {
287
+ #if CONFIG_ASYNC_TCP_USE_WDT
288
+ if (esp_task_wdt_add (NULL ) != ESP_OK) {
289
+ log_w (" Failed to add async task to WDT" );
290
+ }
291
+ #endif
225
292
lwip_event_packet_t * packet = NULL ;
226
293
for (;;) {
227
294
if (_get_async_event (&packet)) {
228
- #if CONFIG_ASYNC_TCP_USE_WDT
229
- if (esp_task_wdt_add (NULL ) != ESP_OK) {
230
- log_e (" Failed to add async task to WDT" );
231
- }
232
- #endif
233
295
_handle_async_event (packet);
296
+ }
234
297
#if CONFIG_ASYNC_TCP_USE_WDT
235
- if (esp_task_wdt_delete (NULL ) != ESP_OK) {
236
- log_e (" Failed to remove loop task from WDT" );
237
- }
298
+ esp_task_wdt_reset ();
238
299
#endif
239
- }
240
300
}
301
+ #if CONFIG_ASYNC_TCP_USE_WDT
302
+ esp_task_wdt_delete (NULL );
303
+ #endif
241
304
vTaskDelete (NULL );
242
305
_async_service_task_handle = NULL ;
243
306
}
@@ -311,6 +374,7 @@ static int8_t _tcp_connected(void* arg, tcp_pcb* pcb, int8_t err) {
311
374
312
375
static int8_t _tcp_poll (void * arg, struct tcp_pcb * pcb) {
313
376
// throttle polling events queing when event queue is getting filled up, let it handle _onack's
377
+ // log_d("qs:%u", uxQueueMessagesWaiting(_async_queue));
314
378
if (uxQueueMessagesWaiting (_async_queue) > (rand () % CONFIG_ASYNC_TCP_QUEUE_SIZE / 2 + CONFIG_ASYNC_TCP_QUEUE_SIZE / 4 )) {
315
379
log_d (" throttling" );
316
380
return ERR_OK;
@@ -321,7 +385,8 @@ static int8_t _tcp_poll(void* arg, struct tcp_pcb* pcb) {
321
385
e->event = LWIP_TCP_POLL;
322
386
e->arg = arg;
323
387
e->poll .pcb = pcb;
324
- if (!_send_async_event (&e)) {
388
+ // poll events are not critical 'cause those are repetitive, so we may not wait the queue in any case
389
+ if (!_send_async_event (&e, 0 )) {
325
390
free ((void *)(e));
326
391
}
327
392
return ERR_OK;
@@ -612,7 +677,7 @@ AsyncClient::AsyncClient(tcp_pcb* pcb)
612
677
tcp_recv (_pcb, &_tcp_recv);
613
678
tcp_sent (_pcb, &_tcp_sent);
614
679
tcp_err (_pcb, &_tcp_error);
615
- tcp_poll (_pcb, &_tcp_poll, 1 );
680
+ tcp_poll (_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER );
616
681
if (!_allocate_closed_slot ()) {
617
682
_close ();
618
683
}
@@ -643,7 +708,7 @@ AsyncClient& AsyncClient::operator=(const AsyncClient& other) {
643
708
tcp_recv (_pcb, &_tcp_recv);
644
709
tcp_sent (_pcb, &_tcp_sent);
645
710
tcp_err (_pcb, &_tcp_error);
646
- tcp_poll (_pcb, &_tcp_poll, 1 );
711
+ tcp_poll (_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER );
647
712
}
648
713
return *this ;
649
714
}
@@ -741,7 +806,7 @@ bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) {
741
806
tcp_err (pcb, &_tcp_error);
742
807
tcp_recv (pcb, &_tcp_recv);
743
808
tcp_sent (pcb, &_tcp_sent);
744
- tcp_poll (pcb, &_tcp_poll, 1 );
809
+ tcp_poll (pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER );
745
810
TCP_MUTEX_UNLOCK ();
746
811
747
812
esp_err_t err = _tcp_connect (pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected);
@@ -1090,10 +1155,6 @@ void AsyncClient::_dns_found(struct ip_addr* ipaddr) {
1090
1155
* Public Helper Methods
1091
1156
* */
1092
1157
1093
- void AsyncClient::stop () {
1094
- close (false );
1095
- }
1096
-
1097
1158
bool AsyncClient::free () {
1098
1159
if (!_pcb) {
1099
1160
return true ;
@@ -1104,13 +1165,6 @@ bool AsyncClient::free() {
1104
1165
return false ;
1105
1166
}
1106
1167
1107
- size_t AsyncClient::write (const char * data) {
1108
- if (data == NULL ) {
1109
- return 0 ;
1110
- }
1111
- return write (data, strlen (data));
1112
- }
1113
-
1114
1168
size_t AsyncClient::write (const char * data, size_t size, uint8_t apiflags) {
1115
1169
size_t will_send = add (data, size, apiflags);
1116
1170
if (!will_send || !send ()) {
0 commit comments