Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Threading
{
internal static class UnixHandleAsyncContextUnsafeAccess
{
private const string AsyncContextTypeName = "System.Threading.UnixHandleAsyncContext, System.Private.CoreLib";
private const string OperationTypeName = "System.Threading.UnixHandleAsyncContext+Operation, System.Private.CoreLib";
private const string DelegateOperationTypeName = "System.Threading.UnixHandleAsyncContext+DelegateOperation, System.Private.CoreLib";

public enum AsyncResult
{
Pending = 0,
Completed = 1,
Aborted = 2,
}

public enum SyncResult
{
Completed = 1,
Aborted = 2,
TimedOut = 4,
}

public enum OnCompletedResult
{
Completed = 1,
Aborted = 2,
Canceled = 3,
}

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
[return: UnsafeAccessorType(AsyncContextTypeName)]
private static extern object CreateAsyncContext(SafeHandle handle);

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
[return: UnsafeAccessorType(DelegateOperationTypeName)]
private static extern object CreateDelegateOperation(Func<SafeHandle, bool> tryComplete, Action<int> onCompleted);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "IsReadReady")]
private static extern bool IsReadReady(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
out int observedSequenceNumber);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "IsWriteReady")]
private static extern bool IsWriteReady(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
out int observedSequenceNumber);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "StartAsyncReadAsInt")]
private static extern int StartAsyncRead(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
[UnsafeAccessorType(OperationTypeName)] object operation,
int observedSequenceNumber,
CancellationToken cancellationToken);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "StartAsyncWriteAsInt")]
private static extern int StartAsyncWrite(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
[UnsafeAccessorType(OperationTypeName)] object operation,
int observedSequenceNumber,
CancellationToken cancellationToken);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ReadAsInt")]
private static extern int ReadSync(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
[UnsafeAccessorType(OperationTypeName)] object operation,
int observedSequenceNumber,
int timeout);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "WriteAsInt")]
private static extern int WriteSync(
[UnsafeAccessorType(AsyncContextTypeName)] object context,
[UnsafeAccessorType(OperationTypeName)] object operation,
int observedSequenceNumber,
int timeout);

[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "AbortAndDispose")]
private static extern bool AbortAndDispose(
[UnsafeAccessorType(AsyncContextTypeName)] object context);

public sealed class AsyncContext
{
private readonly object _context;

public AsyncContext(SafeHandle handle)
{
_context = CreateAsyncContext(handle);
}

public bool IsReadReady(out int observedSequenceNumber)
=> UnixHandleAsyncContextUnsafeAccess.IsReadReady(_context, out observedSequenceNumber);

public bool IsWriteReady(out int observedSequenceNumber)
=> UnixHandleAsyncContextUnsafeAccess.IsWriteReady(_context, out observedSequenceNumber);

public AsyncResult StartAsyncRead(Operation operation, int observedSequenceNumber, CancellationToken cancellationToken)
=> (AsyncResult)UnixHandleAsyncContextUnsafeAccess.StartAsyncRead(_context, operation.Instance, observedSequenceNumber, cancellationToken);

public AsyncResult StartAsyncWrite(Operation operation, int observedSequenceNumber, CancellationToken cancellationToken)
=> (AsyncResult)UnixHandleAsyncContextUnsafeAccess.StartAsyncWrite(_context, operation.Instance, observedSequenceNumber, cancellationToken);

public SyncResult Read(Operation operation, int observedSequenceNumber, int timeout)
=> (SyncResult)ReadSync(_context, operation.Instance, observedSequenceNumber, timeout);

public SyncResult Write(Operation operation, int observedSequenceNumber, int timeout)
=> (SyncResult)WriteSync(_context, operation.Instance, observedSequenceNumber, timeout);

public bool AbortAndDispose()
=> UnixHandleAsyncContextUnsafeAccess.AbortAndDispose(_context);

public static Operation CreateOperation(Func<SafeHandle, bool> tryComplete, Action<OnCompletedResult> onCompleted)
=> new Operation(tryComplete, result => onCompleted((OnCompletedResult)result));

public readonly struct Operation
{
public object Instance { get; }

internal Operation(Func<SafeHandle, bool> tryComplete, Action<int> onCompleted)
{
Instance = CreateDelegateOperation(tryComplete, onCompleted);
}
}
}
}
}
13 changes: 13 additions & 0 deletions src/libraries/System.IO.Ports/src/System.IO.Ports.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ System.IO.Ports.SerialPort</PackageDescription>
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<IsPartialFacadeAssembly Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETFramework'">true</IsPartialFacadeAssembly>
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(IsPartialFacadeAssembly)' != 'true' and '$(TargetPlatformIdentifier)' == ''">SR.PlatformNotSupported_IOPorts</GeneratePlatformNotSupportedAssemblyMessage>
<!-- TODO: Remove NetCoreAppCurrent once it is updated to net12.0. -->
<UseUnixAsyncContext Condition="'$(TargetPlatformIdentifier)' == 'unix' and ($([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '$(NetCoreAppCurrent)')) or $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net12.0')))">true</UseUnixAsyncContext>
</PropertyGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' != ''">
Expand Down Expand Up @@ -142,6 +144,17 @@ System.IO.Ports.SerialPort</PackageDescription>
Link="Common\Interop\Unix\Interop.Poll.Structs.cs" />
</ItemGroup>

<ItemGroup Condition="'$(UseUnixAsyncContext)' == 'true'">
<Compile Include="$(CommonPath)System\Threading\UnixHandleAsyncContextUnsafeAccess.cs"
Link="Common\System\Threading\UnixHandleAsyncContextUnsafeAccess.cs" />
<Compile Include="System\IO\Ports\SafeSerialDeviceHandle.UnixAsyncContext.cs" />
<Compile Include="System\IO\Ports\SerialStream.UnixAsyncContext.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'unix' and '$(UseUnixAsyncContext)' != 'true'">
<Compile Include="System\IO\Ports\SerialStream.UnixPollLoop.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToAsyncResult.cs" Link="Common\System\Threading\Tasks\TaskToAsyncResult.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace System.IO.Ports
{
internal sealed class SafeSerialDeviceHandle : SafeHandleMinusOneIsInvalid
internal sealed partial class SafeSerialDeviceHandle : SafeHandleMinusOneIsInvalid
{
// When the user calls Dispose, some operations for a pending read event might be in flight.
// If these use the handle, the user Dispose call won't actually release the handle immediately
// which causes opening the port after the Dispose to fail (EBUSY).
// DisposeLock guards these operations so that they can not happen concurrent with the Dispose.
private object DisposeLock => this;

public SafeSerialDeviceHandle() : base(ownsHandle: true)
{
}
Expand All @@ -34,6 +41,44 @@ internal static SafeSerialDeviceHandle Open(string portName)
return handle;
}

// Gets the amount input data buffered by the handle.
partial void GetBufferedCount(ref int count);

// Get the amount of bytes that can be read from the handle plus the amount buffered by the caller.
// When throwOnDispose is false, returns 0 when disposed instead of throwing.
internal int GetBytesToRead(int buffered, bool throwOnDispose = true)
{
lock (DisposeLock)
{
if (!throwOnDispose && IsClosed)
{
return 0;
}

try
{
return buffered + BytesToRead;
}
catch (ObjectDisposedException) when (!throwOnDispose)
{
return 0;
}
}
}

// Gets the amount of bytes that can be read from the handle.
private int BytesToRead
{
get
{
Debug.Assert(Monitor.IsEntered(DisposeLock));

int buffered = 0;
GetBufferedCount(ref buffered);
return Math.Max(Interop.Termios.TermiosGetAvailableBytes(this, true), 0) + buffered;
}
}

protected override bool ReleaseHandle()
{
Interop.Serial.Shutdown(handle, SocketShutdown.Both);
Expand Down
Loading
Loading