Solved

Acumatica Conncurent Requests per login

  • 26 March 2024
  • 1 reply
  • 41 views

Userlevel 1

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?

Program Results

 

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("---------------------");
}
}

 

 

icon

Best answer by Dmitrii Naumov 26 March 2024, 17:17

View original

1 reply

Userlevel 7
Badge +5

@aaldrich for concurrency you need to open several “connections”/”sessions”. 

So it may be the same user but you need to log in multiple times. 

If you use token, you can use the same token, but you need “api:concurrent_access” scope for this to work. 

For an example, see here: https://help.acumatica.com/(W(7))/Help?ScreenId=ShowWiki&pageid=a8fbe87f-647a-48dd-b713-de6c123a4de4

Reply


About Acumatica ERP system
Acumatica Cloud ERP provides the best business management solution for transforming your company to thrive in the new digital economy. Built on a future-proof platform with open architecture for rapid integrations, scalability, and ease of use, Acumatica delivers unparalleled value to small and midmarket organizations. Connected Business. Delivered.
© 2008 — 2024  Acumatica, Inc. All rights reserved