[bug] Some QtQuick Canvas drawing coordinates cause application-wide deadlock

asked 2015-04-04 01:54:54 +0300

MartinK gravatar image

updated 2017-03-19 19:53:05 +0300

Description of problem:

Some X & Y coordinate combinations cause a deadlock in the QtQuick Canvas element - this apparently also deadlocks the GUI thread, resulting in the whole application irrecoverably freezing. This is usually followed by the "application is not responding dialog".

Version-Release number of selected component (if applicable):

Name: qt5-qtdeclarative-qtquick Version: 5.2.1+git19-1.23.3

How reproducible:

always

Steps to Reproduce:

  1. save the following minimal reproducer to a file:
import QtQuick 2.0

Canvas {
    id : canvas
    width : 800
    height : 600

    onPaint : {
        console.log("PAINT!")
        var ctx = canvas.getContext("2d")

        // original X & Y values known to cause the deadlock
        //var destX = -2995504
        //var destY = 10467643

        // but these "bigger numbers" don't cause the deadlock
        //var destX =  -3000000000000000
        //var destY =   5000000000000000

        var destX =  -30000000
        var destY =   50000000

        // line width 7 - 5 triggers the deadlock
        ctx.lineWidth = 7
        ctx.arc(destX, destY, 3, 0, 2.0 * Math.PI)
        ctx.stroke()
    }

    onPainted : {
        console.log("PAINTED!")
    }
    Timer {
        interval : 10
        running : true
        repeat : true
        onTriggered : {
            console.log("TIMER TRIGGERED")
            canvas.requestPaint()
        }
    }
}
  1. run the file with qmlscene

Actual results:

The application immediately freezes once launched. Checking the log reveals that the deadlock happened after the onPaint handler fired but before the onPainted handled fired.

Example output:

$ qmlscene canvas_test.qml
[D] QWaylandEglClientBufferIntegration::QWaylandEglClientBufferIntegration:62 - Using Wayland-EGL
[D] onPaint:9 - PAINT!

End of strace output (strace qmlscene canvas_test.qml):

recvmsg(28, {msg_name(0)=NULL, msg_iov(1)=[{"l\2\1\1_\0\0\0C\1\0\0.\0\0\0\6\1s\0\7\0\0\0:1.2296\0"..., 2048}], msg_controllen=0, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 159
recvmsg(28, 0xbed3cb78, MSG_CMSG_CLOEXEC) = -1 EAGAIN (Resource temporarily unavailable)
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
clock_gettime(CLOCK_MONOTONIC, {350351, 705681374}) = 0
poll([{fd=4, events=POLLIN}, {fd=28, events=POLLIN}], 2, 0) = 1 ([{fd=4, revents=POLLIN}])
read(4, "\5\0\0\0\0\0\0\0", 16)         = 8
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0) = 31
connect(31, {sa_family=AF_LOCAL, sun_path=@"/tmp/maliit-server/dbus-bgxOt3rfge"}, 37) = 0
fcntl64(31, F_GETFL)                    = 0x2 (flags O_RDWR)
fcntl64(31, F_SETFL, O_RDWR|O_NONBLOCK) = 0
geteuid32()                             = 100000
getsockname(31, {sa_family=AF_LOCAL, NULL}, [2]) = 0
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
clock_gettime(CLOCK_MONOTONIC, {350351, 711724408}) = 0
poll([{fd=31, events=POLLOUT}], 1, 0)   = 1 ([{fd=31, revents=POLLOUT}])
send(31, "\0", 1, MSG_NOSIGNAL)         = 1
send(31, "AUTH EXTERNAL 313030303030\r\n", 28, MSG_NOSIGNAL) = 28
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
clock_gettime(CLOCK_MONOTONIC, {350351, 718286288}) = 0
poll([{fd=4, events=POLLIN}, {fd=28, events=POLLIN}, {fd=31, events=POLLIN}], 3, 0) = 2 ([{fd=4, revents=POLLIN}, {fd=31, revents=POLLIN}])
read(4, "\7\0\0\0\0\0\0\0", 16)         = 8
read(31, "OK 9c84e1b6d2d892e2427cba0855141"..., 2048) = 37
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
clock_gettime(CLOCK_MONOTONIC, {350351, 721826651}) = 0
write(2, "[D] onPaint:9 - PAINT!\n", 23[D] onPaint:9 - PAINT!
) = 23
mmap2(NULL, 1921024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x46300000

Expected results:

Canvas safely handles the slightly bogus coordinates without deadlocking the whole application. The coordinates are far outside of the bounding box and should be ignored anyway. This is what happens on desktop (Qt 5.4 on Fedora 21 64 bit) - Canvas has no issues with the weird coordinates and no deadlock occurs.

Example output: (from desktop)

$ qmlscene canvas_test.qml 
qml: PAINT!
qml: PAINTED!
qml: TIMER TRIGGERED
qml: TIMER TRIGGERED
qml: PAINT!
qml: PAINTED!
qml: TIMER TRIGGERED
qml: TIMER TRIGGERED
qml: PAINT!
qml: PAINTED!
qml: TIMER TRIGGERED
qml: TIMER TRIGGERED
qml: PAINT!
qml: PAINTED!
qml: TIMER TRIGGERED
qml: TIMER TRIGGERED
qml: PAINT!
qml: PAINTED!

etc.

Additional info:

Note from the minimal reproducer that there seem to be a range of the coordinates that cause the issue - it is not just big numbers causing the deadlock - if the number are too big, the deadlock does not happen. It also seems to depend on line width.

Also even though the coordinates that are causing this deadlock are really rather bogus and not something most applications would want to normally use when drawing on the canvas, this bug is still a serious issue as any miscalculated coordinates that hit the deadlock range will freeze the whole application.

This can easily lead to data loss or worse if a Canvas using OS component would be affected.

Update

Filled on the Mer Bugzilla as 881.

edit retag flag offensive close delete

Comments

1

What happens if you change the Canvas renderStrategy from the default "Immediate" to "Threaded" or "Cooperative"?

otsaloma ( 2015-04-04 04:10:30 +0300 )edit
1

@Osmo Salomaa Good idea! :)

So with renderStrategy : Canvas.Threaded the output looks like this:

[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!
[D] onTriggered:39 - TIMER TRIGGERED
[D] onPaint:11 - PAINT!

No the absence of any PAINTED! messages. ;-) This is because the onPainted signal handler never fires and the timer just queues more and more painting requests. This manifests as a massive memory leak which eventually triggers the OOM killer that kills the application. :) Just to remove the possibility of the timer firing too often and not providing enough time for the painting I tried with a one second interval and the memory leak still happens, only slower.

And renderStrategy : Canvas.Cooperative is the same as with the default rendering strategy - deadlock/freeze after the first PAINT! message.

MartinK ( 2015-04-04 13:02:43 +0300 )edit