Hello everyone,
I’ve been testing Concurrent Requests in Acumatica. Per the license we should have 6 concurrent requests and 500 requests per minute. I put together a contrived example to send off requests concurrently. The program runs through 2 scenarios. 8 Concurrent requests from a single user and then 8 requests each from 3 different users. However, the results were unexpected.
It seems for Web API requests, that requests are not processed concurrently in Acumatica for a single user. In the first test the first request takes 600 ms and the second request (exactly the same as the first) takes 1018ms. And the trend continues with around a 400-500 ms delay between each request.
In the second test, It looks like there is a little concurrency, but I’m not getting a concurrency of 6.
Further, if you switch the request to be an OData call from a GI concurrency does seem to work as expected.
I’ve provided code below to try it for yourself.
So my question is: Why do the Web API requests not run concurrently here?
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class Program
{
public static string tenant = "";
public static string hostURL = ""; //no slash at the end
public static string ItemId = ""; //inventory id of any stock item
public static string user1 = ""; //first username
public static string user2 = ""; //second username
public static string user3 = ""; //third username
public static string client_id = "";
public static string client_secret = "";
public static string pwd = ""; //all users share a password
public static string scope = "api";
public static Stopwatch programTime = new Stopwatch();
private static HttpClient[] clients = new HttpClient[] { null, null, null }; //uses the same HttpClient per login
static HttpClient GetClient(int i)
{
var socketsHandler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 50
};
Program.clients[i] = Program.clients[i] ?? new HttpClient(socketsHandler);
return Program.clients[i];
}
public static async Task Main(string[] args)
{
programTime.Start();
string token1 = null;
string token2 = null;
string token3 = null;
try
{
var requestsPerLogin = 8;
token1 = await Login(GetClient(0), user1);
var client1Response = new List<string>();
var client2Response = new List<string>();
var client3Response = new List<string>();
// Send requests using the first login only
List<Task> tasks1 = new List<Task>();
for (var i = 0; i < requestsPerLogin; i++)
{
var tsk = SendRequest(GetClient(0), client1Response, i, token1);
tasks1.Add(tsk);
}
await Task.WhenAll(tasks1);
WriteHeader();
Console.WriteLine(string.Join(Environment.NewLine, client1Response));
client1Response.Clear();
//DELAY
await Task.Delay(250);
token2 = await Login(GetClient(1), user2);
token3 = await Login(GetClient(2), user3);
// Send requests using all logins
List<Task> tasksA = new List<Task>();
for (var i = 0; i < requestsPerLogin; i++)
{
var tsk = SendRequest(GetClient(0), client1Response, i, token1);
tasksA.Add(tsk);
}
List<Task> tasksB = new List<Task>();
for (var i = 0; i < requestsPerLogin; i++)
{
var tsk = SendRequest(GetClient(1), client2Response, i, token2);
tasksB.Add(tsk);
}
List<Task> tasksC = new List<Task>();
for (var i = 0; i < requestsPerLogin; i++)
{
var tsk = SendRequest(GetClient(2), client3Response, i, token3);
tasksC.Add(tsk);
}
await Task.WhenAll(tasksA);
await Task.WhenAll(tasksB);
await Task.WhenAll(tasksC);
Console.WriteLine("-----");
Console.WriteLine("CLIENT 1");
WriteHeader();
Console.WriteLine(string.Join(Environment.NewLine, client1Response));
Console.WriteLine("CLIENT 2");
WriteHeader();
Console.WriteLine(string.Join(Environment.NewLine, client2Response));
Console.WriteLine("CLIENT 3");
WriteHeader();
Console.WriteLine(string.Join(Environment.NewLine, client3Response));
await Logout(GetClient(0), token1);
await Logout(GetClient(1), token2);
await Logout(GetClient(2), token3);
}
catch (Exception ex)
{
try
{
await Logout(GetClient(0), token1);
}
finally { }
try
{
await Logout(GetClient(1), token2);
}
finally { }
try
{
await Logout(GetClient(2), token3);
}
finally { }
}
}
private static async Task SendRequest(HttpClient client, List<string> sb, int i, string token)
{
var sendTime = programTime.ElapsedMilliseconds.ToString("00000");
var sw = new Stopwatch();
sw.Start();
var request = $"{Program.hostURL}/entity/Default/20.200.001/StockItem/{ItemId}?$select=DefaultPrice,BaseUOM,PurchaseUOM,SalesUOM,UOMConversions/ConversionFactor,UOMConversions/FromUOM,UOMConversions/MultiplyOrDivide,UOMConversions/ToUOM&$expand=UOMConversions";
//request = $"{Program.hostURL}/OData/{Program.tenant}/WACcAddress?$top=1";
var httpRequest = new HttpRequestMessage(HttpMethod.Get, request);
if (token != "")
{
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
var response = await client.SendAsync(httpRequest);
sw.Stop();
if (response.IsSuccessStatusCode == false)
{
Console.WriteLine("ERROR: " + response.StatusCode);
}
var ms = sw.ElapsedMilliseconds;
sb.Add($"{sendTime} | {i.ToString("00")} {response.StatusCode} | {ms}");
}
private static async Task<string> Login(HttpClient client, string username)
{
var queryParams = new Dictionary<string, string>()
{
{"grant_type", "password"},
{"client_id", client_id},
{"client_secret", client_secret},
{"username", username},
{"password", pwd },
{ "scope", scope}
};
var requestUri = $"{hostURL}/identity/connect/token";
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
request.Headers.ConnectionClose = true;
request.Content = new FormUrlEncodedContent(queryParams);
var res = await client.SendAsync(request);
var result = await res.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<dynamic>(result);
return json.access_token;
}
private static async Task Logout(HttpClient client, string token)
{
var request = $"{hostURL}/entity/auth/logout";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, request);
if (token != "")
{
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
var res = await client.SendAsync(httpRequest);
var content = await res.Content.ReadAsStringAsync();
Console.WriteLine("Logout");
Console.WriteLine(content);
}
private static void WriteHeader()
{
Console.WriteLine(" PRGM | #RES | RES ");
Console.WriteLine(" TM | | TIME ");
Console.WriteLine("---------------------");
}
}
Best answer by Dmitrii Naumov
View original