How to fix "Attempting to call back into JSAPI during the sweeping phase of GC" error when working with ListView?

Hi,

I have been tinkering with the ListView API. However, I get the error below when I listen for the unbind event of SignalListItemFactory. I’m not sure what the unbind event is all about but I’m not doing anything in the callback. Something like:

factory.connect("unbind", (_, listItem) => {});

The error below occurs when I close the application window.

Attempting to call back into JSAPI during the sweeping phase of GC. This is most likely caused by not destroying a Clutter actor or Gtk+ widget with ::destroy signals connected, but can also be caused by using the destroy(), dispose(), or remove() vfuncs. Because it would crash the application, it has been blocked and the JS callback not invoked.

The offending signal was unbind on GtkSignalListItemFactory 0x556f50c18520.
== Stack trace for context 0x556f502d2170 ==

Below is the code I am trying to experiment with. To see the above error on the command line, run the code and close the application window after that. Try commenting out the code where I listen for the unbind event. The error seems to disappear.

#!usr/bin/env -S gjs -m

import Gtk from "gi://Gtk?version=4.0";
import Gio from "gi://Gio?version=2.0";
import GObject from "gi://GObject";
import system from "system";

const Country = GObject.registerClass(
  {
    GTypeName: "Country",
    Properties: {
      countryName: GObject.ParamSpec.string(
        "countryName",
        "Example Property",
        "A property that holds country name",
        GObject.ParamFlags.READWRITE,
        ""
      ),
      countryId: GObject.ParamSpec.string(
        "countryId",
        "Example Property",
        "A property that holds country Id",
        GObject.ParamFlags.READWRITE,
        ""
      ),
    },
  },
  class Country extends GObject.Object {
    constructor(countryId, countryName) {
      super();
      this._countryId = countryId;
      this._countryName = countryName;
    }

    set countryId(countryId) {
      this._countryId = countryId;
    }

    get countryId() {
      return this._countryId;
    }

    set countryName(countryName) {
      this._countryName = countryName;
    }

    get countryName() {
      return this._countryName;
    }
  }
);

const app = Gtk.Application.new(
  "com.gnome.test",
  Gio.ApplicationFlags.DEFAULT_FLAGS
);

app.connect("startup", (app) => {
  console.log("Inside startup signal handler");
});

app.connect("shutdown", (app) => {
  console.log("Inside shutdown signal handler");
});

app.connect("activate", (app) => {
  let activeWindow = app.activeWindow;

  if (!activeWindow) {
    activeWindow = new Gtk.ApplicationWindow({
      application: app,
      defaultWidth: 450,
      defaultHeight: 650,
      title: "Hello World",
    });
  }

  const model = Gtk.NoSelection.new(Gio.ListStore.new(Country));

  for (let i = 0; i < 1e4; i++) {
    model.model.append(
      new Country(`${i}`, `${Math.floor(Math.random() * 1e4)}`)
    );
  }

  const factory = new Gtk.SignalListItemFactory();

  factory.connect("setup", (_, listItem) => {
    const label = new Gtk.Label();
    listItem.set_child(label);
  });

  factory.connect("bind", (_, listItem) => {
    const label = listItem.child;
    const country = listItem.item;

    country.bind_property(
      "countryId",
      label,
      "label",
      GObject.BindingFlags.SYNC_CREATE
    );
  });

  factory.connect("unbind", (_, listItem) => {});

  const listView = new Gtk.ListView({
    model,
    factory,
    show_separators: true,
  });

  const scrolledWindow = new Gtk.ScrolledWindow({
    child: listView,
    min_content_height: 300,
    min_content_width: 300,
  });

  const vBox = new Gtk.Box({
    orientation: Gtk.Orientation.VERTICAL,
    valign: Gtk.Align.CENTER,
    halign: Gtk.Align.CENTER,
  });

  vBox.append(scrolledWindow);

  activeWindow.child = vBox;
  activeWindow.present();
});

app.run([system.programInvocationName, ...ARGV]);

Similarly, when I try rapidly scrolling through the list up and down for some time, I see the error below and it stops updating the list items in the view.

Attempting to call back into JSAPI during the sweeping phase of GC. This is most likely caused by not destroying a Clutter actor or Gtk+ widget with ::destroy signals connected, but can also be caused by using the destroy(), dispose(), or remove() vfuncs. Because it would crash the application, it has been blocked and the JS callback not invoked.

The offending signal was shutdown on GtkApplication 0x563255205280.
== Stack trace for context 0x563255220170 ==
#0 5632552a7088 i file:///home/mawa/Documents/Projects/learn-gjs/list-view.js:131 (965e63cc3d0 @ 532)
#1 7fffeb5a5910 b self-hosted:2408 (965e63a9650 @ 753)
#2 5632552a6fa8 i self-hosted:2355 (965e63a9600 @ 375)

This seems to be a problem with signal handlers resolving when something else is done blocking the main loop (or something).

If you test your app by scrolling top to bottom a few times, then watch in some system monitor for the CPU usage to return to idle, exiting will not trigger criticals. Regardless, I think you should open a new issue.

Where do you think the issue is? Is it in the code I have written above or in gjs?

Based on the stack I would say GJS, but it’s best to report it either way and the issue can be moved if necessary.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.