fix(Apps/SOAP): prevent shutdown deadlock on in-flight commands (#26335)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
This commit is contained in:
@@ -20,6 +20,8 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "World.h"
|
#include "World.h"
|
||||||
#include "soapStub.h"
|
#include "soapStub.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
void ACSoapThread(const std::string& host, uint16 port)
|
void ACSoapThread(const std::string& host, uint16 port)
|
||||||
{
|
{
|
||||||
@@ -108,21 +110,28 @@ int ns1__executeCommand(soap* soap, char* command, char** result)
|
|||||||
return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string");
|
return soap_sender_fault(soap, "Command can not be empty", "The supplied command was an empty string");
|
||||||
|
|
||||||
LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command);
|
LOG_DEBUG("network.soap", "ACSoap: got command '{}'", command);
|
||||||
SOAPCommand connection;
|
|
||||||
|
// Shared so the object survives if we stop waiting below: the queued command keeps a raw
|
||||||
|
// pointer to it and the world thread may still run it after that. The extra reference is
|
||||||
|
// released by commandFinished() once the world side is done with it.
|
||||||
|
auto connection = std::make_shared<SOAPCommand>();
|
||||||
|
connection->m_self = connection;
|
||||||
|
|
||||||
// commands are executed in the world thread. We have to wait for them to be completed
|
// commands are executed in the world thread. We have to wait for them to be completed
|
||||||
|
sWorld->QueueCliCommand(new CliCommandHolder(connection.get(), command, &SOAPCommand::print, &SOAPCommand::commandFinished));
|
||||||
|
|
||||||
|
// Wait for the command to finish, but bail on shutdown: ProcessCliCommands() (which fulfils
|
||||||
|
// the promise) stops once the world loop exits, so an unbounded wait here would deadlock.
|
||||||
|
std::future<void> finished = connection->finishedPromise.get_future();
|
||||||
|
while (finished.wait_for(std::chrono::seconds(1)) != std::future_status::ready)
|
||||||
{
|
{
|
||||||
// CliCommandHolder will be deleted from world, accessing after queueing is NOT save
|
if (World::IsStopped())
|
||||||
CliCommandHolder* cmd = new CliCommandHolder(&connection, command, &SOAPCommand::print, &SOAPCommand::commandFinished);
|
return soap_receiver_fault(soap, "Server is shutting down", "Command aborted: the server is shutting down");
|
||||||
sWorld->QueueCliCommand(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until the command has finished executing
|
|
||||||
connection.finishedPromise.get_future().wait();
|
|
||||||
|
|
||||||
// The command has finished executing already
|
// The command has finished executing already
|
||||||
char* printBuffer = soap_strdup(soap, connection.m_printBuffer.c_str());
|
char* printBuffer = soap_strdup(soap, connection->m_printBuffer.c_str());
|
||||||
if (connection.hasCommandSucceeded())
|
if (connection->hasCommandSucceeded())
|
||||||
{
|
{
|
||||||
*result = printBuffer;
|
*result = printBuffer;
|
||||||
return SOAP_OK;
|
return SOAP_OK;
|
||||||
@@ -135,6 +144,8 @@ void SOAPCommand::commandFinished(void* soapconnection, bool success)
|
|||||||
{
|
{
|
||||||
SOAPCommand* con = (SOAPCommand*)soapconnection;
|
SOAPCommand* con = (SOAPCommand*)soapconnection;
|
||||||
con->setCommandSuccess(success);
|
con->setCommandSuccess(success);
|
||||||
|
// world side is done with us; drop the keep-alive (may free the object)
|
||||||
|
con->m_self.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "Define.h"
|
#include "Define.h"
|
||||||
#include <future>
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
void process_message(struct soap* soap_message);
|
void process_message(struct soap* soap_message);
|
||||||
void ACSoapThread(const std::string& host, uint16 port);
|
void ACSoapThread(const std::string& host, uint16 port);
|
||||||
@@ -58,6 +59,8 @@ public:
|
|||||||
bool m_success;
|
bool m_success;
|
||||||
std::string m_printBuffer;
|
std::string m_printBuffer;
|
||||||
std::promise<void> finishedPromise;
|
std::promise<void> finishedPromise;
|
||||||
|
// keep-alive while a queued command still references this object; released in commandFinished()
|
||||||
|
std::shared_ptr<SOAPCommand> m_self;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user