Fix shutdown race conditions causing bad_weak_ptr crash (v5.4.1)
Build All Platforms / test-linux-unit (push) Waiting to run
Build All Platforms / build-windows-qt (push) Waiting to run
Build All Platforms / build-windows-daemon (push) Waiting to run
Build All Platforms / build-linux-qt (push) Waiting to run
Build All Platforms / build-linux-daemon (push) Waiting to run
Build All Platforms / build-macos (push) Waiting to run
Build All Platforms / release (push) Blocked by required conditions

Fixes multiple concurrency bugs exposed during shutdown when Tor proxy
connections are failing:

- Reorder shutdown: stop network threads before destroying Tor V3 services
- Make RPC listener responsive to fShutdown (poll_one+sleep vs blocking run_one)
- Wrap StopRequests() in try/catch and drain io_service on exit
- Fix leaked CNode AddRef in ThreadSocketHandler2 and ThreadMessageHandler2
  (return→break so Release loop executes)
- Guard vNodes.size() read with cs_vNodes lock (data race)
- Guard Qt UI signal callbacks with fShutdown check (use-after-free)
- Add cs_vNodes lock in CNetCleanup global destructor
- Force-disconnect remaining nodes in StopNode() after threads stop
- Make Tor maintenance thread sleep in 500ms intervals for prompt shutdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-27 21:57:34 -07:00
parent b6013edbe4
commit dfb4b221dd
6 changed files with 66 additions and 18 deletions
+33 -10
View File
@@ -878,10 +878,19 @@ void ThreadSocketHandler2(void* parg)
}
}
}
if (vNodes.size() != nPrevNodeCount)
{
nPrevNodeCount = vNodes.size();
uiInterface.NotifyNumConnectionsChanged(vNodes.size());
// Read vNodes.size() under the lock to avoid data race
unsigned int nNodeCount;
{
LOCK(cs_vNodes);
nNodeCount = vNodes.size();
}
if (nNodeCount != nPrevNodeCount)
{
nPrevNodeCount = nNodeCount;
if (!fShutdown)
uiInterface.NotifyNumConnectionsChanged(nNodeCount);
}
}
@@ -1013,7 +1022,7 @@ void ThreadSocketHandler2(void* parg)
for (CNode* pnode : vNodesCopy)
{
if (fShutdown)
return;
break;
//
// Receive
@@ -1039,7 +1048,7 @@ void ThreadSocketHandler2(void* parg)
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
pnode->CloseSocketDisconnect();
pnode->nLastRecv = GetTime();
pnode->nRecvBytes += nBytes;
pnode->nRecvBytes += nBytes;
}
else if (nBytes == 0)
{
@@ -1104,6 +1113,8 @@ void ThreadSocketHandler2(void* parg)
pnode->Release();
}
if (fShutdown)
return;
MilliSleep(10);
}
}
@@ -1832,6 +1843,9 @@ void ThreadMessageHandler2(void* parg)
pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())];
for (CNode* pnode : vNodesCopy)
{
if (fShutdown)
break;
// Receive messages
{
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
@@ -1839,8 +1853,6 @@ void ThreadMessageHandler2(void* parg)
if (!ProcessMessages(pnode))
pnode->CloseSocketDisconnect();
}
if (fShutdown)
return;
// Send messages
{
@@ -1848,8 +1860,6 @@ void ThreadMessageHandler2(void* parg)
if (lockSend)
SendMessages(pnode, pnode == pnodeTrickle);
}
if (fShutdown)
return;
}
{
@@ -2180,6 +2190,18 @@ bool StopNode()
}
MilliSleep(50);
DumpAddresses();
// Force-disconnect and clean up all remaining nodes now that threads have stopped.
// Close sockets first so any lingering I/O fails immediately.
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
{
pnode->CloseSocketDisconnect();
pnode->Cleanup();
}
}
return true;
}
@@ -2191,7 +2213,8 @@ public:
}
~CNetCleanup()
{
// Close sockets
// Close sockets - acquire lock in case other threads are still winding down
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (pnode->hSocket != INVALID_SOCKET)
closesocket(pnode->hSocket);