/// <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);
     }
 }
Exemple #3
0
 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);
            }
        }