- Take the _asyncsock_mutex in AsyncClient destructor. This should
handle scenarios where a previously-valid client is destroyed by
another task while being examined by the asyncTcpSock task.
- Since the _asyncsock_mutex is released before being taken again by the
AsyncSocketBase destructor, examined objects may be still "half-destroyed"
(AsyncClient destructor terminated and _write_mutex invalid, but still
waiting for lock in AsyncSocketBase destructor). However, _socket may
now be guaranteed to remain valid if it was valid when the
_asyncsock_mutex was taken.
The constant CONFIG_LWIP_MAX_SOCKETS, if available, tells how many
sockets can be opened at once. If already at this limit, and an attempt
is made to accept() an additional incoming connection from a listening
socket, the accept() call will fail. Fix this by checking the socket
list and refusing to add the listening socket to the set of readable
sockets to check in the select() call. This will cause connections to
remain in the listening backlog until some other socket is closed.
In the current architecture, all socket reads proceed before all socket
polls, and the socket poll is where the read timeout check is made. If
the sum of the execution time of all write and read handlers exceeds
that of the next timeout-enabled polled socket, the code would
incorrectly close due to a "timeout", even if some readable data became
available in the meantime. Fix by checking (via select()) one last time
and resetting the timer if the socket has some readable data.
A socket that is "at rest" (no pending reads or writes) is by definition
"writable", and using select() to check for writability will return TRUE
for the socket without any delay. Therefore each active connection must
be checked for pending data to write before being added to the write
check set. This fixes failure to yield to other tasks due to a busy
loop.
Otherwise a buffer queued past _ack that has been fully written yet not
removed from the queue risks triggering an incorrect timeout event. This
has caused incomplete file downloads with ESPAsyncWebServer.
This is a bare-bones program that opens a basic TCP/IP socket to a
particular non-encrypted web server, on port 80, sends a hardcoded
HTTP/1.1 request, dumps the response, and should disconnect when the
remote side closes the connection. All necessary callbacks for this are
installed. The main loop() starts a connection attempt every 20 seconds,
and otherwise just outputs stars to show it is still running while the
AsyncClient does its thing.
This commit implements a policy of writing as many queued buffers as the
writable socket allows, until non-writable, or the write queue is empty.
This has two objectives: 1) any allocated buffers for data copies will
be freed sooner, 2) the write output will be more responsive, fixing at
least one known delay in WiFi scanlist output in YUBOX Now.
Otherwise the socket poll callback races with the RX timeout (if
nonzero) and will immediately disconnect the socket without waiting for
it to actually finish connecting.
The general rule is that at no time must callbacks be removed from the
AsyncTCP object while the object is still live. Clients expect this from
the AsyncTCP library.
Users of AsyncTCP expect callbacks installed on the object to remain
active on close event and do not reinstall them if the just-closed
object is reopened. Fixes failure to react to events after reopening.
Prior to this commit, the mutex that protected the global client socket
list was non-recursive. This forced the release of the mutex before
iterating through sockets on which to call callbacks. However, releasing
the mutex allowed destructors to proceed, therefore possibly
invalidating the object pointers copied to the local list. This in turn
risked calling callbacks on already destroyed objects. Fixed by the use
of a recursive mutex that allows holding the mutex during the callback
invocation.
The discard callback (installed via onDisconnect()) can legitimately
decide to destroy the AsyncTCP client, calling the destructors which in
turn invalidate the write mutex object. Prior to this commit, calling
_clearWriteQueue() (which takes said write mutex) risked accessing
uninitialized memory and causing memory corruption. Fixed.
This prevents a race condicion of the ::add() method modifying the queue
and being preempted by the high-priority asyncTcpSock task that in turn
flushes the same queue to the socket, therefore modifying the queue.