[Mono-bugs] [Bug 449300] New: [PATCH] Exceptions in Novell.Directory.Ldap

bugzilla_noreply at novell.com bugzilla_noreply at novell.com
Wed Nov 26 10:00:52 EST 2008


https://bugzilla.novell.com/show_bug.cgi?id=449300


           Summary: [PATCH] Exceptions in Novell.Directory.Ldap
           Product: Mono: Class Libraries
           Version: SVN
          Platform: i386
        OS/Version: Linux
            Status: NEW
          Keywords: patch
          Severity: Normal
          Priority: P5 - None
         Component: System
        AssignedTo: mono-bugs at lists.ximian.com
        ReportedBy: jlarimer at gmail.com
         QAContact: mono-bugs at lists.ximian.com
          Found By: ---


Created an attachment (id=255825)
 --> (https://bugzilla.novell.com/attachment.cgi?id=255825)
proposed patch

Description of Problem:
-----------------------

The Novell.DirectoryServices.Ldap.Connection class has multiple race conditions
that can cause unhandled exceptions.

There are three big issues I ran into:

1.

  // Just before closing the sockets, abort the reader thread
  if ((reader != null) && (reason != "reader: thread stopping"))
    reader.Abort();

  It's possible for reader to be null by the time reader.Abort() is
called,leading to a NullReferenceException.

2. The other issue is that when Abort() is called on the reader thread, there's
no guarantee the thread will be aborted immediately, so it's possible for
shutdown() to close the socket while the reader thread is still trying to read,
which causes an ObjectDisposedException.

If the code is compiled to run under a JVM (TARGET_JVM), the Abort() doesn't
happen and the reader thread catches the ObjectDisposedException. For now, I
think the non-JVM library should work the same way.

3. If there's a SocketException during connect(), freeWriteSemaphore() can get
called twice and will exception the 2nd time. This is because
freeWriteSemaphore() is called in the catch{} blocks and also in the finally{}
block.

Really, all of Connection.cs should be rewritten to use asynchronous I/O
instead of relying on a reader thread and the semaphore implementation, but the
attached patch fixes most of the issues that I ran into with this class.


Steps to reproduce the problem:
-------------------------------

Here's some test code. I ran it against a working Active Directory server,
fiddling with the various values (THREAD_COUNT, THREAD_WAIT) to get different
results. Some of the exceptions I've seen are below in Actual Results.

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
using System.Threading;

namespace LdapTest {
    class Program {

        static readonly int THREAD_SLEEP = 1000;
        static readonly int THREAD_COUNT = 50;

        static readonly string SERVER = "LDAP://192.168.234.39";
        static readonly string USERNAME = "Administrator";
        static readonly string PASSWORD = "password123";

        static void Main(string[] args) {
            for(int i=0; i<THREAD_COUNT; i++) {
                ThreadStart ts = new ThreadStart(TheThread);
                Thread t = new Thread(ts);
                t.Start();
            }
        }

        static void TheThread() {
            while(true) {
                string path = SERVER;

                DirectoryEntry de = null;
                DirectorySearcher ds = null;
                try {
                    de = new DirectoryEntry(path, USERNAME, PASSWORD,
AuthenticationTypes.Secure);
                    ds = new DirectorySearcher(de);
                    ds.SearchScope = SearchScope.Base;
                    ds.PropertiesToLoad.Add("defaultNamingContext");
                    using (SearchResultCollection src = ds.FindAll()) {
                        foreach (SearchResult sr in src) {
                            ResultPropertyValueCollection rpvc =
sr.Properties["defaultNamingContext"];
                            if (rpvc != null) rpvc[0].ToString();
                        }
                    }
                } finally {
                    if (ds != null)
                        ds.Dispose();
                    if (de != null) {
                        de.Close();
                        de.Dispose();
                    }
                }

                Thread.Sleep(THREAD_SLEEP);
            }
        }
    }
}


Actual Results:
---------------

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object
  at Novell.Directory.Ldap.Connection.shutdown (System.String reason, Int32
semaphoreId, Novell.Directory.Ldap.InterThreadException notifyUser) [0x0011d]
in /home/jlarimer/mono-dev/m
cs/class/Novell.Directory.Ldap/Novell.Directory.Ldap/Connection.cs:1148
  at Novell.Directory.Ldap.Connection.destroyClone (Boolean apiCall) [0x0006d]
in
/home/jlarimer/mono-dev/mcs/class/Novell.Directory.Ldap/Novell.Directory.Ldap/Connection.cs:918
  at Novell.Directory.Ldap.LdapConnection.Disconnect
(Novell.Directory.Ldap.LdapConstraints cons, Boolean how) [0x00000] in
/home/jlarimer/mono-dev/mcs/class/Novell.Directory.Ldap/No
vell.Directory.Ldap/LdapConnection.cs:2116
  at Novell.Directory.Ldap.LdapConnection.Disconnect () [0x00000] in
/home/jlarimer/mono-dev/mcs/class/Novell.Directory.Ldap/Novell.Directory.Ldap/AssemblyInfo.cs:1
  at System.DirectoryServices.DirectorySearcher.Dispose (Boolean disposing)
[0x00021] in
/home/jlarimer/mono-dev/mcs/class/System.DirectoryServices/System.DirectoryServices/Directory
Searcher.cs:712
  at System.ComponentModel.Component.Dispose () [0x00000] in
/home/jlarimer/mono-dev/mcs/class/System/System.ComponentModel/Component.cs:115
  at (wrapper remoting-invoke-with-check)
System.ComponentModel.Component:Dispose ()
  at ldaptest.Program.TheThread () [0x00000]

Unhandled Exception: System.SystemException: Connection.freeWriteSemaphore(-2):
semaphore not owned by any thread
  at Novell.Directory.Ldap.Connection.freeWriteSemaphore (Int32 msgId)
[0x00000]
  at Novell.Directory.Ldap.Connection.connect (System.String host, Int32 port,
Int32 semaphoreId) [0x00000]
  at Novell.Directory.Ldap.Connection.connect (System.String host, Int32 port)
[0x00000]
  at Novell.Directory.Ldap.LdapConnection.Connect (System.String host, Int32
port) [0x00000]
  at System.DirectoryServices.DirectorySearcher.InitBlock () [0x00000]
  at System.DirectoryServices.DirectorySearcher.DoSearch () [0x00000]
  at System.DirectoryServices.DirectorySearcher.get_SrchColl () [0x00000]
  at System.DirectoryServices.DirectorySearcher.FindAll () [0x00000]
  at (wrapper remoting-invoke-with-check)
System.DirectoryServices.DirectorySearcher:FindAll ()
  at ldaptest.Program.TheThread () [0x00000]

Unhandled Exception: System.ObjectDisposedException: The object was used after
being
disposed.
  at System.Net.Sockets.NetworkStream.CheckDisposed () [0x00000]
  at System.Net.Sockets.NetworkStream.Read (System.Byte[] buffer, Int32 offset,
Int32
size) [0x00000]
  at System.IO.Stream.ReadByte () [0x00000]
  at Novell.Directory.Ldap.Asn1.Asn1Identifier..ctor (System.IO.Stream
in_Renamed)
[0x00000]
  at Novell.Directory.Ldap.Connection+ReaderThread.Run () [0x00000]
Expected Results:

None of the above exceptions should happen.

How often does this happen? 

The issues are mostly race conditions, so they happen randomly depending on the
hardware, platform, etc.

Additional Information:
-----------------------

See attached patch.


-- 
Configure bugmail: https://bugzilla.novell.com/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the QA contact for the bug.
You are the assignee for the bug.


More information about the mono-bugs mailing list