ハングしちゃうんですけど!を調べた
昨日の日記で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)してるのね。でも....
なるほど、ハイ、わかりました原因。バグですね。それじゃあ、また来週!!
ちなみに最新のソースでは直っているみたい。入れ替えた方がいいかも。