jcurl / RJCP.DLL.SerialPortStream

SerialPortStream is an independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. Default branch is 2.x and now has support for Mono with help of a C library.
Microsoft Public License
639 stars 199 forks source link

Infinite timeouts in 2.0 Core #35

Closed danclarke closed 7 years ago

danclarke commented 7 years ago

Setting the Read/Write timeouts doesn't seem to do anything in .NET Core 2.0, running version 2.1.2.

Calling Read() blocks forever, as does ReadAsync with a cancellation token. The token is triggered, but the ReadAsync method continues to block. This is both on Windows and Linux ARM.

The only workaround I've found is brutal - spin off the reads into a separate process so that the process can be completely shutdown should a device fail to respond.

jcurl commented 7 years ago

My code doesn't provide a ReadAsync method, that means it's up to the .NET Core 2.0 wrappers for BeginRead and EndRead to do this. The code responsible for the timeouts are:

public override int Read(byte[] buffer, int offset, int count) {
   return InternalBlockingRead(buffer, offset, count);
}

private int InternalBlockingRead(byte[] buffer, int offset, int count) {
  if (m_NativeSerial.IsRunning) {
    bool ready = m_Buffer.Stream.WaitForRead(m_ReadTimeout);
    if (IsDisposed) throw new ObjectDisposedException("SerialPortStream");
    if (!ready) {
      ReadCheckDeviceError();
      return 0;
    }
  }

  return InternalRead(buffer, offset, count);
}

Then InternalRead just reads from a buffer with no waits on any drivers.

The BeginRead method calls InternalBlockingRead.BeginInvoke which creates the block with the ReadTimeout parameter on a .NET worker thread (thread pool according to MSDN docs).

You might want to provide logs as described in the Wiki page, or breakpoint the .NET framework where its blocking to get details on why its blocking.

Does the problem occur when using .NET 4.0 or later on Windows?

danclarke commented 7 years ago

I tried small test console program with .NET 4.6.1 and the issue occurs there too. I enabled tracing in the config, but ended up with an empty logfile.txt. I read from one device successfully, then try to read from a non-existent device and that's when the read never times out. I'm communicating with Modbus devices using small shim:

public sealed class SerialPortAdapter : IStreamResource
{
    private readonly SerialPortStream _stream;
    private bool _disposed;

    public SerialPortAdapter(string port, int baud = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One)
    {
        _stream = new SerialPortStream(port, baud, dataBits, parity, stopBits) { NewLine = "\r\n" };
        _stream.Open();
    }

    public void DiscardInBuffer()
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(SerialPortAdapter));

        _stream.DiscardInBuffer();
    }

    public int Read(byte[] buffer, int offset, int count)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(SerialPortAdapter));

        return _stream.Read(buffer, offset, count);
    }

    public void Write(byte[] buffer, int offset, int count)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(SerialPortAdapter));

        _stream.Write(buffer, offset, count);
    }

    public int InfiniteTimeout => -1;

    public int ReadTimeout
    {
        get => _stream.ReadTimeout;
        set => _stream.ReadTimeout = value;
    }

    public int WriteTimeout
    {
        get => _stream.WriteTimeout;
        set => _stream.WriteTimeout = value;
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        _stream.Close();
        _stream.Dispose();
        _disposed = true;
    }
}

The block happens on this line: return _stream.Read(buffer, offset, count);

I completely missed the Begin/End Async methods, which is annoying since I did look for them. I'll investigate them a bit more along with tracing into the framework and see if I come up with anything.

danclarke commented 7 years ago

OK, egg on my face time. SerialPortStream was returning correctly. The Microsoft System.IO.Ports implementation throws a TimeoutException whereas SerialPortStream returns with 0 bytes read. The 0 bytes meant NModBus kept trying to read in the data it was never going to get. I should've picked this up since it's so obvious.

I haven't got to the bottom of the no trace output, but it's a minor detail.

Apologies for wasting your time.