[gjs] Looking for more information about the error "Attempting to call back into JSAPI during the sweeping phase of GC."

I’m the author of ibus-avro , which is a ibus input method engine for writing bangla using GJS/javascript.

The main code has been written 9 years ago and been running fine with little maintenance over the years. Recently I’ve been getting issues relating to the error mentioned in the title. I’ve trying to understand more why this is happening and why.

Example of a error log:

(gjs:4581): Gjs-CRITICAL **: 01:15:51.510: 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.

(gjs:4581): Gjs-CRITICAL **: 01:15:51.510: The offending signal was process-key-event on IBusEngine 0x55ad961c9230.

I think the gjs code behind this is in https://gitlab.gnome.org/GNOME/gjs/-/blob/master/gi/value.cpp#L145 .

“Attempting to call back into JSAPI during the sweeping phase of GC”. JSAPI is what in this context? Javascript or C? When a GC sweep is happening, is it not supported to handle an event?

1 Like

JSAPI is what in this context?

Basically JavaScript. Likely this means a signal callback was invoked during a collection sweep.

When a GC sweep is happening, is it not supported to handle an event?

JavaScript uses a tracing garbage collector, operating on a tree of references. When an object can no longer be traced to a “rooted” object (ie. not assigned to a variable in scope), it is collected and the memory freed.

To prevent changes to the tree of objects during a garbage collection sweep, all JavaScript execution is halted. This error probably means the signal callback (that executes JavaScript code) is being invoked by C code in the IBus codebase, perhaps by being queued in the event loop.

Thank you for the reply. That’s basically what I suspected too. Problem is, what is the solution? If JavaScript GC is sweeping, events from C land need to be handled, they should be placed in a queue, then when GC is finished, queued events should be handled right?

Right now it just breaks. Once this happens, JavaScript stops receiving events. Any idea how I can solve this?

Same here.

I spent some weeks trying to debug an application, since the 3.38 update. It happens randomly when the application run in background, without any noticeable reason. Sometimes the logs tell me about a ‘draw’ signal, sometimes ‘size_allocate’, sometimes a gstreamer bus message, etc.

The only rational thing I found is the more I remove widgets and features (i.e. the less handlers there are), the less often it happens.

I guess it would depend on the specific API you were using and how those signals are fired, since typically GObject signals are synchronous. I don’t know if it would make a difference if you implement the virtual functions instead of connecting to the signals.

If you think it’s a problem in GJS, the best thing to do would be to find a minimal reproducer and then open an issue on Gitlab.

In general this happens when a signal is fired as a result of a JS object getting cleaned up. Normally that doesn’t happen, because code normally isn’t allowed to run on the main thread during garbage collection. It can happen in two situations that I know of:

  • There is a JS handler connected to the destroy signal of a widget, or a class implements the vfunc_destroy virtual function, or some similar situation. In this case, the signal is triggered when the garbage collector collects the object, which is not allowed.
  • Some C code fires the signal when not on the main thread.

There’s not much you can do about the second case besides avoid connecting to that signal in JS if it’s not guaranteed to be run on the main thread. JS is single threaded, always has been, and probably always will be, so you cannot run JS code on another thread.

To fix the first case, you should in general avoid using the destroy signal for cleanup, and do any cleanup when you are done with the widget instead. When you clean up the widget, also make sure to disconnect any signal handlers that are connected to signals that might fire as a result of the widget being collected.

1 Like

My situation is not the first one. (the destroy etc…)

This are the signals I’m connecting to:

    engine.connect('process-key-event', engine_process_key_event );
    engine.connect('candidate-clicked', engine_candidate_clicked );
    engine.connect('focus-out', engine_focus_out );
    engine.connect('focus-in', engine_focus_in );
    engine.connect('property-activate', engine_property_activate );

full code is : ibus-avro/main-gjs.js at master · sarim/ibus-avro (github.com)

Mainly the process-key-event signal is fired from IBus to my engine for every key event. So user is writing something, after few hundreds or thousands of events processed, suddenly I get the flooding of the error I post in first post. For every subsequent event coming to my engine, I get that error and the event is not handled, essentially the engine is frozen.

I’m not sure how I can control threading here, as this is not a gui application, but an IBus engine, there no widgets or gui elements …

I’m slowly getting the idea that in current gjs / gobject-introspection implementation this is not solvable :confused:

Yeah that seems like the logical next step. The problem is reliably triggering GC in JS and send an event from C at the exact same time GC is running :confused:

Edit: Another thought: I found this article while googling The Big GNOME Shell Memory Leak Has Been Plugged, Might Be Backported To 3.28 - Phoronix
As I understand, because of this, the frequency of triggering GC has been greatly increased. I wonder if my problem arises from that as well, As more GC is happening, more the chance of timing of a event coinciding with that.

Edit 2:
From what I understand the problem is two fold.

  1. Failure to handle a event if GC is running at the time.
  2. Failure to handle every subsequent event if problem 1 happens. I’m pretty sure this is a bug of gjs.

There must be GUI elements, even if IBus hides them from you; the IBus.Engine type has signals such as focus-in and focus-out.

I’m not 100% sure, but I think this might be a misunderstanding. Either the event is triggered in C off the main thread (in which case you cannot guard against it and the only thing we can do is block it) or it is triggered in C on the main thread as a result of collecting the object (in which case you need to disconnect the signal handler before that happens.) There is no third possibility. Code execution on the main thread is paused during GC, so there is no possibility for C code to send an event on the main thread while the GC is running unless it is a direct consequence of GC-ing the object.

No, the gui elements are running on a separate process altogether. There is three main part (As I understand it).

  1. ibus-immodule for gtk (or qt, or clutter etc…)
  2. IBus daemon
  3. IBus engines. (This is what my code is)

All are separate process communicating over DBus. IBus itself wraps DBus events a bit, to make things easier. But all those events like focus-in and focus-outetc… are coming over from IBus Daemon via DBus to my engine.

OK, I see. But the problem can still occur if there are no widgets, for example if the DBus proxy object is destroyed when the DBus name disappears from the bus, and one of the signals is fired in response to the object being destroyed.

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