getObjectByIdから極希に ArrayIndexOutOfBoundsException が発生する。
こちら(http://groups.google.com/group/google-appengine-java/browse_thread/thread/538ad4400617792b/79340c75b1c057d0?#79340c75b1c057d0)でも出ているようですが、サーバーサイドで起きない確証が無いので、ログ(というかエラー出力)を埋め込んで検証した。
org.datanucleus.store.appengine.DatastorePluginRegistry
public ExtensionPoint getExtensionPoint(String id) { ExtensionPoint ep = delegate.getExtensionPoint(id); if ("org.datanucleus.callbackhandler".equals(id)) { for (Extension ext : ep.getExtensions()) { for (ConfigurationElement cfg : ext.getConfigurationElements()) { if (cfg.getAttribute("name").equals("JPA")) { System.err.println("JPA class-name put requesting... cfg=" + cfg.hashCode()); // override with our own callback handler // See DatastoreJPACallbackHandler for the reason why we do this. if (!DatastoreJPACallbackHandler.class.getName().equals(cfg.getAttribute("class-name"))) { System.err.println("INFO name=JPA class-name change to DatastoreJPACallbackHandler.class.getName()"); cfg.putAttribute("class-name", DatastoreJPACallbackHandler.class.getName()); } } } } } }
連続してリクエストされるGWTアプリで以下のような結果に。
ローカル
JPA class-name put requesting... cfg=2547425
JPA class-name put requesting... cfg=2547425
JPA class-name put requesting... cfg=2547425
JPA class-name put requesting... cfg=2547425
GAE
.
: JPA class-name put requesting... cfg=30733141
.: JPA class-name put requesting... cfg=15680921
.: JPA class-name put requesting... cfg=9722773
.: JPA class-name put requesting... cfg=1241493
...というわけでGAE上では大丈夫のようです。
但し、上記のように、「(!DatastoreJPACallbackHandler.class.getName().equals(cfg.getAttribute("class-name")))」のパッチを当てないと、org.datanucleus.plugin.ConfigurationElementの中で、文字列配列が無限増殖して最後にはメモリが枯渇します。なんで増殖するかと言えば、以下のようになっているから。
org.datanucleus.plugin.ConfigurationElement
public void putAttribute(String name, String value) { String[] names = new String[attributeNames.length+1]; System.arraycopy(attributeNames, 0, names, 0, attributeNames.length); names[attributeNames.length] = name; attributeNames = names; attributes.put(name,value); }
マルチスレッドでは1行目と3行目のlengthが同一のものとは限らないので、ArrayIndexOutOfBoundsException が起きます。無駄に配列増殖している意味が分かりませんが、他のクラスのaddXXXXメソッドのパターンも同じなので、どうもコピペしちゃったんじゃないかと思います。attributesがMapなのに、attributeNamesの方は重複するnameが先のDatastorePluginRegistry#getExtensionPoint のおかげで無限に増殖してしまうのでした。DatastorePluginRegistry#getExtensionPointにパッチあてたついでに、こちらも変えちゃいました。これで他のところから似たようなことをされても増殖もArrayIndexOutOfBoundsExceptionも免れることができます。
public void putAttribute(String name, String value) { attributes.put(name,value); Set<String> names = (Set<String>)attributes.keySet(); attributeNames = names.toArray(new String[names.size()]); }
あと、以下のコメントは調べたけど、コンストラクタの方でセットすれば十分で、getExtentionPointするたびにputAttributeする意味は無いようです。
// override with our own callback handler
// See DatastoreJPACallbackHandler for the reason why we do this.
ログに、「INFO name=JPA class-name change to DatastoreJPACallbackHandler.class.getName()」が全く記録されませんでした。
念のため残しているけど無意味にループするだけなので落ち着いたら削除するかも。
コンストラクタの方は以下のようにしています。
DatastorePluginRegistry(PluginRegistry delegate) { this.delegate = delegate; ExtensionPoint ep = delegate.getExtensionPoint("org.datanucleus.callbackhandler"); for (Extension ext : ep.getExtensions()) { for (ConfigurationElement cfg : ext.getConfigurationElements()) { if (cfg.getAttribute("name").equals("JPA")) { // override with our own callback handler // See DatastoreJPACallbackHandler for the reason why we do this. cfg.putAttribute("class-name", DatastoreJPACallbackHandler.class.getName()); } } } }
...バージョン上がったときのマージが面倒だけどね。