GtkProgressBar not getting updated

Hi,

In my application, I have embedded a GtkProgressBar in a stack of widgets, which are used as the title widget for a GtkNotebook page. The GtkProgressBar is supposed to get updated and be a progress indicator for a background process.

However, when using pulsing, the GtkProgressBar is not working properly. I’ve included an example application that when run shows the issue.

#!/usr/bin/python
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
import time
import threading


def run_thread(cb, widget, event):
    while not event.isSet():
        GLib.idle_add(cb, widget)
        time.sleep(0.01)

def add_notebook_page(notebook, title, page):
    box = Gtk.HBox()
    overlay = Gtk.Overlay()
    label = Gtk.Label(title)
    stack = Gtk.Stack()
    progress = Gtk.ProgressBar()
    stack.add_named(progress, "progress")
    overlay.add(stack)
    overlay.add_overlay(label)
    box.pack_start(overlay, True, True, 2)
    button = Gtk.Button("X")
    box.pack_start(button, False, False, 2)
    box.show_all()
    box.progress_ref = progress
    notebook.append_page(page, box)
    return

class App:
    def __init__(self):
        window = Gtk.Window()
        window.connect("destroy", self.on_window_destroy)
        box = Gtk.VBox()
        button = Gtk.Button("Go")
        box.pack_start(button, True, False, 0)
        self.notebook = Gtk.Notebook()
        page = Gtk.Label("This is a notebook page")
        add_notebook_page(self.notebook, "test tab title", page)
        box.pack_start(self.notebook, True, True, 0)
        button.connect("clicked", self.on_button_clicked)
        window.add(box)
        window.show_all()
        self.event = threading.Event()
        self.thread = None
        self.iteration = 0

    def pulse(self, widget):
        print "Iteration %u" % self.iteration
        for i in range(widget.get_n_pages()):
            child = widget.get_nth_page(i)
            tab = widget.get_tab_label(child)
            progress = tab.progress_ref
            progress.pulse()
        self.iteration += 1
        return False

    def on_window_destroy(self, window):
        self.event.set()
        if self.thread:
            self.thread.join()
        Gtk.main_quit()

    def on_button_clicked(self, button):
        self.thread = threading.Thread(
            None, run_thread, "thread-1", (self.pulse, self.notebook, self.event))
        self.thread.start()


if __name__ == "__main__":
    app = App()
    Gtk.main()

When you run the above application and hit the “Go” button, the progress bar starts being updated but then it just stops. The point at which it stops seems random.

Can anyone seem what’s wrong? Thank you.

Threads can’t modify the UI, use GLib.idle_add to call the function that updates the progress

It isn’t caused by multithreading. This simplified version using a 10ms timeout shows similar behavior:

#!/usr/bin/python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib

class App:
    def __init__(self):
        window = Gtk.Window()
        window.connect("destroy", self.on_window_destroy)
        box = Gtk.VBox()
        button = Gtk.Button("Go")
        box.pack_start(button, True, False, 0)
        self.progress = Gtk.ProgressBar()
        box.pack_start(self.progress, True, True, 0)
        button.connect("clicked", self.on_button_clicked)
        window.add(box)
        window.show_all()
        self.iteration = 0

    def pulse(self):
        print("Iteration %u" % self.iteration)
        self.progress.pulse()
        self.iteration += 1
        return True

    def on_window_destroy(self, window):
        Gtk.main_quit()

    def on_button_clicked(self, button):
        GLib.timeout_add(10, self.pulse)

if __name__ == "__main__":
    app = App()
    Gtk.main()

(It sometimes takes 10k+ iterations to stop on my system)

I suspect the problem is triggered by calling Gtk.ProgressBar.pulse faster than the frame clock. gtk_progress_bar_act_mode_enter is using a tick callback for animation.

I think you’ve found a bug, although I’m not sure it makes sense to call pulse in such a tight loop.

1 Like

Here is an example of how we do it in GNOME Builder in a somewhat clean fashion. You can port that code to Python, and instead of using libdazzles frame timing helper, just use a timeout source with 500msec. (Notice that paired with the pulse step to get a bounce about 1x per second).

Also notice the gtk_widget_queue_draw().

https://gitlab.gnome.org/GNOME/gnome-builder/blob/6f3fac7d4d8b30507a63304c344b4b3bfe4c222a/src/libide/gui/ide-gui-global.c#L257

The issue happens even if the pulse is called once every 100th of a second, which is by no means a tight loop.

The code that I posted is just for example. In my actual application, the GtkProgressBar is being pulsed from a GtkCellRenderer data function, which reacts to the update of a GtkTreeModel (yes, I know it’s a bit funky but the tab progress bar is used to indicate progress when the GtkTreeView is not visible).

In the actual application, using gtk_widget_queue_draw() does not work.

Even more interesting!!

If you use this modification on Chris’ example, you can see that the GtkProgressBar stops pulsing after it’s been stopped and then restarted, even with the queue_draw() call.

#!/usr/bin/python
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib

class App:
    def __init__(self):
        window = Gtk.Window()
        window.connect("destroy", self.on_window_destroy)
        box = Gtk.VBox()
        hbox = Gtk.HBox()
        go = Gtk.Button("Go")
        stop = Gtk.Button("Stop")
        hbox.pack_start(go, True, False, 0)
        hbox.pack_start(stop, True, False, 0)
        box.pack_start(hbox, True, False, 0)
        self.progress = Gtk.ProgressBar()
        box.pack_start(self.progress, True, True, 0)
        go.connect("clicked", self.go)
        stop.connect("clicked", self.stop)
        window.add(box)
        window.show_all()
        self.iteration = 0
        self.source = None

    def pulse(self):
        print("Iteration %u" % self.iteration)
        self.progress.pulse()
        self.progress.queue_draw()
        self.iteration += 1
        return True

    def on_window_destroy(self, window):
        Gtk.main_quit()

    def go(self, button):
        self.source = GLib.timeout_add(10, self.pulse)

    def stop(self, button):
        GLib.source_remove(self.source)

if __name__ == "__main__":
    app = App()
    Gtk.main()

Something just seems really broken.

I believe calling pulse resets the frame clock callback, and you’re doing this at a rate faster than the frame clock (meaning you’re unlikely to ever do any actual work).

(Try changing your timeout to be slower, say 100msec). I chose 500 for a reason in the Builder code).

It only resets it if the progress bar’s activity mode changes (toggles):

static void
gtk_progress_bar_set_activity_mode (GtkProgressBar *pbar,
                                gboolean        activity_mode)
{
  GtkProgressBarPrivate *priv = pbar->priv;

  activity_mode = !!activity_mode;

  if (priv->activity_mode != activity_mode)
    {
      priv->activity_mode = activity_mode;

      if (priv->activity_mode)
        gtk_progress_bar_act_mode_enter (pbar);
      else
        gtk_progress_bar_act_mode_leave (pbar);

      gtk_widget_queue_resize (GTK_WIDGET (pbar));
    }
}

(BTW, Discourse’ method of formatting code snippets is awful!! It makes for copy/pasting snippets an incredible pain in the A$*)

Nothing in the rest of the pulse/tick_cb code path seems to reset the callback.

Furthermore, IMHO, if the widget imposes a requirement like this in order for it to work, that’s just a bug. In my mind, probably the main use case for the pulsing in a GtkProgressBar is to give indication of work being done in the background. In this use case, this bug imposes a requirement on that background work for the rate of update to the widget. A requirement, which is actually a burden because background tasks will have to somehow determine an appropriate call rate, which could be somewhat arbitrary.

I think this has a similar cause to https://gitlab.gnome.org/GNOME/gtk/issues/1612

I reverted the bisected commit c6901a8b from gtk-3.24 HEAD, and I’m past 75k without stopping on the version I posted before.

I don’t disagree with you, but I’m literally giving you an example that is battle tested and works.

Just an another data point:

The GtkSpinnerCellRenderer works as it should even at the extremely high change rate that I am using. I am not familiar with the internals of the two implementations but could the spinner implementation be used as an example of how to make the GtkProgressBar work?

We must be having different battles because in my application I limited the pulse calls to once per second and it still doesn’t work reliably.

This seems to work on my computer using GTK3.18 on Ubuntu16.04 and python3. Not sure about the hide and show slowing things down. The progress bar works fine. Give it a try and see if it is of any help or the problem persists.

Eric

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
import _thread
import random

class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Show Progress")
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_default_size(400, 75)

        #The counter variable is shared between threads.
        self.mutex = GLib.Mutex()
        GLib.Mutex.init(self.mutex)
        self.counter = 0 
        self.hide = False 

        self.progress = Gtk.ProgressBar()
        self.progress.set_hexpand(True)
        self.progress.set_vexpand(True)

        self.button = Gtk.Button("Start Progress")
        self.button.set_hexpand(True)
        self.button.set_vexpand(True)
        self.button.connect("clicked", self.show_progress)  

        self.grid = Gtk.Grid()
        self.grid.attach(self.button, 0, 0, 1, 1)
        self.grid.attach(self.progress, 0, 1, 1, 1)
        self.add(self.grid)

    def show_progress(self, widget):
        self.button.set_sensitive(False)
        _thread.start_new_thread(self.thread_load_items, ("new_thread", ))
        #Need a timer to check the progress of the thread.
        self.timout_id = GLib.timeout_add(100, self.check_progress, None)

    def thread_load_items(self, args):
        rand = 0
        #Do some random work with the thread.
        while rand<3000000:
            rand += random.randint(0, 10)
            if(rand % 1000 == 0):
                print("Check " + str(rand))
                self.mutex.lock()
                self.counter += 1
                self.mutex.unlock()

        self.mutex.lock()
        self.counter = 0
        self.mutex.unlock()

    #Check the progress of the working thread from the main thread.
    def check_progress(self, widget):
        temp = 0
        self.mutex.lock()
        temp = self.counter
        self.mutex.unlock()
        print("Check Progress " + str(temp))
        self.progress.pulse()
        if(temp > 0):
            '''
            if(self.hide):
                self.progress.hide()
                self.hide = False
            else:
                self.progress.show()
                self.hide = True
            '''
            return True
        else:
            self.button.set_sensitive(True)
            self.progress.set_fraction(0)
            return False
                     
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

Here is a pretty straight forward port of the routines from Builder with an example test case. (Tested with 3.24).

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib

def startPulsing(progress):
    if getattr(progress, 'PULSE_ID', None) is not None:
        return

    progress.set_fraction(.0)
    progress.set_pulse_step(.5)

    def on_timeout_cb(progress):
        progress.pulse()
        progress.queue_draw()
        return True

    progress.PULSE_ID = GLib.timeout_add(500, on_timeout_cb, progress)

    # start immediately
    on_timeout_cb(progress)

def stopPulsing(progress):
    pulse_id = getattr(progress, 'PULSE_ID', None)
    progress.PULSE_ID = None
    if pulse_id is not None:
        GLib.Source.remove(pulse_id)
    progress.set_fraction(.0)

win = Gtk.Window(title='Progress Test')
win.set_default_size(400, 30)
box = Gtk.Box(visible=True, homogeneous=True, expand=True)
win.add(box)

progress = Gtk.ProgressBar(visible=True)
box.add(progress)

def toggle_pulse(button, progress):
    if getattr(progress, 'PULSE_ID', None) is not None:
        stopPulsing(progress)
    else:
        startPulsing(progress)

button = Gtk.Button(label='Toggle Pulse', visible=True)
button.connect('clicked', toggle_pulse, progress)
box.add(button)

win.present()

Gtk.main()

FWIW, I found what I think is the cause of this bug: https://gitlab.gnome.org/GNOME/gtk/issues/1612#note_481299

In my testing, the change below (hopefully, close to what’s described in your comment) improves but does not fix things. The progress bar does not stop anymore but it still gets large delays in updates (multiple seconds without any movement, after which movement resumes).

diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c
index a0ca0ca1b9..b32e61b6fa 100644
--- a/gdk/gdkframeclockidle.c
+++ b/gdk/gdkframeclockidle.c
@@ -170,7 +170,8 @@ compute_frame_time (GdkFrameClockIdle *idle)
        */
       /* hmm. just fix GTimer? */
       computed_frame_time = priv->frame_time + 1;
-      priv->timer_base += (priv->frame_time - elapsed) + 1;
+      //priv->timer_base += (priv->frame_time - elapsed) + 1;
+      priv->timer_base = 0;
     }
   else
     {

That’s slightly different than what I did. You’re still returning priv->frame_time + 1, whereas I gutted the function to always return g_get_monotonic_time()

OK, so I change my patch to the one below (it would have been better if you posted your patch, to begin with, since the function isn’t that big.) It behaves much better. I don’t get the pauses and the bar continues to update.

diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c
index a0ca0ca1b9..7f9203a616 100644
--- a/gdk/gdkframeclockidle.c
+++ b/gdk/gdkframeclockidle.c
@@ -162,6 +162,7 @@ compute_frame_time (GdkFrameClockIdle *idle)
   gint64 computed_frame_time;
   gint64 elapsed;
 
+  return g_get_monotonic_time();
   elapsed = g_get_monotonic_time () + priv->timer_base;
   if (elapsed < priv->frame_time)
     {

Strangely, I do get the slow-down/speed-up issues described in the bug on my test system even though the pulse is triggered by a consistent timeout_add() call. I also get the warning below but I am guess that’s due to the hack-y nature of the “fix”:

(main2.py:4262): Gtk-WARNING **: 15:04:06.908: Progress tracker frame set backwards, ignoring.

Sorry, I only thought of that change after I’d written most of that comment, and it was long enough already.

I’ve never noticed that error in my testing, but I may have missed it in the debug spew.

I don’t have a clear enough model of the frame clock in my head to understand the full implications of that frame smoothing commit. I’m going to wait for a response before I do anything else.