[Interest] ssue with QFutureWatcher cancel() delay

Roland Hughes roland at logikalsolutions.com
Sat Sep 24 17:33:16 CEST 2022


On 9/24/22 05:00, Sean Murphy wrote:
> I'm having an issue using the QtConcurrent module for processing large
> images, specifically allowing the user to cancel processing an image
> once it has begun.

Ahhh, welcome to the single-threadiness of Qt and one of the seemingly 
limitless faces of QTBUG-12055

https://bugreports.qt.io/browse/QTBUG-12055

Please let me take you on a short journey so you understand how you got 
screwed and perhaps you can find a way to unscrew yourself? I will steal 
a name from Dr. Who to hopefully make this more accessible to those who 
need it.

In the land of real computers with real operating systems we never had 
threads and never needed them. We had proper weighted processes and an 
OS accounting system that kept track of every byte of memory and 
resource allocated by every process. It kept track of all parents and 
children. Whenever you needed a "thread" to do something you created it 
via one of the many library routines and received its pid.

Acolytes of the Papal Mainframe were taught from birth that Gluttony was 
the deadliest of the Seven Deadly Sins. We never loaded big chunks or 
entire files of anything having any size into RAM and we certainly 
didn't create massive things in RAM. We always read/write reasonably 
sized chunks so every process is always yielding the Papal Processor to 
others while waiting for I/O. This yielding made the universe run 
smoother, allowed others to achieve enlightenment, and provided for a 
clean safe kosher kill via library routines or the command line

STOP/ID=pid

The OS would instantly kill the process, check the accounting system and 
free all resources allocated/locked by said pid.

That's what you are believing happens in the world of QFutureWatcher and 
it is not.

In the world of the x86-wanna-be-a-real-computer-one-day-when-I-grow-up 
world, the OS creators never figured out how to create a proper weight 
process. None of the operating systems created for this platform have a 
robust OS accounting system keeping track of its universe. Instead, what 
is a "process" on these platforms is too light to even be a thread on 
the real computers. Since they needed the ability to create other 
things, they invented threads which are lighter still and mostly outside 
of the realm of accounting. Threads can be left dangling when a parent 
is killed off due to the lack of accounting. Many times resources don't 
get freed when a thread is killed off for the very same reason.

In the x86 based Qt world you have the single main event loop where all 
GUI activity must occur. When you use the threading classes, especially 
with things like QFutureWatcher some portion of your thread activity 
will be at the mercy of the main event loop simply because some events 
will be queued there.

The whole QFuture and QThreadPool concept is great . . . if and only if 
you design your application from the mindset of a parent sending their 
kid off to college knowing full well they will only call when they need 
money. If you want them to live by your rules as they did under your 
roof, then you are going to have a rough time of it.

Too many people developing software on the x86 platform, even those 
writing its operating systems and libraries, never attended services at 
the Church of the Papal Mainframe. They never studied to be an acolyte 
and gluttony is the law of this land rather than being shunned for the 
deadliest of the deadly sins as it truly is.

Control returns to an event loop "at idle" or when you call 
processEvents(). Thiago hates people using that, but the design of Qt 
mandates the use of it, repeatedly, in otherwise good designs.

Those processing images tend to be the worst of the sinners. I've seen 
them read entire DVDs of content into RAM, then thread off a function to 
chew on that data and create yet another entire DVD of content in memory.

What does this do?

It creates the tightest loop you've ever seen. There is no yielding of 
the Papal Processor to others unless you get lucky the the dynamic 
memory allocation does it for you. There is no "idle" time for the event 
loop of your thread to process any events that got queued there. This

mLoadWatcher->disconnect();

never happened. It got queued on an event loop that never got processed. 
See QTBUG-12055. Likewise, this

mLoadWatcher->cancel();

got queued behind it. Your finished() signal was a direct connection, 
and it didn't know it wasn't supposed to happen so when the tightest 
loop in the world finished the function pointer assigned to execute when 
finished went ahead and executed.

Before you go thinking "Hey, I'll dig into the QFuture and QThreadPool 
classes and find out how to get a pid for the thread so I can kill it" 
don't. That is designed for things that run to completion and is a house 
of cards when they don't. Lots of things won't be deallocated/freed. The 
thread from the thread pool most likely will not be returned to the pool 
so you will exhaust the pool after just "a few" of these hard cancels.

**Ultimately you have to get rid of your tight loop.**

If you are using a library that has to have the entire image loaded into 
RAM or cannot have its tiling process pause for a processEvents() call 
then it is an improper library for your use case. If you can force said 
function/library to write the output to a file instead of physical RAM, 
you will create I/O yields that should allow your event loop to execute. 
Don't forget to nuke the file on cancel().

Make your thread into a stand-a-lone executable and get good with spawn.

https://linuxhint.com/posix-spawn-c-programming/

or exec

https://linuxhint.com/exec_linux_system_call_c/

Keep in mind that moving to a version of Qt that has the fix for 12055 
won't fix your problem. As far as I know cancel() is still a queued 
task. If it is changed from a queued task to a hard kill it is going to 
leave all kinds of dangling allocations and resources because none of 
the operating systems developed for the x86 have a proper OS accounting 
system. They cannot properly clean up behind a hard kill of a thread. 
They will do a better job (not perfect, but better) cleaning up behind a 
hard kill of an actual stand-a-lone PID created via spawn/exec.

I know that's not what you want to hear, but that's where you are at. No 
good way to kill a Glutton thread.

-- 
Roland Hughes, President
Logikal Solutions
(630)-205-1593

http://www.theminimumyouneedtoknow.com
http://www.infiniteexposure.net
http://www.johnsmith-book.com
http://www.logikalblog.com
http://www.interestingauthors.com/blog



More information about the Interest mailing list