Gjs: GdkPixbuf Memory leak

comipoli がガッツリメモリリークしてる、と今更気がつく。
抜き出しすると、001.jpg – 200.jpg を用意して。
JavaScriptでゼロ埋め処理 – Qiita
ゼロ詰めはこの方法を利用しました。

#!/usr/bin/gjs

const System = imports.system;
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;
const GdkPixbuf = imports.gi.GdkPixbuf;
const PATH = '/home/sasakima-nao/erohon/';

var AWindow = GObject.registerClass({
    GTypeName: "AWindow"
}, class AWindow extends Gtk.ApplicationWindow {
    _init(app) {
        super._init({application: app});
        this.d = new Gtk.DrawingArea();
        this.d.connect('draw', (widget, cr)=> {
            if (this.pixbuf) {
                let aw = widget.get_allocated_width();
                let ah = widget.get_allocated_height();
                let buf = this.pixbuf.scale_simple(aw, ah, GdkPixbuf.InterpType.BILINEAR);
                Gdk.cairo_set_source_pixbuf(cr, buf, 0, 0);
                cr.paint();
            }
        });
        this.add(this.d);
        this.pixbuf = null;
        this.count = 0;
        GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, ()=> {
            this.count += 1;
            if (this.count == 200)
                return false;
            let f = `${PATH}${('00'+this.count).slice(-3)}.jpg`;
            this.pixbuf = GdkPixbuf.Pixbuf.new_from_file(f);
            this.d.queue_draw();
            return true;
        });
        this.show_all();
    }
});

var AApplication = GObject.registerClass({
    GTypeName: "FlApplication"
}, class FlApplication extends Gtk.Application {
    _init(v) {
        GLib.set_prgname("AApplication");
        super._init();
    }
    vfunc_startup() {
        super.vfunc_startup();
        new AWindow(this);
    }
    vfunc_activate() {
        this.active_window.present();
    }
});

new AApplication().run([System.programInvocationName].concat(ARGV));

動かす。

ガベージコレクションは仕事をしろよ。。。。。
完全に GdkPixbuf が原因だよな、自前で破棄しなきゃいけなかったのか。

Reference Counting and Memory Mangement: GDK-PixBuf Reference Manual

buf.unref() では非推奨の gdk_pixbuf_unref を呼び出ししそうだ。
でも JavaScript には call があるじゃないか。
$dispose() を呼び出ししたほうがいいとかもどこかで見つけた。

//buf.unref();
GObject.Object.prototype.unref.call(buf);
cr.$dispose();

結果

動かしてしばらくたってからこんなエラーを吐いて落ちる。
どうやらガベージコレクタが GdkPixbuf を見つけられない意味っぽい。
つまりガベージコレクションは仕事をしているけど実は破棄できない。
結果ゾンビになったメモリ領域が大量発生、なのね。
だけど自前で破棄するとエラー、どうしろっての。。。。。

javascript の delete は単なるプロパティの削除だ。
this にくっつけたりあれやこれや十日間やったけど破棄する手段が見つからない。

もう C で作り直しするしか手段が無いかなぁとも。
こんだけスクリプト言語押しをしといて今更感が凄いけど。
って PyGObject だとどうなんだろう?どうせ同じだと思うけど実験。

#!/usr/bin/env python3

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

PATH = '/home/sasakima-nao/erohon/'

class AWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app)
        self.d = Gtk.DrawingArea()
        self.d.connect('draw', self.on_draw)
        self.add(self.d)
        self.pixbuf = None
        self.count = 0
        GLib.timeout_add(200, self.read_pixbuf)
        self.show_all()

    def on_draw(self, widget, cr):
        if (self.pixbuf):
            aw = widget.get_allocated_width()
            ah = widget.get_allocated_height()
            buf = self.pixbuf.scale_simple(aw, ah, GdkPixbuf.InterpType.BILINEAR)
            Gdk.cairo_set_source_pixbuf(cr, buf, 0, 0)
            cr.paint()

    def read_pixbuf(self):
        self.count += 1
        if self.count == 200:
            return False
        f = '{0}{1:03d}.jpg'.format(PATH, self.count)
        self.pixbuf = GdkPixbuf.Pixbuf.new_from_file(f)
        self.d.queue_draw()
        return True

class AApplication(Gtk.Application):
    __gtype_name__ = 'AApplication'
    def __init__(self):
        GLib.set_prgname('AApplication')
        Gtk.Application.__init__(self)

    def do_startup(self):
        Gtk.Application.do_startup(self)
        AWindow(self)
    
    def do_activate(self):
        self.props.active_window.present()

AApplication().run(sys.argv)

まさかの問題無し!

やっていることは完全に同じなのに PyGObject では普通に破棄されている。
自前でバイナリを扱えるかどうかの差なのですかね。
筆者はいったい十日間何をやっていたんだ!
まあ検索しまくって色々勉強になったけど。
comipoli は PyGObject に戻します、メソッド名付けるのメンドイけど。