S h o r t S t o r i e s

// Tales from software development

Threading issues with legacy unmanaged DLLs

leave a comment »

One of the interfaces that I’ve recently written allows a desktop application to request data from a remote data store.

The desktop application sends its request to a remoting server running as a Windows Service on a LAN server that then calls the customer’s interface to retrieve the data. The customer’s interface is implemented as a Delphi 6 DLL that was written in 2002 and has not been updated since. The Windows Service uses P/Invoke to call the DLL.

The development of the interface was difficult because there was no test environment and the customer would not allow any testing in their live environment. The call to the DLL had to be replaced with a mock function.

The initial implementation of the client/server interface was deployed in April and was tested successfully. There were suspicions that the customer’s testing wasn’t very rigorous but this was their responsibility and we didn’t believe that there were any problems with the interface.

Inevitably, it was only a couple of weeks ago in the week before the go-live date for the project that the customer did some serious testing and discovered that there were serious problems with the interface. The DLL failed with errors and exceptions on approximately 40% of the calls that were made to it. Curiously, one of the error messages was “KILLED :”. The customer didn’t know what this signified and it had never been seen in any other interface that called the DLL.

After a bit of head scratching I began to think that this was a threading issue. The Windows Service was receiving requests from multiple clients and each would be running on its own thread.

The first step was to synchronize the calls to the DLL so that only one request was made at a time. This was a quick fix – all that had to be done was to wrap the code that called the DLL in a lock(object) {…} block.

When this version was deployed there was a slight improvement in the incidence of errors but it was still averaging between 25% and 30% of the calls being made to the DLL.

If the problem was in my code rather than the DLL then the most likely reason was that the DLL needed to be called on the same thread each time, i.e. once it was loaded and initialised in the service’s process memory it would only work when called with the same thread it was first called on.

The solution required goes against the grain. These days we tend to be trying to find ways to implement multiple theaded processing but this problem required a multiple threads to be handled by a singleton thread.

It’s not the kind of code that you’d want to write on a regular basis but it does provide a solution to this particular problem. It’s a static class that starts a thread that’s used to make the calls to the DLL on. The thread runs continously but is despatched using a ManualResetEvent. Separate ManualResetEvent objects are also used to signal that the call to the DLL has been completed and whether the thread should exit. When the Windows Service is stopped it should call the Stop() method to terminate the DLL caller thread.

Once this code was implemented the error rate for calling the DLL dropped to less than 0.5%. It’s likely that whatever problems remain are in the DLL rather than a problem with the way it’s being called. 
 

namespace Vitality.Integration.DataInterface
{
    using System;
    using System.Threading;

    public static class DLLCaller
    {
        private static object lockObject;
        private static Thread dllThread;
        private static string functionArgument;
        private static string functionResponse;
        private static ManualResetEvent callRequested = new ManualResetEvent(false);
        private static ManualResetEvent callComplete = new ManualResetEvent(false);
        private static ManualResetEvent stopRequested = new ManualResetEvent(false);

        [DllImport("DataSvr.dll", CharSet = CharSet.Ansi)]
        private static extern string RequestData([MarshalAs(UnmanagedType.AnsiBStr)] string functionArgument);

        static DLLCaller()
        {
            lockObject = new object();
            Start();
        }

        public static string HandleIFSRequest(string functionArgument)
        {
            lock (lockObject)
            {
                DLLCaller.functionArgument = functionArgument;
                DLLCaller.functionResponse = string.Empty;

                // Signal the DLL caller thread to make the call to the DLL:
                DLLCaller.callComplete.Reset();
                DLLCaller.callRequested.Set();

                // Wait for the call to complete:
                DLLCaller.callComplete.WaitOne();
                return DLLCaller.functionResponse;
            }
        }

        public static void Start()
        {
            // Reset the wait handles:
            DLLCaller.callRequested.Reset();
            DLLCaller.callComplete.Reset();
            DLLCaller.stopRequested.Reset();

            // Start the DLL call processing thread:
            ThreadStart threadStart = new ThreadStart(DataRequest);
            DLLCaller.dllThread = new Thread(threadStart);
            DLLCaller.dllThread.Start();
        }

        public static void Stop()
        {
            // Signal that a stop has been requested:
            DLLCaller.stopRequested.Set();
        }

        private static void CallDataRequest()
        {
            do
            {
                // Wait on the exit requested and call requested handles:
                if (WaitHandle.WaitAny(new WaitHandle[] { stopRequested, callRequested }) == 0)
                {
                    // If WaitAny returns 0 it indicates that the first element in the array was
                    // signalled, i.e. stop has been requested.
                    break;
                }

                try
                {
                    DLLCaller.functionResponse = RequestData(functionArgument);
                }
                catch (Exception exception)
                {
                    DLLCaller.functionResponse = exception.Message;
                }

                DLLCaller.callRequested.Reset();
                DLLCaller.callComplete.Set();
            }
            while (true);
        }
    }
}
Advertisements

Written by Sea Monkey

September 30, 2010 at 8:18 am

Posted in Development

Tagged with

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: