Skip to content

Commit

Permalink
fix(qgisserver): suppress wait in fcgi response destructor by using t…
Browse files Browse the repository at this point in the history
…imed_mutex
  • Loading branch information
benoitdm-oslandia committed Dec 6, 2024
1 parent bb734ce commit fcebbdc
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 36 deletions.
98 changes: 75 additions & 23 deletions src/server/qgsfcgiserverresponse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

#include "qgslogger.h"

#include <unistd.h>
#include <mutex>
#include <chrono>

#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
#include <sys/types.h>
#include <sys/socket.h>
Expand Down Expand Up @@ -54,41 +58,54 @@ typedef struct QgsFCGXStreamData
} QgsFCGXStreamData;
#endif

QgsSocketMonitoringThread::QgsSocketMonitoringThread( bool *isResponseFinished, QgsFeedback *feedback )
: mIsResponseFinished( isResponseFinished )
, mFeedback( feedback )
// to be able to use 333ms expression as a duration
using namespace std::chrono_literals;

// used to synchronize socket monitoring thread and fcgi response
std::timed_mutex gSocketMonitoringMutex;


// QgsSocketMonitoringThread constructor
QgsSocketMonitoringThread::QgsSocketMonitoringThread( std::shared_ptr<QgsFeedback> feedback )
: mFeedback( feedback )
, mIpcFd( -1 )
{
setObjectName( "FCGI socket monitor" );
Q_ASSERT( mIsResponseFinished );
Q_ASSERT( mFeedback );

#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
if ( FCGI_stdout && FCGI_stdout->fcgx_stream && FCGI_stdout->fcgx_stream->data )
{
QgsFCGXStreamData *stream = static_cast<QgsFCGXStreamData *>( FCGI_stdin->fcgx_stream->data );
QgsFCGXStreamData *stream = static_cast<QgsFCGXStreamData *>( FCGI_stdout->fcgx_stream->data );
if ( stream && stream->reqDataPtr )
{
mIpcFd = stream->reqDataPtr->ipcFd;
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdin stream data is null! Socket monitoring disable." ),
QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdout stream data is null! Socket monitoring disabled." ),
QStringLiteral( "FCGIServer" ),
Qgis::MessageLevel::Warning );
}
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdin is null! Socket monitoring disable." ),
QgsMessageLog::logMessage( QStringLiteral( "FCGI_stdout is null! Socket monitoring disabled." ),
QStringLiteral( "FCGIServer" ),
Qgis::MessageLevel::Warning );
}
#endif
}

// Informs the thread to quit
void QgsSocketMonitoringThread::setResponseFinished( bool responseFinished )
{
mIsResponseFinished.store( responseFinished );
}

void QgsSocketMonitoringThread::run( )
{
const pid_t threadId = gettid();

if ( mIpcFd < 0 )
{
QgsMessageLog::logMessage( QStringLiteral( "Socket monitoring disabled: no socket fd!" ),
Expand All @@ -98,33 +115,55 @@ void QgsSocketMonitoringThread::run( )
}

#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
mIsResponseFinished.store( false );
char c;
while ( !*mIsResponseFinished )
while ( !mIsResponseFinished.load() )
{
const ssize_t x = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT ); // see https://stackoverflow.com/a/12402596
if ( x < 0 )
if ( x != 0 )
{
// Ie. we are still connected but we have an 'error' as there is nothing to read
QgsDebugMsgLevel( QStringLiteral( "FCGIServer: remote socket still connected. errno: %1" ).arg( errno ), 5 );
QgsDebugMsgLevel( QStringLiteral( "FCGIServer %1: remote socket still connected. errno: %2, x: %3" )
.arg( threadId ).arg( errno ).arg( x ), 5 );
}
else
{
// socket closed, nothing can be read
QgsDebugMsgLevel( QStringLiteral( "FCGIServer: remote socket has been closed! errno: %1" ).arg( errno ), 2 );
mFeedback->cancel();
break;
// double check...
ssize_t x2 = 0;
for ( int i = 0; !mIsResponseFinished.load() && x2 == 0 && i < 10; i++ )
{
x2 = recv( mIpcFd, &c, 1, MSG_PEEK | MSG_DONTWAIT ); // see https://stackoverflow.com/a/12402596
std::this_thread::sleep_for( 50ms );
}
if ( x2 == 0 )
{
// socket closed, nothing can be read
QgsDebugMsgLevel( QStringLiteral( "FCGIServer %1: remote socket has been closed! errno: %2, x: %3" )
.arg( threadId ).arg( errno ).arg( x2 ), 2 );
mFeedback->cancel();
break;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "FCGIServer::run %1: remote socket is not closed in fact! errno: %2, x: %3" )
.arg( threadId ).arg( errno ).arg( x2 ), 1 );
}

}

QThread::msleep( 333L );
// If lock is acquired this means the response has finished and we will exit the while loop
// else we will wait max for 333ms.
if ( gSocketMonitoringMutex.try_lock_for( 333ms ) )
gSocketMonitoringMutex.unlock();
}

if ( *mIsResponseFinished )
if ( mIsResponseFinished.load() )
{
QgsDebugMsgLevel( QStringLiteral( "FCGIServer: socket monitoring quits normally." ), 2 );
QgsDebugMsgLevel( QStringLiteral( "FCGIServer::run %1: socket monitoring quits normally." ).arg( threadId ), 2 );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "FCGIServer: socket monitoring quits: no more socket." ), 2 );
QgsDebugMsgLevel( QStringLiteral( "FCGIServer::run %1: socket monitoring quits: no more socket." ).arg( threadId ), 2 );
}
#endif
}
Expand All @@ -141,15 +180,28 @@ QgsFcgiServerResponse::QgsFcgiServerResponse( QgsServerRequest::Method method )
mBuffer.open( QIODevice::ReadWrite );
setDefaultHeaders();

mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( &mFinished, mFeedback.get() );
mSocketMonitoringThread->start();
mSocketMonitoringThread = std::make_unique<QgsSocketMonitoringThread>( mFeedback );

// Lock the thread mutex: every try_lock will take 333ms
gSocketMonitoringMutex.lock();

// Start the monitoring thread
mThread = std::thread( &QgsSocketMonitoringThread::run, mSocketMonitoringThread.get() );
}

QgsFcgiServerResponse::~QgsFcgiServerResponse()
{
mFinished = true;
mSocketMonitoringThread->exit();
mSocketMonitoringThread->wait();

// Inform the thread to quit asap
mSocketMonitoringThread->setResponseFinished( mFinished );

// Release the mutex so the try_lock in the thread will not wait anymore and
// the thread will end its loop as we have set 'setResponseFinished' to true
gSocketMonitoringMutex.unlock();

// Just to be sure
mThread.join();
}

void QgsFcgiServerResponse::removeHeader( const QString &key )
Expand Down
38 changes: 25 additions & 13 deletions src/server/qgsfcgiserverresponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,38 @@
#include "qgsserverresponse.h"

#include <QBuffer>
#include <QThread>
#include <thread>

/**
* \ingroup server
* \class QgsSocketMonitoringThread
* \brief Thread used to monitor the fcgi socket
* \since QGIS 3.36
*/
class QgsSocketMonitoringThread: public QThread
class QgsSocketMonitoringThread
{
Q_OBJECT

public:

/**
* \brief QgsSocketMonitoringThread
* \param isResponseFinished
* \param feedback
* Constructor for QgsSocketMonitoringThread
* \param feedback used to cancel rendering jobs when socket timedout
*/
QgsSocketMonitoringThread( std::shared_ptr<QgsFeedback> feedback );

/**
* main thread function
*/
QgsSocketMonitoringThread( bool *isResponseFinished, QgsFeedback *feedback );
void run( );
void run();

/**
* Controls thread life
* \param responseFinished stop the thread if true
*/
void setResponseFinished( bool responseFinished );

private:
bool *mIsResponseFinished = nullptr;
QgsFeedback *mFeedback = nullptr;
std::atomic_bool mIsResponseFinished;
std::shared_ptr<QgsFeedback> mFeedback;
int mIpcFd = -1;
};

Expand All @@ -68,7 +75,8 @@ class SERVER_EXPORT QgsFcgiServerResponse: public QgsServerResponse
* \param method The HTTP method (Get by default)
*/
QgsFcgiServerResponse( QgsServerRequest::Method method = QgsServerRequest::GetMethod );
virtual ~QgsFcgiServerResponse();

virtual ~QgsFcgiServerResponse() override;

void setHeader( const QString &key, const QString &value ) override;

Expand Down Expand Up @@ -117,8 +125,12 @@ class SERVER_EXPORT QgsFcgiServerResponse: public QgsServerResponse
QgsServerRequest::Method mMethod;
int mStatusCode = 0;

// encapsulate thread data
std::unique_ptr<QgsSocketMonitoringThread> mSocketMonitoringThread;
std::unique_ptr<QgsFeedback> mFeedback;
// real thread object. Used to join.
std::thread mThread;
// Used to cancel rendering jobs
std::shared_ptr<QgsFeedback> mFeedback;
};

#endif

0 comments on commit fcebbdc

Please sign in to comment.