rayfillのプログラムネタ帳

2010-10-28

[] quicklisp on windows上の処理系(clozureCL&sbcl)でちゃんと動かないときのパッチ 16:47

versionは2010101600で当ててます。

原因はql:*quicklisp-home*がドライブレターが入ってないので自分で正しいパスをセットしてやれば動きます。

自分は面倒だったのでパッチ用意しました。

--- quicklisp.org/setup.lisp    2010-10-28 13:02:57.808556700 +0900
+++ quicklisp/setup.lisp        2010-10-28 13:29:34.191965700 +0900
@@ -10,7 +10,13 @@
   (error "This file must be LOADed to set up quicklisp."))

 (defvar *quicklisp-home*
-  (pathname (directory-namestring *load-truename*)))
+  (cond
+    ((or (member :win32 *features*)
+        (member :windows *features*))
+     (make-pathname :device (pathname-device *load-truename*)
+                   :directory (pathname-directory *load-truename*)))
+    (t (pathname (directory-namestring *load-truename*)))))
+

 (defun qmerge (pathname)
   (merge-pathnames pathname *quicklisp-home*))

clojure clsbclで動作するはずです(clispは入ってないので試してないです)。

トラックバック - http://d.hatena.ne.jp/rayfill/20101028

2010-10-20

[] quicklisp on sbcl@ubuntu が動かないのでどうにかしてみた 00:06

ubuntuにquicklispをinstallして(ql:add-to-init-file)してsbcl再起動すると起動に失敗します。

調べてみるとcommon-lisp-controllerとやらでロードする場所に #p"~/.clc/systems/" が指定してあり、これが引っかかるようです。

cl-asdfでinstallされているasdf(version 1.7 カスタマイズされてる?)だと~/をhome directoryとして変換しているようですが、quicklispでasdf 2.0に書き換えられて、そのせいでだめになっているようです。

なので ~/.clc/systems を (merge-pathnames (make-pathname :directory '(:relative ".clc" "systems")) (user-homedir-pathname)) に書き換えてやればうまくいくはず・・・

ということで /etc/common-lisp/source-registry.conf.d/02-common-lisp-controller-userdir.conf を書き換えてみて再起動してみましたが同じところで停止。

書き換えた部分が関数として評価されてなくてマクロに食わされているようです。

#. リードマクロを使って強制的に評価させてみると通りました。

最終的に

/etc/common-lisp/source-registry.conf.d/02-common-lisp-controller-userdir.confを次のように書き換えました。

;(:directory  #p"~/.clc/systems/")
(:directory #.(merge-pathnames (make-pathname :directory '(:relative ".clc" "systems")) (user-homedir-pathname)))
トラックバック - http://d.hatena.ne.jp/rayfill/20101020

2009-10-14

[][] ミスってた 10:38

2009-09-28 - rayfillのプログラムネタ帳で書いた記事ですが一部コード間違ってた模様です。

(add-hook 'jde-mode-hook
	  #'(local-set-key "\C-c\C-v\C-c" 'my-jde-compile))

を以下のように読み替えてください

(add-hook 'jde-mode-hook
	  #'(lambda () (local-set-key "\C-c\C-v\C-c" 'my-jde-compile)))

うーん、何でこんなところ書き間違えてたんだろ・・・

トラックバック - http://d.hatena.ne.jp/rayfill/20091014

2009-09-29

[] Finalizerの動作 17:45

こないだfinalizerでハマったので記録として残しときます。

javaにはfinalizerと呼ばれる仕組みがあり、Object#finalize()をオーバーライドしたクラスのインスタンスはガベージコレクションされる際、finalize()メソッドが呼び出される、と言う四組です。

これをつかうことでリソース後始末をしたりすることができます。

ただし、finalize()メソッドは呼び出されないこともあり、確実なリソースの回収手段ではない、ということに注意が必要です。

でもまぁ、nativeなリソース(JNIとかJavaの外でつかわれるリソースとか)の管理にはつかわれたりしていたりします。

で、自分が作ったのはnativeの正規表現ライブラリを呼び出すやつでした*1

いちいちJNIでスタブメソッドを書きたくなかったのでJNA(Java Native Access: https://jna.dev.java.net/)をつかって楽をすることにしました。

で、出来上がったのかこれ > OneDrive

で、こんな風につかいます。

public class RegexTest {
    public static void main(String[] args) {
        
        RegexT regex = new RegexT();

        int result =
            PosixRegex.INSTANCE.regcomp(regex, "[0-9]+([a-z]+)[0-9]+", 1);

        if (result != 0)
            throw new RuntimeException("regex compile failed.");

        RegMatchT[] matches = (RegMatchT[])new RegMatchT().toArray(2);
        result =
            PosixRegex.INSTANCE.regexec(regex,
                                        "abcd0123456jfdaskl3493210rewio",
                                        matches.length, matches, 0);
        if (result != 0)
            System.out.println("not matched.");
        else {
            System.out.println("matched.");
            System.out.println("all match position: " +
                               matches[0].rm_so + " to " +
                               matches[0].rm_eo);
            System.out.println("group operator match opsition: " +
                               matches[1].rm_so + " to " +
                               matches[1].rm_eo);
        }

        //PosixRegex.INSTANCE.regfree(reg);
    }
}

動作結果。

cd /home/alfeim/source/jde/
/usr/lib/jvm/java-6-sun/bin/java -classpath /home/alfeim/source/jde:/home/alfeim/java/lib/jna/linux-i386.jar:/home/alfeim/java/lib/jna/jna.jar RegexTest

matched.
all match position: 4 to 25
group operator match opsition: 11 to 18

Process RegexTest finished

これだけなら、めでたしめでたし、なんですが、実は問題があります。

public class RegexTest {
    public static void main(String[] args) {
        
        for (int i = 0; i < 100; ++i) {

            RegexT regex = new RegexT();

            int result =
                PosixRegex.INSTANCE.regcomp(regex, "[0-9]+([a-z]+)[0-9]+", 1);

            if (result != 0)
                throw new RuntimeException("regex compile failed.");

            RegMatchT[] matches = (RegMatchT[])new RegMatchT().toArray(2);
            result =
                PosixRegex.INSTANCE.regexec(regex,
                                            "abcd0123456jfdaskl3493210rewio",
                                            matches.length, matches, 0);
            if (result != 0)
                System.out.println("not matched.");
            else {
                System.out.println("matched.");
                System.out.println("all match position: " +
                                   matches[0].rm_so + " to " +
                                   matches[0].rm_eo);
                System.out.println("group operator match opsition: " +
                                   matches[1].rm_so + " to " +
                                   matches[1].rm_eo);
            }

            //PosixRegex.INSTANCE.regfree(reg);
        }
    }
}

ループの中に入れて実行してみます。

cd /home/alfeim/source/jde/
/usr/lib/jvm/java-6-sun/bin/java -classpath /home/alfeim/source/jde:/home/alfeim/java/lib/jna/linux-i386.jar:/home/alfeim/java/lib/jna/jna.jar RegexTest

matched.
all match position: 4 to 25
group operator match opsition: 11 to 18
matched.

...snip...

all match position: 4 to 25
group operator match opsition: 11 to 18
matched.
all match position: 4 to 25
group operator match opsition: 11 to 18
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0xb4e04ee5, pid=5445, tid=3040222064
#
# JRE version: 6.0_15-b03
# Java VM: Java HotSpot(TM) Client VM (14.1-b02 mixed mode, sharing linux-x86 )
# Problematic frame:
# C  [jna1250405488460372990.tmp+0x4ee5]  Java_com_sun_jna_Pointer__1setPointer+0x21
#
matched.
# An error report file with more information is saved as:
# /home/alfeim/source/jde/hs_err_pid5445.log
all match position: 4 to 25
group operator match opsition: 11 to 18
matched.
all match position: 4 to 25
group operator match opsition: 11 to 18
matched.

... snip ...

all match position: 4 to 25
group operator match opsition: 11 to 18
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

Process RegexTest aborted (core dumped)

JVMごと落ちてしまいました。

原因はクラスRegexTのfinalize()メソッドにあります。

    @Override
    protected void finalize() {
    PosixRegex.INSTANCE.regfree(this);
    }

RegexTのインスタンスファイナライズされるときにregfree()を呼び出して正規表現処理につかったリソースを開放しているのですが、何がまずかったのでしょうか?

JNAではnativeの関数を呼び出すときに、引数として特定のJavaオブジェクトを渡し、それをメモリ上にC互換のデータとして展開してから関数に渡しています。

そして、その展開先のメモリを管理しているのがcom.sun.jna.Memoryです。このクラスでは、要求されたときにmallocを呼び出してメモリを確保し、finalize()でそのメモリを開放しています。

そしてCの構造体のJNA版であるところのcom.sun.jna.Structureのインスタンスが自己のシリアライズ用としてメンバにMemoryのインスタンスを持っているのです。

JNAでの関数呼び出しのシーケンスは次のようになります。

1) 引数となるJavaオブジェクトをMemoryなどに変換

2) 変換したMemoryが持っているメモリのポインタ引数として関数を呼び出す

3) Memory上のデータをJavaオブジェクトに書き戻す*2

といった動作をしています。

で、ここで問題になったのが 1) の部分です。

RegexT#finalize()時点ではStructureが持っているMemoryのインスタンスは、ファイナライズ処理されているRegexTのインスタンスから到達可能なので、まだファイナライズされてないだろう、とおもってたのですが、実はそれが間違いでした*3

ファイナライズ自体の動作順序は不定だそうで、さらにガベージコレクションの対象となるのは、ユーザコードから到達不可能になったものすべてがなるようです*4



実際に循環構造をもつオブジェクトファイナライズ処理をつけてみて動きをみてみます。

class CircularRef {

    static class Circular {
        public int id;
        public Circular next;

        public Circular(int id) {
            this.id = id;
        }

        @Override
        protected void finalize() {
            synchronized(System.out) {
                System.out.println("finalize: " + id);
            }
        }
    }

    public static void main(String[] args) {

        for (int i = 0; i < 100; i += 2) {

            Circular head = new Circular(i + 1);
            Circular next = new Circular(i + 2);
            head.next = next;
            next.next = head;

            System.gc();
            System.runFinalization();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
cd /home/alfeim/source/jde/
/usr/lib/jvm/java-6-sun/bin/java -classpath /home/alfeim/source/jde:/home/alfeim/java/lib/jna/linux-i386.jar:/home/alfeim/java/lib/jna/jna.jar CircularRef

finalize: 2
finalize: 1
finalize: 4
finalize: 3
... snip ...
finalize: 96
finalize: 95
finalize: 98
finalize: 97

Process CircularRef finished

ということでちゃんと循環していても回収されてファイナライズされました。

つぎに通常のプログラムコードから見えなくなったオブジェクトファイナライズが順不定で呼び出されることを確認しておきます。

class FinalizeTest {

    public static boolean isChildFinalized[] = null;

    static class Finalizable {
        int id;
        Finalizable next;
    
        public Finalizable(int id, Finalizable next) {
            this.id = id;
            this.next = next;
        }
    
        @Override
        protected void finalize() {

            synchronized (System.out) {
                if (next == null)
                    isChildFinalized[id] = true;
            }
        }
    }

    public static void main(String[] args) {

        FinalizeTest.isChildFinalized = new boolean[1000];
    
        Finalizable f = null;
        for (int i = 0; i < 1000; ++i) {
            FinalizeTest.isChildFinalized[i] = false;
            new Finalizable(i, new Finalizable(i, null));
            System.gc();
        }
        System.runFinalization();

        for (int i = 0; i < 1000; ++i) {
            if (FinalizeTest.isChildFinalized[i])
                System.out.println("child finalize when parent first at id = " + i);
        }
    }
}

結果

cd /home/alfeim/source/jde/
/usr/lib/jvm/java-6-sun/bin/java -classpath /home/alfeim/source/jde:/home/alfeim/java/lib/jna/linux-i386.jar:/home/alfeim/java/lib/jna/jna.jar FinalizeTest

child finalize when parent first at id = 0
child finalize when parent first at id = 1
child finalize when parent first at id = 2
child finalize when parent first at id = 3
child finalize when parent first at id = 4
child finalize when parent first at id = 5
child finalize when parent first at id = 6
...snip...
child finalize when parent first at id = 994
child finalize when parent first at id = 995
child finalize when parent first at id = 996
child finalize when parent first at id = 997
child finalize when parent first at id = 998
child finalize when parent first at id = 999

Process FinalizeTest finished

というようにガベージコレクションの対象内になっているオブジェクト同士でのファイナライズ順序も不定です。

これらの事実より RegexTのインスタンスのfinalize()が呼び出されたとき、"運良く" 親クラスStructureのMemoryインスタンスがfinalize()されていない場合はちゃんと動きますが、"運悪く" さきにMemoryインスタンスのfinalize()が呼び出された場合、さされているメモリ位置はすでにfree()されてしまった場所なのでAccess violationでJVMごと落っこちることになるのでした。

なので複合的なリソースをfinalize()で開放したい場合は一つのfinalize()でやるか、nativeなリソースだとnative側に押しやってjava側ではハンドルだけで管理するとかしないと後々面倒にことになるようです。

*1Javaにある正規表現をつかえない理由があったんです

*2:設定によっては自動的には行われないようにすることもできます

*3Java SE Specifications の12.6.2 で書いてあります

*4:参照が循環しているリンクドリスト等があることを考えれば当然でした。コード書いてる最中は気がつきませんでしたが

トラックバック - http://d.hatena.ne.jp/rayfill/20090929

2009-09-28

[][] 昨日のjdeeネタ 15:28

うーむ。見直してみるとなんかひどいな。

とりあえず何が言いたかったか、というとオリジナルのソースがいくつかバグってるので Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.(パッチファイル) 当てれば動くようになるよ、ってことだけなんです。

[][] jdeeのコンパイルウィンドウでのエラーメッセージが化ける 17:53

とまぁ、そうやって動くようにしたjdeeですがコンパイルエラー時のエラーメッセージが化けます。

process-coding-system周りをいじってみてもダメでした。

ググってみると皆さんbean shellの表示localeをenにして逃げているようです。

まぁ確かに簡単な方法なんですが*1、せっかくの日本語環境なのに日本語がでないのも負けた気がするので何とかしてみます。

まずは原因追求から。

JDEEはデフォルトではbean shellをサーバとして立ち上げておいて、それに外からファイルコンパイル指示をすることでJVMの起動のオーバヘッドをなくしています。結局裏ではprocessが動いてるわけで、これがMeadowのbufferと結びついてエラーメッセージを表示してるわけです。

なのでbean shellの起動から追いかけていくと・・・

beanshell.el内のbsh-comint-buffer-execという関数までたどり着きます。

(defmethod bsh-comint-buffer-exec ((this bsh-comint-buffer) vm vm-args)
  (let ((win32-start-process-show-window t)
        (w32-start-process-show-window t)
        (w32-quote-process-args ?\") ;; Emacs
        (win32-quote-process-args ?\") ;; XEmacs
        (windowed-process-io t)
        (process-connection-type nil)
        ;; XEmacs addition
        (coding-system-for-read
         (if (or (member system-type '(cygwin32 cygwin))
                 (eq system-type 'windows-nt))
             'raw-text-dos)))
    (comint-exec (oref this buffer) "bsh"  vm  nil vm-args))
...

comint-execがprocessを起動してbufferと結びつける処理をしています。

で、その直前のletスペシャルフォームでcoding-system-for-readと言う名前の変数windowsだとraw-text-dosというシンボルを束縛しています。

何か怪しいですねぇ。

describe-bariableしてみると

coding-system-for-read is a variable defined in `C source code'.
Its value is nil


Documentation:
Specify the coding system for read operations.
It is useful to bind this variable with `let', but do not set it globally.
If the value is a coding system, it is used for decoding on read operation.
If not, an appropriate element is used from one of the coding system alists:
There are three such tables, `file-coding-system-alist',
`process-coding-system-alist', and `network-coding-system-alist'.

[back]

ということで他のcoding-systemの設定をこの変数上書きされているようです。

なのでこれを何とかする必要があるみたいです。


で、どうしたかというとcomint-execがプログラムを実行した後にcomint-exec-hookを呼び出してくれますのでこれで何とかしてみましょう。

で、何とかした結果こうなりました。

(defun jde-coding-reset ()
  (with-current-buffer buffer
    (set-buffer-process-coding-system 'undecided-dos 'undecided-dos)))

(defun my-jde-compile ()
  (interactive)
  (let (coding-system-for-read
	(comint-exec-hook comint-exec-hook))
    (add-hook 'comint-exec-hook #'jde-coding-reset)
    (jde-compile)))

(add-hook 'jde-mode-hook
	  #'(local-set-key "\C-c\C-v\C-c" 'my-jde-compile))

これでjde-modeでC-c C-v C-cするとmy-jde-compileが呼び出されることになり、なかでjde-compileを呼び出しますが、呼び出し前にcomint-exec-hookにjde-coding-resetをしこんで置きます。

こうすることで内部でbean shellを起動したあとにbean shellに割り付けられたbufferに対してset-buffer-process-coding-systemでencode/decodeを再設定しています。

これでエラーメッセージがちゃんとでるようになりますが、設定しているcoding-systemがundecided-dosなので自動判定の順序などによっては化けるかもしれません。その場合は適当なのにかえてやってください。

*1:bean shellの起動オプションでうまくいかないような書き込みもあってもしかしたらBean Shellのソース変更して再構築、というあまり簡単じゃない場合もあるかもしれませんが