/// <summary> /// Initializes a new instance of the <see cref="ConcurrentRequestExecutor"/> class with the specified /// connection and memory pool. /// </summary> /// <param name="connection">The <see cref="S7Connection"/> that is used for this executor.</param> /// <param name="memoryPool"> /// The <see cref="MemoryPool{T}" /> that is used for request and response memory allocations. /// </param> public ConcurrentRequestExecutor(S7Connection connection, MemoryPool <byte>?memoryPool = default) { if (connection.Parameters == null) { Sally7CommunicationSetupException.ThrowConnectionParametersNotSet(); } Connection = connection; socket = connection.TcpClient.Client; bufferSize = connection.Parameters.GetRequiredBufferSize(); maxRequests = connection.Parameters.MaximumNumberOfConcurrentRequests; this.memoryPool = memoryPool ?? MemoryPool <byte> .Shared; jobPool = new JobPool(connection.Parameters.MaximumNumberOfConcurrentRequests); sendSignal = new Signal(); receiveSignal = new Signal(); if (!sendSignal.TryInit()) { Sally7Exception.ThrowFailedToInitSendingSignal(); } if (!receiveSignal.TryInit()) { Sally7Exception.ThrowFailedToInitReceivingSignal(); } reader = new SocketTpktReader(socket); #if !NETSTANDARD2_1_OR_GREATER && !NET5_0_OR_GREATER sendAwaitable = new SocketAwaitable(new SocketAsyncEventArgs()); #endif }
public void ReturnJobId(int jobId) { if (!jobIdPool.Writer.TryWrite(jobId)) { Sally7Exception.ThrowFailedToReturnJobIDToPool(jobId); } }
public void GetResult() { if (EventArgs.SocketError != SocketError.Success) { Sally7Exception.ThrowSocketException(EventArgs.SocketError); } }
public JobPool(int maxNumberOfConcurrentRequests) { jobIdPool = Channel.CreateBounded <int>(maxNumberOfConcurrentRequests); requests = new Request[maxNumberOfConcurrentRequests]; for (int i = 0; i < maxNumberOfConcurrentRequests; ++i) { if (!jobIdPool.Writer.TryWrite(i + 1)) { Sally7Exception.ThrowFailedToInitJobPool(); } requests[i] = new Request(); } }
public async ValueTask <int> ReadAsync(Memory <byte> message, CancellationToken cancellationToken) { if (!MemoryMarshal.TryGetArray <byte>(message, out var segment)) { Sally7Exception.ThrowMemoryWasNotArrayBased(); } args.SetBuffer(segment.Array, segment.Offset, TpktSize); int count = 0; do { if (count > 0) { args.SetBuffer(segment.Offset + count, TpktSize - count); } cancellationToken.ThrowIfCancellationRequested(); await socket.ReceiveAsync(awaitable); if (args.BytesTransferred <= 0) { TpktException.ThrowConnectionWasClosedWhileReading(); } count += args.BytesTransferred; } while (count < TpktSize); int receivedLength = GetTpktLength(message.Span); while (count < receivedLength) { cancellationToken.ThrowIfCancellationRequested(); args.SetBuffer(segment.Offset + count, receivedLength - count); await socket.ReceiveAsync(awaitable); if (args.BytesTransferred <= 0) { TpktException.ThrowConnectionWasClosedWhileReading(); } count += args.BytesTransferred; } return(receivedLength); }
/// <inheritdoc/> public async ValueTask <Memory <byte> > PerformRequest(ReadOnlyMemory <byte> request, Memory <byte> response, CancellationToken cancellationToken) { int jobId = await jobPool.RentJobIdAsync(cancellationToken).ConfigureAwait(false); try { jobPool.SetBufferForRequest(jobId, response); using (IMemoryOwner <byte> mo = memoryPool.Rent(request.Length)) { request.CopyTo(mo.Memory); mo.Memory.Span[JobIdIndex] = (byte)jobId; _ = await sendSignal.WaitAsync(cancellationToken).ConfigureAwait(false); try { #if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER int written = await socket.SendAsync(mo.Memory.Slice(0, request.Length), SocketFlags.None, cancellationToken).ConfigureAwait(false); Debug.Assert(written == request.Length); #else if (!MemoryMarshal.TryGetArray(mo.Memory.Slice(0, request.Length), out ArraySegment <byte> segment)) { Sally7Exception.ThrowMemoryWasNotArrayBased(); } sendAwaitable.EventArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); await socket.SendAsync(sendAwaitable); #endif } finally { if (!sendSignal.TryRelease()) { Sally7Exception.ThrowFailedToSignalSendDone(); } } } // Always wait for a response. The number of received responses should always equal the // number of requests, so a single response must be received. Request rec; int length; using (IMemoryOwner <byte> mo = memoryPool.Rent(bufferSize)) { _ = await receiveSignal.WaitAsync(cancellationToken).ConfigureAwait(false); try { length = await reader.ReadAsync(mo.Memory, cancellationToken).ConfigureAwait(false); } finally { if (!receiveSignal.TryRelease()) { Sally7Exception.ThrowFailedToSignalReceiveDone(); } } Memory <byte> message = mo.Memory.Slice(0, length); int replyJobId = mo.Memory.Span[JobIdIndex]; if ((uint)(replyJobId - 1) >= (uint)maxRequests) { S7CommunicationException.ThrowInvalidJobID(replyJobId, message); } rec = jobPool.GetRequest(replyJobId); message.CopyTo(rec.Buffer); } rec.Complete(length); // await the actual completion before returning this job ID to the pool return(await jobPool.GetRequest(jobId)); } finally { jobPool.ReturnJobId(jobId); } }