using EgwCoreLib.Lux.Core.Generic; using EgwCoreLib.Lux.Core.RestPayload; using EgwMultiEngineManager.Data; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System; using System.Collections.Generic; using System.Diagnostics; using static Egw.Window.Data.Enums; namespace EgwCoreLib.Lux.Data.Services { public class ProdService : BaseServ { #region Public Constructors public ProdService(IConfiguration configuration, IConnectionMultiplexer RedisConn, IRedisService redisService) : base(configuration, RedisConn) { // conf redis service _redisService = redisService; _db = redisConn.GetDatabase(); chPub = _config.GetValue("ServerConf:ChannelPub") ?? ""; Log.Info($"ProdService | Init OK"); } private readonly IDatabase _db; #endregion Public Constructors #region Public Enums public enum QueueType { waiting, running, done } #endregion Public Enums #region Public Methods /// /// Accoda una richiesta di calcolo /// /// /// /// /// public async Task EnqueueRequest(string reqType, string reqUid, CalcRequestDTO currRequest) { bool done = false; int nId = 1; // salvo su cache x successivo reinvio da currRequest QuestionDTO calcRequest = new QuestionDTO(nId, currRequest.EnvType, currRequest.DictExec); // salvo in cache contenuto della richiesta x UID string currKey = $"{redisBaseKey}:{currRequest.EnvType}:{reqUid.Replace("/", ":")}"; done = await _redisService.SetAsync(currKey, calcRequest.sProcessArgs); // invio ed accodo! var qWaitKey = qKey(currRequest.EnvType, QueueType.waiting); _redisService.QueuePush(qWaitKey, (RedisValue)reqUid); // dizionario richieste: è il serializzato dell'elenco degli UID da calcolare... List currList = await _redisService.QueueListAllAsync(qWaitKey); Dictionary calcDict = new Dictionary(); calcDict.Add("ReqLen", $"{calcDict.Count}"); string listReq = JsonConvert.SerializeObject(currList); calcDict.Add("ReqList", listReq); // preparo richiesta di calcolo x UID da inviare QuestionDTO chRequest = new QuestionDTO(nId, currRequest.EnvType, calcDict); // invio sul channel redis della richiesta di processing await _redisService.PublishAsync(chPub, chRequest.sProcessArgs); // ritorno return done; } /// /// Restituzione singolo Job specifico dalla coda ORDINI /// /// environment richiesto /// /// public async Task GetJob(Constants.EXECENVIRONMENTS cEnv, string id) { Stopwatch sw = new Stopwatch(); sw.Start(); string result = ""; // recupero richiesta serializzata string currKey = $"{redisBaseKey}:{cEnv}:{id.Replace("/", ":")}"; var rawRes = await _redisService.GetAsync(currKey); if (!string.IsNullOrEmpty(rawRes)) { result = rawRes; } sw.Stop(); Log.Info($"GetJob | {id} | {sw.Elapsed.TotalMilliseconds:N3} ms"); return result; } /// /// Restituisce il contenuto della prox richiesta da eseguire x Environment /// /// environment richiesto /// tipo coda (default waiting) /// public async Task GetNext(Constants.EXECENVIRONMENTS cEnv, QueueType qType = QueueType.waiting) { Stopwatch sw = new Stopwatch(); sw.Start(); string result = ""; RedisKey rKey = qKey(cEnv, qType); // prendo dalla coda primo job (rimuovendolo...) var rawReq = await _redisService.QueuePopAsync(rKey); if (rawReq.HasValue) { string reqUid = $"{rawReq}"; // metto UID in coda running nella successiva.. se non ultima... if (qType < QueueType.running) { // FixMe ToDo !!!: salvataggio data-ora per indicare avvio calcolo... _redisService.QueuePush(qKey(cEnv, qType + 1), (RedisValue)reqUid); } // recupero richiesta serializzata string currKey = $"{redisBaseKey}:{cEnv}:{reqUid.Replace("/", ":")}"; var rawRes = await _redisService.GetAsync(currKey); if (!string.IsNullOrEmpty(rawRes)) { result = rawRes; } } sw.Stop(); Log.Info($"GetNext | {rKey} | {sw.Elapsed.TotalMilliseconds:N3} ms"); return result; } /// /// Verifica un job in una coda, se trovato lo "avanza" a fatto (per estimation/balance, ...) /// /// environment richiesto /// chiave da cercare x avanzarla /// coda di default a run --> done /// public async Task SetDone(Constants.EXECENVIRONMENTS cEnv, string codJob, QueueType qType = QueueType.running) { Stopwatch sw = new Stopwatch(); sw.Start(); bool fatto = false; // recupero lista completa SENZA eliminare... var listRunning = await _redisService.QueueListAllAsync(qKey(cEnv, QueueType.running)); if (listRunning != null && listRunning.Contains(codJob)) { var numRem = await _redisService.QueueRemoveAsync(qKey(cEnv, QueueType.running), (RedisValue)codJob); if (numRem > 0) { // elimino eventuali duplicati sulla coda di destinazione var listDone = await _redisService.QueueListAllAsync(qKey(cEnv, QueueType.done)); if (listDone != null && listDone.Contains(codJob)) { await _redisService.QueueRemoveAsync(qKey(cEnv, QueueType.done), (RedisValue)codJob); } // accodo nuovi! var numAdd = _redisService.QueuePush(qKey(cEnv, QueueType.done), codJob); fatto = numAdd > 0; } } sw.Stop(); Log.Info($"SetDone | env: {cEnv} | qFrom: {qKey(cEnv, QueueType.running)} | reqUid: {codJob} | fatto: {fatto} | {sw.Elapsed.TotalMilliseconds:N3} ms"); return fatto; } /// /// Numero di Job in attesa su coda x environment e tipo /// /// environment richiesto /// tipo coda (default waiting) /// public async Task QueueLen(Constants.EXECENVIRONMENTS cEnv, QueueType qType = QueueType.waiting) { Stopwatch sw = new Stopwatch(); sw.Start(); RedisKey rKey = qKey(cEnv, qType); long numWaiting = await _redisService.QueueCountAsync(rKey); sw.Stop(); Log.Info($"QueueLen | {rKey} | {sw.Elapsed.TotalMilliseconds:N3} ms"); return numWaiting; } /// /// Restituisce elenco corrente delle richieste x environment e tipo /// /// environment richiesto /// tipo coda /// public async Task> QueueListAllAsync(Constants.EXECENVIRONMENTS cEnv, QueueType qType) { RedisKey rKey = qKey(cEnv, qType); List currList = await _redisService.QueueListAllAsync(rKey); return currList; } /// /// Reset coda del tipo richiesto... /// /// environment richiesto /// tipo coda /// public bool QueueReset(Constants.EXECENVIRONMENTS cEnv, QueueType qType) { RedisKey rKey = qKey(cEnv, qType); bool answ = _redisService.QueueReset(rKey); return answ; } /// /// Reset coda del tipo richiestoversione Async... /// /// environment richiesto /// tipo coda /// public async Task QueueResetAsync(Constants.EXECENVIRONMENTS cEnv, QueueType qType) { RedisKey rKey = qKey(cEnv, qType); bool answ = await _redisService.QueueResetAsync(rKey); return answ; } /// /// Reset coda running (riaccoda job su waiting)... /// /// environment richiesto /// public async Task ResetRunning(Constants.EXECENVIRONMENTS cEnv) { bool fatto = false; RedisKey rKey = qKey(cEnv, QueueType.running); var listRunning = await _redisService.QueuePopAllAsync(rKey); if (listRunning != null) { foreach (var item in listRunning) { if (item.HasValue) { // FixMe ToDo !!!: salvataggio data-ora per indicare avvio calcolo... _redisService.QueuePush(qKey(cEnv, QueueType.waiting), item); } } } return fatto; } /// /// Rimette un run un singolo job eliminando da coda Run e mettendo in coda waiting /// /// environment richiesto /// cod Job da resettare /// public async Task ReRunJob(Constants.EXECENVIRONMENTS cEnv, string codJob) { bool fatto = false; RedisKey rKey = qKey(cEnv, QueueType.running); // recupero lista completa SENZA eliminare... var listRunning = await _redisService.QueueListAllAsync(rKey); // se non trova usa coda Done... if (listRunning == null || !listRunning.Contains(codJob)) { rKey = qKey(cEnv, QueueType.done); // recupero lista completa SENZA eliminare... listRunning = await _redisService.QueueListAllAsync(rKey); } if (listRunning != null && listRunning.Contains(codJob)) { var numRem = await _redisService.QueueRemoveAsync(rKey, (RedisValue)codJob); if (numRem > 0) { var numAdd = _redisService.QueuePush(qKey(cEnv, QueueType.waiting), codJob); fatto = numAdd > 0; } } return fatto; } /// /// Richiesta di esecuzione di un job di tipo Balance /// /// /// /// public async Task BalanceJob(string codJob, BalanceReqDto balData) { bool fatto = false; #if false RedisKey rKey = qKey(QueueType.running); // recupero lista completa SENZA eliminare... var listRunning = await _redisService.QueueListAllAsync(rKey); if (listRunning != null && listRunning.Contains(codJob)) { var numRem = await _redisService.QueueRemoveAsync(rKey, (RedisValue)codJob); if (numRem > 0) { var numAdd = _redisService.QueuePush(qKey(QueueType.waiting), codJob); fatto = numAdd > 0; } } #endif return fatto; } #endregion Public Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IRedisService _redisService; private readonly string chPub = ""; private string redisBaseKey = "Lux:Prod"; #endregion Private Fields #region Private Methods /// /// Calcolo coda dato Environment e tipo coda /// /// /// /// private string qKey(Constants.EXECENVIRONMENTS cEnvir, QueueType qType) { RedisKey rKey = (RedisKey)$"{redisBaseKey}:{cEnvir}:CalcQueue"; switch (qType) { case QueueType.waiting: rKey = (RedisKey)$"{redisBaseKey}:{cEnvir}:CalcQueue:Wait"; break; case QueueType.running: rKey = (RedisKey)$"{redisBaseKey}:{cEnvir}:CalcQueue:Run"; break; case QueueType.done: rKey = (RedisKey)$"{redisBaseKey}:{cEnvir}:CalcQueue:Done"; break; default: //rKey = (RedisKey)$"{redisBaseKey}:{cEnvir}:CalcQueue"; break; } return rKey; } /// /// Esegue avanzamento nella coda logica tra wait/run/done se possibile... /// /// Chiave hash del contenuto richiesta RUID /// public async Task UpdateQueue(Constants.EXECENVIRONMENTS cEnvir, string hashKey) { // recupero info accessorie da redis var tipo = await _db.HashGetAsync(hashKey, "tipo"); // verifico SE si tratta di un create/estimate e nel caso processo coda req x sistemare if (!tipo.IsNull && !string.IsNullOrEmpty(tipo)) { string rKey = ""; var UID = await _db.HashGetAsync(hashKey, "UID"); var parti = ((string)tipo).Split('-'); if (parti.Length == 2) { if (parti[0] == "ORDER") { if (parti.Length == 2) { rKey = $"{parti[0]}:{parti[1]}:{UID}"; } } // cerco solo tipo create / 5... else if (parti[0] == "5") { if (int.TryParse(parti[0], out int iMode) && int.TryParse(parti[1], out int iSubMode)) { // Validazione e Cast in un colpo solo bool modeValido = Enum.IsDefined(typeof(QuestionModes), iMode); bool subModeValido = Enum.IsDefined(typeof(QuestionOrderSubModes), iSubMode); if (modeValido && subModeValido) { var cMode = (QuestionModes)iMode; var cSubMode = (QuestionOrderSubModes)iSubMode; rKey = $"{cMode}:{cSubMode}:{UID}"; } } } if (!string.IsNullOrEmpty(rKey)) { await SetDone(cEnvir, rKey, QueueType.running); } } } } #endregion Private Methods } }