Skip to content

Commit 646eab7

Browse files
committed
Fix self-deadlock onConnect introduced by ul-gh/fix_#884 branch
Commit 344062a intended to protect the client list for AsyncEventSource from concurrent access by wrapping accesses with locking. However, this introduces a self-deadlock scenario if application code in the onConnect callback (now called with the client list lock held) tries to broadcast a message to existing connections in the same AsyncEventSource (for example, to alert of a new incoming connection). The broadcast call tries to take the lock but blocks because the lock is already taken, by the same task, before calling the callback. Fixed by making use of the AsyncWebLockGuard class which is tailor-made to address this scenario.
1 parent 600e1d1 commit 646eab7

File tree

2 files changed

+7
-14
lines changed

2 files changed

+7
-14
lines changed

src/AsyncEventSource.cpp

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -298,38 +298,34 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
298298
client->write((const char *)temp, 2053);
299299
free(temp);
300300
}*/
301-
_client_queue_lock.lock();
301+
AsyncWebLockGuard l(_client_queue_lock);
302302
_clients.add(client);
303303
if(_connectcb)
304304
_connectcb(client);
305-
_client_queue_lock.unlock();
306305
}
307306

308307
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
309-
_client_queue_lock.lock();
308+
AsyncWebLockGuard l(_client_queue_lock);
310309
_clients.remove(client);
311-
_client_queue_lock.unlock();
312310
}
313311

314312
void AsyncEventSource::close(){
315313
// While the whole loop is not done, the linked list is locked and so the
316314
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
317315
// is called very early
318-
_client_queue_lock.lock();
316+
AsyncWebLockGuard l(_client_queue_lock);
319317
for(const auto &c: _clients){
320318
if(c->connected())
321319
c->close();
322320
}
323-
_client_queue_lock.unlock();
324321
}
325322

326323
// pmb fix
327324
size_t AsyncEventSource::avgPacketsWaiting() const {
328325
size_t aql = 0;
329326
uint32_t nConnectedClients = 0;
330-
_client_queue_lock.lock();
327+
AsyncWebLockGuard l(_client_queue_lock);
331328
if (_clients.isEmpty()) {
332-
_client_queue_lock.unlock();
333329
return 0;
334330
}
335331
for(const auto &c: _clients){
@@ -338,29 +334,26 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
338334
++nConnectedClients;
339335
}
340336
}
341-
_client_queue_lock.unlock();
342337
return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up
343338
}
344339

345340
void AsyncEventSource::send(
346341
const char *message, const char *event, uint32_t id, uint32_t reconnect){
347342
String ev = generateEventMessage(message, event, id, reconnect);
348-
_client_queue_lock.lock();
343+
AsyncWebLockGuard l(_client_queue_lock);
349344
for(const auto &c: _clients){
350345
if(c->connected()) {
351346
c->_write(ev.c_str(), ev.length());
352347
}
353348
}
354-
_client_queue_lock.unlock();
355349
}
356350

357351
size_t AsyncEventSource::count() const {
358352
size_t n_clients;
359-
_client_queue_lock.lock();
353+
AsyncWebLockGuard l(_client_queue_lock);
360354
n_clients = _clients.count_if([](AsyncEventSourceClient *c){
361355
return c->connected();
362356
});
363-
_client_queue_lock.unlock();
364357
return n_clients;
365358
}
366359

src/AsyncEventSource.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class AsyncEventSource: public AsyncWebHandler {
104104
LinkedList<AsyncEventSourceClient *> _clients;
105105
// Same as for individual messages, protect mutations of _clients list
106106
// since simultaneous access from different tasks is possible
107-
AsyncPlainLock _client_queue_lock;
107+
AsyncWebLock _client_queue_lock;
108108
ArEventHandlerFunction _connectcb;
109109
ArAuthorizeConnectHandler _authorizeConnectHandler;
110110
public:

0 commit comments

Comments
 (0)