dotnet/runtime

`SerialPort.Write()` does not block until all data is written.

Open

#96.267 aberto em 22 de dez. de 2023

Ver no GitHub
 (5 comments) (0 reactions) (0 assignees)C# (5.445 forks)batch import
area-System.IO.Portshelp wanted

Métricas do repositório

Stars
 (17.886 stars)
Métricas de merge de PR
 (Mesclagem média 12d 11h) (661 fundiu PRs em 30d)

Description

Description

SerialPort.Write() does not block until all data is written. Also, no TimeoutException is thrown if it fails to write all data by the time the WriteTimeout expires.

One can see at https://github.com/dotnet/runtime/blob/0cf461b302f58c7add3f6dc405873fb2212b513f/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L1446-L1492 and https://github.com/dotnet/runtime/blob/0cf461b302f58c7add3f6dc405873fb2212b513f/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L1352-L1395 that the number of bytes written is ignored.

Perhaps it should be checked and an error thrown if it is not correct.

Or, perhaps, the Write... functions should be called repeatedly until all data is written.

Or, perhaps, the Write... functions should return the number of bytes actually written, so the user can do something about it.

Reproduction Steps

The following code will reproduce the issue. This is a loopback test, so the port's TX and RX pins must be shorted.


using System.IO.Ports;

int[] BaudRates =
{
    9600,
    19200,
    38400,
    57600,
    115200,
    230400,
    460800
};

Parity[] Parities =
{
    Parity.None,
    Parity.Odd,
    Parity.Even
};

string Device = args.Length < 1 ? "COM1" : args[0];

using (var port = new SerialPort(Device))
{
    const int timeout = 100;
    port.ReadTimeout = timeout;

    // 100ms is too short of a time to send 256 bytes to the tx buffer
    // on some devices.
    port.WriteTimeout = timeout;

    const int portBufferSize = 4096;
    port.ReadBufferSize = portBufferSize;
    port.WriteBufferSize = portBufferSize;

    foreach (var baud in BaudRates)
    {
        foreach (var parity in Parities)
        {
            Console.WriteLine($"Starting loopback test for {Device} - {baud}, {parity}");

            try
            {
                port.Open();
                port.DiscardInBuffer();
                port.DiscardOutBuffer();

                const int numOfBytes = 2048;

                // Fill the txBytes
                byte[] txBytes = new byte[numOfBytes];
                for (int i = 0; i < numOfBytes; i++)
                {
                    txBytes[i] = (byte)(i & 0xFF);
                }

                // This succeeds, but it shouldn't.  Because the `WriteTimeout` was too short
                // only a fraction of the bytes have been written.  You won't be able to observe
                // this until `port.Read()` is called and the data returned is only a fraction
                // of what it should be.
                port.Write(txBytes, 0, txBytes.Length);

                // This will report 0, but it's a lie.  `port.Write()` failed to write all bytes
                Console.WriteLine($"{port.BytesToWrite} bytes to write.");

                // Give time for data to traverse and fill the rx buffer.  This is more than
                // enough time for the data to traverse to the rx buffer.
                Thread.Sleep(1000);

                // Read the data from the port's rx buffer.  
                byte[] rxBytes = new byte[numOfBytes];
                var numBytesRead = port.Read(rxBytes, 0, rxBytes.Length);
                if (numBytesRead < numOfBytes)
                {
                    Console.Error.WriteLine($"`port.Read()` was only able to read {numBytesRead} bytes");
                }

                // Verify all bytes received are correct.
                for (int i = 0; i < numOfBytes; i++)
                {
                    if (rxBytes[i] != txBytes[i])
                    {
                        Console.Error.WriteLine(string.Format("{0} - {1}, {2}: Expected {3}, received {4}.", Device, baud.ToString(), parity.ToString(), i.ToString(), rxBytes[i].ToString()));
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{Device} - {baud}, {parity}:  {ex.Message}");
                return;
            }
            finally
            {
                port.Close();
            }
        }
    }
}


Expected behavior

SerialPort.Write() should block until all data has been written, or should throw a TimeoutException if all data cannot be written by the time WriteTimeout expires.

Actual behavior

The program fails after the call to port.Read() when it starts verifying that each byte received matches each byte sent. Since the WriteTimeout expired, only some of the data was sent, yet no TimeoutException was thrown. So, when verifying the data, it will only succeed with the few bytes that it received. After which it will fail.

Regression?

Not sure.

Known Workarounds

As long as WriteTimeout in increased to a high enough value to allow enough time for the data to be written to the serial port's TX buffer, everything will work fine. The problem, though, is when it fails, it does so silently, and you'll only know it failed by verifying the data on the receiving end of the serial transmission.

Also, if data is transmitted in small chucks (e.g. 64 bytes at a time) then one can avoid the symptom.

Configuration

No response

Other information

No response

Guia do colaborador