Skip to content
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
7 changes: 7 additions & 0 deletions src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public partial class TWN4ReaderDevice : IDisposable

private static readonly object syncRoot = new object();
private static List<TWN4ReaderDevice> instance;
// Serialize access to the underlying transport to prevent interleaved async commands.
private readonly SemaphoreSlim _transportLock = new SemaphoreSlim(1, 1);
private readonly Func<string, IReaderTransport> _transportFactory;
private IReaderTransport _transport;

Expand Down Expand Up @@ -811,6 +813,7 @@ public async Task<ResponseParser> CallFunctionAsync(byte[] CMD)
/// <returns></returns>
private async Task<byte[]> DoTXRXAsync(byte[] CMD)
{
await _transportLock.WaitAsync().ConfigureAwait(false);
try
{
EnsureTransport();
Expand All @@ -836,6 +839,10 @@ private async Task<byte[]> DoTXRXAsync(byte[] CMD)
{
throw new ReaderException("Call was not successfull, error " + Enum.GetName(typeof(ReaderError), ReaderError.NotOpen), null);
}
finally
{
_transportLock.Release();
}

}// End of DoTXRXAsync

Expand Down
91 changes: 91 additions & 0 deletions tests/Elatec.NET.Tests/ConcurrentReaderTransport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elatec.NET.Interfaces;

namespace Elatec.NET.Tests
{
/// <summary>
/// Transport stub that detects overlapping Write/Read cycles to ensure calls are serialized.
/// </summary>
public class ConcurrentReaderTransport : IReaderTransport
{
private int _inFlight;

public ConcurrentReaderTransport(string portName)
{
PortName = portName;
}

public string PortName { get; }

public int ReadTimeout { get; set; }

public int WriteTimeout { get; set; }

public bool IsOpen { get; private set; }

public bool OverlapDetected { get; private set; }

public Queue<string> Responses { get; } = new Queue<string>();

public List<string> WrittenLines { get; } = new List<string>();

public TaskCompletionSource<bool> FirstWriteSeen { get; } = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

public TaskCompletionSource<bool> AllowRead { get; } = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

public event EventHandler<Exception> ErrorReceived;

public Task ConnectAsync()
{
IsOpen = true;
return Task.CompletedTask;
}

public Task DisconnectAsync()
{
IsOpen = false;
return Task.CompletedTask;
}

public void DiscardInBuffer()
{
}

public void DiscardOutBuffer()
{
}

public async Task<string> ReadLineAsync()
{
await AllowRead.Task.ConfigureAwait(false);
Interlocked.Exchange(ref _inFlight, 0);

if (Responses.Count == 0)
{
throw new InvalidOperationException("No responses configured.");
}

return Responses.Dequeue();
}

public Task WriteLineAsync(string data)
{
if (Interlocked.CompareExchange(ref _inFlight, 1, 0) == 1)
{
OverlapDetected = true;
}

WrittenLines.Add(data);
FirstWriteSeen.TrySetResult(true);
return Task.CompletedTask;
}

public void Dispose()
{
IsOpen = false;
}
}
}
19 changes: 19 additions & 0 deletions tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,24 @@ public async Task SleepAsync_UsesSysApi()
Assert.Equal(0x01, result);
Assert.Single(transport.WrittenLines, "00070500000001000000");
}

[Fact]
public async Task CallFunctionRawAsync_SerializesConcurrentCalls()
{
var transport = new ConcurrentReaderTransport("COM8");
transport.Responses.Enqueue("00");
transport.Responses.Enqueue("00");
var device = new TWN4ReaderDevice("COM8", _ => transport);

var firstTask = device.CallFunctionRawAsync(new byte[] { 0xAA });
await transport.FirstWriteSeen.Task;
var secondTask = device.CallFunctionRawAsync(new byte[] { 0xBB });

transport.AllowRead.TrySetResult(true);
await Task.WhenAll(firstTask, secondTask);

Assert.False(transport.OverlapDetected);
Assert.Equal(2, transport.WrittenLines.Count);
}
}
}
Loading