views:

68

answers:

3

I am using .NET 2.0 and the finally block does not seem to be getting executed if the Thread times out. For example, if I see the message "Child Thread Timed Out...", I will not see the message "Finally block started...". This means that the database objects (Oracle.DataAccess) may not be getting cleaned up properly. Is there a way to force the cleanup inside the child thread, or should the cleanup be moved to the main thread and pass in the database objects to the child thread?

   private void runThread(string strSP, object objThreadParameter)
    {
        try
        {
            bool blnThreadCompletedOK = true;

            Thread threadHelper = new Thread(getData);
            threadHelper.Start(objThreadParameter);

            // Wait for called thread.  
            blnThreadCompletedOK = threadHelper.Join(THREAD_TIMEOUT);
            if (blnThreadCompletedOK)
            {
                // Thread has completed and should have stopped running.
                // i.e. the thread has processed normally or an exception has been copied to the objExceptionThread object.
                if (objExceptionThread != null)
                {
                    throw objExceptionThread;
                }
            }
            else
            {
                System.Diagnostics.EventLog.WriteEntry("Main thread", "Child Thread Timed Out...", System.Diagnostics.EventLogEntryType.Warning);

                // Main thread has timed out waiting for the child thread.  Likely the child thread is still running.
                if (threadHelper.IsAlive)
                {
                    threadHelper.Abort();  // This will trigger the exception handling in the child thread and cause the finally
                                           // block to be executed.
                }
                throw (new Exception("The call to " + strSP + "() timed out as it exceeded " + (THREAD_TIMEOUT / 1000).ToString() + " seconds"));
            }
        }
        catch (Exception exc)
        {
            throw new PivotalApplicationException(exc.Message, exc, mrsysSystem);
        }
    }


    private void getData(object objThreadParameter)
    {
        OracleCommand oraCmd = null;
        OracleConnection oraConn = null;
        OracleDataReader dr = null;

        try
        {              
            // Initialization.
            int intMAX_RETRIES = 20;       // Maximum number of retries.
            int intRETRY_DROP_POOL = 5;    // At some point, if connections are still failing, try clearing the pool.

            // Other initialization stuff...

            // Now execute the SP.
            for (int i = 1; i <= intMAX_RETRIES; i++)
            {
                try
                {
                    try
                    {
                        // Setup Oracle connection and initialize Oracle command object.
                        getOracleConnection(out oraConn, connString);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up connection - " + exc.Message);
                    }

                    try
                    {
                        oraCmd = new OracleCommand(strSP, oraConn);
                        setupCommand (out oraCmd);
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() setting up parameters - " + exc.Message);
                    }

                    try
                    {
                        dr = oraCmd.ExecuteReader();
                        break; // Success, so, leave the for loop.
                    }
                    catch (Exception exc)
                    {
                        throw new Exception("Error in getData() executing command.\n\n" + strParametersMsg + " \n\n" + exc.Message);
                    }
                }
                catch (Exception excInner)
                {

                    if (i >= intMAX_RETRIES)
                    {
                        throw new Exception(excInner.Message);
                    }
                    else
                    {
                        // Cleanup oraCmd, oraConn, oraDr...
                    }
                }
            }

            try
            {
                // Process results...
            }
            catch (Exception exc)
            {
                throw new Exception("Error in getData() processing results - " + exc.Message);
            }

            // Now set the variables that are shared between the Main thread and this thread...

        }
        catch (Exception exc)
        {
            logMessage(exc.Source + " " + exc.Message);
            objExceptionThread = exc;  // Initialize exception in Main Thread...
        }
        finally
        {
            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block started...", System.Diagnostics.EventLogEntryType.Warning);

            // With .NET 2.0 and later, the finally block should always be executed correctly for a Thread.Abort()
            if (!(dr == null))
            {
                dr.Dispose();
            }
            if (!(oraCmd == null))
            {
                oraCmd.Dispose();
            }
            if (!(oraConn == null))
            {
                oraConn.Close();
                oraConn.Dispose();
            }

            System.Diagnostics.EventLog.WriteEntry("Child Thread", "Finally block completed...", System.Diagnostics.EventLogEntryType.Warning);
        }
    }
+2  A: 

You should only Abort a thread if that thread has nothing of importance to do any more. In other cases I'd recommend setting some kind of notification (i.e. setting a boolean property on the thread) that causes your thread to shut down gracefully. Having said that according to the documentation the finally block should be executed in .NET 2.0:

When a call is made to the Abort method to destroy a thread, the common language runtime throws a ThreadAbortException. ThreadAbortException is a special exception that can be caught, but it will automatically be raised again at the end of the catch block. When this exception is raised, the runtime executes all the finally blocks before ending the thread.

My best guess as to what's happening is that the main thread is exiting before the finally block on your thread has a chance to get executed. Try putting in a Thread.Sleep after aborting your thread to see if that changes the behavior.

Edit: I wrote a simple example with .NET 2.0 which produces the following output that shows that the finally block is executed.

Alive and kicking

Alive and kicking

Alive and kicking

Exception

Finally

class ThreadTest
{
    public ThreadTest() { }

    public void test()
    {
        try
        {
            while (true)
            {
                Console.WriteLine("Alive and kicking");
                Thread.Sleep(2000);
            }
        }

        catch (Exception ex)
        {
            Console.WriteLine("Exception");
        }

        finally
        {
            Console.WriteLine("Finally");

        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        ThreadTest myThreadTest = new ThreadTest();
        Thread myThread = new Thread(new ThreadStart(myThreadTest.test));
        myThread.Start();
        Thread.Sleep(5000);
        bool status = myThread.Join(1000);
        if (myThread.IsAlive)
        {
            myThread.Abort();
        }
        Thread.Sleep(5000);
    }
}
BrokenGlass
+1  A: 

You can only interrupt a thread when it's in managed code. If it's in native code, then the runtime schedules the ThreadAbortException to be thrown when the native code returns. And finally blocks would execute after that. The finally block will not run until the native function returns and managed execution resumes.

If your problem is that the native code is hung, then the finally block cannot run.

Ben Voigt
A: 

Thanks - The problem does seem to be one of timing. If I check the threadHelper.IsAlive property repeatedly and keep the main thread running, the finally block does execute. So, I think the code hangs on dr = oraCmd.ExecuteReader(); The Thread.Join() returns, attempts to Abort() - but can't at that time, and then the main thread ends so the child threads are also killed. I think that does leave an open connection though.

ODP.NET is supposed to be a managed data provider (http://wiki.oracle.com/page/Oracle+Data+Provider+for+.Net) and it also has a command timeout property.

"CommandTimeout Specifies the number of seconds the command is allowed to execute before terminating the execution with an exception".

I will investigate further why the CommandTimeout does not seem to be respected and if that still fails, I may try Ending Application Domains per the man who seems to have all the books.

http://www.albahari.com/threading/part4.aspx#_Aborting_Threads

Thanks for the help!

JulianM