using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading.Tasks; using Sockeye.Biz; using Microsoft.Extensions.Logging; namespace Sockeye.Util { //Track processes and kill any that go past their expiry date internal static class ReportRenderManager { internal static ConcurrentBag _baginstances; static ReportRenderManager() { _baginstances = new ConcurrentBag(); } internal class ReportRenderInstanceInfo { internal int ReporterProcessId { get; set; } internal DateTime Expires { get; set; } internal Guid JobId { get; set; } internal ReportRenderInstanceInfo(Guid jobId, DateTime expires) { JobId = jobId; Expires = expires; ReporterProcessId = -1; } } internal static async Task KillExpiredRenders(ILogger log) { log.LogDebug("Clear potential expired render jobs check"); //check for expired and remove var Instances = _baginstances.ToArray(); var dtNow = DateTime.UtcNow; foreach (ReportRenderInstanceInfo i in Instances) { if (i.Expires < dtNow) { log.LogDebug($"attempting close of expired process {i.ReporterProcessId} for job {i.JobId}"); await CloseRenderProcess(i, log, true); } } } internal static async Task CloseRenderProcess(ReportRenderInstanceInfo instance, ILogger log, bool force) { if (force) log.LogDebug($"Force CloseRenderProcess on report render instance id {instance.ReporterProcessId} expiry {instance.Expires.ToString()} utc"); else log.LogDebug($"Normal CloseRenderProcess on report render instance id {instance.ReporterProcessId}"); try { //either way, clear the job so the client gets informed if (force) { if (instance.Expires < DateTime.UtcNow) { var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { timeout = true, timeoutsetting = ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT } }, Newtonsoft.Json.Formatting.None); await JobsBiz.LogJobAsync(instance.JobId, json); await JobsBiz.UpdateJobStatusAsync(instance.JobId, JobStatus.Failed); } else { var json = Newtonsoft.Json.JsonConvert.SerializeObject(new { rendererror = new { cancelled = true} }, Newtonsoft.Json.Formatting.None); await JobsBiz.LogJobAsync(instance.JobId, json); await JobsBiz.UpdateJobStatusAsync(instance.JobId, JobStatus.Completed); } } if (instance.ReporterProcessId != -1)//if a job doesn't have a process id yet it will be -1 { var p = Process.GetProcessById(instance.ReporterProcessId); if (p != null) { //we have an existing process //try to kill it p.Kill(true); if (p.HasExited == false) { log.LogWarning($"Expired report render instance id {instance.ReporterProcessId} could not be force closed"); 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 block 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 } 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; } } internal static void AddJob(Guid jobId, ILogger log) { log.LogDebug($"AddJob - {jobId} to the collection"); _baginstances.Add(new ReportRenderInstanceInfo(jobId, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT))); log.LogDebug($"AddJob - there are currently {_baginstances.Count} instances in the collection"); } internal static void SetProcess(Guid jobId, int processId, ILogger log) { log.LogDebug($"SetProcess - setting {jobId} to render process id {processId}"); foreach (var i in _baginstances) { if (i.JobId == jobId) { i.ReporterProcessId = processId; break; } } } internal static async Task RemoveJob(Guid jobId, ILogger log, bool force) { foreach (var i in _baginstances) { if (i.JobId == jobId) { await CloseRenderProcess(i, log, force); break; } } } internal static bool KeepGoing(Guid jobId) { //if job id is empty it means it was called from outside of a job (report designer get data for example) if(jobId==Guid.Empty) return true; foreach (var i in _baginstances) { if (i.JobId == jobId) { return true; } } return false; } }//eoc }//eons