예제 #1
0
        public DataSegment Deserialize(ReadOnlySequence <byte> buffer)
        {
            // length
            SequencePosition?pos = buffer.PositionOf((byte)',');

            if (pos == null)
            {
                throw new DataMismatchException("should contain comma-separated values");
            }

            ReadOnlySequence <byte> lengthBytes = buffer.Slice(0, pos.Value);
            int numSize = s_encoding.GetChars(lengthBytes.ToArray(), _charBuffer);
            int length  = int.Parse(_charBuffer.AsSpan().Slice(0, numSize));

            buffer = buffer.Slice(buffer.GetPosition(1, pos.Value));

            // checksum
            pos = buffer.PositionOf((byte)',');
            if (pos == null)
            {
                throw new DataMismatchException("should contain comma-separated values");
            }

            ReadOnlySequence <byte> checksumBytes = buffer.Slice(0, pos.Value);

            numSize = s_encoding.GetChars(checksumBytes.ToArray(), _charBuffer);
            ulong checksum = ulong.Parse(_charBuffer.AsSpan().Slice(0, numSize));

            buffer = buffer.Slice(buffer.GetPosition(1, pos.Value));

            // payload
            if (length != (int)buffer.Length)
            {
                throw new DataMismatchException("declared length does not match payload length");
            }

            var chunk = new DataSegment((int)buffer.Length);

            buffer.CopyTo(chunk.AsSpan());

            if (checksum != chunk.Checksum)
            {
                chunk.Return();
                throw new DataMismatchException("declared checksum doesn't match payload checksum");
            }

            return(chunk);
        }
예제 #2
0
        protected override async Task HandleConnection(SslStream sslStream, TcpClient client, CancellationToken token)
        {
            using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(token);
            DateTime lastReadTime = DateTime.Now;

            var serializer = new DataSegmentSerializer();

            _ = Task.Run(Monitor);
            await sslStream.ReadLinesUsingPipesAsync(Callback, cts.Token, separator : '\n');

            async Task Callback(ReadOnlySequence <byte> buffer)
            {
                lastReadTime = DateTime.Now;

                if (buffer.Length == 0)
                {
                    // got an empty line, client is closing the connection
                    // echo back the empty line and tear down.
                    sslStream.WriteByte((byte)'\n');
                    await sslStream.FlushAsync(token);

                    cts.Cancel();
                    return;
                }

                DataSegment?chunk = null;

                try
                {
                    chunk = serializer.Deserialize(buffer);
                    await serializer.SerializeAsync(sslStream, chunk.Value, token : token);

                    sslStream.WriteByte((byte)'\n');
                    await sslStream.FlushAsync(token);
                }
                catch (DataMismatchException e)
                {
                    if (_config.LogServer)
                    {
                        lock (Console.Out)
                        {
                            Console.ForegroundColor = ConsoleColor.Yellow;
                            Console.WriteLine($"Server: {e.Message}");
                            Console.ResetColor();
                        }
                    }
                }
                finally
                {
                    chunk?.Return();
                }
            }

            async Task Monitor()
            {
                do
                {
                    await Task.Delay(1000);

                    if (DateTime.Now - lastReadTime >= TimeSpan.FromSeconds(10))
                    {
                        cts.Cancel();
                    }
                } while (!cts.IsCancellationRequested);
            }
        }
예제 #3
0
        protected override async Task HandleConnection(int workerId, long jobId, SslStream stream, TcpClient client, Random random, TimeSpan duration, CancellationToken token)
        {
            // token used for signalling cooperative cancellation; do not pass this to SslStream methods
            using var connectionLifetimeToken = new CancellationTokenSource(duration);

            long     messagesInFlight = 0;
            DateTime lastWrite        = DateTime.Now;
            DateTime lastRead         = DateTime.Now;

            await StressTaskExtensions.WhenAllThrowOnFirstException(token, Sender, Receiver, Monitor);

            async Task Sender(CancellationToken token)
            {
                var serializer = new DataSegmentSerializer();

                while (!token.IsCancellationRequested && !connectionLifetimeToken.IsCancellationRequested)
                {
                    await ApplyBackpressure();

                    DataSegment chunk = DataSegment.CreateRandom(random, _config.MaxBufferLength);
                    try
                    {
                        await serializer.SerializeAsync(stream, chunk, random, token);

                        stream.WriteByte((byte)'\n');
                        await stream.FlushAsync(token);

                        Interlocked.Increment(ref messagesInFlight);
                        lastWrite = DateTime.Now;
                    }
                    finally
                    {
                        chunk.Return();
                    }
                }

                // write an empty line to signal completion to the server
                stream.WriteByte((byte)'\n');
                await stream.FlushAsync(token);

                /// Polls until number of in-flight messages falls below threshold
                async Task ApplyBackpressure()
                {
                    if (Volatile.Read(ref messagesInFlight) > 5000)
                    {
                        Stopwatch stopwatch = Stopwatch.StartNew();
                        bool      isLogged  = false;

                        while (!token.IsCancellationRequested && !connectionLifetimeToken.IsCancellationRequested && Volatile.Read(ref messagesInFlight) > 2000)
                        {
                            // only log if tx has been suspended for a while
                            if (!isLogged && stopwatch.ElapsedMilliseconds >= 1000)
                            {
                                isLogged = true;
                                lock (Console.Out)
                                {
                                    Console.ForegroundColor = ConsoleColor.Yellow;
                                    Console.WriteLine($"worker #{workerId}: applying backpressure");
                                    Console.WriteLine();
                                    Console.ResetColor();
                                }
                            }

                            await Task.Delay(20);
                        }

                        if (isLogged)
                        {
                            Console.WriteLine($"worker #{workerId}: resumed tx after {stopwatch.Elapsed}");
                        }
                    }
                }
            }

            async Task Receiver(CancellationToken token)
            {
                var serializer = new DataSegmentSerializer();

                using var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
                await stream.ReadLinesUsingPipesAsync(Callback, cts.Token, separator : '\n');

                Task Callback(ReadOnlySequence <byte> buffer)
                {
                    if (buffer.Length == 0 && connectionLifetimeToken.IsCancellationRequested)
                    {
                        // server echoed back empty buffer sent by client,
                        // signal cancellation and complete the connection.
                        cts.Cancel();
                        return(Task.CompletedTask);
                    }

                    // deserialize to validate the checksum, then discard
                    DataSegment chunk = serializer.Deserialize(buffer);

                    chunk.Return();
                    Interlocked.Decrement(ref messagesInFlight);
                    lastRead = DateTime.Now;
                    return(Task.CompletedTask);
                }
            }

            async Task Monitor(CancellationToken token)
            {
                do
                {
                    await Task.Delay(500);

                    if ((DateTime.Now - lastWrite) >= TimeSpan.FromSeconds(10))
                    {
                        throw new Exception($"worker #{workerId} job #{jobId} has stopped writing bytes to server");
                    }

                    if ((DateTime.Now - lastRead) >= TimeSpan.FromSeconds(10))
                    {
                        throw new Exception($"worker #{workerId} job #{jobId} has stopped receiving bytes from server");
                    }
                }while(!token.IsCancellationRequested && !connectionLifetimeToken.IsCancellationRequested);
            }
        }