Debugging Multi-Threaded Applications with SmartInspect

Introduction
Benefits of Using SmartInspect
Tracking the Execution Flow
Example of Using SmartInspect
Conclusion

Introduction

With traditional debuggers it can be hard to debug multi-threaded applications efficiently. They alter the execution behavior and thus applications do not behave in the same way as without a debugger. Examples which suffer from this fact are synchronization problems or the execution flow of applications in general. This makes reliable debugging of multi-threaded applications difficult.

A possible solution to this problem is to use simple print statements in the source code to get at least some trivial information about software applications. Although this approach seems to be quite good at first, it tends to have its own share of problems. It can only be used to output simple messages or trivial data types. Furthermore it can be hard to navigate through those messages and find the relevant information. SmartInspect addresses these problems and provides a powerful solution for debugging and monitoring multi-threaded applications. For a detailed introduction to SmartInspect itself, please refer to its product description.

Benefits of Using SmartInspect

Among other things, SmartInspect has been designed to simplify the debugging of multi-threaded applications. Therefore the libraries have several features intended for this purpose. On the one hand they are fully thread-safe and on the other hand they provide several logging methods to record the execution flow of a multi-threaded application. Furthermore each log message contains the ID of the thread it was originally originated from.

Using the recorded information from the libraries the SmartInspect Console can display the entire execution flow of your application in a clear and convenient way. It is able to show an overview of all threads/processes and provides advanced filter and navigation functionality to find the information you need. It is possible, for example, to display only log messages which belong to a particular thread or process.

Tracking the Execution Flow

The SmartInspect libraries provide several methods to track the execution flow of multi-threaded applications. At first, by using the EnterProcess and LeaveProcess methods you can visualize your application in the Process Flow toolbox in the SmartInspect Console. If you also use the EnterThread and LeaveThread methods consequently then you are able to follow the entire program execution in great detail. Please see the next section for a detailed example of how to apply these tips.

Process Flow toolbox used by a multi-threaded server application Process Flow toolbox used by a multi-threaded server application (larger image)

Example of Using SmartInspect

The following example shows the usage of SmartInspect in a multi-threaded server application. This example is written in C#, but the Delphi and Java libraries provide the same functionality. The server application is a simple TCP timeserver which just returns the current time to every connected client and then closes the socket connection. The timeserver uses an own thread for every client. This thread is represented by the following ClientThread class. As you can see this class uses the EnterThread and LeaveThread methods for a better understanding of the execution flow and identification in the Console. You will be able to see every thread including information like thread ID, execution duration, start time and more.

class ClientThread
{
    private Socket fSocket;

    public ClientThread(Socket client)
    {
        this.fSocket = client;
    }

    private void HandleClient()
    {
        SiAuto.Main.EnterMethod(this, "HandleClient");
        try {
            StreamWriter w =
               new StreamWriter(new NetworkStream(this.fSocket));

            try {
                SiAuto.Main.LogMessage("Sending date to client.");
                w.WriteLine(DateTime.Now.ToString());
            } finally {
                w.Close();
            }
        } finally {
            SiAuto.Main.LeaveMethod(this, "HandleClient");
        }
    }

    public void Run()
    {
        SiAuto.Main.EnterThread("ClientThread");
        try {
            SiAuto.Main.LogMessage(
                "Got new client connection from " +
                this.fSocket.RemoteEndPoint.ToString()
            );
	
            try {
                try {
                    HandleClient();
                } finally {
                    SiAuto.Main.LogMessage("Closing socket.");
                    this.fSocket.Close();
                }
            } catch (Exception e) {
                SiAuto.Main.LogException(e);
            }
        } finally {
            SiAuto.Main.LeaveThread("ClientThread");
        }
    }
}

The following class is the remaining part of the timeserver. As you can see in this example, it is common practice to enable the logging of information at runtime if some condition is true. This class just waits for an incoming client and then creates and starts a new thread for it. It uses the EnterProcess and LeaveProcess methods to generate information about its process which will then be displayed by the Process Flow toolbox.

public class TimeServer
{
    public static void Main(String[] args)
    {	
        if (args.Length > 0) {
            /* 
             * Logging depends on command line argument,
             * it can enabled or disabled at runtime.
             */
            SiAuto.Si.Enabled = args[0].Equals("/debug");
         }

         SiAuto.Main.EnterProcess("TimeServer");
         try {
             try {
                 Socket server = new Socket(
                     AddressFamily.InterNetwork,
                     SocketType.Stream, ProtocolType.Tcp
                 );

                 server.Bind(new IPEndPoint(IPAddress.Any, 1037));
                 server.Listen(15);

                 while (true) {
                     Socket client = server.Accept();
                     if (client != null) {
                         ClientThread c = new ClientThread(client);
                         new Thread(new ThreadStart(c.Run)).Start();
                     }
                 }
             } catch (Exception e) {
                 SiAuto.Main.LogException(e);
                 Console.Write(e.ToString());
             }
         } finally {
             SiAuto.Main.LeaveProcess("TimeServer");
         }
    }
}

The following screenshot shows an example execution of the timeserver program above. In the lower part of the picture you can see the client and main threads with detailed information. In the upper part the log messages are displayed. You can easily filter the log and display only the information important to you, for example, to display only log messages of a particular thread or process. A convenient way to create such a new view for a thread is to double click on the thread in question in the Process Flow toolbox. Furthermore you can use an AutoView rule with the thread ID as trigger, which can automatically create a new view for you when a thread arrives at the Console.

SmartInspect used in a multi-threaded server application SmartInspect used in a multi-threaded server application (larger image)

Conclusion

After reading this article you should now have an overview over the SmartInspect way to debug and monitor multi-threaded applications. If you have any further questions about this topic do not hesitate to contact me at tg@gurock.com. By the way, you can download the full source code of the timeserver example here.

TimeServer.zip (6 KB)

Try SmartInspect

Get started in minutes and try SmartInspect free for 30 days.

Try SmartInspect