Avoiding segfault on exit when using QML.jl

My real code runs great, but for some reason gives a segfault when exiting the Julia REPL. Then I saw JuliaPropertyMaps should be a function in the docs .

I modified the doc example to show my problem (I need “Hi” to become “Hello”):

using QML

# Propertymap is a pointer, so it needs to be a function and not a const value
props() = JuliaPropertyMap(
    "hi" => "Hi",
)

world() = " world!"

# Convenience function to call all relevant function macros
function define_funcs()
  @qmlfunction world
end

function main(qml_file)
  define_funcs()
  loadqml(qml_file; props=props())
  exec()
end


mktempdir() do folder
            path = joinpath(folder, "frommodule.qml")
            write(path, """
                  import QtQuick
                  import QtQuick.Controls
                  import org.julialang
                  
                  ApplicationWindow {
                    id: mainWin
                    title: "My Application"
                    width: 400
                    height: 400
                    visible: true
                  
                    Rectangle {
                      anchors.fill: parent
                      color: "red"
                  
                      Button {
                        anchors.centerIn: parent
                        text: props.hi + Julia.world()
                        onClicked: { props.hi = "Hello"; console.log("Value:", props.hi) }
                      }
                    }
                  }            
                  """)
            main(path)

          end

props()

Results:

Qt Debug: Value: Hello (file:///tmp/jl_Lq0Tc5/frommodule.qml:19, expression for onClicked)

julia> props()
JuliaPropertyMap with 1 entry:
  "hi" => "Hi"

If I use this method to avoid the segfault (not sure yet if that will work for this case), how can I pass the new value to Julia?

Bonus
Here is the error I am trying to avoid on exiting Julia:

[2093301] signal 11 (1): Segmentation fault
in expression starting at none:0
unknown function (ip: 0x20)
_ZN5jlcxx6detail11CallFunctorIvJPN7qmlwrap14JuliaItemModelEEE5applyEPKvNS_13WrappedCppPtrE at /home/user/.julia/artifacts/7bc654121da55f7ea29f116c66155e8e5ebb43ff/lib/libjlqml.so (unknown line)
Allocations: 42114717 (Pool: 42113248; Big: 1469); GC: 25
Segmentation fault (core dumped)

The relevant code to the segfault looks like the below. Not a MWE unfortunately.

Julia:

mutable struct Ivals
    name :: String
    idx  :: Int
    sel  :: Bool
end

function update_params(dat)
    params["ivals"] = new_ivals
end

@qmlfunction load_data
@qmlfunction update_params

ivals  = JuliaItemModel(Ivals.(names(dat)[ivals0], ivals0, false))
params = JuliaPropertyMap("ivals" = ivals)

loadqml(qmlfile,  params  = params)

QML:

params.dat = Julia.load_data(params.fpath)
Julia.update_params(params.dat)

Additional info
Say instead I try to assign the new values from the QML (which works for other values that are not JuliaItemModel):

Julia:

function update_params(dat)
    return new_ivals
end

QML:

params.ivals = Julia.update_params(params.dat)

Before ivals is updated:

println(ivals)
QML.JuliaItemModelAllocated(Ptr{Nothing} @0x00000000035fee70)

println(propertynames(ivals))
(:cpp_object,)

After update:

println(ivals)
QML.QObjectDereferenced(Ptr{Nothing} @0x00000000058be580)

println(propertynames(ivals))
(:cpp_object,)

Then I start seeing a different error to track down:

MethodError: no method matching length(::QML.QObjectDereferenced)

Maybe that is the easiest fix? Any ideas?

What is the output of versioninfo()?

And what is the output of

using Pkg
Pkg.status()

?

julia> versioninfo()
Julia Version 1.11.5
Commit 760b2e5b739 (2025-04-14 06:53 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 4 × Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, skylake)
Threads: 1 default, 0 interactive, 1 GC (on 4 virtual cores)

julia> Pkg.status()
  [336ed68f] CSV v0.10.15
  [1f15a43c] CxxWrap v0.16.2
  [3b531cbf] DataConvenience v0.3.6
  [a93c6f00] DataFrames v1.7.0
  [c601a237] Interact v0.10.5
  [510215fc] Observables v0.5.5
  [d96e819e] Parameters v0.12.3
  [2db162a6] QML v0.9.2
  [2913bbd2] StatsBase v0.34.4
  [f3b207a7] StatsPlots v0.15.7

I had the same segfault on my Linux AMD Threadripper as well.

Would you like me to work out a MWE or anything?

I thought this was already known behavior based on the docs.

Yes, indeed, it would be nice to have an MWE.

Ok… now it is actually using the temp folder. Run this, click the button, close the Qt window, then exit julia. You should see the segfault.

MWE:

using QML

# const qml_file = joinpath(pwd(), "code", "qml", "test.qml")

function update_props()
    props["fruits"] = fruits2
end

function main(qml_file, props)
    loadqml(qml_file, props = props)
    exec()
end

mutable struct Fruit
    name::String
    cost::Float64
end

fruits  = JuliaItemModel([Fruit("apple", 1.0), Fruit("orange", 2.0)])
fruits2 = JuliaItemModel([Fruit("banana", 1.0), Fruit("pear", 2.0)])

props = JuliaPropertyMap("fruits" => fruits)

@qmlfunction update_props

# main(qml_file, props)


mktempdir() do folder
            path = joinpath(folder, "test.qml")
            write(path, """
                  import QtQuick
                  import QtQuick.Controls
                  import QtQuick.Layouts
                  import org.julialang

                  ApplicationWindow {
                      id: mainWin
                      title: "My Application"
                      width: 400
                      height: 400
                      visible: true
                    
                      ColumnLayout{
                        anchors.fill: parent
                    
                          Button {
                              height: 200
                              Layout.fillWidth: true
                              text: "Update Fruit"
                              onClicked: { 
                                  Julia.update_props()
                                  console.log("Value:", props.fruits) 
                              }
                         }
                          ListView {
                              model: props.fruits
                              Layout.fillHeight: true
                              Layout.fillWidth: true
                              delegate: Row {
                                  Text {
                                      text: name
                                  }
                              }
                          }
                      }
                  }
                  """)
            main(path, props)
          end

Error:

[2151785] signal 11 (1): Segmentation fault
in expression starting at none:0
unknown function (ip: 0x20)
_ZN5jlcxx6detail11CallFunctorIvJPN7qmlwrap14JuliaItemModelEEE5applyEPKvNS_13WrappedCppPtrE at /home/user/.julia/artifacts/7bc654121da55f7ea29f116c66155e8e5ebb43ff/lib/libjlqml.so (unknown line)
Allocations: 5190401 (Pool: 5189939; Big: 462); GC: 6
Segmentation fault (core dumped)

I can reproduce the issue, but only if I click on “update fruit” before closing the window and exiting Julia.

Where in the documentation did you find the indication that this would be to be expected?

Yes, it occurs when replacing a JuliaItemModel.

Not specifically this scenario, just guessed it was related to this:

I see the same error using a Connections block:

using QML

# const qml_file = joinpath(pwd(), "code", "qml", "test.qml")

function update_props()
    props["fruits"] = fruits2
end

function main(qml_file, props)
    loadqml(qml_file, props = props)
    exec()
end

mutable struct Fruit
    name::String
    cost::Float64
end

fruits  = JuliaItemModel([Fruit("apple", 1.0), Fruit("orange", 2.0)])
fruits2 = JuliaItemModel([Fruit("banana", 1.0), Fruit("pear", 2.0)])

props = JuliaPropertyMap("fruits" => fruits)

@qmlfunction update_props

# main(qml_file, props)


mktempdir() do folder
            path = joinpath(folder, "test.qml")
            write(path, """
                  import QtQuick
                  import QtQuick.Controls
                  import QtQuick.Layouts
                  import org.julialang

                  ApplicationWindow {
                      id: mainWin
                      title: "My Application"
                      width: 400
                      height: 400
                      visible: true

                      Connections {
                        target: button
                        // onClicked: Julia.update_props()
                        function onClicked() { Julia.update_props() }
                      }
                    
                      ColumnLayout{
                        anchors.fill: parent
                    
                          Button {
                              id: button
                              height: 200
                              Layout.fillWidth: true
                              text: "Update Fruit"
                              // onClicked: { 
                              //     Julia.update_props()
                              //     console.log("Value:", props.fruits) 
                              // }
                         }
                          ListView {
                              model: props.fruits
                              Layout.fillHeight: true
                              Layout.fillWidth: true
                              delegate: Row {
                                  Text {
                                      text: name
                                  }
                              }
                          }
                      }
                  }
                  """)
            main(path, props)
          end

Also the syntax shown in the readme is deprecated and returns a warning:

QML Connections: Implicitly defined onFoo properties in Connections are deprecated. Use this syntax instead: function onFoo(<arguments>)

From the QML docs:

When connecting to signals in QML, the usual way is to create an “on” handler that reacts when a signal is received
[…]
Note: For backwards compatibility you can also specify the signal handlers without function, like you would specify them directly in the target object. This is not recommended. If you specify one signal handler this way, then all signal handlers specified as function in the same Connections object are ignored.

So it seems I was using it as expected after all, is there a workaround?

I suggest that you create an issue at: GitHub · Where software is built

My knowledge is somewhat limited, and other developers should comment on this question. Still not clear to me if:

  • this is a bug
  • or an error in the documentation
  • or wrong usage of the package

Done: Segfault when exiting Julia after replacing a JuliaItemModel · Issue #218 · JuliaGraphics/QML.jl · GitHub

This works. First clear the array in QML, then push to it from Julia:

using QML

# const qml_file = joinpath(pwd(), "code", "qml", "test.qml")

function update_props()
    props["fruits"] = fruits2
end

# First clear props in QML
function update_props()
    [push!(props["fruits"], fruits2[i]) for i in 1:length(fruits2)]
end


function main(qml_file, props)
    loadqml(qml_file, props = props)
    exec()
end

mutable struct Fruit
    name::String
    cost::Float64
end

fruits  = JuliaItemModel([Fruit("apple", 1.0), Fruit("orange", 2.0)])
fruits2 = JuliaItemModel([Fruit("banana", 1.0), Fruit("pear", 2.0)])

props = JuliaPropertyMap("fruits" => fruits)

@qmlfunction update_props

# main(qml_file, props)


mktempdir() do folder
            path = joinpath(folder, "test.qml")
            write(path, """
                  import QtQuick
                  import QtQuick.Controls
                  import QtQuick.Layouts
                  import org.julialang

                  ApplicationWindow {
                      id: mainWin
                      title: "My Application"
                      width: 400
                      height: 400
                      visible: true
                    
                      ColumnLayout{
                        anchors.fill: parent
                    
                          Button {
                              height: 200
                              Layout.fillWidth: true
                              text: "Update Fruit"
                              onClicked: {
                                  // First clear then update from julia
                                  props.fruits.clear() 
                                  Julia.update_props()
                              }
                         }
                          ListView {
                              model: props.fruits
                              Layout.fillHeight: true
                              Layout.fillWidth: true
                              delegate: Row {
                                  Text {
                                      text: name
                                  }
                              }
                          }
                      }
                  }
                  """)
            main(path, props)
          end

Still there should be a warning instead of that segfault.