Skip to content

Commit 096fe1c

Browse files
authored
Merge pull request #329 from jolavillette/fix/shutdown-streamer-thread-teardown-race
Stop per-peer streamer threads at shutdown to avoid teardown crash
2 parents d5d9bf1 + 6815f5d commit 096fe1c

4 files changed

Lines changed: 73 additions & 4 deletions

File tree

src/pqi/pqiperson.cc

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,12 +431,25 @@ int pqiperson::fullstopthreads()
431431
<< PeerId().toStdString() << std::endl;
432432
#endif
433433

434-
RS_STACK_MUTEX(mPersonMtx);
434+
/* Snapshot the child connections under mPersonMtx, then release it BEFORE
435+
* joining their threads. A child's streamer thread may still be delivering a
436+
* received item that synchronously replies on the same stack through
437+
* pqiperson::SendItem(), which locks mPersonMtx; holding mPersonMtx across the
438+
* (blocking) fullstop() join would deadlock -- exactly the reason
439+
* pqipersongrp::fullstopAllThreads() already drops coreMtx before waiting.
440+
* Observed at shutdown as an endless RsThread::waitWhileStopping() on a
441+
* "pqi <peer>" thread that was answering an RTT ping. */
442+
std::list<pqiconnect *> children;
443+
{
444+
RS_STACK_MUTEX(mPersonMtx);
445+
for(std::map<uint32_t, pqiconnect *>::iterator it = kids.begin(); it != kids.end(); ++it)
446+
children.push_back(it->second);
447+
}
435448

436-
std::map<uint32_t, pqiconnect *>::iterator it;
437-
for(it = kids.begin(); it != kids.end(); ++it)
438-
(it->second)->fullstop(); // WAIT FOR THREAD TO STOP.
449+
for(std::list<pqiconnect *>::iterator it = children.begin(); it != children.end(); ++it)
450+
(*it)->fullstop(); // WAIT FOR THREAD TO STOP.
439451

452+
RS_STACK_MUTEX(mPersonMtx);
440453
activepqi = NULL;
441454
active = false;
442455
lastHeartbeatReceived = 0;

src/pqi/pqipersongrp.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,53 @@ int pqipersongrp::removePeer(const RsPeerId& id)
460460
return 1;
461461
}
462462

463+
int pqipersongrp::fullstopAllThreads()
464+
{
465+
/* Stop every peer's network I/O threads at shutdown. Unlike removePeer() this
466+
* does NOT delete the persons, it only makes sure no pqithreadstreamer is left
467+
* running once RetroShare tears down its static objects (otherwise those
468+
* detached threads keep deserialising incoming packets and crash/flood the log
469+
* when the static SmallObject allocator and its mutex get destroyed).
470+
*
471+
* Two phases on purpose:
472+
* 1) while holding coreMtx, signal every peer to stop and close its sockets.
473+
* reset() calls askForStop() on the connection threads and closes the
474+
* sockets; both are non-blocking, so holding coreMtx here is safe (same as
475+
* removePeer()).
476+
* 2) release coreMtx, THEN wait for the threads to really stop.
477+
* We must NOT hold coreMtx while waiting: a peer thread that is delivering a
478+
* received item may still need coreMtx to queue a reply
479+
* (pqihandler::queueOutRsItem), so it would never finish and we would wait for
480+
* it forever -> deadlock. */
481+
482+
std::list<pqiperson *> persons;
483+
484+
{
485+
RsStackMutex stack(coreMtx); /**************** LOCKED MUTEX ****************/
486+
487+
for(std::map<RsPeerId, SearchModule *>::iterator it = mods.begin(); it != mods.end(); ++it)
488+
{
489+
// mods also holds non-pqiperson interfaces (e.g. the pqiloopback for
490+
// our own node) which have no network I/O threads. Only pqiperson has
491+
// connection threads to stop, so skip anything that is not one -- a
492+
// blind cast would crash on the loopback.
493+
pqiperson *p = dynamic_cast<pqiperson *>(it->second->pqi);
494+
if(!p)
495+
continue;
496+
p -> stoplistening();
497+
p -> reset(); // askForStop() the threads + close the sockets (non-blocking)
498+
persons.push_back(p);
499+
}
500+
}
501+
502+
// coreMtx released: peer threads that still need it can now make progress and
503+
// notice the stop request. Only now do we wait for them to actually stop.
504+
for(std::list<pqiperson *>::iterator it = persons.begin(); it != persons.end(); ++it)
505+
(*it) -> fullstopthreads(); // WAIT FOR THREADS TO STOP.
506+
507+
return 1;
508+
}
509+
463510
int pqipersongrp::tagHeartbeatRecvd(const RsPeerId& id)
464511
{
465512
std::map<RsPeerId, SearchModule *>::iterator it;

src/pqi/pqipersongrp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ virtual void statusChanged();
6666
/******************* Peer Control **************************/
6767
virtual int addPeer(const RsPeerId& id); /* can be overloaded for testing */
6868
int removePeer(const RsPeerId& id);
69+
int fullstopAllThreads(); /* stop every peer's I/O threads (used at shutdown) */
6970
int connectPeer(const RsPeerId& id
7071
#ifdef WINDOWS_SYS
7172
///////////////////////////////////////////////////////////

src/rsserver/p3face-config.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ void RsServer::rsGlobalShutDown()
103103

104104
fullstop();
105105

106+
/* Stop the per-peer network I/O threads (pqithreadstreamer). They are
107+
* otherwise never stopped at shutdown and keep deserialising incoming
108+
* packets, which crashes/floods the log once the static SmallObject
109+
* allocator and its mutex are destroyed during process teardown.
110+
* Must run after fullstop() so the RsServer tick thread is no longer
111+
* iterating the peer list concurrently. */
112+
if(pqih) pqih->fullstopAllThreads();
113+
106114
#ifdef RS_JSONAPI
107115
if(rsJsonApi) rsJsonApi->fullstop();
108116
#endif

0 commit comments

Comments
 (0)