Skip to content

Commit

Permalink
Don't block when canceling canvas render jobs
Browse files Browse the repository at this point in the history
In some cases canceling render jobs can take a long time. Eg when
using database layers over a sloooooow connection, canceling a job
can be blocked by minutes while waiting for the first batch of feature
fetching to finish. (Since eg postgres features are fetched in batches
of 2000 with no opportunity to abort mid-way through this).

This meant that while the first render allows the GUI to remain
responsive, any subsequent render operations which occured before
the first render completes locks up the whole ui until the first
render can finish cancellation.

With this change, the render cancelation happens with blocking.
It means that you can pan and zoom around a map over of slow
connection without any ui locks.

(cherry-picked from 3b56b79)
  • Loading branch information
nyalldawson committed Mar 2, 2017
1 parent 90fae26 commit 90f381b
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 11 deletions.
1 change: 1 addition & 0 deletions python/core/qgsmaprenderercustompainterjob.sip
Expand Up @@ -19,6 +19,7 @@ class QgsMapRendererCustomPainterJob : QgsMapRendererJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;
virtual QgsLabelingResults* takeLabelingResults() /Transfer/;
Expand Down
2 changes: 2 additions & 0 deletions python/core/qgsmaprendererjob.sip
Expand Up @@ -56,6 +56,8 @@ class QgsMapRendererJob : QObject
//! Does nothing if the rendering is not active.
virtual void cancel() = 0;

virtual void cancelWithoutBlocking() = 0;

//! Block until the job has finished.
virtual void waitForFinished() = 0;

Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprendererparalleljob.sip
Expand Up @@ -18,6 +18,7 @@ class QgsMapRendererParallelJob : QgsMapRendererQImageJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;

Expand Down
1 change: 1 addition & 0 deletions python/core/qgsmaprenderersequentialjob.sip
Expand Up @@ -19,6 +19,7 @@ class QgsMapRendererSequentialJob : QgsMapRendererQImageJob

virtual void start();
virtual void cancel();
virtual void cancelWithoutBlocking();
virtual void waitForFinished();
virtual bool isActive() const;

Expand Down
28 changes: 19 additions & 9 deletions src/core/qgsmaprenderercustompainterjob.cpp
Expand Up @@ -126,14 +126,7 @@ void QgsMapRendererCustomPainterJob::cancel()

QgsDebugMsg( "QPAINTER cancelling" );
disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( futureFinished() ) );

mLabelingRenderContext.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}
cancelWithoutBlocking();

QTime t;
t.start();
Expand All @@ -144,7 +137,24 @@ void QgsMapRendererCustomPainterJob::cancel()

futureFinished();

QgsDebugMsg( "QPAINTER cancelled" );
QgsDebugMsg( "QPAINTER canceled" );
}

void QgsMapRendererCustomPainterJob::cancelWithoutBlocking()
{
if ( !isActive() )
{
QgsDebugMsg( "QPAINTER not running!" );
return;
}

mLabelingRenderContext.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}
}

void QgsMapRendererCustomPainterJob::waitForFinished()
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprenderercustompainterjob.h
Expand Up @@ -38,6 +38,7 @@ class CORE_EXPORT QgsMapRendererCustomPainterJob : public QgsMapRendererJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;
virtual QgsLabelingResults* takeLabelingResults() override;
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsmaprendererjob.h
Expand Up @@ -96,6 +96,13 @@ class CORE_EXPORT QgsMapRendererJob : public QObject
//! Does nothing if the rendering is not active.
virtual void cancel() = 0;

/**
* Triggers cancelation of the rendering job without blocking. The render job will continue
* to operate until it is able to cancel, at which stage the finished() signal will be emitted.
* Does nothing if the rendering is not active.
*/
virtual void cancelWithoutBlocking() = 0;

//! Block until the job has finished.
virtual void waitForFinished() = 0;

Expand Down Expand Up @@ -206,6 +213,7 @@ class CORE_EXPORT QgsMapRendererQImageJob : public QgsMapRendererJob

//! Get a preview/resulting image
virtual QImage renderedImage() = 0;

};


Expand Down
22 changes: 22 additions & 0 deletions src/core/qgsmaprendererparalleljob.cpp
Expand Up @@ -123,6 +123,28 @@ void QgsMapRendererParallelJob::cancel()
Q_ASSERT( mStatus == Idle );
}

void QgsMapRendererParallelJob::cancelWithoutBlocking()
{
if ( !isActive() )
return;

QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );

mLabelingRenderContext.setRenderingStopped( true );
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer && it->renderer->feedback() )
it->renderer->feedback()->cancel();
}

if ( mStatus == RenderingLayers )
{
disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( renderLayersFinished() ) );
connect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( renderingFinished() ) );
}
}

void QgsMapRendererParallelJob::waitForFinished()
{
if ( !isActive() )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprendererparalleljob.h
Expand Up @@ -35,6 +35,7 @@ class CORE_EXPORT QgsMapRendererParallelJob : public QgsMapRendererQImageJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;

Expand Down
9 changes: 9 additions & 0 deletions src/core/qgsmaprenderersequentialjob.cpp
Expand Up @@ -86,6 +86,15 @@ void QgsMapRendererSequentialJob::cancel()
Q_ASSERT( !mInternalJob && !mPainter );
}

void QgsMapRendererSequentialJob::cancelWithoutBlocking()
{
if ( !isActive() )
return;

QgsDebugMsg( "sequential - cancel internal" );
mInternalJob->cancelWithoutBlocking();
}

void QgsMapRendererSequentialJob::waitForFinished()
{
if ( !isActive() )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmaprenderersequentialjob.h
Expand Up @@ -37,6 +37,7 @@ class CORE_EXPORT QgsMapRendererSequentialJob : public QgsMapRendererQImageJob

virtual void start() override;
virtual void cancel() override;
virtual void cancelWithoutBlocking() override;
virtual void waitForFinished() override;
virtual bool isActive() const override;

Expand Down
6 changes: 4 additions & 2 deletions src/gui/qgsmapcanvas.cpp
Expand Up @@ -835,8 +835,10 @@ void QgsMapCanvas::stopRendering()
{
QgsDebugMsg( "CANVAS stop rendering!" );
mJobCancelled = true;
mJob->cancel();
Q_ASSERT( !mJob ); // no need to delete here: already deleted in finished()
disconnect( mJob, SIGNAL( finished() ), this, SLOT( rendererJobFinished() ) );
connect( mJob, SIGNAL( finished() ), mJob, SLOT( deleteLater() ) );
mJob->cancelWithoutBlocking();
mJob = nullptr;
}
}

Expand Down

1 comment on commit 90f381b

@mhugent
Copy link
Contributor

@mhugent mhugent commented on 90f381b Dec 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We observe frequent crashes with PG raster layers (via gdal provider) after this commit. It seems there is a conflict if the last render request is not yet finished and the new one is already coming (although each one has its own GDAL dataset).

Please sign in to comment.