Skip to content

Commit aa78d30

Browse files
Make distributed session loading asynchronous to improve scalability … (#1209)
* Make distributed session loading asynchronous to improve scalability (#1195) * Introduce CustomRedisSessionStore to reduce excessive EXPIRE operations in Redis and improve session handling performance. Converted ProcessRestRequest method in GXRouting.cs to fully async implementation. * Ensures sessions load asynchronously * Apply suggestions from FAzzatto -Simplified exception handling by replacing await Task.FromException(ex) with throw -Refactored to throw PageNotFoundException directly when the route cannot be handled, removed unnecessary result task, and moved CommitSessionAsync() to a finally block for clearer async flow Removed CustomRedisSessionStore which was previously used as an experiment to reduce synchronous Redis Refresh calls. Now that LoadAsync is executed at the start of each request, those synchronous Refresh calls no longer appear to be necessary, because they were caused by the previous synchronous load. * Refactored method to remove the result variable and await tasks directly, preserving the original behavior. --------- Co-authored-by: claudiamurialdo <c.murialdo@globant.com> (cherry picked from commit bb22cbb) # Conflicts: # dotnet/src/dotnetcore/GxClasses.Web/Middleware/GXRouting.cs # dotnet/src/dotnetcore/GxNetCoreStartup/SessionHelper.cs # dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs # dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs * Update SessionHelper.cs * Update SessionHelper.cs * Update SessionHelper.cs
1 parent ed3e062 commit aa78d30

File tree

6 files changed

+146
-127
lines changed

6 files changed

+146
-127
lines changed

dotnet/src/dotnetcore/GxClasses.Web/Middleware/GXRouting.cs

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ public GXRouting(string baseURL)
6262
}
6363

6464
static public List<ControllerInfo> GetRouteController(Dictionary<string, string> apiPaths,
65-
Dictionary<string, List<string>> sValid,
66-
Dictionary<string, Dictionary<string, SingleMap>> sMap,
67-
Dictionary<string, Dictionary<Tuple<string, string>, string>> sMapData,
68-
string basePath, string verb, string path)
65+
Dictionary<string, List<string>> sValid,
66+
Dictionary<string, Dictionary<string, SingleMap>> sMap,
67+
Dictionary<string, Dictionary<Tuple<string, string>, string>> sMapData,
68+
string basePath, string verb, string path)
6969
{
7070
List<ControllerInfo> result = new List<ControllerInfo>();
7171
string parms = string.Empty;
@@ -78,10 +78,10 @@ static public List<ControllerInfo> GetRouteController(Dictionary<string, string>
7878
int questionMarkIdx = path.IndexOf(QUESTIONMARK);
7979
string controller;
8080
if (apiPaths.ContainsKey(basePath)
81-
&& sValid.ContainsKey(basePath)
82-
&& sMap.ContainsKey(basePath)
83-
&& sMapData.ContainsKey(basePath)
84-
)
81+
&& sValid.ContainsKey(basePath)
82+
&& sMap.ContainsKey(basePath)
83+
&& sMapData.ContainsKey(basePath)
84+
)
8585
{
8686
if (sValid[basePath].Contains(path.ToLower()))
8787
{
@@ -174,12 +174,12 @@ internal async Task RouteHttpService(HttpContext context)
174174
HandlerFactory handlerFactory = new HandlerFactory();
175175
await handlerFactory.Invoke(context);
176176
}
177-
catch (Exception ex)
177+
catch
178178
{
179-
await Task.FromException(ex);
179+
throw;
180180
}
181181
}
182-
public Task ProcessRestRequest(HttpContext context)
182+
public async Task ProcessRestRequest(HttpContext context)
183183
{
184184
try
185185
{
@@ -188,7 +188,6 @@ public Task ProcessRestRequest(HttpContext context)
188188
IHttpContextAccessor contextAccessor = context.RequestServices.GetService<IHttpContextAccessor>();
189189
context = new GxHttpContextAccesor(contextAccessor);
190190
}
191-
Task result = Task.CompletedTask;
192191
string path = context.Request.Path.ToString();
193192
string actualPath = string.Empty;
194193
bool isServiceInPath = ServiceInPath(path, out actualPath);
@@ -220,37 +219,40 @@ public Task ProcessRestRequest(HttpContext context)
220219
else
221220
{
222221
if ((path.Contains(oauthRoute) && (AzureDeploy.GAM == "true")) || path.Contains("gxmulticall"))
223-
return (RouteHttpService(context));
222+
{
223+
await (RouteHttpService(context));
224+
return;
225+
}
224226
controllerWithParms = GetGxRouteValue(path);
225227
GXLogging.Debug(log, $"Running Azure functions. ControllerWithParms :{controllerWithParms} path:{path}");
226228
}
227-
229+
228230
List<ControllerInfo> controllers = GetRouteController(servicesPathUrl, servicesValidPath, servicesMap, servicesMapData, actualPath, context.Request.Method, controllerWithParms);
229231
GxRestWrapper controller = null;
230232
ControllerInfo controllerInfo = controllers.FirstOrDefault(c => (controller = GetController(context, c)) != null);
231-
233+
232234

233235
if (controller != null)
234236
{
235237
if (HttpMethods.IsGet(context.Request.Method) && (controllerInfo.Verb == null || HttpMethods.IsGet(controllerInfo.Verb)))
236238
{
237-
result = controller.Get(controllerInfo.Parameters);
239+
await controller.Get(controllerInfo.Parameters);
238240
}
239241
else if (HttpMethods.IsPost(context.Request.Method) && (controllerInfo.Verb == null || HttpMethods.IsPost(controllerInfo.Verb)))
240242
{
241-
result = controller.Post();
243+
await controller.Post();
242244
}
243245
else if (HttpMethods.IsDelete(context.Request.Method) && (controllerInfo.Verb == null || HttpMethods.IsDelete(controllerInfo.Verb)))
244246
{
245-
result = controller.Delete(controllerInfo.Parameters);
247+
await controller.Delete(controllerInfo.Parameters);
246248
}
247249
else if (HttpMethods.IsPut(context.Request.Method) && (controllerInfo.Verb == null || HttpMethods.IsPut(controllerInfo.Verb)))
248250
{
249-
result = controller.Put(controllerInfo.Parameters);
251+
await controller.Put(controllerInfo.Parameters);
250252
}
251253
else if (HttpMethods.IsPatch(context.Request.Method) && (controllerInfo.Verb == null || HttpMethods.IsPatch(controllerInfo.Verb)))
252254
{
253-
result = controller.Patch(controllerInfo.Parameters);
255+
await controller.Patch(controllerInfo.Parameters);
254256
}
255257
else if (HttpMethods.IsOptions(context.Request.Method))
256258
{
@@ -284,18 +286,20 @@ public Task ProcessRestRequest(HttpContext context)
284286
{
285287
GXLogging.Error(log, $"ProcessRestRequest controller not found path:{path} controllerWithParms:{controllerWithParms}");
286288
context.Response.Headers.Clear();
287-
result = Task.FromException(new PageNotFoundException(path));
289+
throw new PageNotFoundException(path);
288290
}
289291
}
290-
context.CommitSession();
291-
292-
return result;
292+
293293
}
294294
catch (Exception ex)
295295
{
296296
GXLogging.Error(log, "ProcessRestRequest", ex);
297297
HttpHelper.SetUnexpectedError(context, HttpStatusCode.InternalServerError, ex);
298-
return Task.FromException(ex);
298+
throw;
299+
}
300+
finally
301+
{
302+
await context.CommitSessionAsync();
299303
}
300304
}
301305

@@ -374,20 +378,21 @@ public bool ServiceInPath(String path, out String actualPath)
374378
return true;
375379
}
376380
}
377-
else {
381+
else
382+
{
378383
return true;
379-
}
384+
}
380385
}
381386

382-
private String FindPath(string innerPath, Dictionary<string,string > servicesPathUrl, bool startTxt)
387+
private String FindPath(string innerPath, Dictionary<string, string> servicesPathUrl, bool startTxt)
383388
{
384389
string actualPath = String.Empty;
385-
foreach (string subPath in from String subPath in servicesPathUrl.Keys
386-
select subPath)
390+
foreach (string subPath in from String subPath in servicesPathUrl.Keys
391+
select subPath)
387392
{
388393
bool match = false;
389394
innerPath = innerPath.ToLower();
390-
match = (startTxt)? innerPath.StartsWith($"/{subPath.ToLower()}"): innerPath.Contains($"/{subPath.ToLower()}");
395+
match = (startTxt) ? innerPath.StartsWith($"/{subPath.ToLower()}") : innerPath.Contains($"/{subPath.ToLower()}");
391396
if (match)
392397
{
393398
actualPath = subPath.ToLower();
@@ -431,7 +436,7 @@ public GxRestWrapper GetController(HttpContext context, ControllerInfo controlle
431436
bool privateDirExists = Directory.Exists(privateDir);
432437

433438
GXLogging.Debug(log, $"PrivateDir:{privateDir} asssemblycontroller:{asssemblycontroller}");
434-
string svcFile=null;
439+
string svcFile = null;
435440
if (privateDirExists && File.Exists(Path.Combine(privateDir, $"{asssemblycontroller.ToLower()}.grp.json")))
436441
{
437442
controller = tmpController;
@@ -498,7 +503,7 @@ string SvcFile(string controller)
498503
GXLogging.Warn(log, "Service file not found:" + controllerFullName);
499504
return null;
500505
}
501-
506+
502507
}
503508
public void ServicesGroupSetting()
504509
{
@@ -524,7 +529,7 @@ public void ServicesGroupSetting()
524529
string mapPath = (m.BasePath.EndsWith("/")) ? m.BasePath : m.BasePath + "/";
525530
string mapPathLower = mapPath.ToLower();
526531
string mNameLower = m.Name.ToLower();
527-
servicesPathUrl[mapPathLower]= mNameLower;
532+
servicesPathUrl[mapPathLower] = mNameLower;
528533
if (!RestAPIHelpers.ServiceAsController())
529534
{
530535
GXLogging.Debug(log, $"addServicesPathUrl key:{mapPathLower} value:{mNameLower}");
@@ -575,7 +580,8 @@ public void ServicesGroupSetting()
575580
}
576581
}
577582
}
578-
}catch (Exception ex)
583+
}
584+
catch (Exception ex)
579585
{
580586
GXLogging.Error(log, $"Error Loading Services Group Settings", ex);
581587
throw;
@@ -676,7 +682,7 @@ public class Binding
676682

677683
[DataContract()]
678684
public class MapGroup
679-
{
685+
{
680686
String _objectType;
681687
String _name;
682688
String _basePath;

dotnet/src/dotnetcore/GxClasses.Web/Middleware/HandlerFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ public async Task Invoke(HttpContext context)
8989
}
9090
else
9191
{
92-
await Task.FromException(new PageNotFoundException(url));
92+
throw new PageNotFoundException(url);
9393
}
9494
}
9595
catch (Exception ex)
9696
{
9797
GXLogging.Error(log, $"Handler Factory failed creating {url}", ex);
98-
await Task.FromException(ex);
98+
throw;
9999
}
100100
}
101101
private static string ObjectUrl(string requestPath, string basePath)

dotnet/src/dotnetcore/GxClasses/Domain/HttpSessionState.cs

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections;
3+
using System.Collections.Concurrent;
34
using System.Collections.Generic;
45
using System.Linq;
56
using System.Net;
67
using System.Reflection;
8+
using System.Threading;
79
using Microsoft.AspNetCore.Http;
810

911
namespace GeneXus.Http
@@ -32,34 +34,13 @@ public static CookieCollection GetCookies(this CookieContainer container)
3234
return allCookies;
3335
}
3436
}
35-
internal class LockTracker : IDisposable
37+
public static class LockTracker
3638
{
37-
private static Dictionary<string, LockTracker> _locks = new Dictionary<string, LockTracker>();
38-
private int _activeUses = 0;
39-
private readonly string _id;
39+
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
4040

41-
private LockTracker(string id) => _id = id;
42-
43-
internal static LockTracker Get(string id)
44-
{
45-
lock (_locks)
46-
{
47-
if (!_locks.ContainsKey(id))
48-
_locks.Add(id, new LockTracker(id));
49-
var res = _locks[id];
50-
res._activeUses += 1;
51-
return res;
52-
}
53-
}
54-
55-
void IDisposable.Dispose()
41+
public static SemaphoreSlim Get(string sessionId)
5642
{
57-
lock (_locks)
58-
{
59-
_activeUses--;
60-
if (_activeUses == 0)
61-
_locks.Remove(_id);
62-
}
43+
return _locks.GetOrAdd(sessionId, _ => new SemaphoreSlim(1, 1));
6344
}
6445
}
6546
internal class HttpSyncSessionState : HttpSessionState

dotnet/src/dotnetcore/GxNetCoreStartup/SessionHelper.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ public async Task Invoke(HttpContext context)
6969
if (!string.IsNullOrEmpty(subdomain))
7070
context.Items[AppContext.TENANT_ID] = subdomain;
7171
}
72-
7372
await _next(context);
7473
}
7574
}

0 commit comments

Comments
 (0)