quicklisp on windows上の処理系(clozureCL&sbcl)でちゃんと動かないときのパッチ
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*))
quicklisp on sbcl@ubuntu が動かないのでどうにかしてみた
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)))
ミスってた
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)))
うーん、何でこんなところ書き間違えてたんだろ・・・
Finalizerの動作
こないだ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側ではハンドルだけで管理するとかしないと後々面倒にことになるようです。
jdeeのコンパイルウィンドウでのエラーメッセージが化ける
とまぁ、そうやって動くようにした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のソース変更して再構築、というあまり簡単じゃない場合もあるかもしれませんが
昨日のjdeeネタ
うーむ。見直してみるとなんかひどいな。
とりあえず何が言いたかったか、というとオリジナルのソースがいくつかバグってるので Microsoft OneDrive - Access files anywhere. Create docs with free Office Online.(パッチファイル) 当てれば動くようになるよ、ってことだけなんです。
Meadow3でjdeeを使えるようにする
久しぶりに家の環境でjavaを使おうとMeadow3にjdeeをセットアップしようとするとちゃんと動かないご様子。
プアなマシンなのでeclipseを入れるのもなぁ、ということでエラーを追いかけて修正してみることにしました。
入れたもの
Meadow 3.02-r4261-3
apel 10.701
cedet 1.0pre6-1
elib 1.0-1
elscreen 1.4.5-1
imagemagick 6.2.8-0-Q16-1
info-ja-emacs 21.3.50-2
jdee 2.3.5.1-1
misc 0.7-1
psvn 16070-2
以上をnetinstallでインストールしました。
meadow起動後、(require 'jde) してみると*Backtrace*バッファにエラーが表示されちゃんとjdeが読み込まれません。
エラーは wrong-number-of-arguments ということでどこかの関数呼び出しの引数の個数がおかしいようです。
backtraceログのエラー箇所から近いところはcompiler-macroexpandやらbyte-optimize-*、byte-compile-*ばかりなのでとりあえずは関係なさそうなので適当に読み飛ばしていくと・・・
require(efc) としているところが見つかりました。
とりあえずここから追いかけていくことにして見ます。
emacs lispのrequireはfeature名に.elc(なければ.el)をつけたファイルをloadしてくれるようなのでefc.el、またはefc.elcというファイルがあるはずです。
探してみると $(MEADOW_INSTALL_DIR)/packages/lisp/jde/lisp/efc.el というファイルが見つかりました。
ほかの拡張子がelのファイルは拡張子elcのバイトコンパイルされたファイルが見つかりますが
efc-xemacs.el、efc.el、jde-xemacs.elの三つだけバイトコンパイルファイルがないようです。
efc-xemacs.elとjde-xemacs.elは名前からしてxemacs用でしょうから関係なさそうです。
というわけでefc.elをバイトコンパイルしてみます。
するとefc.el:646:42 でWrong number of argumentsでエラーが発生してとまりました。
該当行を見てみると efc-coll-addというメソッドを第一引数をefc-listで特殊化して宣言しています。
C-x C-e (eval-last-sexp) で (defmethod efc-coll-add ...) を評価しようとしても同様のエラーが起こりました。こいつがクロのようです。
*Backtrace*をよくみてみるとエラー箇所の近い呼び出しに compiler-macroexpand( (typep item) ) がありました。同様の呼び出しがefc-coll-add のerror関数呼び出しの第二引数にあります。
さて、common-lispではtypep関数は引数を二つとって第一引数が第二引数で指定した型と等しいかどうかを調べる関数のはずです。common-lispとemacs-lispの違いはあれど普通は同じような関数のはず。
describe-functionでしらべてみると・・・
typep is a compiled Lisp function in `cl-macs.el'. (typep object type) Check that object is of type type. type is a Common Lisp-style type specifier. [back]
ということでどうもcommon-lispと同様の関数のようです。とするとこの部分の呼び出しがおかしいということになります。error関数のエラーメッセージのプレースホルダからみてどうもtypepで判定するのが必要ではなく、type-ofと書くべきところのようですので(typep item) を (type-of item) に書き換えました。
書き換えて保存し、再度byte-compile-fileすると別の箇所でエラーになりました。
(defmethod initialize-instance ((this efc-hash-table) &rest fields) ...)のなかでのエラーのようです。
エラーをみても該当箇所がわかりません。eval-last-sexp してもそのまま通ってしまいます。調べてみるとdefmethod自体はlisp macroなのでどうも展開された形を評価したときにエラーがあるように思われます。というわけでmacroexpandしてみます。
(macroexpand '(defmethod initialize-instance ((this efc-hash-table) &rest fields) "Hash table constructor." (call-next-method) (oset table (make-hash-table)))) (eieio-defmethod (quote initialize-instance) (quote ((... &rest fields) "Hash table constructor." (call-next-method) (oset table ...))))
ありゃ、省略されてしまいました。
たしかこの辺制御する変数が・・・ありました。eval-expression-print-levelです。4になっているのでnilにセットしてからmacroexpandしてみます。
(setq eval-expression-print-level nil) (macroexpand '(defmethod initialize-instance ((this efc-hash-table) &rest fields) "Hash table constructor." (call-next-method) (oset table (make-hash-table)))) (eieio-defmethod (quote initialize-instance) (quote (((this efc-hash-table) &rest fields) "Hash table constructor." (call-next-method) (oset table (make-hash-table)))))
出ました。
整形すると以下のようになります。
(eieio-defmethod 'initialize-instance '(((this efc-hash-table) &rest fields) "Hash table constructor." (call-next-method) (oset table (make-hash-table))))
バイトコンパイルのエラー出力をもう一度見てみると・・・
efc.el:760:61:Error: Wrong number of arguments: #[(obj slot value) "\303\304 D F\207" [obj slot value eieio-oset quote] 4 ("g:/Emacsen/Meadow3/packages/lisp/cedet/eieio/eieio.elc" . 41185)], 2
eieio-osetというシンボルが見えます。もしかして・・・とosetをdescribe-functionしてみると・・・
oset is a Lisp macro in `eieio.el'. (oset obj slot value) Set the value in obj for slot slot to value. slot is the slot name as specified in `defclass' or the tag created with in the :initarg slot. value can be any Lisp object. [back]
で、というわけでマクロでした。
macroexpandしてみましょう。
(macroexpand '(oset table (make-hash-table)))
でました。どうもこいつがクロのようです。
上のosetの定義をよく見てみると・・・
(oset obj slot value) となっています。ですが 呼び出しは(oset table (make-hash-table))となっています。引数の個数が足りません。どうもobjがないようです。
文脈からみてobjは関数の第一引数のthisのようですからthisを付け足します。
(oset table (make-hash-table)) を (oset this table (make-hash-table)) に変更してもう一度バイトコンパイルします。
またエラー・・・えーかげんにせーよとか思いつつもみていきます。
今度はefc-coll-putという関数でwrong-number-of-argumentsです。
またtypepのようなので最初のエラーと同じように修正します。そしてバイトコンパイル・・・やっとうまくいったようです。
そして(require 'jde)してみると・・・
いきました。やっと動くようになったようです。
最後に変更部分のdiffをつけときます。
diff -u "g:/Meadow3/packages/lisp/jde/lisp/efc.el.orig" "g:/Meadow3/packages/lisp/jde/lisp/efc.el" --- g:/Meadow3/packages/lisp/jde/lisp/efc.el.orig 2009-09-27 05:32:14.710375000 +0900 +++ g:/Meadow3/packages/lisp/jde/lisp/efc.el 2009-09-27 05:56:20.272875000 +0900 @@ -648,7 +648,7 @@ (if (efc-coll-type-compatible-p this item) (oset this items (append (oref this items) (list item))) (error "Tried to add an item of type %s to a list of items of type %s" - (typep item) (oref this elem-type)))) + (type-of item) (oref this elem-type)))) (defmethod efc-coll-iterator ((this efc-list)) "Return an iterator for this list." @@ -760,14 +760,14 @@ (defmethod initialize-instance ((this efc-hash-table) &rest fields) "Hash table constructor." (call-next-method) - (oset table (make-hash-table))) + (oset this table (make-hash-table))) (defmethod efc-coll-put ((this efc-hash-table) key value) "Put an item into the table." (if (efc-coll-type-compatible-p this value) (puthash key value (oref this table)) (error "Tried to add an item of type %s to a hash table of items of type %s" - (typep value) (oref this elem-type)))) + (type-of value) (oref this elem-type)))) (defmethod efc-coll-get ((this efc-hash-table) key) "Get an item from the table." Diff finished. Sun Sep 27 05:58:26 2009