読者です 読者をやめる 読者になる 読者になる

たぷつきません

おなかがでてきた。もうたぷついてるやん。

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());
        }
      }
    }
  }

...バージョン上がったときのマージが面倒だけどね。