You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+13-3Lines changed: 13 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -470,9 +470,19 @@ Common HTTPS errors:
470
470
471
471
## Thread Safety
472
472
473
-
- AsyncTCP callbacks run on the lwIP/WiFi task while `loop()` (or the auto-loop task) runs on a different core. Since v2.1 the library guards against use-after-free by holding `RequestContext` in `std::shared_ptr` (captured by transport lambdas) and using an `std::atomic<bool> cancelled` flag that is set before cleanup erases the context.
474
-
- On ESP32 with `ASYNC_HTTP_ENABLE_AUTOLOOP`, a recursive mutex protects shared containers (`_activeRequests`, `_pendingQueue`, etc.).
475
-
- Callbacks are still executed in the context of the network event loop — keep them lightweight and non-blocking.
473
+
The client is internally synchronized on ESP32. The model has three actors:
474
+
475
+
-**lwIP/WiFi task (`tcpip_thread`)** — where AsyncTCP fires data/disconnect/error callbacks. These handlers do *no* heavy work and never take the client lock: they only copy the payload into a thread-safe `WorkerBuffer` (PSRAM-backed) and return immediately, so the network task is never blocked.
476
+
-**Worker task (`AsyncHttpWorker`)** — drains the `WorkerBuffer`, takes the recursive client mutex, runs the actual response parsing (`handleData`/`handleDisconnect`/`handleTransportError`), then releases the lock and dispatches user callbacks.
477
+
-**Your task(s)** — the public API (`get`, `post`, `setHeader`, …) takes the same recursive mutex, so configuration and request submission are safe to call from any task.
478
+
479
+
Key guarantees and details:
480
+
481
+
-**Single recursive mutex** (`_reqMutex`) protects all shared state (`_activeRequests`, `_pendingQueue`, headers, etc.). It is recursive so a user callback may re-enter the client (start/abort a request, change config) without deadlocking. Ownership is queried via FreeRTOS's native `xSemaphoreGetMutexHolder()` — no manual depth bookkeeping.
482
+
-**User callbacks run outside the lock.** Success/error/body-chunk callbacks are queued and dispatched by `dispatchCallbacks()` after the mutex is released, so you can safely call client methods from inside a callback. A callback may run either on the worker task or on the task that submitted work — do not assume a fixed thread, and keep callbacks non-blocking (a blocking callback can stall the worker).
483
+
-**Use-after-free protection.**`RequestContext` is held in `std::shared_ptr` captured by the transport lambdas, and an `std::atomic<bool> cancelled` flag (set before cleanup erases the context) makes in-flight callbacks no-op safely.
484
+
-**Back-pressure.** If the `WorkerBuffer` reaches its hard ceiling (`ASYNC_HTTP_RING_BUFFER_MAX`, default 64 KiB) the transport is closed, which surfaces as a normal disconnect/error on that request.
485
+
-**Clean shutdown.** The destructor does not `vTaskDelete()` the worker (which could be mid-parse holding the lock). It sets an exit flag, wakes the worker, and waits for it to leave its loop and self-delete before destroying the mutex. Note: destroying an `AsyncHttpClient` while requests are still in flight is still best avoided — abort outstanding requests (or let them finish) before destruction, since open transports hold lambdas that reference the client.
0 commit comments