Skip to content

Commit db15b0c

Browse files
claudiamurialdoclaudiamurialdo
authored andcommitted
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.
1 parent d09808b commit db15b0c

File tree

5 files changed

+156
-128
lines changed

5 files changed

+156
-128
lines changed

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

Lines changed: 71 additions & 67 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);
@@ -214,43 +213,46 @@ public Task ProcessRestRequest(HttpContext context)
214213
}
215214
string controllerPath = path.ToLower().Split(actualPath).Last<string>();
216215
controllerWithParms = controllerPath.Split(QUESTIONMARK).First<string>();
217-
216+
218217
}
219218
}
220219
else
221220
{
222221
if (path.Contains(oauthRoute) && (AzureDeploy.GAM == "true"))
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

@@ -376,18 +380,18 @@ public bool ServiceInPath(String path, out String actualPath)
376380
}
377381
else {
378382
return true;
379-
}
383+
}
380384
}
381385

382-
private String FindPath(string innerPath, Dictionary<string,string > servicesPathUrl, bool startTxt)
386+
private String FindPath(string innerPath, Dictionary<string, string> servicesPathUrl, bool startTxt)
383387
{
384388
string actualPath = String.Empty;
385389
foreach (var subPath in from String subPath in servicesPathUrl.Keys
386-
select subPath)
390+
select subPath)
387391
{
388392
bool match = false;
389393
innerPath = innerPath.ToLower();
390-
match = (startTxt)? innerPath.StartsWith($"/{subPath.ToLower()}"): innerPath.Contains($"/{subPath.ToLower()}");
394+
match = (startTxt) ? innerPath.StartsWith($"/{subPath.ToLower()}") : innerPath.Contains($"/{subPath.ToLower()}");
391395
if (match)
392396
{
393397
actualPath = subPath.ToLower();
@@ -431,7 +435,7 @@ public GxRestWrapper GetController(HttpContext context, ControllerInfo controlle
431435
bool privateDirExists = Directory.Exists(privateDir);
432436

433437
GXLogging.Debug(log, $"PrivateDir:{privateDir} asssemblycontroller:{asssemblycontroller}");
434-
string svcFile=null;
438+
string svcFile = null;
435439
if (privateDirExists && File.Exists(Path.Combine(privateDir, $"{asssemblycontroller.ToLower()}.grp.json")))
436440
{
437441
controller = tmpController;
@@ -498,7 +502,7 @@ string SvcFile(string controller)
498502
GXLogging.Warn(log, "Service file not found:" + controllerFullName);
499503
return null;
500504
}
501-
505+
502506
}
503507
public void ServicesGroupSetting()
504508
{
@@ -524,54 +528,54 @@ public void ServicesGroupSetting()
524528
string mapPath = (m.BasePath.EndsWith("/")) ? m.BasePath : m.BasePath + "/";
525529
string mapPathLower = mapPath.ToLower();
526530
string mNameLower = m.Name.ToLower();
527-
servicesPathUrl[mapPathLower]= mNameLower;
528-
GXLogging.Debug(log, $"addServicesPathUrl key:{mapPathLower} value:{mNameLower}");
529-
foreach (SingleMap sm in m.Mappings)
530-
{
531-
if (sm.Verb == null)
532-
sm.Verb = "GET";
533-
if (String.IsNullOrEmpty(sm.Path))
534-
sm.Path = sm.Name;
535-
else
536-
{
537-
sm.Path = Regex.Replace(sm.Path, "^/|/$", "");
538-
}
539-
if (sm.VariableAlias == null)
540-
sm.VariableAlias = new Dictionary<string, string>();
541-
else
531+
servicesPathUrl[mapPathLower] = mNameLower;
532+
GXLogging.Debug(log, $"addServicesPathUrl key:{mapPathLower} value:{mNameLower}");
533+
foreach (SingleMap sm in m.Mappings)
542534
{
543-
Dictionary<string, string> vMap = new Dictionary<string, string>();
544-
foreach (KeyValuePair<string, string> v in sm.VariableAlias)
535+
if (sm.Verb == null)
536+
sm.Verb = "GET";
537+
if (String.IsNullOrEmpty(sm.Path))
538+
sm.Path = sm.Name;
539+
else
545540
{
546-
vMap.Add(v.Key.ToLower(), v.Value.ToLower());
541+
sm.Path = Regex.Replace(sm.Path, "^/|/$", "");
547542
}
548-
sm.VariableAlias = vMap;
549-
}
550-
if (servicesMap.ContainsKey(mapPathLower))
551-
{
552-
if (!servicesMap[mapPathLower].ContainsKey(sm.Name.ToLower()))
543+
if (sm.VariableAlias == null)
544+
sm.VariableAlias = new Dictionary<string, string>();
545+
else
546+
{
547+
Dictionary<string, string> vMap = new Dictionary<string, string>();
548+
foreach (KeyValuePair<string, string> v in sm.VariableAlias)
549+
{
550+
vMap.Add(v.Key.ToLower(), v.Value.ToLower());
551+
}
552+
sm.VariableAlias = vMap;
553+
}
554+
if (servicesMap.ContainsKey(mapPathLower))
553555
{
556+
if (!servicesMap[mapPathLower].ContainsKey(sm.Name.ToLower()))
557+
{
558+
servicesValidPath[mapPathLower].Add(sm.Path.ToLower());
559+
560+
servicesMapData[mapPathLower].Add(Tuple.Create(sm.Path.ToLower(), sm.Verb.ToUpper()), sm.Name.ToLower());
561+
servicesMap[mapPathLower].Add(sm.Name.ToLower(), sm);
562+
}
563+
}
564+
else
565+
{
566+
servicesValidPath.Add(mapPathLower, new List<string>());
554567
servicesValidPath[mapPathLower].Add(sm.Path.ToLower());
555568

569+
servicesMapData.Add(mapPathLower, new Dictionary<Tuple<string, string>, string>());
556570
servicesMapData[mapPathLower].Add(Tuple.Create(sm.Path.ToLower(), sm.Verb.ToUpper()), sm.Name.ToLower());
571+
servicesMap.Add(mapPathLower, new Dictionary<string, SingleMap>());
557572
servicesMap[mapPathLower].Add(sm.Name.ToLower(), sm);
558573
}
559574
}
560-
else
561-
{
562-
servicesValidPath.Add(mapPathLower, new List<string>());
563-
servicesValidPath[mapPathLower].Add(sm.Path.ToLower());
564-
565-
servicesMapData.Add(mapPathLower, new Dictionary<Tuple<string, string>, string>());
566-
servicesMapData[mapPathLower].Add(Tuple.Create(sm.Path.ToLower(), sm.Verb.ToUpper()), sm.Name.ToLower());
567-
servicesMap.Add(mapPathLower, new Dictionary<string, SingleMap>());
568-
servicesMap[mapPathLower].Add(sm.Name.ToLower(), sm);
569-
}
570575
}
571576
}
572577
}
573578
}
574-
}
575579
}catch (Exception ex)
576580
{
577581
GXLogging.Error(log, $"Error Loading Services Group Settings", ex);
@@ -673,7 +677,7 @@ public class Binding
673677

674678
[DataContract()]
675679
public class MapGroup
676-
{
680+
{
677681
String _objectType;
678682
String _name;
679683
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
public static bool IsAspxHandler(string path, 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

0 commit comments

Comments
 (0)