Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SerialPort class occasionally hangs on Dispose

I have written a .net 4.0 console application which periodically talks to a GSM modem to get a list of the receieved SMS messages (it is a USB modem but the code connects to it via a serial port driver and sends AT commands - incidentally it is a Sierra Wireless modem but I can't change it and I have the latest driver). What happens is that after some period of time (maybe hours, maybe days) it just stops working. Here is a log snippet...

2012-04-17 23:07:31 DEBUG Modem Check (108) - Executing AT command 'AT+CPMS="ME"'...
2012-04-17 23:07:31 DEBUG Modem Check (108) - Finished executing 'AT+CPMS="ME"'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Detaching event handlers for 'COM13'
2012-04-17 23:07:31 DEBUG Modem Check (108) - Disposing the SerialPort for 'COM13'

That is the end of the log - nothing more even though I would expect to see at least one more statement, here is the relevant code:

internal T Execute()
{
    var modemPort = new SerialPort();
    T ret;

    try
    {
        modemPort.ErrorReceived += ModemPortErrorReceived;

        modemPort.PortName = _descriptor.PortName;
        modemPort.Handshake = Handshake.None;
        modemPort.DataBits = 8;
        modemPort.StopBits = StopBits.One;
        modemPort.Parity = Parity.None;
        modemPort.ReadTimeout = ReadTimeout;
        modemPort.WriteTimeout = WriteTimeout;
        modemPort.NewLine = "\r\n";
        modemPort.BaudRate = _descriptor.Baud;

        if (!modemPort.IsOpen)
        {
            modemPort.Open();
        }

        ret = _command.Execute(modemPort, _logger);

        _logger.Debug("Detaching event handlers for '{0}'",
                      _descriptor.PortName);

        modemPort.ErrorReceived -= ModemPortErrorReceived;

        _logger.Debug("Disposing the SerialPort for '{0}'",
                      _descriptor.PortName);
    }
    catch (IOException ex)
    {
        _logger.Error(ex.Message);

        throw new CommandException(
            string.Format(CultureInfo.CurrentCulture,
                          ModemWrapperStrings.COMMAND_ERROR,
                          ex.Message),
            ex);
    }
    catch (UnauthorizedAccessException ex)
    {
        _logger.Error(ex.Message);

        throw new CommandException(
            string.Format(CultureInfo.CurrentCulture,
                          ModemWrapperStrings.COMMAND_ERROR,
                          ex.Message),
            ex);
    }
    finally
    {
        modemPort.Dispose();

        _logger.Debug("Modem on port '{0}' disposed",
                      _descriptor.PortName);
    }

    return ret;
}

As you can see it hangs on the Dispose method of the SerialPort class.

I did some Googling and I came to this issue: Serial Port Close Hangs the application from this thread: serial port hangs whilst closing. The consensious seems to be to close the port in a different thread but is that just for a forms application? In my case I have a simple console application so I do not think it applies (it is just running in a loop in the main thread). I am not even sure it is actually this issue (my feeling is that it is more likely that there is an issue with the serial port driver from the modem but I don't know and perhaps I am being unfair to the modem). As far as I see it I have three choices:

  1. Close the port in a different thread
  2. Put in a delay before closing the port
  3. Leave the port open forever

I don't really like any of those workarounds but I am thinking of leaving the port open and just seeing what happens (I have this feeling that it will leak memory or worse, expose some other issue with the modem but maybe I am just pessimistic and if that is the case I could probably get away with closing it every 24 hours, say, and re-opening it again) so my question is...

Is there an alternative problem with this code that could be causing this bevahior or is there an alternative workaround to what I have outlined above?

like image 407
kmp Avatar asked Apr 18 '12 12:04

kmp


2 Answers

If you are using the DataRecieved event or any other events from your serial port object you should remove your event handler(s) from it before disposing of the serial port.

mySerial.DataReceived -= DataReceivedHandler;
mySerial.Dispose();

The hang occurs because you have an event firing on a disposed object... which is obviously a bug.

However, in your case you have done that.. and the hang is occurring because the port has not closed. possibly a thread.sleep might allow the port to "settle" before you try to reopen it. Its probably hardware specific as well... which is why there is no best practice.

The same as for a Forms control: How to remove all event handlers from a control

like image 78
cb88 Avatar answered Sep 25 '22 15:09

cb88


SerialPort is somewhat prone to deadlock. By far the most common cause is the one you found, it is triggered by using Invoke() in a DataReceived event handler. Clearly not your case here.

These deadlocks are related to a worker thread that SerialPort starts behind the curtain. That thread helps to detect asynchronous events on the port, the underlying native winapi is WaitCommEvent(). That worker makes the DataReceived, PinChanged and ErrorReceived events work. Note how you do use ErrorReceived.

The Dispose() method does the same thing as the Close() method, it signals the worker thread to exit. The flaw however is that it doesn't wait for the thread to exit. That's a recipe for trouble, the kind that is explicitly documented in the MSDN article for SerialPort.Close() in the Remarks section:

The best practice for any application is to wait for some amount of time after calling the Close method before attempting to call the Open method, as the port may not be closed instantly.

Which is, frankly, the worst possible practice for "best practice" advice since it doesn't at all specify exactly how long you are supposed to wait. For a good reason, there is no guaranteed safe value. Waiting for a second or two should be 99.9% good. The 0.1% failure mode occurs when the machine is heavily loaded and the worker thread simply doesn't get enough cycles to detect the close condition in time. Utterly undebuggable of course.

Punt this problem, only ever open a serial port at the start of your program and close it when exiting. Short from threading trouble, this also ensures that you don't randomly lose access to the port when another program jumps in and steals the port away from you. And note that closing the port isn't actually necessary anymore, Windows will take care of it if you don't.

like image 22
Hans Passant Avatar answered Sep 26 '22 15:09

Hans Passant