Skip to content

Commit

Permalink
Fix high CPU load issues when closing the client.
Browse files Browse the repository at this point in the history
  • Loading branch information
chkr1011 committed May 31, 2020
1 parent acb45af commit aeb103b
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 51 deletions.
5 changes: 1 addition & 4 deletions Build/CoAPnet.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>CoAPnet is a high performance .NET library for CoAP based communication. It provides a CoAP client and a CoAP server. It also has DTLS support out of the box.</description>
<releaseNotes>
[DTLS] Improved error handling and exposed received DTLS alerts.
[DTLS] Improved support for cancellation token.
[Client] Fixed high CPU load issues when closing the client.
</releaseNotes>
<copyright>Copyright Christian Kratky 2019-2020</copyright>
<tags>CoAP HTTP DTLS Message Telemetry Transport CoAPClient CoAPServer NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies>
<group targetFramework="netstandard1.3">
<dependency id="NETStandard.Library" version="1.3.0" />
<dependency id="System.Net.Security" version="4.3.2" />
</group>
<group targetFramework="netstandard2.0">
<dependency id="NETStandard.Library" version="2.0.0" />
<dependency id="System.Net.Security" version="4.3.2" />
</group>
<group targetFramework="net452">
</group>
Expand Down
Binary file added Build/nuget.exe
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static CoapClientConnectOptionsBuilder WithDtlsTransportLayer(this CoapCl
throw new ArgumentNullException(nameof(options));
}

clientConnectOptionsBuilder.WithTransportLayer(new DtlsCoapTransportLayer()
clientConnectOptionsBuilder.WithTransportLayer(() => new DtlsCoapTransportLayer
{
Credentials = options.Credentials
});
Expand Down
2 changes: 1 addition & 1 deletion Source/CoAPnet.Extensions.DTLS/DtlsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace CoAPnet.Extensions.DTLS
{
public class DtlsClient : DefaultTlsClient
public sealed class DtlsClient : DefaultTlsClient
{
readonly ProtocolVersion _protocolVersion;
readonly PreSharedKey _preSharedKey;
Expand Down
22 changes: 19 additions & 3 deletions Source/CoAPnet.Extensions.DTLS/DtlsCoapTransportLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using Org.BouncyCastle.Crypto.Tls;
using Org.BouncyCastle.Security;
using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using CoAPnet.Internal;

namespace CoAPnet.Extensions.DTLS
{
Expand Down Expand Up @@ -72,12 +72,28 @@ public async Task ConnectAsync(CoapTransportLayerConnectOptions connectOptions,

public Task<int> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
if (buffer.Array == null)
{
throw new ArgumentNullException(nameof(buffer));
}

cancellationToken.ThrowIfCancellationRequested();

using (cancellationToken.Register(() => _udpTransport.Close()))
{
var received = _dtlsTransport.Receive(buffer.Array, buffer.Offset, buffer.Count, 0);
return Task.FromResult(received);
try
{
var received = _dtlsTransport.Receive(buffer.Array, buffer.Offset, buffer.Count, 0);
return Task.FromResult(received);
}
catch (ObjectDisposedException)
{
return Task.FromResult(0);
}
catch (SocketException)
{
return Task.FromResult(0);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Source/CoAPnet.Extensions.DTLS/PreSharedKeyWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace CoAPnet.Extensions.DTLS
{
public class PreSharedKeyWrapper : TlsPskIdentity
public sealed class PreSharedKeyWrapper : TlsPskIdentity
{
readonly PreSharedKey _preSharedKey;

Expand Down
16 changes: 6 additions & 10 deletions Source/CoAPnet.Extensions.DTLS/UdpTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ public int Receive(byte[] buf, int off, int len, int waitMillis)
}

EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
var length = _socket.ReceiveFrom(buf, off, len, SocketFlags.None, ref remoteEndPoint);

Console.WriteLine(length);

return length;
return _socket.ReceiveFrom(buf, off, len, SocketFlags.None, ref remoteEndPoint);
}

public void Send(byte[] buf, int off, int len)
Expand All @@ -61,17 +57,17 @@ public void Send(byte[] buf, int off, int len)
_socket.SendTo(buf, off, len, SocketFlags.None, _connectOptions.EndPoint);
}

public void Close()
{
Dispose();
}

public void Dispose()
{
_isDisposed = true;

// There is no need to call "Disconnect" because we use UDP.
_socket?.Dispose();
}

public void Close()
{
Dispose();
}
}
}
5 changes: 3 additions & 2 deletions Source/CoAPnet.Tests/CoAPnet.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.2.1">
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CoAPnet.Extensions.DTLS\CoAPnet.Extensions.DTLS.csproj" />
<ProjectReference Include="..\CoAPnet\CoAPnet.csproj" />
</ItemGroup>

Expand Down
66 changes: 66 additions & 0 deletions Source/CoAPnet.Tests/CoapClient_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Threading;
using System.Threading.Tasks;
using CoAPnet.Client;
using CoAPnet.Exceptions;
using CoAPnet.Extensions.DTLS;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CoAPnet.Tests
{
[TestClass]
public class CoapClient_Tests
{
[TestMethod]
[ExpectedException(typeof(CoapCommunicationException))]
public async Task Connect_Invalid_Host()
{
using (var coapClient = new CoapFactory().CreateClient())
{
await coapClient.ConnectAsync(new CoapClientConnectOptions()
{
Host = "invalid_host"
}, CancellationToken.None);
}
}

[TestMethod]
[ExpectedException(typeof(CoapCommunicationException))]
public async Task Connect_Valid_Host_With_Invalid_Port_Dtls()
{
using (var coapClient = new CoapFactory().CreateClient())
{
var options = new CoapClientConnectOptionsBuilder()
.WithHost("127.0.0.1")
.WithPort(5555)
.WithDtlsTransportLayer(o =>
{
o.WithPreSharedKey("a", "b");
})
.Build();

await coapClient.ConnectAsync(options, CancellationToken.None);
}
}

[TestMethod]
[ExpectedException(typeof(CoapCommunicationTimedOutException))]
public async Task Timeout_When_No_Response_Received()
{
using (var coapClient = new CoapFactory().CreateClient())
{
var options = new CoapClientConnectOptionsBuilder()
.WithHost("127.0.0.1")
.WithPort(5555)
.Build();

await coapClient.ConnectAsync(options, CancellationToken.None);

var request = new CoapRequestBuilder()
.WithMethod(CoapRequestMethod.Get)
.Build();

await coapClient.RequestAsync(request, CancellationToken.None);
}
}
}
}
13 changes: 10 additions & 3 deletions Source/CoAPnet/Client/CoapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ internal async Task<CoapMessage> RequestAsync(CoapMessage requestMessage, Cancel

public void Dispose()
{
_cancellationToken?.Cancel(false);
_cancellationToken?.Dispose();

_lowLevelClient?.Dispose();
}

Expand All @@ -160,13 +163,17 @@ async Task ReceiveMessages(CancellationToken cancellationToken)
try
{
var message = await _lowLevelClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);

if (cancellationToken.IsCancellationRequested)
{
return;
}

if (message == null)
{
continue;
}

cancellationToken.ThrowIfCancellationRequested();

if (!_messageDispatcher.TryHandleReceivedMessage(message))
{
if (!await _observationManager.TryHandleReceivedMessage(message).ConfigureAwait(false))
Expand All @@ -180,7 +187,7 @@ async Task ReceiveMessages(CancellationToken cancellationToken)
}
catch (Exception exception)
{
_logger.Error(nameof(CoapClient), exception, "Error while receiving message.");
_logger.Error(nameof(CoapClient), exception, "Error while receiving messages.");
}
}
}
Expand Down
5 changes: 1 addition & 4 deletions Source/CoAPnet/Client/CoapClientConnectOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ public string Host

public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);

public ICoapTransportLayer TransportLayer
{
get; set;
}
public Func<ICoapTransportLayer> TransportLayerFactory { get; set; } = () => new UdpCoapTransportLayer();
}
}
30 changes: 15 additions & 15 deletions Source/CoAPnet/Client/CoapClientConnectOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,9 @@ public class CoapClientConnectOptionsBuilder
{
readonly CoapClientConnectOptions _options = new CoapClientConnectOptions
{
TransportLayer = new UdpCoapTransportLayer() // This is the protocols default transport.
TransportLayerFactory = () => new UdpCoapTransportLayer() // This is the protocols default transport.
};

public CoapClientConnectOptions Build()
{
if (_options.TransportLayer == null)
{
throw new CoapClientConfigurationInvalidException("Transport layer is not set.", null);
}

return _options;
}

public CoapClientConnectOptionsBuilder WithHost(string value)
{
_options.Host = value ?? throw new ArgumentNullException(nameof(value));
Expand All @@ -45,20 +35,30 @@ public CoapClientConnectOptionsBuilder WithUnencryptedPort()

public CoapClientConnectOptionsBuilder WithTcpTransportLayer()
{
_options.TransportLayer = new TcpCoapTransportLayer();
_options.TransportLayerFactory = () => new TcpCoapTransportLayer();
return this;
}

public CoapClientConnectOptionsBuilder WithTransportLayer(ICoapTransportLayer value)
public CoapClientConnectOptionsBuilder WithTransportLayer(Func<ICoapTransportLayer> transportLayerFactory)
{
_options.TransportLayer = value ?? throw new ArgumentNullException(nameof(value));
_options.TransportLayerFactory = transportLayerFactory ?? throw new ArgumentNullException(nameof(transportLayerFactory));
return this;
}

public CoapClientConnectOptionsBuilder WithUdpTransportLayer()
{
_options.TransportLayer = new UdpCoapTransportLayer();
_options.TransportLayerFactory = () => new UdpCoapTransportLayer();
return this;
}

public CoapClientConnectOptions Build()
{
if (_options.TransportLayerFactory == null)
{
throw new CoapClientConfigurationInvalidException("Transport layer is not set.", null);
}

return _options;
}
}
}
28 changes: 21 additions & 7 deletions Source/CoAPnet/LowLevelClient/LowLevelCoapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ public async Task ConnectAsync(CoapClientConnectOptions options, CancellationTok
{
_connectOptions = options ?? throw new ArgumentNullException(nameof(options));

var transportLayer = options.TransportLayerFactory?.Invoke();

if (transportLayer == null)
{
throw new InvalidOperationException("No CoAP transport layer is set.");
}

cancellationToken.ThrowIfCancellationRequested();

_transportLayerAdapter = new CoapTransportLayerAdapter(options.TransportLayer, _logger);
_transportLayerAdapter = new CoapTransportLayerAdapter(transportLayer, _logger);
try
{
var transportLayerConnectOptions = new CoapTransportLayerConnectOptions
Expand Down Expand Up @@ -93,14 +100,21 @@ async Task<IPEndPoint> ResolveIPEndPoint(CoapClientConnectOptions connectOptions
await Task.FromResult(0);
throw new NotSupportedException("Resolving DNS end points is not supported for NETSTANDARD1_3. Please pass IP address instead.");
#else
var hostIPAddresses = await Dns.GetHostAddressesAsync(connectOptions.Host).ConfigureAwait(false);
if (hostIPAddresses.Length == 0)
try
{
throw new CoapCommunicationException("Failed to resolve DNS end point", null);
}
var hostIPAddresses = await Dns.GetHostAddressesAsync(connectOptions.Host).ConfigureAwait(false);
if (hostIPAddresses.Length == 0)
{
throw new CoapCommunicationException("Failed to resolve DNS end point", null);
}

// We only use the first address for now.
return new IPEndPoint(hostIPAddresses[0], _connectOptions.Port);
// We only use the first address for now.
return new IPEndPoint(hostIPAddresses[0], _connectOptions.Port);
}
catch (Exception exception)
{
throw new CoapCommunicationException("Error while resolving DNS name.", exception);
}
#endif
}
}
Expand Down

0 comments on commit aeb103b

Please sign in to comment.