Heavy IOChannel use starves UI painting



Hi,

I wrote a little application on top of the Glibmm frontend to
GIOChannels which displays a progressbar while a file is copied.

When the channels are constantly available for reading and writing
(and thus their callbacks are always being executed), though, the UI
becomes totally unresponsive. This is a little perplexing to me, since
a stated purpose of using GIOChannels is to integrate IO into the
mainloop so that the UI stays snappy.

Have I got the wrong paradigm in mind? That is, should one expect that
constant execution of the iochannel handlers will starve execution of
the UI drawing inside the mainloop's thread? Source is attached. The
channel setup is in the FileCopyWindow::start_copy() method of
filecopywindow.cc.

Thanks
#include "filecopywindow.h"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <iostream>
using namespace std;

#include <glib/gmem.h>
#include <gtkmm/main.h>
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/progressbar.h>
#include <gtkmm/button.h>

#include <glibmm/iochannel.h>

static const guint BUFSIZE = (32 * 1024);

static const char *
IOStatus_to_cstr (Glib::IOStatus status)
{
    switch (status)
    {
        case Glib::IO_STATUS_ERROR:
            return "IO_STATUS_ERROR";
        case Glib::IO_STATUS_NORMAL:
            return "IO_STATUS_NORMAL";
        case Glib::IO_STATUS_EOF:
            return "IO_STATUS_EOF";
        case Glib::IO_STATUS_AGAIN:
            return "IO_STATUS_AGAIN";
        default:
            return "UNKNOWN";
    }
}

static inline Glib::ustring
basename (const Glib::ustring &path)
{
    std::string::size_type index = path.find_last_of('/');

    if (index >= 0)
    {
        return path.substr(index + 1);
    }
    else
    {
        return path;
    }
}

FileCopyWindow::FileCopyWindow (const Glib::ustring &from,
                                const Glib::ustring &to )
    : Gtk::Window(Gtk::WINDOW_POPUP),
    copy_started(false),
    from_filename(from),
    to_filename(to),
    vbox(),
    label(basename(from_filename)),
    bar()
{
    set_title(basename(from_filename));
    vbox.add(bar);
    vbox.add(label);
    add(vbox);
}

void
FileCopyWindow::start_copy ()
{
    // idempotency
    if (copy_started)
        return;
    else
        copy_started = true;

    int fd = ::open(from_filename.c_str(), O_RDONLY);
    if (fd < 0)
    {
        buf[strlen("Couldn't open ") + from_filename.length() + 1];
        sprintf(buf, "Couldn't open %s", from_filename.c_str());
        perror(buf);
        exit(1);
    }
    read_channel = Glib::IOChannel::create_from_fd(fd);
    read_channel->set_encoding();
    read_connection = Glib::signal_io().connect
        (
        sigc::mem_fun(*this, &FileCopyWindow::on_read_ready),
        fd,
        Glib::IO_IN
        );
    struct stat stat_buf;
    fstat(fd, &stat_buf);

    // if target doesn't yet exist, create it with same permissions as source
    fd = ::open
        (
        to_filename.c_str(),
        O_WRONLY | O_CREAT,
        stat_buf.st_mode
        );

    if (fd < 0)
    {
        char buf[strlen("Couldn't open ") + to_filename.length() + 1];
        sprintf(buf, "Couldn't open %s", to_filename.c_str());
        perror(buf);
        exit(1);
    }
    write_channel = Glib::IOChannel::create_from_fd(fd);
    write_channel->set_encoding();
    write_connection = Glib::signal_io().connect
        (
        sigc::mem_fun(*this, &FileCopyWindow::on_write_ready),
        fd,
        Glib::IO_OUT
        );

    // Initially set buffer to NULL
    buf = NULL;
    bytes_copied = 0;
    bytes_total = stat_buf.st_size;
}

FileCopyWindow::~FileCopyWindow ()
{
}

bool
FileCopyWindow::on_read_ready (Glib::IOCondition cond)
{
    if (bytes_copied == bytes_total)
    {
        // do nothing
    }
    else if (!buf && cond & Glib::IO_IN)
    {
        buf = (char *) g_malloc(BUFSIZE);
        Glib::IOStatus result = read_channel->read
            (
            buf,
            BUFSIZE,
            buf_count
            );
        buf_offset = 0;
    }

    return true;
}

bool
FileCopyWindow::on_write_ready (Glib::IOCondition cond)
{
    if (buf && cond & Glib::IO_OUT)
    {
        gsize num_written;

        Glib::IOStatus result = write_channel->write
            (
            buf + buf_offset,
            buf_count,
            num_written
            );

        bytes_copied += num_written;

        if (buf_count == num_written)
        {
            g_free(buf);
            buf = NULL;

            // Reasonable time to update progress bar
            double oldPercentage = bar.get_fraction();
            double newPercentage = (bytes_copied * 1.0) / (bytes_total);

            // Require at least 1% change
            if (newPercentage - oldPercentage > 0.01)
            {
                bar.set_fraction(newPercentage);
            }
        }
        else
        {
            buf_offset += num_written;
            buf_count -= num_written;
        }
    }

    if (bytes_copied == bytes_total)
    {
        write_channel->close();
        write_connection.disconnect();
        
        read_channel->close();
        read_connection.disconnect();

        // emit signal
        m_signal_copy_completed.emit();
    }

    return true;
}
#ifndef _FILE_COPY_WINDOW_H
#define _FILE_COPY_WINDOW_H

#include <gtkmm/window.h>
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <gtkmm/progressbar.h>

class FileCopyWindow : public Gtk::Window
{
public:
    FileCopyWindow (const Glib::ustring &from, const Glib::ustring &to);
    virtual ~FileCopyWindow ();

    void start_copy();

    sigc::signal0<void> &
    signal_copy_completed()
    {
        return m_signal_copy_completed;
    }

    Gtk::VBox *
    get_vbox()
    {
        return &vbox;
    }

    const Glib::ustring &
    get_from_filename()
    {
        return from_filename;
    }

    const Glib::ustring &
    get_to_filename()
    {
        return to_filename;
    }

private:
    bool on_read_ready (Glib::IOCondition io_condition);
    bool on_write_ready (Glib::IOCondition io_condition);

    const Glib::ustring from_filename;
    const Glib::ustring to_filename;

    bool copy_started;

    Gtk::VBox vbox;
    Gtk::Label label;
    Gtk::ProgressBar bar;

    Glib::RefPtr<Glib::IOChannel> read_channel;
    Glib::RefPtr<Glib::IOChannel> write_channel;

    sigc::connection read_connection;
    sigc::connection write_connection;

    sigc::signal0<void> m_signal_copy_completed;

    gulong bytes_total;
    gulong bytes_copied;

    char *buf;
    guint buf_offset;
    guint buf_count;
};

#endif // _FILE_COPY_WINDOW_H
#include <gtkmm/main.h>
#include <gtkmm/button.h>
#include "filecopywindow.h"

#include <iostream>
using namespace std;

void
done_callback ()
{
    cout << "Copy complete!" << endl;
    cout.flush();
}

void
start_copy_callback (FileCopyWindow *w)
{
    w->start_copy();
}

int
main (int argc, char **argv)
{
    if (argc != 3)
    {
        cout << "Usage: " << argv[0] << " [source] [destination]" << endl;
        exit(1);
    }

    Gtk::Main kit(argc, argv);
    FileCopyWindow w(argv[1], argv[2]);
    cout.flush();
    w.show_all();
    w.set_resizable(false);

    int width, height;
    w.get_size(width, height);
    cout << width << "x" << height << endl;
    Glib::RefPtr<const Gdk::Screen> screen = w.get_screen();
    w.move
        (
        (screen->get_width() - width) / 2,
        (screen->get_height() - height) / 2
        );

    // hook up signal for "copy complete"
    w.signal_copy_completed().connect(sigc::ptr_fun(&done_callback));
    w.signal_copy_completed().connect(sigc::ptr_fun(&Gtk::Main::quit));

    // Make the copy start immediately
    //w.start_copy();

    cout << "Running main loop" << endl;
    cout.flush();
    kit.run(w);
    return 0;
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]