mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-31 09:14:11 -05:00
Dedicated python env for ri-cloud-api (#14831)
- Add ri-cloud-api python env preferences - Prevent loop of restart/reconnect when manually clicking "start"/"Authenticate"
This commit is contained in:
1 parent
3046c7e09d
commit
f015af98ee
11 files changed
+320
-26
No files matched your search
@@ -61,6 +61,13 @@ constexpr int readinessPollIntervalMs = 250;
|
||||
|
||||
// Number of ports scanned, starting at the wanted port, when looking for a free port to bind to.
|
||||
constexpr int portRangeLength = 100;
|
||||
|
||||
// Shared hint appended to errors caused by a service that never responded, pointing at the most likely
|
||||
// fix without claiming to have diagnosed the exact cause.
|
||||
const std::string pythonEnvironmentHint =
|
||||
"This could be caused by the configured Python environment (Preferences -> RI Cloud API, or "
|
||||
"Preferences -> Scripting) missing the packages required by ri-cloud-api. Check the log above for "
|
||||
"details, and fix the environment if needed.";
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -74,7 +81,10 @@ RiaCloudApiService::RiaCloudApiService( const QString& serverAddress, int wanted
|
||||
, m_wantedPort( wantedPort )
|
||||
, m_port( -1 )
|
||||
, m_consecutiveFailures( 0 )
|
||||
, m_consecutiveLaunchFailures( 0 )
|
||||
, m_isResponding( false )
|
||||
, m_hasRespondedSinceStart( false )
|
||||
, m_hasGivenUp( false )
|
||||
{
|
||||
// Never route loopback requests through a system/corporate proxy, which can add several seconds
|
||||
// per request.
|
||||
@@ -143,14 +153,31 @@ QString RiaCloudApiService::serverUrl() const
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaCloudApiService::start()
|
||||
void RiaCloudApiService::start( StartTrigger trigger )
|
||||
{
|
||||
// An Automatic call (e.g. Sumo's tokenReady, which can fire well after the give-up, such as on a
|
||||
// background token refresh) must not silently re-arm a retry loop that already gave up. Only an
|
||||
// explicit Manual start/restart may do that.
|
||||
if ( trigger == StartTrigger::Automatic && m_hasGivenUp ) return;
|
||||
|
||||
if ( isRunning() ) return;
|
||||
|
||||
m_consecutiveLaunchFailures = 0;
|
||||
m_hasRespondedSinceStart = false;
|
||||
m_hasGivenUp = false;
|
||||
|
||||
launchProcess();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaCloudApiService::launchProcess()
|
||||
{
|
||||
// Clean up any stale QProcess instance (e.g. after an unexpected exit) before re-starting.
|
||||
if ( m_process ) stop();
|
||||
|
||||
const QString pythonExecutable = RiaApplication::instance()->pythonPath();
|
||||
const QString pythonExecutable = RiaApplication::instance()->cloudApiPythonPath();
|
||||
if ( pythonExecutable.isEmpty() )
|
||||
{
|
||||
RiaLogging::error( "Cloud API service: no Python executable configured, cannot start service." );
|
||||
@@ -209,7 +236,8 @@ void RiaCloudApiService::start()
|
||||
|
||||
// A non-zero exit means the service never came up, or died. Anything else is an
|
||||
// ordinary shutdown.
|
||||
if ( exitCode != 0 || exitStatus != QProcess::NormalExit )
|
||||
const bool failed = exitCode != 0 || exitStatus != QProcess::NormalExit;
|
||||
if ( failed )
|
||||
{
|
||||
RiaLogging::error( message );
|
||||
}
|
||||
@@ -220,6 +248,15 @@ void RiaCloudApiService::start()
|
||||
|
||||
m_isResponding = false;
|
||||
emit statusChanged();
|
||||
|
||||
// React immediately rather than waiting for a timer. Deferred via singleShot, since
|
||||
// restarting the process here, during its own finished() signal, would not be safe.
|
||||
if ( failed )
|
||||
{
|
||||
m_startupTimer.stop();
|
||||
m_healthTimer.stop();
|
||||
QTimer::singleShot( 0, this, &RiaCloudApiService::autoRestart );
|
||||
}
|
||||
} );
|
||||
|
||||
m_consecutiveFailures = 0;
|
||||
@@ -248,7 +285,7 @@ bool RiaCloudApiService::waitUntilResponding( int timeoutMs )
|
||||
{
|
||||
if ( m_isResponding ) return true;
|
||||
|
||||
if ( !isRunning() ) start();
|
||||
if ( !isRunning() ) start( StartTrigger::Automatic );
|
||||
|
||||
// Nested event loops below let the periodic health check fire and restart() the service while this
|
||||
// call is still waiting for it to boot. Stop both timers for the duration of the wait to avoid that.
|
||||
@@ -277,7 +314,9 @@ bool RiaCloudApiService::waitUntilResponding( int timeoutMs )
|
||||
|
||||
if ( answered )
|
||||
{
|
||||
m_consecutiveFailures = 0;
|
||||
m_consecutiveFailures = 0;
|
||||
m_consecutiveLaunchFailures = 0;
|
||||
m_hasRespondedSinceStart = true;
|
||||
|
||||
if ( !m_isResponding )
|
||||
{
|
||||
@@ -305,7 +344,14 @@ bool RiaCloudApiService::waitUntilResponding( int timeoutMs )
|
||||
delayLoop.exec();
|
||||
}
|
||||
|
||||
RiaLogging::error( std::format( "Cloud API service: not responding after {} ms, giving up.", elapsed.elapsed() ) );
|
||||
if ( m_hasGivenUp )
|
||||
{
|
||||
RiaLogging::error( std::format( "Cloud API service: not available, giving up. {}", pythonEnvironmentHint ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
RiaLogging::error( std::format( "Cloud API service: not responding after {} ms, giving up.", elapsed.elapsed() ) );
|
||||
}
|
||||
|
||||
// Resume background health-checking if the process is still running, so it can still recover on
|
||||
// its own without another blocking wait.
|
||||
@@ -351,6 +397,35 @@ void RiaCloudApiService::restart()
|
||||
start();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Called when the process exits/crashes, or when a health check times out. If the service has never
|
||||
/// answered a health check since the current start (see m_hasRespondedSinceStart), this is treated as a
|
||||
/// configuration problem rather than a transient crash: retrying is unlikely to help, so this gives up
|
||||
/// immediately instead of restarting, and sets m_hasGivenUp so a later Automatic start() (e.g. Sumo's
|
||||
/// tokenReady) does not quietly re-arm the loop. If the service HAD been responding, this is instead
|
||||
/// treated as an ordinary crash of an otherwise working server, and is retried no matter how many times it
|
||||
/// takes to come back.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaCloudApiService::autoRestart()
|
||||
{
|
||||
m_consecutiveLaunchFailures++;
|
||||
|
||||
if ( !m_hasRespondedSinceStart )
|
||||
{
|
||||
RiaLogging::error( std::format( "Cloud API service: giving up after {} failed launch attempt{}. {}",
|
||||
m_consecutiveLaunchFailures,
|
||||
m_consecutiveLaunchFailures == 1 ? "" : "s",
|
||||
pythonEnvironmentHint ) );
|
||||
m_hasGivenUp = true;
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
RiaLogging::warning( "Cloud API service: not responding, restarting." );
|
||||
stop();
|
||||
launchProcess();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -370,7 +445,7 @@ void RiaCloudApiService::onHealthCheck()
|
||||
{
|
||||
if ( !isRunning() || m_port < 0 )
|
||||
{
|
||||
restart();
|
||||
autoRestart();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -390,8 +465,10 @@ void RiaCloudApiService::onHealthCheck()
|
||||
const int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
|
||||
if ( reply->error() == QNetworkReply::NoError && statusCode == 200 )
|
||||
{
|
||||
m_consecutiveFailures = 0;
|
||||
m_isResponding = true;
|
||||
m_consecutiveFailures = 0;
|
||||
m_consecutiveLaunchFailures = 0;
|
||||
m_hasRespondedSinceStart = true;
|
||||
m_isResponding = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -404,7 +481,7 @@ void RiaCloudApiService::onHealthCheck()
|
||||
|
||||
if ( !m_isResponding && m_consecutiveFailures >= maxConsecutiveFailures )
|
||||
{
|
||||
restart();
|
||||
autoRestart();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -48,7 +48,18 @@ public:
|
||||
RiaCloudApiService( const QString& serverAddress, int wantedPort, QObject* parent = nullptr );
|
||||
~RiaCloudApiService() override;
|
||||
|
||||
void start();
|
||||
// Manual: an explicit user action (Start/Restart button). Automatic: triggered by application logic
|
||||
// (e.g. Sumo authentication completing, or waitUntilResponding()).
|
||||
//
|
||||
// Does not affect whether autoRestart() gives up (see autoRestart()), only what happens after it has:
|
||||
// a later Automatic call is then a no-op (see m_hasGivenUp), while a Manual one tries again.
|
||||
enum class StartTrigger
|
||||
{
|
||||
Manual,
|
||||
Automatic
|
||||
};
|
||||
|
||||
void start( StartTrigger trigger = StartTrigger::Manual );
|
||||
void stop();
|
||||
void restart();
|
||||
|
||||
@@ -84,6 +95,15 @@ private slots:
|
||||
void onReadyReadStandardOutput();
|
||||
|
||||
private:
|
||||
// Called after a crash or an unresponsive health check. Gives up immediately if the service has never
|
||||
// responded since this start (see m_hasRespondedSinceStart) -- likely a config problem, not worth
|
||||
// retrying. Otherwise retries indefinitely, treating it as a crash of a previously working server.
|
||||
void autoRestart();
|
||||
|
||||
// Shared process-launch implementation used by start() and by autoRestart()'s retry. Unlike start(),
|
||||
// leaves m_consecutiveLaunchFailures and m_hasRespondedSinceStart untouched.
|
||||
void launchProcess();
|
||||
|
||||
static int findAvailablePortNumber( int firstPort );
|
||||
static QString serviceWorkingDirectory();
|
||||
static QProcessEnvironment buildProcessEnvironment( const QString& workingDirectory );
|
||||
@@ -99,5 +119,14 @@ private:
|
||||
|
||||
int m_port;
|
||||
int m_consecutiveFailures;
|
||||
int m_consecutiveLaunchFailures;
|
||||
bool m_isResponding;
|
||||
|
||||
// True once the current run has answered a health check; autoRestart() then retries indefinitely
|
||||
// instead of giving up on the first failure. Reset to false in start().
|
||||
bool m_hasRespondedSinceStart;
|
||||
|
||||
// True once autoRestart() has given up on a run that never responded. Blocks a later Automatic
|
||||
// start() (e.g. Sumo's tokenReady) from re-arming the loop; a Manual start/restart clears it.
|
||||
bool m_hasGivenUp;
|
||||
};
|
||||
Reference in new issue
Block a user