// TODO(mathip): add support for requestId parsing.
        public async Task <string> GetFilenameAsync(string request)
        {
            var client = await _lookupDispatcher.TakeAsync();

            var filename     = Path.GetRandomFileName();
            var binaryWriter = new BinaryWriter(File.Open(filename, FileMode.OpenOrCreate));

            var ct  = new CancellationTokenSource(_timeout);
            var res = new TaskCompletionSource <string>();

            ct.Token.Register(() => res.TrySetCanceled(), false);

            void SocketClientOnMessageReceived(object sender, SocketMessageEventArgs args)
            {
                // check for errors
                if (args.Message[0] == IQFeedDefault.PrototolErrorCharacter && args.Message[1] == IQFeedDefault.ProtocolDelimiterCharacter)
                {
                    // at this level, we might have true negative, further checks needed
                    var received     = Encoding.ASCII.GetString(args.Message, 0, args.Count);
                    var messages     = received.SplitFeedLine();
                    var errorMessage = ParseErrorMessage(messages);

                    if (!string.IsNullOrEmpty(errorMessage))
                    {
                        // error has been confirmed
                        res.TrySetException(_exceptionFactory.CreateNew(request, errorMessage, received));
                        return;
                    }
                }

                binaryWriter.Write(args.Message, 0, args.Count);

                // check if the message end
                if (args.Message.EndsWith(args.Count, _endOfMsgBytes))
                {
                    res.TrySetResult(filename);
                }
            }

            client.MessageReceived += SocketClientOnMessageReceived;
            await _lookupRateLimiter.WaitAsync().ConfigureAwait(false);

            client.Send(request);

            await res.Task.ContinueWith(x =>
            {
                binaryWriter.Close();
                client.MessageReceived -= SocketClientOnMessageReceived;
                _lookupDispatcher.Add(client);
                ct.Dispose();
                if (res.Task.IsFaulted)
                {
                    File.Delete(filename);
                }
            }, TaskContinuationOptions.None).ConfigureAwait(false);

            return(await res.Task.ConfigureAwait(false));
        }
Esempio n. 2
0
        protected async Task <IEnumerable <T> > GetMessagesAsync <T>(string request, Func <byte[], int, MessageContainer <T> > messageHandler)
        {
            var client = await _lookupDispatcher.TakeAsync();

            var messages        = new List <T>();
            var invalidMessages = new List <InvalidMessage <T> >();
            var ct  = new CancellationTokenSource(_timeout);
            var res = new TaskCompletionSource <IEnumerable <T> >();

            ct.Token.Register(() => res.TrySetCanceled(), false);

            void SocketClientOnMessageReceived(object sender, SocketMessageEventArgs args)
            {
                var container = messageHandler(args.Message, args.Count);

                // exception must be throw at the very end when all messages have been received and parsed to avoid
                // continuation in the next request since we don't use request id
                if (container.ErrorMessage != null)
                {
                    res.TrySetException(_exceptionFactory.CreateNew(request, container.ErrorMessage, container.MessageTrace));
                    return;
                }

                messages.AddRange(container.Messages);
                invalidMessages.AddRange(container.InvalidMessages);

                if (!container.End)
                {
                    return;
                }

                if (invalidMessages.Count > 0)
                {
                    res.TrySetException(_exceptionFactory.CreateNew(request, invalidMessages, messages));
                    return;
                }

                res.TrySetResult(messages);
            }

            client.MessageReceived += SocketClientOnMessageReceived;
            await _lookupRateLimiter.WaitAsync().ConfigureAwait(false);

            client.Send(request);

            await res.Task.ContinueWith(x =>
            {
                client.MessageReceived -= SocketClientOnMessageReceived;
                _lookupDispatcher.Add(client);
                ct.Dispose();
            }, TaskContinuationOptions.None).ConfigureAwait(false);

            return(await res.Task.ConfigureAwait(false));
        }
        public async Task Should_Rate_Limit_By_Throughput()
        {
            // Arrange
            var totalSeconds = TimeSpan.FromSeconds(15).TotalSeconds;
            var seconds      = 0;

            var requestsPerSecond = 50;
            var requestsCount     = 0;
            var requests          = new List <int>();

            var lookupRateLimiter = new LookupRateLimiter(requestsPerSecond);
            var cts = new CancellationTokenSource();

            // Act
            _ = Task.Run(async() =>
            {
                while (true)
                {
                    await lookupRateLimiter.WaitAsync();
                    Interlocked.Increment(ref requestsCount);
                }
            }, cts.Token);

            while (seconds < totalSeconds)
            {
                var sw = Stopwatch.StartNew();
                await Task.Delay(TimeSpan.FromSeconds(1));

                sw.Stop();

                Console.WriteLine($"requests: {requestsCount}/{sw.Elapsed.TotalMilliseconds}ms");
                requests.Add(requestsCount);
                Interlocked.Exchange(ref requestsCount, 0);
                seconds++;
            }

            cts.Cancel();
            cts.Dispose();

            // Assert
            Assert.That(requests.Average(), Is.EqualTo(requestsPerSecond).Within(2).Percent);
        }
        public async Task Should_Rate_Limit_By_Throughput()
        {
            // Arrange
            var debug             = false;
            var totalSeconds      = TimeSpan.FromSeconds(10).TotalSeconds;
            var requestsPerSecond = 50;
            var numberOfClients   = 15;
            var testResults       = new List <TestResult>();

            var lookupRateLimiter = new LookupRateLimiter(requestsPerSecond);
            var cts = new CancellationTokenSource();
            await Task.Delay(1000);

            var tasks = new Task[numberOfClients];
            var mutex = new object();

            var requestsCount = 0;
            var seconds       = 0;

            for (var i = 0; i < numberOfClients; i++)
            {
                var taskId = i;
                tasks[i] = new Task(async() =>
                {
                    while (true)
                    {
                        await lookupRateLimiter.WaitAsync().ConfigureAwait(false);
                        if (debug)
                        {
                            Console.WriteLine($"Executed task #{taskId}");
                        }
                        lock (mutex)
                            requestsCount++;
                    }
                }, cts.Token);
            }

            // Act
            for (var i = 0; i < numberOfClients; i++)
            {
                tasks[i].Start();
            }

            while (seconds < totalSeconds)
            {
                var sw = Stopwatch.StartNew();
                await Task.Delay(TimeSpan.FromSeconds(1));

                sw.Stop();

                Console.WriteLine($"requests: {requestsCount}/{sw.Elapsed.TotalMilliseconds}ms");
                lock (mutex)
                {
                    testResults.Add(new TestResult(requestsCount, sw.Elapsed.TotalMilliseconds));
                    requestsCount = 0;
                }
                seconds++;
            }

            cts.Cancel();
            cts.Dispose();

            // Assert

            // initial burst
            var initialBurst = testResults.First();
            var calcInitialBustRequestsPerSecond = initialBurst.NumberOfRequest * 1000 / initialBurst.TotalMilliseconds;
            var expectedInitialBurst             = lookupRateLimiter.RequestsPerSecond + lookupRateLimiter.MaxCount;

            Assert.That(calcInitialBustRequestsPerSecond, Is.LessThan(expectedInitialBurst).Within(5).Percent);
            Console.WriteLine($"Initial burst {calcInitialBustRequestsPerSecond} requests/second");

            // average
            var testResultsWithoutInitialBurst = testResults.Skip(1).ToList();
            var totalMs               = testResultsWithoutInitialBurst.Sum(x => x.TotalMilliseconds);
            var totalRequests         = testResultsWithoutInitialBurst.Sum(x => x.NumberOfRequest);
            var calcRequestsPerSecond = totalRequests * 1000 / totalMs;
            var variationPercentage   = Math.Abs(calcRequestsPerSecond - requestsPerSecond) / requestsPerSecond * 100;

            Console.WriteLine($"Average {calcRequestsPerSecond} requests/second");
            Console.WriteLine($"Variation {variationPercentage} %");
            Assert.That(calcRequestsPerSecond, Is.EqualTo(requestsPerSecond).Within(1).Percent);
        }