From 1c6fa5916dbbf64455383ce657683432d79e33fe Mon Sep 17 00:00:00 2001 From: jolavillette Date: Tue, 11 Aug 2026 22:55:33 +0200 Subject: [PATCH] fix(shutdown): stop the JSON API before plugins delete their providers rsGlobalShutDown() stopped the JSON API almost last, after stopPlugins(). A plugin that registered a JsonApiResourceProvider deletes it in its stop(), but the running restbed service still holds the restbed::Resource objects that provider returned, and their handlers capture it. Any request served between stopPlugins() and the fullstop at the end of the function therefore dereferences freed memory. The window is not theoretical: everything in between -- UPnP teardown, the auto-proxy shutdown, all registered service threads, the RsServer tick thread and the per-peer streamers -- can take tens of seconds, and a web interface polls throughout. Move the fullstop to the top of the function. It also keeps an API client from touching the configuration after ConfigFinalSave(), and it must stay outside the wasReady branch: retroshare-service and Android start the JSON API before login, so a shutdown from that state has to stop it too. Without this, a plugin has to restart the whole JSON API from its stop() to make deleting its own provider safe, which costs a burst-protection wait and brings the server back up in the middle of teardown. --- src/rsserver/p3face-config.cc | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/rsserver/p3face-config.cc b/src/rsserver/p3face-config.cc index 16a309ca5..6af2767c7 100644 --- a/src/rsserver/p3face-config.cc +++ b/src/rsserver/p3face-config.cc @@ -84,6 +84,21 @@ void RsServer::rsGlobalShutDown() bool wasReady = coreReady; coreReady = false; +#ifdef RS_JSONAPI + /* Stop the JSON API before anything else. Plugins delete their + * JsonApiResourceProvider in stopPlugins() below, while the restbed service + * still holds the resources that provider handed out -- their handlers + * capture it, so serving a request in that window dereferences freed + * memory. The window is not small: everything between stopPlugins() and the + * end of this function can take tens of seconds, and a web interface polls + * throughout. Stopping first also keeps an API client from touching the + * configuration after ConfigFinalSave(). + * + * Not inside the wasReady branch: retroshare-service and Android start the + * JSON API before login, so a shutdown from that state must stop it too. */ + if(rsJsonApi) rsJsonApi->fullstop(); +#endif + if(wasReady) { /* Close the incoming-connection listener FIRST, before anything else. @@ -123,10 +138,6 @@ void RsServer::rsGlobalShutDown() * iterating the peer list concurrently. */ if(pqih) pqih->fullstopAllThreads(); -#ifdef RS_JSONAPI - if(rsJsonApi) rsJsonApi->fullstop(); -#endif - AuthPGP::exit(); // close all databases