// 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)); }
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); }