Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elide repeated Action-delegate allocation by caching it #1324

Merged
merged 2 commits into from
Jan 8, 2022
Merged
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
10 changes: 8 additions & 2 deletions Source/MQTTnet/Implementations/CrossPlatformSocket.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
Expand All @@ -11,25 +11,31 @@ namespace MQTTnet.Implementations
public sealed class CrossPlatformSocket : IDisposable
{
readonly Socket _socket;
readonly Action _socketDisposeAction;

NetworkStream _networkStream;

public CrossPlatformSocket(AddressFamily addressFamily)
{
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
_socketDisposeAction = _socket.Dispose;
}

public CrossPlatformSocket()
{
// Having this constructor is important because avoiding the address family as parameter
// will make use of dual mode in the .net framework.
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

_socketDisposeAction = _socket.Dispose;
}

public CrossPlatformSocket(Socket socket)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_networkStream = new NetworkStream(socket, true);

_socketDisposeAction = _socket.Dispose;
}

public bool NoDelay
Expand Down Expand Up @@ -112,7 +118,7 @@ public async Task ConnectAsync(string host, int port, CancellationToken cancella
_networkStream?.Dispose();

// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(() => _socket.Dispose()))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the bug also fixed in this case so adding the pragma here works 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.

Socket.ConnectAsync(string, int, CancellationToken) exists only starting from .NET 6.
So for the moment we have to stay at the current code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK.

using (cancellationToken.Register(_socketDisposeAction))
{
cancellationToken.ThrowIfCancellationRequested();

Expand Down
36 changes: 25 additions & 11 deletions Source/MQTTnet/Implementations/MqttTcpChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@ public sealed class MqttTcpChannel : IMqttChannel
{
readonly IMqttClientOptions _clientOptions;
readonly MqttClientTcpOptions _tcpOptions;
readonly Action _disposeAction;

Stream _stream;

public MqttTcpChannel(IMqttClientOptions clientOptions)
public MqttTcpChannel()
{
_disposeAction = Dispose;
}

public MqttTcpChannel(IMqttClientOptions clientOptions) : this()
{
_clientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
_tcpOptions = (MqttClientTcpOptions)clientOptions.ChannelOptions;

IsSecureConnection = clientOptions.ChannelOptions?.TlsOptions?.UseTls == true;
}

public MqttTcpChannel(Stream stream, string endpoint, X509Certificate2 clientCertificate)
public MqttTcpChannel(Stream stream, string endpoint, X509Certificate2 clientCertificate) : this()
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));

Expand Down Expand Up @@ -149,11 +155,15 @@ public async Task<int> ReadAsync(byte[] buffer, int offset, int count, Cancellat
return 0;
}

#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
return await stream.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430
Copy link
Member Author

Choose a reason for hiding this comment

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

Question: starting with .NET Core 3.0 this workaround isn't needed anymore, so the code could be conditionally compiled for a "legacy" target and for new targets.

W/o the workaround perf in general should be better, as the cost of creating, and disposing the cancellationtoken-registration can be avoided.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If it is fixed we should add a compiler switch (NETCOREAPP3_0_OR_GREATER).

using (cancellationToken.Register(Dispose))
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the above code I agree that the lambda is not required but here we already have a method without wrapping it. Do we really need that optimization here 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.

From my profiling it showed up in WriteAsync (L187), so it's just duplicated here.

But when the conditional compilation is added for .NET Core 3.0 and newer, this won't be hit. So for the legacy code we could

  • keep the state of master-branch
  • keep the cached delegate of PR's current state

I'm leaning to the latter, as the change isn't huge and so older targets can benefit from that change too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, now I see your concern. Yes we need it. Whether it's a delegate created from a method group or a lambda, the delegate won't be cached. Checked the IL for both.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK then we should keep it.

using (cancellationToken.Register(_disposeAction))
{
return await stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
#endif
}
catch (ObjectDisposedException)
{
Expand All @@ -177,18 +187,22 @@ public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationT

try
{
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(Dispose))
{
var stream = _stream;
var stream = _stream;

if (stream == null)
{
throw new MqttCommunicationException("The TCP connection is closed.");
}
if (stream == null)
{
throw new MqttCommunicationException("The TCP connection is closed.");
}

#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
await stream.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_disposeAction))
{
await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
#endif
}
catch (ObjectDisposedException)
{
Expand Down