Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

SerialPort - initial async implementation on Linux and bugfixes#33027

Merged
krwq merged 10 commits intodotnet:masterfrom
krwq:serial_ports2
Oct 30, 2018
Merged

SerialPort - initial async implementation on Linux and bugfixes#33027
krwq merged 10 commits intodotnet:masterfrom
krwq:serial_ports2

Conversation

@krwq
Copy link
Member

@krwq krwq commented Oct 25, 2018

  • adds async implementation on Linux
    • supports cancellation token
    • multiple writes/reads at the same time will work correctly and be processed in order the writes were requested (reads and writes separately)
  • multiple random bug fixes
  • test fixes

Most interesting file is SerialStream.Unix.cs

There are still lots of failures, all of them are marked with custom [KnownFailure] - most of the tests won't run on CI because it doesn't have physical port.

Things known to still be broken on Linux:

  • Timeout 0-30ms is currently rounded up to 30ms since 0 is not realistic with current implementation (0-2ms is more realistic but we should not rely that much on timing precision) and we could as well always throw in such cases - I do not think this is worth fixing unless there is some reasonable use case
  • Parity replacement
  • DTREnable
  • BytesToWrite (looks ok through code inspection but seem to be misreporting, possibly data from the queue should be added there)
  • DataReceived event can fire more than once (SerialStream does it correctly but code on top of that in SerialPort is not) - this is likely also issue on Windows - I've added a comment why this happens how this can be fixed on Linux (likely something similar can be done on Windows)
  • Some tests related to StopBits are failing when measuring the transmission time - this might be a test issue but needs to be looked at
  • non standard BaudRates are currently not supported but I've seen some article explaining how to emulate those - there are also couple of test failures (or rather hangs) but likely those are test issues (except for when non-standard baud rate is used) - currently allowed baud rate ranges are hard coded on linux (couldn't find an easy way to get that info from OS - perhaps just trying to set it and seeing if OS allows it is a way to go)
  • software emulated RTS is not working (XonXoff)
  • flushing is not working
  • [test] HasHardwareFlowControl is not working correctly - this can be checked similarly as RTS related tests are doing it (this might have a broader meaning than just RTS/CTS so needs more look)

Things which might need improvement:

  • Dispose implementation (i.e. Shutdown call might need to be moved to a new SafeHandle class)

@krwq krwq self-assigned this Oct 25, 2018
// I.e: Let _receivedBytesThreshold be 8 - when we get an event when 7 bytes are available
// BytesToRead can change while we run this event and thus
// we virtually can get 2 events when 8th byte arrives
// I.e. we might want to add total bytes available as internal field in the args event
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue number associated with this? Do we plan to fix it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently tracking all of the issues in the PR description, once this will be stable enough I'll file them so that I don't do much churn with creating issues.

}

public override bool CanRead
static void CheckBaudRate(int baudRate)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: private. Also, is this something that can be shared with Windows, and thus moved into the shared file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunatelly this can't be shared as it depends on the driver - Windows currently supports more baud rates. I'm currently tracking expanding the range of baud rates on Linux by trying to emulate non supported ranges (I saw an article somewhere on how to do that)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the rates also depend on Driver and kernel version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll explore this subject separately - I wanted to mimic the behavior as much as possible and throw only when I'm sure the baud rates are out of range.

private enum ThrowAt { Set, Open };

#region Test Cases
[KnownFailure] // either hanging or really slow
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the KnownFailure attribute? How does that differ from ActiveIssue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't disable anything by default, it's more like a temporary thing which helps to manage test cases while I still work on them and gives hints what can still be broken and what should already work - I'd think of it as adding a xunit trait at this point. I can technically remove it since I don't believe any of those test cases run in the CI but I think it is still useful as long as still issues are being polished up

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: those test cases were failing before (assuming you got null modem connected)

throw new IOException();
}
if ((_handshake == Handshake.RequestToSend || _handshake == Handshake.RequestToSendXOnXOff))
throw new InvalidOperationException(SR.CantSetRtsWithHandshaking);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw new InvalidOperationException(SR.CantSetRtsWithHandshaking); [](start = 20, length = 66)

why we throw in the getter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad design from the past - matching Windows behavior :(

// returns number of bytes read/written
private static int DoIORequest(ConcurrentQueue<SerialStreamIORequest> q, RequestProcessor op)
{
// assumes dequeue-ing happens on a single thread
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assumes dequeue-ing happens on a single threa [](start = 15, length = 45)

why this assumption?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only called in the IOLoop which is a single thread. If that wasn't true then TryDequeue could dequeue wrong item than we peeked

@tarekgh
Copy link
Member

tarekgh commented Oct 26, 2018

I added minor comments. modulo @stephentoub comments it LGTM

SerialStreamIORequest result = new SerialStreamIORequest(cancellationToken, buffer);
_writeQueue.Enqueue(result);
return result.Task;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should override the Read/WriteAsync methods that take a {ReadOnly}Memory<byte> as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub this currently targets netstandard so those are not present there yet

SafeFileHandle handle = Interop.Serial.SerialPortOpen(portName);
if (handle.IsInvalid)
{
throw new UnauthorizedAccessException(string.Format(SR.UnauthorizedAccess_IODenied_Port, portName));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason this might fail is because of unauthorized access? Is that the only exception we might throw on Windows?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Windows does GetExceptionForLastWin32Error which in the most common case throws UnauthorizedAccessException. On Linux equivalent function (Interop.GetIOException(Interop.Sys.GetLastErrorInfo())) throws:
System.IO.IOException: Device or resource busy. The tests and users I think will expect UnauthorizedAccessException (even though IOException probably makes would make more sense if we rewrote it from scratch).

Is there any specific error you have in mind? I think always throwing UnauthorizedAccessException makes sense as it is the most common case but perhaps we could fix that in Interop.GetIOException.

thoughts?

}
remove
{
_dataReceived -= value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you stop the loop if _dataReceived becomes null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does stop after 2s if there are no new read/write requests

bool hasPendingReads = !_readQueue.IsEmpty;
bool hasPendingWrites = !_writeQueue.IsEmpty;

bool hasPendingIO = hasPendingReads || hasPendingWrites;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to store these into locals rather than just making this be:

bool hasPendingIO = !_readQueue.IsEmpty || !_writeQueue.IsEmpty;

? From my perspective, that makes the code easier to read, but there's also a (small) perf benefit, in that we don't have to check whether there are any writes if there are reads.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will create follow up PR with this change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually no, I use them later when calling poll

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a squash.

@krwq krwq merged commit d69f113 into dotnet:master Oct 30, 2018
@karelz karelz added this to the 3.0 milestone Nov 15, 2018
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…et/corefx#33027)

* SerialPort - initial async implementation on Linux and bugfixes

* Apply review feedback

* Fix dequeueing on Release builds

* Remove InternalResources.Windows.cs (use Win32Marshal.cs from Common folder)

* Fix build errors on Windows

* fix build errors on uap

* Apply review feedback (+ IOLoop pausing when no activity)

* Fix comment

* apply feedback

* Remove code duplication


Commit migrated from dotnet/corefx@d69f113
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants