newConnection pushed the client into clients while holding map_mutex
but started its read thread after releasing it; in that window the
client was a live member of clients with no read thread and could be
marked, closed and deleted by the drainer before start() ran. Start
the thread inside the locked section: the thread can only wait for
map_mutex after the function has released it, so this is not a
deadlock, and the 'everything in clients is started' invariant holds.
~DispatcherServer dropped clients/servers pending in rmrf_clients/
rmrf_servers without closing or deleting them (object leak of live
PIObjects). Collect them together with the main maps, then run the
same stop/close/delete sequence. The sets are disjoint with the main
maps by construction (items are removed from the main maps when
marked), so there are no duplicates.
cleanClients() (timer thread) called close()/stopAndWait() and
disconnectClient() (read thread) did the same while holding
map_mutex, while client read threads wait for the same lock in
registerServer/registerClient/disconnectClient — a guaranteed
deadlock (the -w watchdog aborts exactly this). disconnectClient
could also wait for its own read thread (self-wait in
waitThreadedReadFinished) and the destructor never stopped the timer
or drained the clients, leaving dangling lambda captures of this.
Restructure around a single drainer (the timer thread):
- cleanClients: under map_mutex only mark (move rmrf_* out, mark
ping-timeout/orphaned clients); after the unlock close
(stopAndWait) and delete the connections, then delete the servers
- disconnectClient: only mark under the lock, then request a
non-blocking stop() on the affected connections; the drainer waits
and deletes (no self-wait, nothing blocks while holding the lock)
- newConnection: start the read thread after releasing the lock
- ~DispatcherServer: stop watchdog/timer first, then close and delete
all clients before the servers (raw-pointer order) and eth
The watchdog is now a PIThread member so the destructor can stop it;
setMaxConnections takes map_mutex like its readers.
in almost all methods removed timeouts in milliseconds, replaced to PISystemTime
PITimer rewrite, remove internal impl, now only thread implementation, API similar to PIThread
PITimer API no longer pass void*
PIPeer, PIConnection improved stability on reinit and exit
PISystemTime new methods
pisd now exit without hanging