Qt: Memory leak

Created on 12 Oct 2017  ·  10Comments  ·  Source: therecipe/qt

package main

import (
    "time"

    "github.com/therecipe/qt/core"
)

func main() {
    for i := 0; i < 100000; i++ {
        point := core.NewQPoint()
        point.DestroyQPoint()
        time.Sleep(1)
    }
}

Hi,

Using Win7 64-bit. This program will cause a memory leak.
I tried DestroyQPoint(), But it does not work.

Thanks.

All 10 comments

@yizheneng
Out of pure curiosity how are you detecting a memory leak here. Looking at the binding code for NewQPoint and DestroyQPoint it is extremely straight forward. Some things to note from Go's perspective.

  • Go's Garbage Collector doesn't typically release memory back to the os after a collection, so memory usage will not appear to shrink, just grow slower as the GC reuses memory for app.
  • How you are testing for the memory leak in a running application is important, I have not had much luck with valgrind on Go applications, much more luck with the profile tools that Go has, plus things like ReadMemStats
  • with time.Sleep(1) your example is way to fast to look for memory leaks while running, Perhaps reduce the loop drastically, and try time.Sleep(1 * time.Seconds)
  • The bindings that @therecipe has setup does use / probably has to use (he would know better than I) runtime.SetFinalizer when destroying the C version of the Qt Objects.
  • I think this is the only way to deal with binding from a GC environment like Go to a C++ library like Qt
  • One drawback of SetFinalizer is it is not guaranteed to run before the application exits. So I do think that your example, could in theory have something that might look like a memory retained after the program exits. If this is what you are detecting?
  • However your example is not actually real world, if you really want to dig deeper into what you are potentially seeing, I would recommend, first mention how you are measuring it, and second setup the Actual QApplication, and QWindow, including the window show, the QApplication_exec loop, and then your code in the middle, before measuring.

@rashwell
Thinks for your answer.
Here is my code. https://github.com/yizheneng/MemoryLeakingCode.git
I want to move a window without title bar.
When moved the window it cause this issues.

package widgets

import (
    "github.com/therecipe/qt/core"
    "github.com/therecipe/qt/gui"
    "github.com/therecipe/qt/widgets"
)

type Title struct {
    *widgets.QWidget
    mainWindows *widgets.QWidget

    mouseFlag bool
    windowPos *core.QPoint
    mousePos  *core.QPoint
}

func NewTitle(mainWindows *widgets.QWidget) *Title {
    title := &Title{}
    title.QWidget = widgets.NewQWidget(nil, 0)
    title.mainWindows = mainWindows
    title.mouseFlag = false
    title.windowPos = nil
    title.mousePos = nil

    title.SetObjectName("Title")

    title.ConnectMousePressEvent(func(event *gui.QMouseEvent) {
        title.mouseFlag = true

        if title.windowPos != nil {
            title.windowPos.DestroyQPoint()
        }

        if title.mousePos != nil {
            title.mousePos.DestroyQPoint()
        }

        title.windowPos = mainWindows.Pos()
        title.mousePos = event.GlobalPos()
    })

    title.ConnectMouseReleaseEvent(func(event *gui.QMouseEvent) {
        title.mouseFlag = false
    })

    title.ConnectMouseMoveEvent(func(event *gui.QMouseEvent) {
        if title.mouseFlag {
            mainWindows.Move2(title.windowPos.X()+event.GlobalPos().X()-title.mousePos.X(), title.windowPos.Y()+event.GlobalPos().Y()-title.mousePos.Y())
        }
    })

    return title
}

I did take a look at your example at your repository.
I will try and help you get this example in working shape (prob later this evening).
But I wanted to throw out a couple of notes:

First with your goal in mind check out the RasterWindow Example that is included.

Some quick notes, about how your code though currently is:
First a QWidget which you are trying to subclass and create your own Title bar widget, is just a container, in your current example it doesn't have its own Paint, so it doesn't have anything to latch onto.
Second, when you connect to mouse events, you have to let them move on as well. Otherwise you are interrupting the event stack and it is halting with your routine. So think like this, it is a stack, you want to update your stuff, but then pass the event back onto the default.

Finally, because I see in your code a lot of what I tried the first time, I tried this but you should not do structs like:

type Title struct {
    *widgets.QWidget
    mainWindows *widgets.QWidget

    mouseFlag bool
    windowPos *core.QPoint
    mousePos  *core.QPoint
}

Instead you do like this:

type Title struct {
    widgets.QWidget
    mainWindows *widgets.QWidget

    mouseFlag bool
    windowPos *core.QPoint
    mousePos  *core.QPoint
}

Not a pointer but the actual object you are sub classing. But I'll have to give you a better example this evening because then you would switch NewTitle method more like:

func initTitle(mainWindows *widgets.QWidget) *Title {
    title := NewTitle(nil, core.Qt__Widget)  // qmoc Automatically will create this NewTitle method 
    title.QWidget = widgets.NewQWidget(nil, 0)
    title.mainWindows = mainWindows
    title.mouseFlag = false
    title.windowPos = nil
    title.mousePos = nil

...

Again Sorry for the quick reply, off to work, but if I get time this evening I can prob get your concept of a frameless window with your own title bar working.

Ok to have a working example for me, I borrowed a lot from the example Advanced Clock

I did my example below with a bit more for your Title Widget, because yours wasn't actually drawing or didn't contain any widgets itself, and when I tried your example from your repository there basically wasn't anything to click on to start the mouse events. If you want to instead use style sheet stuff to style a QWidget that is subclassed you would still have to connect a paint event and gather and use QStyleOption, etc See the bottom of this page QtWidget Background

For now though since I was adding a paint event to your Title Widget, I just colored it there, and gathered the title from your main window and painted it in the Title Widget. It was fun figuring out which DrawText method in this qt library to use to get the title text to center :).

This got me to a working example that I think meets your intent of a frame-less window with your own title bar widget for dragging the window.

To try and troubleshoot your mention of memory leaks though, I tried to use your same logic for gathering the mouse and main window position, and Destroying QPoint etc. But I am afraid I can't recreate your mention of a memory leak.

My guess is that your app crashes because you are listening on mouse events for a title widget that had no drawable area, or object to click on in the first place, so when you tried to get the pos of the event, it simply failed on the Qt side of things because there isn't a position of a widget that has no area defined.

Anyway here is my version of a working example.

Or almost working, it turns out that while this works, on reviewing I shouldn't be resetting the layout of a QMainWindow. But I assume you could just use widgets instead, and create your own SetWindowTitle type of method for the title widget. But since it runs as is hopefully this will give you a start.

package main

import (
    "os"

    "github.com/therecipe/qt/core"
    "github.com/therecipe/qt/gui"
    "github.com/therecipe/qt/widgets"
)

// Title a replacement window Title bar widget
type Title struct {
    widgets.QWidget

    _      func() `constructor:"init"`
    render func(painter *gui.QPainter)

    parentw   *widgets.QMainWindow
    mouseFlag bool
    windowPos *core.QPoint
    mousePos  *core.QPoint
}

func (twidget *Title) init() {
    twidget.SetObjectName("Title")
    twidget.Resize2(800, 50)
    twidget.ConnectPaintEvent(twidget.renderNow)

    twidget.mouseFlag = false
    twidget.windowPos = nil
    twidget.mousePos = nil

    twidget.ConnectMouseMoveEvent(twidget.moveevent)
    twidget.ConnectMousePressEvent(twidget.pressevent)
    twidget.ConnectMouseReleaseEvent(twidget.releaseevent)

    twidget.render = twidget.renderFunc
}

func (twidget *Title) moveevent(ev *gui.QMouseEvent) {
    nMouseState := ev.MouseState()
    bLeftButtonPressed := bool(nMouseState == core.Qt__LeftButton)
    if bLeftButtonPressed {
        if twidget.mouseFlag {
            twidget.parentw.Move2(twidget.windowPos.X()+ev.GlobalPos().X()-twidget.mousePos.X(), twidget.windowPos.Y()+ev.GlobalPos().Y()-twidget.mousePos.Y())
        }
    }
}

func (twidget *Title) pressevent(ev *gui.QMouseEvent) {
    nMouseState := ev.MouseState()
    bLeftButtonPressed := bool(nMouseState == core.Qt__LeftButton)
    if bLeftButtonPressed {
        twidget.mouseFlag = true
        if twidget.windowPos != nil {
            twidget.windowPos.DestroyQPoint()
        }

        if twidget.mousePos != nil {
            twidget.mousePos.DestroyQPoint()
        }

        twidget.windowPos = twidget.parentw.Pos()
        twidget.mousePos = ev.GlobalPos()
    }
}

func (twidget *Title) releaseevent(ev *gui.QMouseEvent) {
    twidget.mouseFlag = false
}

func (twidget *Title) renderNow(ev *gui.QPaintEvent) {
    qr3 := gui.NewQRegion2(0, 0, twidget.Width(), twidget.Height(), gui.QRegion__Rectangle)
    twidget.BackingStore().BeginPaint(qr3)
    device := twidget.BackingStore().PaintDevice()
    painter := gui.NewQPainter2(device)
    defer painter.DestroyQPainter()
    twidget.render(painter)
    twidget.BackingStore().EndPaint()
    twidget.BackingStore().Flush(gui.NewQRegion2(0, 0, twidget.Width(), twidget.Height(), gui.QRegion__Rectangle), nil, core.NewQPoint())
}

func (twidget *Title) renderFunc(p *gui.QPainter) {
    background := gui.NewQPolygon3([]*core.QPoint{
        core.NewQPoint2(0, 0),
        core.NewQPoint2(twidget.Width(), 0),
        core.NewQPoint2(twidget.Width(), twidget.Height()),
        core.NewQPoint2(0, twidget.Height()),
    })
    backgroundColor := gui.NewQColor3(127, 0, 127, 255)
    p.SetPen3(core.Qt__NoPen)
    p.SetBrush(gui.NewQBrush3(backgroundColor, 1))
    p.Save()
    p.DrawConvexPolygon4(background)
    p.Restore()
    textColor := gui.NewQColor3(230, 230, 230, 255)
    windowtitle := twidget.parentw.WindowTitle()
    p.SetPen2(textColor)
    rect := twidget.Rect()
    p.Save()
    p.DrawText6(rect, int(core.Qt__AlignCenter), windowtitle, nil)
    p.Restore()
}

func main() {
    app := widgets.NewQApplication(len(os.Args), os.Args)

    mainw := widgets.NewQMainWindow(nil, core.Qt__Widget)
    mainw.SetMinimumSize2(800, 600)
    mainw.SetWindowFlags(core.Qt__FramelessWindowHint)

    titleWidget := NewTitle(mainw, core.Qt__FramelessWindowHint)
    titleWidget.parentw = mainw
    mainWidget := widgets.NewQWidget(mainw, core.Qt__Widget)
    label := widgets.NewQLabel2("Main Widget", mainWidget, core.Qt__Widget)

    vbox := widgets.NewQVBoxLayout()
    vbox.AddWidget(label, 0, core.Qt__AlignCenter)
    mainWidget.SetLayout(vbox)
    mainWidget.SetGeometry2(0, titleWidget.Height(), mainw.Width(), mainw.Height()-titleWidget.Height())

    gbox := widgets.NewQGridLayout(mainw)
    gbox.AddWidget(titleWidget, 0, 0, core.Qt__AlignTop)
    gbox.AddWidget(mainWidget, 1, 0, core.Qt__AlignBottom)

    mainw.SetWindowTitle("起重机监控中心")
    mainw.SetObjectName("MainWindow")
    mainw.SetLayout(gbox)

    mainw.Show()

    app.Exec()
}

I did notice the example OpenGlWidget code leaks memory slowly as it animates.

single file example:

package main

import (
    "os"
    "github.com/therecipe/qt/core"
    "github.com/therecipe/qt/gui"
    "github.com/therecipe/qt/widgets"
)

type Window struct {
    widgets.QWidget
    _      func() `constructor:"init"`
    helper *Helper
}

type Helper struct {
    core.QObject
    _           func() `constructor:"init"`
    background  *gui.QBrush
    circleBrush *gui.QBrush
    textFont    *gui.QFont
    circlePen   *gui.QPen
    textPen     *gui.QPen
}

type GLWidget struct {
    widgets.QOpenGLWidget
    _       func() `constructor:"init"`
    _       func() `slot:"animate"`
    Helper  *Helper
    elapsed int
}

func main() {
    widgets.NewQApplication(len(os.Args), os.Args)

    fmt := gui.NewQSurfaceFormat()
    fmt.SetSamples(4)
    gui.QSurfaceFormat_SetDefaultFormat(fmt)

    window := NewWindow(nil, 0)
    window.Show()

    widgets.QApplication_Exec()
}

func (w *Window) init() {
    w.helper = NewHelper(w)
    w.SetWindowTitle("2D Painting on OpenGL Widgets")

    openGL := NewGLWidget(w, 0)
    openGL.Helper = w.helper

    openGLLabel := widgets.NewQLabel2("OpenGL", w, 0)
    openGLLabel.SetAlignment(core.Qt__AlignHCenter)

    layout := widgets.NewQGridLayout(nil)
    layout.AddWidget(openGL, 0, 1, 0)
    layout.AddWidget(openGLLabel, 1, 1, 0)
    w.SetLayout(layout)

    timer := core.NewQTimer(nil)
    timer.ConnectTimeout(openGL.Animate)
    timer.Start(50)
}

func (w *GLWidget) init() {
    w.SetFixedSize2(200, 200)
    w.SetAutoFillBackground(false)

    w.ConnectAnimate(w.animate)
    w.ConnectPaintEvent(w.paintEvent)
}

func (w *GLWidget) animate() {
    w.elapsed = (w.elapsed + 50) % 1000
    w.Update()
}

func (w *GLWidget) paintEvent(event *gui.QPaintEvent) {
    painter := gui.NewQPainter2(w)
    painter.SetRenderHint(gui.QPainter__Antialiasing, true)
    w.Helper.Paint(painter, event, w.elapsed)
    painter.DestroyQPainter()
}

func (h *Helper) init() {
    gradient := gui.NewQLinearGradient2(core.NewQPointF3(50, -20), core.NewQPointF3(80, 20))
    gradient.SetColorAt(0, gui.NewQColor2(core.Qt__white))
    gradient.SetColorAt(1, gui.NewQColor3(0xa6, 0xce, 0x39, 255))

    h.background = gui.NewQBrush3(gui.NewQColor3(64, 32, 64, 255), 1)
    h.circleBrush = gui.NewQBrush10(gradient)
    h.circlePen = gui.NewQPen3(gui.NewQColor2(core.Qt__black))
    h.circlePen.SetWidth(1)
    h.textPen = gui.NewQPen3(gui.NewQColor2(core.Qt__white))
    h.textFont = gui.NewQFont()
    h.textFont.SetPixelSize(50)
}

func (h *Helper) Paint(painter *gui.QPainter, event *gui.QPaintEvent, elapsed int) {
    painter.FillRect3(event.Rect(), h.background)
    painter.Translate3(100, 100)

    painter.Save()
    painter.SetBrush(h.circleBrush)
    painter.SetPen(h.circlePen)
    painter.Rotate(float64(elapsed) * float64(0.03))

    r := float64(elapsed) / 1000
    n := float64(30)
    for i := 0; i < int(n); {
        i++
        painter.Rotate(30)
        factor := (float64(i) + r) / n
        radius := 0 + 120*factor
        circleRadius := 1 + factor*20
        painter.DrawEllipse(core.NewQRectF4(radius, -circleRadius, circleRadius*2, circleRadius*2))
    }
    painter.Restore()

    painter.SetPen(h.textPen)
    painter.SetFont(h.textFont)
    painter.DrawText6(core.NewQRect4(-50, -50, 100, 100), int(core.Qt__AlignCenter), "Qt", core.NewQRect())
}

I found even window focus cause memory increase, this is a very serious problem.

@yizheneng @rashwell @5k3105
I just fixed some memory leaks with: https://github.com/therecipe/qt/commit/299f21022aa217faace4fae811ddd2ede4e35c9d

Please let me know if this fixes the issues you were experiencing.
I tested the examples posted in here, and they shouldn't leak memory anymore.

Hi @therecipe

I've just started looking into Qt over the past couple of days, and am trying to write a modular "bar" application for Linux, similar to things like Polybar, using this library. I've only just really started working on it, but I noticed some interesting behaviour around memory usage.

I've made other persistent Go applications that make use of native UI frameworks and not really noticed any memory "issues" with them.

All I've made so far is a bar with 2 things on it - a clock, and a button that opens up a menu to trigger shutdown, restart, and log off (for i3 at least). But even with such a small amount of functionality, I'm seeing memory usage start at around 32MiB. I left it running overnight last night as I noticed the memory gradually increasing over time, and it got to about 56MiB overnight.

My question really is, is this normal? I understand that Go and your OS don't necessarily actually free up memory when it's done with it unless there's a more urgent need to do so. But I have observed other Go applications' memory increase, and then decrease again and stay roughly the same. Perhaps the behaviour is normal for Qt and it does something internally that means it's memory increases up to a point and then is either cleared, or just stays the same?

Feel free to take a look at the source code if it is helps. I'm going to try investigate more myself, but I don't know C or C++ to look into that side of it in much depth.

@seeruk
Hey

Yeah, for widget applications the memory usage starts at around 30mb and then usually grows to something between 50-75mb and stays there, this is normal. And your observations match what I observed, the memory is not really freed unless there is an urgent need for it. (If you want to test it, you can force this with stress)

Btw, maybe take a look at this example to see how you can test the function that you suspect is leaking memory.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

angiglesias picture angiglesias  ·  5Comments

iMaxopoly picture iMaxopoly  ·  3Comments

xlucas picture xlucas  ·  7Comments

medvednikov picture medvednikov  ·  5Comments

quantonganh picture quantonganh  ·  6Comments