ハングしちゃうんですけど!を調べた

昨日の日記でGrailsがハングすることを書きましたが調べてみました。スタックトレースを見ると、Groovyで書かれたクラス(コントローラ)のClass#newInstance()で止まっているみたいで、怖くて使えないです。もう、Grailsの話題じゃないです。
スタックトレースを見る限りでは、org.codehaus.groovy.runtime.metaclass.MemoryAwareConcurrentReadMapが怪しい。Grails 0.5に付属のGroovy(1.1 BETA-1)のこのクラスのソースはここ。その中で、wait()したままの箇所はここ。

    private void waitForWriteState() {
        synchronized (writeQueue) {
            while (concurrentReads!=0) {
                try {
                    writeQueue.wait();
                } catch (InterruptedException e) {}
            }
        }
    }

MemoryAwareConcurrentReadMapって言うのはいわゆるReaderWriterLock機能を持ったMapなのだろう。とすると、concurrentReadsはその名のとおり現在getしているスレッド数で、put時にはgetしているやつがいなくなるまで待つのだろうと思われる。で、concurrentReadsの定義はこれ、

    private volatile long concurrentReads = 0;

volatile宣言してあってもマルチスレッド環境では、synchronizedが必要。Java初心者の方は調べてくださいね。ここでは説明しませんけれど。とすると、synchronizedされているのはどのオブジェクト?writeQueue?他のconcurrentReadsをアクセスしているところはというと、

    private void lockWrite() {
        synchronized (writeLock) {
            concurrentReads++;
        }
    }
    
    private void unlockWrite(){
        synchronized (writeLock) {
            concurrentReads--;
        }
        synchronized (writeQueue) {
            writeQueue.notify();
        }
    }

    public Object get(Object key) {
        int hash = key.hashCode();
        lockWrite();        
        try {
            int index = index(hash,table.length);

            for (Entry current = table[index]; current!=null; current=current.next) {
                if (hash!=current.hash) continue;
                Object oldKey = current.getKey();
                Object oldValue = current.getValue();
                if (!current.isValid()) continue;
                if (key==oldKey || key.equals(oldKey)) {
                    return oldValue;
                }
            }
        } finally {
            unlockWrite();
        }
        return null;
    }

おやおや、writeLockで排他しているみたいだ。それなら、waitForWriteState()の呼出元はどうなっているんだろう?いくつかあるが、例えばput()

    public void put(Object key, Object value) {
        if (value == null) {
            remove(key);
            return;
        }

        synchronized(writeLock) {
            waitForWriteState();
            putNonBlocking(key,value,false);
        }
    }

呼び出す前にsynchronized(writeLock)してるのね。でも....
なるほど、ハイ、わかりました原因。バグですね。それじゃあ、また来週!!
ちなみに最新のソースでは直っているみたい。入れ替えた方がいいかも。