8

LDAP 接続プールを使用しているときに、ドキュメントにそうではないとclose()書かれているにもかかわらず、コンテキストを呼び出してもプールに返されないように見えることに気付きました。したがって、アイテムがすでに最大サイズになっているときにプールからアイテムを取得しようとすると、ハングします。

最小限のケースに絞り込むことができました。関連するすべてのオブジェクトを決定論的に呼び出していると思いclose()ますが、実際にオブジェクトをプールに返すのはガベージ コレクションに依存しているように見えますが、これは予想外です。なぜこうなった?閉じる必要がある他のオブジェクトはありますか?

以下のコード スニペットでは:

  • 問題を強調するために、最大プール サイズを人為的に 1 に設定しました。
  • プールからを取得しDirContext(行 (2))、それをプールに戻そうとし (行 (4))、プールから別のオブジェクトを取得し (行 (6))、同じ返されたオブジェクトを返す必要があります。
  • 代わりに、2 番目の要求 (行 (6)) は への内部呼び出しでハングしますObject.wait()。プールされたオブジェクトが利用可能になるのを待っていると思います。
  • (1) をコメントアウトしてプーリングをオフにすると、ハングしません (ただし、プーリングが必要です!)。
  • (3) をコメントアウトすると - への呼び出しSearchResults.next()- 正常に動作します。
  • 行 (5) のコメントを外して、'return to pool' 呼び出しとプールへの新しいオブジェクトの要求の間にガベージ コレクションを強制すると、ハングしません。

行(3)をコメントアウトすると問題が解消されるため、おそらくその戻り値を正しく閉じておらず、プールされた接続を開いたままにしています。ただし、この場合、メソッドresults.next()は a を返します。これにはメソッドがなく、ドキュメントにクリーンに閉じる方法に関するガイダンスもありません。SearchResultclose

テストケース:

@Test
public void testHangs() throws NamingException {

    System.setProperty("com.sun.jndi.ldap.connect.pool.debug", "fine");
    System.setProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1");

    Hashtable<String,String> env = new Hashtable<String,String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, passwd);
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.PROVIDER_URL, ldapUrl);

    // use a connection pool
    env.put("com.sun.jndi.ldap.connect.pool", "true"); // -----------------  (1)

    // get a context from the pool.
    DirContext context = new InitialDirContext(env); // -------------------- (2)
    NamingEnumeration<SearchResult> results = context.search("", query, getSC());
    // obviously the next two lines would normally be in a 
    // while(results.hasMore()) { ... = results.next(); } loop.
    assertTrue(results.hasMore()); // this is only a problem when there are some results.
    results.next(); // ----------------------------------------------------- (3)

    // ensure the context is returned to the pool.
    results.close();
    context.close(); // ---------------------------------------------------- (4)

    //System.gc(); // ------------------------------------------------------ (5)

    new InitialDirContext(env);  // hangs here! ---------------------------- (6)
}

コードをそのまま使用すると、コンソールに次のように表示されます。

Create com.sun.jndi.ldap.LdapClient@1a7bf11[ldapad:389]
Use com.sun.jndi.ldap.LdapClient@1a7bf11

一方、GC を強制すると、さらに次のように表示されます。

Release com.sun.jndi.ldap.LdapClient@93dee9 <-- on GC
Use com.sun.jndi.ldap.LdapClient@93dee9     <-- on new InitialDirContext
4

2 に答える 2

8

少し調査した結果、SearchResult オブジェクトに LdapCtx オブジェクトへの参照が含まれているため、LDAP 接続がプールに返されないことがわかりました。

交換すれば

results.next();

以下で

SeachResult ob = results.next();
((Context)ob.getObject()).close();

接続はプールに正しく返されます。これは、デフォルトの実装のバグのようです。

Spring LDAPTemplate を使用する場合、SearchResult を構築するプロセスの一部として LdapCtx を閉じるカスタム「java.naming.factory.object」が環境に提供されるため、問題は存在しません。これは、Spring LDAP ライブラリをクラスパスに追加し、以下を InitialContext に追加することで簡単にデモストレーションできます。

java.naming.factory.object=org.springframework.ldap.core.support.DefaultDirObjectFactory

これが完了すると、 SearchResult が保持するオブジェクトがcom.sun.jndi.ldap.LdapCtx:com.sun.jndi.ldap.LdapCtxからorg.springframework.ldap.core.DirContextAdapterに変更されます。DefaultDirObjectFactoryクラスは、DirContextAdapterの作成を担当し、 DirContextAdapterDirectoryManagerに返す前に LdapCtx を閉じます。DefaultDirObjectFactoryからの finally ブロックは次のとおりです。

finally {
        // It seems that the object supplied to the obj parameter is a
        // DirContext instance with reference to the same Ldap connection as
        // the original context. Since it is not the same instance (that's
        // the nameCtx parameter) this one really needs to be closed in
        // order to correctly clean up and return the connection to the pool
        // when we're finished with the surrounding operation.
        if (obj instanceof Context) {

            Context ctx = (Context) obj;
            try {
                ctx.close();
            }
            catch (Exception e) {
                // Never mind this
            }

        }
    }
于 2012-08-15T08:58:18.757 に答える
4

SearchControlsオブジェクトを変更してreturningObjFlag属性を false にします。通常、オブジェクト自体は必要なく、その nameInNamespace とその属性だけが必要です。サブコンテキストを作成または変更する場合は、オブジェクト自体のみが必要です。

于 2012-08-15T11:06:00.220 に答える