Monday, December 30, 2013

LdapContext を close() するときのことについて

久しぶりに LDAP をさわってみています。 いろいろ忘れてしまったので, まずは手元の環境に用意した LDAP サーバー (OpenLDAP 2.4.38) に簡易認証で接続して, そのあとその接続を閉じるということから始めてみました。 そんな中で, 接続を閉じる際に, UNBIND リクエストを送らずに接続を閉じてしまうケースがあったので, それについてメモを残しておくことにしました。

特に何もせずに接続を閉じた場合

たとえば, 次のようなコードを書いて, ためしてみたとします。

        Hashtable<String, Object> environment;
        Control[] controls;
        
        // ..
        
        LdapContext context = null;
        try {
            context = new InitialLdapContext(environment, controls);
        } catch (NamingException e) {
            e.printStackTrace(System.err);
        } finally {
            if (context != null) {
                try {
                    context.close();
                } catch (NamingException e) {
                    e.printStackTrace(System.err);
                }
            }
        }

JNDI の Context インターフェースや, そこから派生したインターフェースやクラスたちは, AutoCloseable インターフェースを継承または実装していないようです。 なので, ここでは try-with-resources の構文を使わない書き方にしています。

これを実行してみると, LDAP サーバー側のログには, たとえば次のように出力されます。

Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 fd=10 ACCEPT from IP=192.168.1.2:61811 (IP=0.0.0.0:389)
Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 op=0 BIND dn="cn=Manager,dc=localdomain" method=128
Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 op=0 BIND dn="cn=Manager,dc=localdomain" mech=SIMPLE ssf=0
Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 op=0 RESULT tag=97 err=0 text=
Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 op=1 UNBIND
Dec 30 18:18:52 vm001 slapd[24887]: conn=1150 fd=10 closed

接続を閉じる直前に UNBIND がおこなわれています。

検索をおこなってから接続を閉じた場合

次は, LDAP サーバーに接続したら, 何かを検索するということやってみます。

        Hashtable<String, Object> environment;
        Control[] controls;
        
        // ..
        
        LdapContext context = null;
        try {
            context = new InitialLdapContext(environment, controls);
            LdapName base = new LdapName("ou=Wonderland,dc=localdomain");
            Attributes attrs = new BasicAttributes();
            attrs.put("objectClass", "inetOrgPerson");
            NamingEnumeration<SearchResult> results
                    = context.search(base, attrs);
            if (results.hasMore())
                System.out.println(results.next().getNameInNamespace());
        } catch (NamingException e) {
            e.printStackTrace(System.err);
        } finally {
            if (context != null) {
                try {
                    context.close();
                } catch (NamingException e) {
                    e.printStackTrace(System.err);
                }
            }
        }

このコードでは, objectClassinetOrgPerson であるようなオブジェクトを検索し, 最初に見つかったオブジェクトの DN を標準出力に書き出すということをしています。

これを実行すると, LDAP サーバー側のログには, 次のように出力されました。

Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 fd=10 ACCEPT from IP=192.168.1.2:61814 (IP=0.0.0.0:389)
Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 op=0 BIND dn="cn=Manager,dc=localdomain" method=128
Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 op=0 BIND dn="cn=Manager,dc=localdomain" mech=SIMPLE ssf=0
Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 op=0 RESULT tag=97 err=0 text=
Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 op=1 SRCH base="ou=Wonderland,dc=localdomain" scope=1 deref=3 filter="(&(objectClass=inetOrgPerson))"
Dec 30 18:20:01 vm001 slapd[24887]: conn=1151 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
Dec 30 18:20:02 vm001 slapd[24887]: conn=1151 fd=10 closed (connection lost)

どうやら UNBIND せずに接続を閉じてしまったようです。 (しかも, 詳細は省きますが, この接続は LdapContext クラスの close() メソッドによって閉じられたわけではなく, このコードを実行した JVM インスタンスの終了時に閉じられたものでした。)

なぜでしょう。

少し変えてみると

検索結果を取り扱っている箇所を, 次のように, 少し変えてみました。

            NamingEnumeration<SearchResult> results
                    = context.search(base, attrs);
            while (results.hasMore())
                System.out.println(results.next().getNameInNamespace());

違いは, ifwhile に変えただけです。

手元の環境では, 実際にこの検索にヒットするオブジェクトは 1 件しかないので, 実質的に何も違いはないはずだと思ったのですが, 実行してみると, ログは次のようになりました。

Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 fd=10 ACCEPT from IP=192.168.1.2:61815 (IP=0.0.0.0:389)
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=0 BIND dn="cn=Manager,dc=localdomain" method=128
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=0 BIND dn="cn=Manager,dc=localdomain" mech=SIMPLE ssf=0
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=0 RESULT tag=97 err=0 text=
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=1 SRCH base="ou=Wonderland,dc=localdomain" scope=1 deref=3 filter="(&(objectClass=inetOrgPerson))"
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 op=2 UNBIND
Dec 30 18:20:54 vm001 slapd[24887]: conn=1152 fd=10 closed

今度は UNBIND してから接続を閉じています。

なぜでしょう。

NamingEnumeration#hasMore() の効果

何か違いがあるとしたら, 後者のコードでは NamingEnumeration クラスの hasMore メソッドが 1 回多く呼ばれていますので, ひょっとしたらそれが何らかの効果を持っているのかもしれないと思いました。

そこで, NamingEnumeration クラスの Javadoc を読んでみましたところ, hasMore メソッドではなく, close メソッドのほうに, 次のような記述が見つかりました。

Closes this enumeration. After this method has been invoked on this enumeration, the enumeration becomes invalid and subsequent invocation of any of its methods will yield undefined results. This method is intended for aborting an enumeration to free up resources. If an enumeration proceeds to the end--that is, until hasMoreElements() or hasMore() returns false-- resources will be freed up automatically and there is no need to explicitly call close().

NamingEnumeration (Java Platform SE 7 )

おそらく, わたしは大事なことを見落としていたのだと思います。 NamingEnumeration のインスタンスに対しては, 用が済んだら, まず原則として close() を実行するべきだったのでしょう。 それをせぬまま LdapContext インスタンスに対して close() を実行しても, UNBIND はおこなわれず, 接続を閉じることもおこなわれない, ということなのだと思います。

この点, たとえば次のようなコードでためしてみたら, きちんと UNBIND してから接続を閉じていました。

            NamingEnumeration<SearchResult> results
                    = context.search(base, attrs);
            if (results.hasMore())
                System.out.println(results.next().getNameInNamespace());
            results.close();

そのうえで, 原則は原則なのですが, 例外的には hasMore() を (false が返って来るようになるまで) 繰り返し実行すれば, close() は明示的に実行しなくてもいいよ, ということなのでしょう。 それが, close メソッドの Javadoc に記載されていたことの意味なのだと思います。 だからこそ, 先ほど ifwhile に書き換えただけで, UNBIND がおこなわれ, 接続が閉じられるようになったということなのでしょう。

ちなみに

ちなみに, というほどでもないんですが, NamingEnumerationclose() し忘れたまま LdapContextclose() した場合, その時点では接続は閉じられませんが, それでもその後 NamingEnumeration が GC された時点で UNBIND がおこなわれ, 接続は閉じられるようです。 その根拠をきちんと調べたわけではないのですが, try-catch-finally ブロックの外側で System.gc() してみたら, とりあえずそうなったので, たぶんそういうことなのかなと思います。

以上です。

No comments:

Post a Comment