This commit is contained in:
2021-10-27 23:18:59 +00:00
parent bc802e2956
commit a900abf261
4 changed files with 66 additions and 31 deletions

2
.vscode/launch.json vendored
View File

@@ -50,7 +50,7 @@
"AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=raven;Database=AyaNova;CommandTimeout=120;", "AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=raven;Database=AyaNova;CommandTimeout=120;",
//"AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=abraxis;Database=AyaNova;CommandTimeout=120;", //"AYANOVA_DB_CONNECTION": "Server=localhost;Username=postgres;Password=abraxis;Database=AyaNova;CommandTimeout=120;",
"AYANOVA_USE_URLS": "http://*:7575;", "AYANOVA_USE_URLS": "http://*:7575;",
"AYANOVA_REPORT_RENDERING_TIMEOUT": "5000", "AYANOVA_REPORT_RENDERING_TIMEOUT": "2000",
"AYANOVA_REPORT_RENDERING_MAX_INSTANCES": "2", "AYANOVA_REPORT_RENDERING_MAX_INSTANCES": "2",
"AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles", "AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles",
"AYANOVA_FOLDER_BACKUP_FILES": "c:\\temp\\RavenTestData\\backupfiles", "AYANOVA_FOLDER_BACKUP_FILES": "c:\\temp\\RavenTestData\\backupfiles",

View File

@@ -200,7 +200,7 @@ namespace AyaNova.Api.Controllers
//Note that this *should* normally return a 503 however we're pretty tightly wired into that meaning the server is closed at the client end which //Note that this *should* normally return a 503 however we're pretty tightly wired into that meaning the server is closed at the client end which
//handles it at a lower level //handles it at a lower level
//returning an OK method here allows the client to handle it at the level of the report dialog rather than the api handler which will short circuit if it was a 503 //returning an OK method here allows the client to handle it at the level of the report dialog rather than the api handler which will short circuit if it was a 503
if (!Util.ReportRenderManager.RenderSlotAvailable()) if (!Util.ReportRenderManager.RenderSlotAvailable(log))
return Ok(ApiOkResponse.Response(new { busy = true, retryafter = DateTime.UtcNow.AddMilliseconds(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT) })); return Ok(ApiOkResponse.Response(new { busy = true, retryafter = DateTime.UtcNow.AddMilliseconds(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT) }));
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext); ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);

View File

@@ -434,7 +434,7 @@ namespace AyaNova.Biz
log.LogDebug("Initializing report system"); log.LogDebug("Initializing report system");
var ReportJSFolderPath = Path.Combine(ServerBootConfig.AYANOVA_CONTENT_ROOT_PATH, "resource", "rpt"); var ReportJSFolderPath = Path.Combine(ServerBootConfig.AYANOVA_CONTENT_ROOT_PATH, "resource", "rpt");
var lo = new LaunchOptions { Headless = true }; var lo = new LaunchOptions { Headless = true };
bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
if (!isWindows) if (!isWindows)
{ {
@@ -489,7 +489,7 @@ namespace AyaNova.Biz
using (var browser = await Puppeteer.LaunchAsync(lo)) using (var browser = await Puppeteer.LaunchAsync(lo))
using (var page = await browser.NewPageAsync()) using (var page = await browser.NewPageAsync())
{ {
//mark this process so it can be cancelled if it times out //track this process so it can be cancelled if it times out
ReportRenderManager.AddProcess(browser.Process.Id); ReportRenderManager.AddProcess(browser.Process.Id);
try try
@@ -732,6 +732,7 @@ namespace AyaNova.Biz
{ {
log.LogDebug($"Closing browser"); log.LogDebug($"Closing browser");
await browser.CloseAsync(); await browser.CloseAsync();
ReportRenderManager.RemoveProcess(browser.Process.Id, log);
} }
} }
} }

View File

@@ -44,55 +44,77 @@ namespace AyaNova.Util
public class ReportRenderInstanceInfo public class ReportRenderInstanceInfo
{ {
public int ReporterProcessId { get; set; } public int ReporterProcessId { get; set; }
public DateTime Started { get; set; } public DateTime Expires { get; set; }
public ReportRenderInstanceInfo(int processId) public ReportRenderInstanceInfo(int processId)
{ {
ReporterProcessId = processId; ReporterProcessId = processId;
Started = DateTime.UtcNow; Expires = DateTime.UtcNow.AddMilliseconds(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT);
} }
} }
public static bool RenderSlotAvailable() public static bool RenderSlotAvailable(ILogger log)
{ {
if (_baginstances.Count >= ServerBootConfig.AYANOVA_REPORT_RENDERING_MAX_INSTANCES) log.LogTrace("RenderSlotAvailable check");
var count = _baginstances.Count;
#if (DEBUG)
log.LogInformation($"DBG: RenderSlotAvailable check, there are currently {count} instances in the bag");
#endif
if (count >= ServerBootConfig.AYANOVA_REPORT_RENDERING_MAX_INSTANCES)
{ {
log.LogTrace($"RenderSlotAvailable there are no free report rendering slots available, current count is {count}, checking for expired slots to force closed");
//check for expired and remove //check for expired and remove
var Instances = _baginstances.ToArray(); var Instances = _baginstances.ToArray();
ReportRenderInstanceInfo oldest = null; var dtNow = DateTime.UtcNow;
foreach (ReportRenderInstanceInfo i in Instances) foreach (ReportRenderInstanceInfo i in Instances)
{ {
if (oldest == null) if (i.Expires < dtNow)
{ {
oldest = i; #if (DEBUG)
continue; log.LogInformation($"DBG: RenderSlotAvailable attempting kill of expired process {i.ReporterProcessId}");
#endif
ForceCloseProcess(i, log);
} }
if (i.Started < oldest.Started) oldest = i;
} }
if (oldest != null) }
{ //allow to continue if there are now fewer than max instances in the bag
try return _baginstances.Count < ServerBootConfig.AYANOVA_REPORT_RENDERING_MAX_INSTANCES;
{ }
var p = Process.GetProcessById(oldest.ReporterProcessId);
if (p != null)
{
//try to kill it
p.Kill();
if (p?.HasExited == false) return false;//can't kill it so can't free up a slot
}
_baginstances.TryTake(out oldest);
return true;//process that was there is now not there so while not perfect system we will consider it free
} private static bool ForceCloseProcess(ReportRenderInstanceInfo instance, ILogger log)
catch (ArgumentException) {
log.LogTrace($"ForceCloseProcess on instance id {instance.ReporterProcessId} started {instance.Expires.ToString()} utc");
try
{
var p = Process.GetProcessById(instance.ReporterProcessId);
if (p != null)
{
//we have an existing process
//try to kill it
p.Kill();
if (p.HasExited == false)
{ {
return true;//no process available / not running
log.LogDebug($"RenderSlotAvailable oldest slot could not be stopped");
return false;//can't kill it so can't free up a slot
} }
} }
//remove it from the list, it's either gone or killed at this point
//this would not be unexpected since it will normally just close on it's own
//at the finally in render report
_baginstances.TryTake(out instance);
return true;//process that was there is now not there so while not perfect system we will consider it free
} }
return true; catch (ArgumentException)
{
//do nothing, this is normal, the process could not be found and this means it's already been removed:
//ArgumentException
//The process specified by the processId parameter is not running. The identifier might be expired.
_baginstances.TryTake(out instance);
return true;
}
} }
@@ -101,6 +123,18 @@ namespace AyaNova.Util
_baginstances.Add(new ReportRenderInstanceInfo(processId)); _baginstances.Add(new ReportRenderInstanceInfo(processId));
} }
internal static void RemoveProcess(int processId, ILogger log)
{
foreach (var i in _baginstances)
{
if (i.ReporterProcessId == processId)
{
ForceCloseProcess(i, log);
break;
}
}
}
// internal async static Task EnsureReporterAvailableAsync(ILogger log) // internal async static Task EnsureReporterAvailableAsync(ILogger log)