diff --git a/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs b/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs index 4ca050297..219769018 100644 --- a/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs +++ b/dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs @@ -2102,6 +2102,8 @@ public virtual short GetHttpSecure() { try { + if (HttpContext==null) + return 0; if (_HttpContext.Request.GetIsSecureFrontEnd()) { GXLogging.Debug(log, "Front-End-Https header activated"); @@ -3414,7 +3416,7 @@ public IGxSession GetSession() } internal bool IsStandalone => this._session is GxSession || this._isSumbited || this.HttpContext == null; - + internal bool IsSubmited => this._isSumbited; internal void SetSession(IGxSession value) { if (value != null) diff --git a/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs b/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs index 56c25735d..1cc07c9fe 100644 --- a/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs +++ b/dotnet/src/dotnetframework/GxClasses/Core/GXUtilsCommon.cs @@ -43,6 +43,7 @@ using GeneXus.Services; using GeneXus.Http; using System.Security; +using System.Threading.Tasks; using System.Drawing.Imaging; namespace GeneXus.Utils @@ -56,7 +57,6 @@ public class GxDefaultProps public static string WORKSTATION = "GX_WRKST"; } - public class ThreadSafeRandom { private static RNGCryptoServiceProvider random = new RNGCryptoServiceProvider(); @@ -67,6 +67,8 @@ public static double NextDouble() byte[] bytes = new Byte[8]; random.GetBytes(bytes); + + ulong ul = BitConverter.ToUInt64(bytes, 0) / (1 << 11); double d = ul / (double)(1UL << 53); return d; @@ -5740,5 +5742,68 @@ public static string EncodeNonAsciiCharacters(string value) return sb.ToString(); } } + internal class ThreadUtil + { + static readonly ILog log = log4net.LogManager.GetLogger(typeof(ThreadUtil)); + private static ConcurrentDictionary events = new ConcurrentDictionary(); + const int MAX_WAIT_HANDLES = 64; + internal static void Submit(WaitCallback callbak, object state) + { + try + { + ManualResetEvent resetEvent = new ManualResetEvent(false); + Guid eventGuid = Guid.NewGuid(); + ThreadPool.QueueUserWorkItem( + arg => + { + callbak(state); + resetEvent.Set(); + events.TryRemove(eventGuid, out ManualResetEvent _); + + }); + events[eventGuid]= resetEvent; + } + catch (Exception ex) + { + GXLogging.Error(log, $"Submit error", ex); + } + } + internal static void WaitForEnd() + { + int remainingSubmits = events.Count; + if (remainingSubmits > 0) + { + GXLogging.Debug(log, "Waiting for " + remainingSubmits + " submitted procs to end..."); + WaitForAll(); + events.Clear(); + } + } + public static void WaitForAll() + { + try + { + List evtList =new List (); + foreach (ManualResetEvent evt in events.Values) + { + evtList.Add(evt); + //Avoid WaitHandle.WaitAll limitation. It can only handle 64 waithandles + if (evtList.Count == MAX_WAIT_HANDLES) + { + WaitHandle.WaitAll(evtList.ToArray()); + evtList.Clear(); + } + } + if (evtList.Count>0) + { + WaitHandle.WaitAll(evtList.ToArray()); + evtList.Clear(); + } + } + catch (Exception ex) + { + GXLogging.Error(log, $"WaitForAll pending threads error", ex); + } + } + } } \ No newline at end of file diff --git a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs index da4ceea02..0b7352fde 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/GXWebProcedure.cs @@ -7,6 +7,7 @@ namespace GeneXus.Procedure using GeneXus.Http; using System.Threading; using GeneXus.Mime; + using GeneXus.Utils; public class GXWebProcedure : GXHttpHandler { @@ -191,5 +192,10 @@ protected static WaitCallback PropagateCulture(WaitCallback action) { return GXProcedure.PropagateCulture(action); } + protected void Submit(Action executeMethod, object state) + { + ThreadUtil.Submit(PropagateCulture(new WaitCallback(executeMethod)), state); + } + } } \ No newline at end of file diff --git a/dotnet/src/dotnetframework/GxClasses/Model/gxproc.cs b/dotnet/src/dotnetframework/GxClasses/Model/gxproc.cs index a44702de1..adf4adb96 100644 --- a/dotnet/src/dotnetframework/GxClasses/Model/gxproc.cs +++ b/dotnet/src/dotnetframework/GxClasses/Model/gxproc.cs @@ -65,6 +65,10 @@ public bool DisconnectAtCleanup get{ return disconnectUserAtCleanup;} set{ disconnectUserAtCleanup=value;} } + protected void Submit(Action executeMethod, object state) + { + ThreadUtil.Submit(PropagateCulture(new WaitCallback(executeMethod)), state); + } public static WaitCallback PropagateCulture(WaitCallback action) { var currentCulture = Thread.CurrentThread.CurrentCulture; @@ -87,6 +91,9 @@ protected void exitApplication() } private void exitApplication(bool flushBatchCursor) { + if (IsMain && !(context as GxContext).IsSubmited) + ThreadUtil.WaitForEnd(); + if (flushBatchCursor) { foreach (IGxDataStore ds in context.DataStores) diff --git a/dotnet/test/DotNetCoreUnitTest/DotNetCoreUnitTest.csproj b/dotnet/test/DotNetCoreUnitTest/DotNetCoreUnitTest.csproj index e532e5ef2..9db4d42d9 100644 --- a/dotnet/test/DotNetCoreUnitTest/DotNetCoreUnitTest.csproj +++ b/dotnet/test/DotNetCoreUnitTest/DotNetCoreUnitTest.csproj @@ -1,4 +1,4 @@ - + net6.0 CS8032;1701;1702;NU1701 diff --git a/dotnet/test/DotNetCoreUnitTest/Submit/SubmitTest.cs b/dotnet/test/DotNetCoreUnitTest/Submit/SubmitTest.cs new file mode 100644 index 000000000..546dc43d7 --- /dev/null +++ b/dotnet/test/DotNetCoreUnitTest/Submit/SubmitTest.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using GeneXus.Application; +using GeneXus.Procedure; +using GeneXus.Utils; +using Lucene.Net.Support; +using Xunit; +using System.Collections.Concurrent; + + +namespace UnitTesting +{ + public class SubmitTest + { + internal static ConcurrentDictionary numbers = new ConcurrentDictionary(); + [Fact] + public void SubmitAll() + { + MainProc proc = new MainProc(); + proc.execute(); + } + } + public class MainProc : GXProcedure + { + const int THREAD_NUMBER = 2000; + public MainProc() + { + context = new GxContext(); + IsMain = true; + } + public void execute() + { + initialize(); + executePrivate(); + } + + void executePrivate() + { + for (int i = 0; i < THREAD_NUMBER; i++) + { + SubmitProc proc = new SubmitProc(); + proc.executeSubmit(); + } + cleanup(); + Assert.Equal(THREAD_NUMBER, SubmitTest.numbers.Count); + } + public override void cleanup() + { + ExitApp(); + } + + public override void initialize() + { + + } + } + public class SubmitProc : GXProcedure + { + public SubmitProc() + { + context = new GxContext(); + } + public void executeSubmit() + { + SubmitProc submitProc; + submitProc = new SubmitProc(); + submitProc.context.SetSubmitInitialConfig(context); + submitProc.initialize(); + Submit(executePrivateCatch, submitProc); + } + void executePrivateCatch(object stateInfo) + { + ((SubmitProc)stateInfo).executePrivate(); + } + void executePrivate() + { + int millisecondsToWait = (int)ThreadSafeRandom.NextDouble()*10; + Thread.Sleep(millisecondsToWait); + SubmitTest.numbers[Guid.NewGuid()]=Thread.CurrentThread.ManagedThreadId; + } + + public override void initialize() + { + + } + } +}