// перенести вызов в основной поток для наглядности?
        private int CalcAddProcessesCount(EventKeyNames eventKeysSet, int taskPackageCount)
        {
            int balanceOfTasksAndProcesses = eventKeysSet.BalanceOfTasksAndProcesses;
            int maxProcessesCountOnServer  = eventKeysSet.MaxProcessesCountOnServer;
            int toAddProcessesCount;

            switch (balanceOfTasksAndProcesses)
            {
            // 0 - автовыбор - создаём процессов по числу задач
            case 0:
                toAddProcessesCount = taskPackageCount;
                return(toAddProcessesCount);

            // больше нуля - основной вариант - делим количество задач на эту константу и если она больше максимума, берём константу максимума
            case > 0:
                int multiplier = 10000;     // from constants
                toAddProcessesCount = (taskPackageCount * multiplier / balanceOfTasksAndProcesses) / multiplier;
                // если константа максимума неправильная - 0 или отрицательная, игнорируем ее
                if (toAddProcessesCount > maxProcessesCountOnServer && maxProcessesCountOnServer > 0)
                {
                    toAddProcessesCount = maxProcessesCountOnServer;
                }
                if (toAddProcessesCount < 1)
                {
                    toAddProcessesCount = 1;
                }
                return(toAddProcessesCount);

            // меньше нуля - тайный вариант для настройки - количество процессов равно константе (с обратным знаком, естественно)
            case < 0:
                toAddProcessesCount = balanceOfTasksAndProcesses * -1;
                _logger.LogInformation(517, "CalcAddProcessesCount calculated total {1} processes are necessary.", toAddProcessesCount);
                return(toAddProcessesCount);
            }
        }
        public async Task <int> FrontServerEmulationMain(EventKeyNames eventKeysSet) // _logger = 300
        {
            // получаем условия задач по стартовому ключу
            int tasksPackagesCount = await FrontServerFetchConditions(eventKeysSet.EventKeyFrom, eventKeysSet.EventFieldFrom);

            // начинаем цикл создания и размещения пакетов задач
            _logger.LogInformation(30010, " - Creation cycle of key EventKeyFrontGivesTask fields started with {1} steps.", tasksPackagesCount);

            for (int i = 0; i < tasksPackagesCount; i++)
            {
                // guid - главный номер задания, используемый в дальнейшем для доступа к результатам
                string taskPackageGuid = Guid.NewGuid().ToString();
                int    tasksCount      = Math.Abs(taskPackageGuid.GetHashCode()) % 10; // просто (псевдо)случайное число
                if (tasksCount < 3)
                {
                    tasksCount += 3;
                }
                // создаём пакет задач (в реальности, опять же, пакет задач положил отдельный контроллер)
                Dictionary <string, int> taskPackage = FrontServerCreateTasks(tasksCount, eventKeysSet);

                // при создании пакета сначала создаётся пакет задач в ключе, а потом этот номер создаётся в виде поля в подписном ключе

                // создаем ключ taskPackageGuid и кладем в него пакет
                // записываем ключ taskPackageGuid пакета задач в поле ключа eventKeyFrontGivesTask и в значение ключа - тоже taskPackageGuid
                // дополняем taskPackageGuid префиксом PrefixPackage
                string taskPackagePrefixGuid = $"{eventKeysSet.PrefixPackage}:{taskPackageGuid}";
                int    inPackageTaskCount    = await FrontServerSetTasks(taskPackage, eventKeysSet, taskPackagePrefixGuid);

                // можно возвращать количество созданных задач и проверять, что не нуль - но это чтобы хоть что-то проверять (или проверять наличие созданных ключей)
                // на создание ключа с пакетом задач уйдёт заметное время, поэтому промежуточный ключ оправдан (наверное)
            }
            return(tasksPackagesCount);
        }
Example #3
0
        // подписываемся на ключ сообщения о появлении свободных задач
        public void SubscribeOnEventRun(EventKeyNames eventKeysSet)
        {
            string eventKeyFrontGivesTask = eventKeysSet.EventKeyFrontGivesTask;

            _logger.LogInformation(201, "This BackServer subscribed on key {0}.", eventKeyFrontGivesTask);

            // типовая блокировка множественной подписки до специального разрешения повторной подписки
            bool flagToBlockEventRun = true;

            _keyEvents.Subscribe(eventKeyFrontGivesTask, async(string key, KeyEvent cmd) =>
            {
                if (cmd == eventKeysSet.EventCmd && flagToBlockEventRun)
                {
                    // временная защёлка, чтобы подписка выполнялась один раз
                    flagToBlockEventRun = false;
                    _logger.LogInformation(301, "Key {Key} with command {Cmd} was received, flagToBlockEventRun = {Flag}.", eventKeyFrontGivesTask, cmd, flagToBlockEventRun);

                    // вернуть изменённое значение flagEvent из FetchKeysOnEventRun для возобновления подписки
                    flagToBlockEventRun = await _captures.FetchKeysOnEventRun(eventKeysSet);

                    _logger.LogInformation(901, "END - FetchKeysOnEventRun finished and This BackServer waits the next event.");
                }
            });

            string eventKeyCommand = $"Key = {eventKeyFrontGivesTask}, Command = {eventKeysSet.EventCmd}";

            _logger.LogInformation(19205, "You subscribed on event - {EventKey}.", eventKeyCommand);
        }
Example #4
0
        public void SubscribeOnEventCheck(EventKeyNames eventKeysSet, string newlyPackageGuid)
        {
            // eventKey - tasks package guid, где взять?
            //string newlyPackageGuid = "tasks package guid, где взять?"; // надо получить в guidField или получить ключ, где можно взять?
            _logger.LogInformation(205, "This BackServer subscribed on key {0}.", newlyPackageGuid);

            // типовая блокировка множественной подписки до специального разрешения повторной подписки
            bool flagToBlockEventCheck = true;

            _keyEvents.Subscribe(newlyPackageGuid, async(string key, KeyEvent cmd) =>
            {
                if (cmd == eventKeysSet.EventCmd && flagToBlockEventCheck)
                {
                    // временная защёлка, чтобы подписка выполнялась один раз
                    flagToBlockEventCheck = false;
                    _logger.LogInformation(306, "Key {Key} with command {Cmd} was received, flagToBlockEventCheck = {Flag}.", newlyPackageGuid, cmd, flagToBlockEventCheck);

                    // вернуть изменённое значение flagEvent из FetchKeysOnEventRun для возобновления подписки
                    flagToBlockEventCheck = await _processing.CheckingAllTasksCompletion(eventKeysSet);

                    // что будет, если во время ожидания FetchKeysOnEventRun придёт новое сообщение по подписке? проверить экспериментально
                    _logger.LogInformation(906, "END - FetchKeysOnEventRun finished and This BackServer waits the next event.");
                }
            });

            string eventKeyCommand = $"Key = {newlyPackageGuid}, Command = {eventKeysSet.EventCmd}";

            _logger.LogInformation(19206, "You subscribed on event - {EventKey}.", eventKeyCommand);
        }
Example #5
0
        private async Task <string> FindFreshPackageGuidFileld(EventKeyNames eventKeysSet)
        {
            string backServerPrefixGuid = eventKeysSet.BackServerPrefixGuid;
            string newlyPackageGuid;
            // этот код до --- можно выделить в отдельный метод, посмотреть остальные применения
            IDictionary <string, int> packagesList = await _cache.GetHashedAllAsync <int>(backServerPrefixGuid);

            int packagesListCount = packagesList.Count;

            if (packagesListCount == 0)
            // тогда возвращаемся с пустыми руками
            {
                return(null);
            }

            foreach (var p in packagesList)
            {
                var(packageGuid, packageStateInit) = p;
                if (packageStateInit < 0)
                {
                    newlyPackageGuid = packageGuid;
                    return(newlyPackageGuid);
                }
                _logger.LogInformation(501, "Tasks package field {1} found with value {2}, total fileds {3}.", packageGuid, packageStateInit, packagesListCount);
            }

            _logger.LogInformation(511, "The newly created tasks package field was not found in total fileds {0}.", packagesListCount);
            return(null);
        }
        public async Task <bool> CheckingAllTasksCompletion(EventKeyNames eventKeysSet) // Main for Check
        {
            // ----------------- вы находитесь здесь


            // подписку оформить в отдельном методе, а этот вызывать оттуда
            // можно ставить блокировку на подписку и не отвлекаться на события, пока не закончена очередная проверка

            return(default);
        private async Task <int> AddProcessesToPerformingTasks(EventKeyNames eventKeysSet, int taskPackageCount)
        {
            string backServerPrefixGuid = eventKeysSet.BackServerPrefixGuid;
            string backServerGuid       = eventKeysSet.BackServerGuid;
            string prefixProcessAdd     = eventKeysSet.PrefixProcessAdd;
            string eventFieldBack       = eventKeysSet.EventFieldBack;
            string eventKeyProcessAdd   = $"{prefixProcessAdd}:{backServerGuid}"; // process:add:(this server guid)

            // вычисляем нужное количество процессов - перенести вызов в основной поток для наглядности?
            int toAddProcessesCount = CalcAddProcessesCount(eventKeysSet, taskPackageCount);

            // создаём ключ добавления процессов и в значении нужное количество процессов
            await _cache.SetHashedAsync(eventKeyProcessAdd, eventFieldBack, toAddProcessesCount); // TimeSpan.FromDays - !!!

            _logger.LogInformation(518, "This BackServer ask to start {0} processes, key = {1}, field = {2}.", toAddProcessesCount, eventKeyProcessAdd, eventFieldBack);
            return(toAddProcessesCount);
        }
        private async Task <int> CancelExistingProcesses(EventKeyNames eventKeysSet, int toCancelProcessesCount, int completionPercentage)
        {
            string backServerPrefixGuid  = eventKeysSet.BackServerPrefixGuid;
            string backServerGuid        = eventKeysSet.BackServerGuid;
            string prefixProcessCancel   = eventKeysSet.PrefixProcessCancel;
            string eventFieldBack        = eventKeysSet.EventFieldBack;
            string eventKeyProcessCancel = $"{prefixProcessCancel}:{backServerGuid}"; // process:cancel:(this server guid)

            int cancelExistingProcesses = 0;

            // создаём ключ удаления процессов и в значении нужное количество процессов
            await _cache.SetHashedAsync(eventKeyProcessCancel, eventFieldBack, toCancelProcessesCount); // TimeSpan.FromDays - !!!

            _logger.LogInformation(519, "This BackServer ask to CANCEL {0} processes, key = {1}, field = {2}.", toCancelProcessesCount, eventKeyProcessCancel, eventFieldBack);

            return(cancelExistingProcesses);
        }
Example #9
0
        // вызвать из монитора или откуда-то из сервиса?
        // точно не из монитора - там неизвестен гуид пакета
        // можно из первого места, где получаем гуид пакета
        // в мониторе подписываемся на ключ сервера и когда там появится номер пакета задач, подписываемся на него

        public void SubscribeOnEventServerGuid(EventKeyNames eventKeysSet)
        {
            string backServerPrefixGuid = eventKeysSet.BackServerPrefixGuid;

            _logger.LogInformation(19701, "This BackServer subscribed on key {0}.", backServerPrefixGuid);

            // типовая блокировка множественной подписки до специального разрешения повторной подписки
            // здесь не надо блокировать - пока что
            //bool flagToBlockEventCheck = true;

            _keyEvents.Subscribe(backServerPrefixGuid, async(string key, KeyEvent cmd) =>
            {
                if (cmd == eventKeysSet.EventCmd) // && flagToBlockEventCheck)
                {
                    // временная защёлка, чтобы подписка выполнялась один раз - нет
                    //flagToBlockEventCheck = false;
                    _logger.LogInformation(19703, "Key {Key} with command {Cmd} was received.", backServerPrefixGuid, cmd); //, flagToBlockEventCheck = {Flag} , flagToBlockEventCheck);

                    // получить ключ guidField - это не так просто, если выполняется уже не первый пакет
                    // надо как-то получить последнее созданное поле
                    // пока в значение там int в процентах и в свежесозданном поле можно ставить -1
                    // но потом там будет класс/модель и будет сложнее
                    // можно использовать разные префиксы для хранения простых процентов и модели
                    // достать все поля, проверить, что размер словаря больше нуля
                    // (пока поля не удаляем, но кто знает, что будет дальше)
                    // перебрать словарь и найти значение меньше нуля - оно должно быть одно
                    // можно искать до первого отрицательного и выйти
                    // или можно перебрать все и, если отрицательное не одно, сообщить об ошибке
                    string newlyPackageGuid = await FindFreshPackageGuidFileld(eventKeysSet);

                    // вернуть изменённое значение flagEvent из FetchKeysOnEventRun для возобновления подписки - нет
                    SubscribeOnEventCheck(eventKeysSet, newlyPackageGuid);

                    // что будет, если во время ожидания FetchKeysOnEventRun придёт новое сообщение по подписке? проверить экспериментально
                    _logger.LogInformation(19705, "END - FetchKeysOnEventRun finished and This BackServer waits the next event.");
                }
            });

            string eventKeyCommand = $"Key = {backServerPrefixGuid}, Command = {eventKeysSet.EventCmd}";

            _logger.LogInformation(19707, "You subscribed on event - {EventKey}.", eventKeyCommand);
        }
        private Dictionary <string, int> FrontServerCreateTasks(int tasksCount, EventKeyNames eventKeysSet)
        {
            Dictionary <string, int> taskPackage = new Dictionary <string, int>();

            for (int i = 0; i < tasksCount; i++)
            {
                string guid       = Guid.NewGuid().ToString();
                int    cycleCount = Math.Abs(guid.GetHashCode()) % 10;

                if (cycleCount < 3)
                {
                    cycleCount += 3;
                }

                // дополняем taskPackageGuid префиксом PrefixPackage
                string taskPackagePrefixGuid = $"{eventKeysSet.PrefixTask}:{guid}";
                taskPackage.Add(taskPackagePrefixGuid, cycleCount);
                _logger.LogInformation(30030, "Task {I} from {TasksCount} with ID {Guid} and {CycleCount} cycles was added to Dictionary.", i, tasksCount, taskPackagePrefixGuid, cycleCount);
            }
            return(taskPackage);
        }
Example #11
0
        public void SubscribeOnEventFrom(EventKeyNames eventKeysSet) // _logger = 201
        {
            // ждёт команды с консоли с количеством генерируемых пакетов
            string   eventKey = eventKeysSet.EventKeyFrom;
            KeyEvent eventCmd = eventKeysSet.EventCmd;

            _keyEvents.Subscribe(eventKey, async(string key, KeyEvent cmd) =>
            {
                if (cmd == eventCmd)
                {
                    // по получению начинает цикл создания пакетов с задачами
                    _logger.LogInformation("Key {Key} with command {Cmd} was received.", eventKey, cmd);
                    int tasksPackagesCount = await _front.FrontServerEmulationMain(eventKeysSet);
                    _logger.LogInformation("Tasks Packages created in count = {0}.", tasksPackagesCount);
                }
            });

            string eventKeyCommand = $"Key = {eventKey}, Command = {eventCmd}";

            _logger.LogInformation("You subscribed on event - {EventKey}.", eventKeyCommand);
            _logger.LogInformation("To start the front emulation please send from Redis console the following command - \n{_}{0} {1} count NN (NN - packages count).", "      ", eventCmd, eventKey);
        }
Example #12
0
        public async Task Monitor()
        {
            // концепция хищных бэк-серверов, борющихся за получение задач
            // контроллеров же в лесу (на фронте) много и желудей, то есть задач, у них тоже много
            // а несколько (много) серверов могут неспешно выполнять задачи из очереди в бэкграунде

            // собрать все константы в один класс
            //EventKeyNames eventKeysSet = InitialiseEventKeyNames();
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();

            // множественные контроллеры по каждому запросу (пользователей) создают очередь - каждый создаёт ключ, на который у back-servers подписка, в нём поле со своим номером, а в значении или имя ключа с заданием или само задание
            // дальше бэк-сервера сами разбирают задания
            // бэк после старта кладёт в ключ ___ поле со своим сгенерированным guid для учета?
            // все бэк-сервера подписаны на базовый ключ и получив сообщение по подписке, стараются взять задание - у кого получилось удалить ключ, тот и взял

            //string test = ThisBackServerGuid.GetThisBackServerGuid(); // guid from static class
            // получаем уникальный номер этого сервера, сгенерированный при старте экземпляра сервера
            //string backServerGuid = $"{eventKeysSet.PrefixBackServer}:{_guid}"; // Guid.NewGuid()
            //EventId aaa = new EventId(222, "INIT");

            string backServerGuid = _guid ?? throw new ArgumentNullException(nameof(_guid));

            eventKeysSet.BackServerGuid = backServerGuid;
            string backServerPrefixGuid = $"{eventKeysSet.PrefixBackServer}:{backServerGuid}";

            eventKeysSet.BackServerPrefixGuid = backServerPrefixGuid;

            _logger.LogInformation(101, "INIT No: {0} - guid of This Server was fetched in MonitorLoop.", backServerPrefixGuid);

            // в значение можно положить время создания сервера
            // проверить, что там за время на ключах, подумать, нужно ли разное время для разных ключей - скажем, кафе и регистрация серверов - день, пакет задач - час
            // регистрируем поле guid сервера на ключе регистрации серверов, а в значение кладём чистый гуид, без префикса
            await _cache.SetHashedAsync <string>(eventKeysSet.EventKeyBackReadiness, backServerPrefixGuid, backServerGuid, TimeSpan.FromDays(eventKeysSet.EventKeyBackReadinessTimeDays));

            // восстановить время жизни ключа регистрации сервера перед новой охотой - где и как?
            // при завершении сервера успеть удалить своё поле из ключа регистрации серверов - обработать cancellationToken

            // подписываемся на ключ сообщения о появлении свободных задач
            _subscribe.SubscribeOnEventRun(eventKeysSet);

            // слишком сложная цепочка guid
            // оставить в общем ключе задач только поле, известное контроллеру и в значении сразу положить сумму задачу в модели
            // первым полем в модели создать номер guid задачи - прямо в модели?
            // оставляем слишком много guid, но добавляем к ним префиксы, чтобы в логах было понятно, что за guid
            // key EventKeyFrontGivesTask, fields - request:guid (model property - PrefixRequest), values - package:guid (PrefixPackage)
            // key package:guid, fileds - task:guid (PrefixTask), values - models
            // key EventKeyBackReadiness, fields - back(server):guid (PrefixBackServer)
            // key EventKeyBacksTasksProceed, fields - request:guid (PrefixRequest), values - package:guid (PrefixPackage)
            // method to fetch package (returns dictionary) from request:guid

            // можно здесь (в while) ждать появления гуид пакета задач, чтобы подписаться на ход его выполнения
            // а можно подписаться на стандартный ключ появления пакета задач - общего для всех серверов, а потом проверять, что это событие на своём сервере
            // хотелось, чтобы вся подписка происходила из monitorLoop, но тут пока никак не узнать номера пакета
            // а если подписываться там, где становится известен номер, придётся перекрёстно подключать сервисы

            while (IsCancellationNotYet())
            {
                var keyStroke = Console.ReadKey();

                if (keyStroke.Key == ConsoleKey.W)
                {
                    _logger.LogInformation("ConsoleKey was received {KeyStroke}.", keyStroke.Key);
                }
            }

            _logger.LogInformation("MonitorLoop was canceled by Token.");
        }
        private async Task <int> FrontServerSetTasks(Dictionary <string, int> taskPackage, EventKeyNames eventKeysSet, string taskPackageGuid)
        {
            int inPackageTaskCount = 0;

            foreach (KeyValuePair <string, int> t in taskPackage)
            {
                (string guid, int cycleCount) = t;
                // записываем пакет задач в ключ пакета задач
                await _cache.SetHashedAsync(taskPackageGuid, guid, cycleCount, TimeSpan.FromDays(eventKeysSet.EventKeyFromTimeDays));

                inPackageTaskCount++;
                _logger.LogInformation(30050, "TaskPackage No. {0}, with Task No. {1} with {2} cycles was set.", taskPackageGuid, guid, cycleCount);
            }

            // только после того, как создан ключ с пакетом задач, можно положить этот ключ в подписной ключ eventKeyFrontGivesTask
            // записываем ключ пакета задач в ключ eventKeyFrontGivesTask, а в поле и в значение - ключ пакета задач
            string eventKeyFrontGivesTask = eventKeysSet.EventKeyFrontGivesTask;

            await _cache.SetHashedAsync(eventKeysSet.EventKeyFrontGivesTask, taskPackageGuid, taskPackageGuid, TimeSpan.FromDays(eventKeysSet.EventKeyFrontGivesTaskTimeDays));

            _logger.LogInformation(30070, " --- Key EventKeyFrontGivesTask = {0} with TaskPackage No. {1} was created.", eventKeysSet.EventKeyFrontGivesTask, taskPackageGuid);
            _logger.LogInformation(30080, " --- Key EventKeyFrontGivesTask with lifetime {0} was created.", eventKeysSet.EventKeyFrontGivesTaskTimeDays);

            bool isExistEventKeyFrontGivesTask = await _cache.KeyExistsAsync(eventKeyFrontGivesTask);

            _logger.LogInformation(30090, " Check the Key EventKeyFrontGivesTask - isExist = {0}.", isExistEventKeyFrontGivesTask);

            // сервера подписаны на ключ eventKeyFrontGivesTask и пойдут забирать задачи, на этом тут всё
            return(inPackageTaskCount);
        }
Example #14
0
        public async Task Monitor() // _logger = 100
        {
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();


            // на старте проверить наличие ключа с константами
            // в сервисе констант при старте удалять ключ и создавать новый
            // константы можно делить по полям, а можно общим классом
            // а можно и то и другое
            // если все константы классом, то получится надо везде держать модель - одинаковый код
            // на старте фронт сразу запускает два (взять из constant) бэка - чтобы были
            int serverCount = 1;
            // пока запустить руками, потом в контейнерах - вроде как не получится


            // тут можно проверить наличие минимум двух бэк-серверов
            // а можно перенести в цикл ожидания нажатия клавиши

            // здесь можно подписаться на ключ регистрации серверов и при оповещении по подписке, обновлять лист серверов и проверять, что все живые
            // ещё менять флаг разрешения задач
            // это уже функции будущего диспетчера

            IDictionary <string, string> tasksList = await _cache.GetHashedAllAsync <string>(eventKeysSet.EventKeyBackReadiness);

            int tasksListCount = tasksList.Count;

            if (tasksListCount < serverCount)
            {
                // если серверов меньше заданного минимума, сидеть здесь и ждать регистрации нужного количества
                _logger.LogInformation(1001, "Please, start {0} instances of BackgroundTasksQueue server", serverCount);
                // или если есть хотя бы один, то не ждать, а работать?
                // ждать - значит поставить флаг разрешения размещения задач в запрещено, подписка сама оповестит, что произошли изменения
            }

            // при старте, если констант вдруг нет, можно подписаться на стандартный общий ключ получения констант
            // когда константы создадутся, они оповестят по подписке, что константы уже есть и тогда по другому стандартному ключу их взять
            // например, constants:fetch - забирать, constants:await - подписка на ожидание
            // в контейнерах будет непросто разбираться, в каком порядке все должны стартовать, редис первым - уже достижение
            // ещё регистрация сервера в ключе может быть, а сам сервер уже (давно) неживой
            // поэтому в значение поля с номером сервера можно класть не тот же самый номер, а ключ для проверки живости сервера, на который тот будет подписан
            // а лучше просто чистый гуид сервера - они к нему применят разные префиксы для подписки из общих констант
            // скажем, сервер будет подписан на динг:гуид, а фронт, получив гуид сервера, подпишется на донг:гуид и сделает запись на динг
            // после этого сервер, увидев запрос, запишет в ключ донг:гуид и фронт узнает, что сервер живой и общается
            // после чего оба ключа надо сразу же удалить

            // добавить дополнительный ключ с количеством пакетов задач
            // в стартовом ключе в значении указывать задержку -
            // положительная - в секундах,
            // 0 - без задержки,
            // отрицательная - случайная задержка, но не более значения в сек

            // generate random integers from 5 to 10
            Random rand = new();

            rand.Next(5, 11);

            // сделать два сообщения в консоли - подсказки, как запустить эмулятор
            // To start tasks batch enter from Redis console the command - hset subscribeOnFrom tasks:count 30 (where 30 is tasks count - from 10 to 50)


            // тут необходимо очистить ключ EventKeyFrontGivesTask, может быть временно, для отладки
            string eventKeyFrontGivesTask = eventKeysSet.EventKeyFrontGivesTask;

            _logger.LogInformation(1005, "Key eventKeyFrontGivesTask = {0} fetched from constants", eventKeyFrontGivesTask);

            // можно не проверять наличие ключа, а сразу пробовать удалить, там внутри есть своя проверка
            bool isExistEventKeyFrontGivesTask = await _cache.KeyExistsAsync(eventKeyFrontGivesTask);

            if (isExistEventKeyFrontGivesTask)
            {
                bool isDeleteSuccess = await _cache.RemoveAsync(eventKeyFrontGivesTask);

                _logger.LogInformation(1009, "FrontServerEmulation reported - isDeleteSuccess of the key {0} is {1}.", eventKeyFrontGivesTask, isDeleteSuccess);
            }

            // новая версия, теперь это только эмулятор контроллера фронт-сервера

            // множественные контроллеры по каждому запросу (пользователей) создают очередь - каждый создаёт ключ, на который у back-servers подписка, в нём поле со своим номером, а в значении или имя ключа с заданием или само задание
            // дальше бэк-сервера сами разбирают задания
            // эмулятор сейчас выполняет старт многих пакетов (задаётся пользователем при старте), в работе от одного контроллера будет один пакет задач
            // все бэк-сервера подписаны на базовый ключ и получив сообщение по подписке, стараются взять задание - у кого получилось удалить ключ, тот и взял
            // у контроллера остаётся базовый ключ, который он вернёт пользователю и тот потом сможет контролировать ход выполнения задания
            // тогда лишняя сущность диспетчера не нужна, но если задание упадёт, восстановить его будет некому


            // второй вариант - диспетчер собирает всё задания (от множества контроллеров) и ставит у себя в очередь, потом берёт по очереди бэк-сервера и выдаёт им задания
            // named it controllers-dispatcher-queue-back-servers
            // тогда диспетчер по подписке на ключ сервера знает о ходе выполнения и если сообщения прекратятся, но ещё не 100%, сможет перезапустить задачу
            // можно усреднять время выполнения каждого этапа задания и хранить предполагаемое полное время выполнения, а по его истечению принимать какие-то меры


            // первый вариант позволяет его потом дополнить надстройкой диспетчера, которому надо будет следить только за целостностью бэк-серверов и давать команду на восстановление ключа задачи (и, возможно, удаление зависшего сервера)


            // план работы эмулятора
            // ждёт команды с консоли с количеством генерируемых пакетов
            // по получению начинает цикл создания пакетов с задачами
            // первый гуид - главный номер задания, второй - ключ доступа к заданию (или один и тот же номер)
            // при создании пакета сначала создаётся пакет задач в ключе, а потом этот номер создаётся в виде поля в подписном ключе
            // собственно, это пока всё (потом можно добавить случайную задержку между генерацией отдельных пакетов)


            _subscribe.SubscribeOnEventFrom(eventKeysSet);

            while (IsCancellationNotYet())
            {
                var keyStroke = Console.ReadKey();

                if (keyStroke.Key == ConsoleKey.W)
                {
                    _logger.LogInformation("ConsoleKey was received {KeyStroke}.", keyStroke.Key);
                }
            }
        }
        public async Task <bool> FetchKeysOnEventRun(EventKeyNames eventKeysSet) // this Main
        {
            string backServerPrefixGuid      = eventKeysSet.BackServerPrefixGuid;
            string eventKeyFrontGivesTask    = eventKeysSet.EventKeyFrontGivesTask;
            string eventKeyBacksTasksProceed = eventKeysSet.EventKeyBacksTasksProceed;

            _logger.LogInformation(401, "This BackServer {0} started FetchKeysOnEventRun.", backServerPrefixGuid);
            _logger.LogInformation(40101, "This BackServer fetched eventKeyFrontGivesTask = {0}.", eventKeyFrontGivesTask);
            _logger.LogInformation(40105, "This BackServer fetched eventKeyBacksTasksProceed = {0}.", eventKeyBacksTasksProceed);

            // начало главного цикла сразу после срабатывания подписки, условие - пока существует ключ распределения задач
            // считать пакет полей из ключа, если задач больше одной, бросить кубик
            // проверить захват задачи, если получилось - выполнять, нет - вернулись на начало главного цикла
            // выполнение - в отдельном методе, достать по ключу задачи весь пакет
            // определить, сколько надо процессов - количество задач в пакете разделить на константу, не менее одного и не более константы
            // запустить процессы в отдельном методе, сложить количество в ключ пакета
            // достать задачи из пакета и запустить их в очередь
            // следующим методом висеть и контролировать ход выполнения всех задач - подписаться на их ключи, собирать ход выполнения каждой, суммировать и складывать общий процент в ключ сервера
            // по окончанию всех задач удалить все процессы?
            // вернуться на начало главного цикла
            bool isExistEventKeyFrontGivesTask = true;

            // нет смысла проверять isDeleteSuccess, достаточно существования ключа задач - есть он, ловим задачи, нет его - возвращаемся
            while (isExistEventKeyFrontGivesTask)
            {
                // проверить существование ключа, может, все задачи давно разобрали и ключ исчез
                isExistEventKeyFrontGivesTask = await _cache.KeyExistsAsync(eventKeyFrontGivesTask);

                _logger.LogInformation(402, "isExistEventKeyFrontGivesTask = {1}.", isExistEventKeyFrontGivesTask);

                if (!isExistEventKeyFrontGivesTask)
                // если ключа нет, тогда возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке
                {
                    return(false);
                }                 // надо true

                // после сообщения подписки об обновлении ключа, достаём список свободных задач
                // список получается неполный! - оказывается, потому, что фронт не успеваем залить остальные поля, когда бэк с первым полем уже здесь
                IDictionary <string, string> tasksList = await _cache.GetHashedAllAsync <string>(eventKeyFrontGivesTask);

                int tasksListCount = tasksList.Count;
                _logger.LogInformation(403, "TasksList fetched - tasks count = {1}.", tasksListCount);

                // временный костыль - 0 - это задач в ключе не осталось - возможно, только что (перед носом) забрали последнюю
                if (tasksListCount == 0)
                // тогда возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке
                {
                    return(true);
                }

                // выбираем случайное поле пакета задач - скорее всего, первая попытка будет только с одним полем, остальные не успеют положить и будет драка, но на второй попытке уже разойдутся по разным полям
                (string tasksPackageGuidField, string tasksPackageGuidValue) = tasksList.ElementAt(DiceRoll(tasksListCount));

                // проверяем захват задачи - пробуем удалить выбранное поле ключа
                // в дальнейшем можно вместо Remove использовать RedLock
                bool isDeleteSuccess = await _cache.RemoveHashedAsync(eventKeyFrontGivesTask, tasksPackageGuidField);

                // здесь может разорваться цепочка между ключом, который известен контроллеру и ключом пакета задач
                _logger.LogInformation(411, "This BackServer reported - isDeleteSuccess = {1}.", isDeleteSuccess);

                if (isDeleteSuccess)
                {
                    _logger.LogInformation(421, "This BackServer fetched taskPackageKey {1} successfully.", tasksPackageGuidField); // победитель по жизни
                    // следующие две регистрации пока непонятно, зачем нужны - доступ к состоянию пакета задач всё равно по ключу пакета

                    // регистрируем полученную задачу на ключе выполняемых/выполненных задач
                    // поле - исходный ключ пакета (известный контроллеру, по нему он найдёт сервер, выполняющий его задание)
                    // пока что поле задачи в кафе и ключ самой задачи совпадают, поэтому контроллер может напрямую читать состояние пакета задач по известному ему ключу
                    await _cache.SetHashedAsync(eventKeyBacksTasksProceed, tasksPackageGuidField, backServerPrefixGuid, TimeSpan.FromDays(eventKeysSet.EventKeyBackServerAuxiliaryTimeDays)); // lifetime!

                    _logger.LogInformation(431, "Tasks package was registered on key {0} - \n      with source package key {1} and original package key {2}.", eventKeyBacksTasksProceed, tasksPackageGuidField, tasksPackageGuidValue);

                    // регистрируем исходный ключ и ключ пакета задач на ключе сервера - чтобы не разорвать цепочку
                    // цепочка уже не актуальна, можно этот ключ использовать для контроля состояния пакета задач
                    // для этого в дальнейшем в значение можно класть общее состояние всех задач пакета в процентах
                    // или не потом, а сейчас класть 0 - тип значения менять нельзя
                    int packageStateInit = -1;                                                                                                                                       // value in percentages, but have set special value for newly created field now
                    await _cache.SetHashedAsync(backServerPrefixGuid, tasksPackageGuidField, packageStateInit, TimeSpan.FromDays(eventKeysSet.EventKeyBackServerAuxiliaryTimeDays)); // lifetime!

                    _logger.LogInformation(441, "This BackServer registered tasks package - \n      with source package key {1} and original package key {2}.", tasksPackageGuidField, tasksPackageGuidValue);


                    // тут подписаться (SubscribeOnEventCheck) на ключ пакета задач для контроля выполнения, но будет много событий
                    // каждая задача будет записывать в этот ключ своё состояние каждый цикл - надо ли так делать?


                    // и по завершению выполнения задач хорошо бы удалить процессы
                    // нужен внутрисерверный ключ (константа), где каждый из сервисов (каждого) сервера может узнать номер сервера, на котором запущен - чтобы правильно подписаться на событие
                    // сервера одинаковые и жёлуди у них тоже одинаковые, разница только в номере, который сервер генерирует при своём старте
                    // вот этот номер нужен сервисам, чтобы подписаться на события своего сервера, а не соседнего

                    // складываем задачи во внутреннюю очередь сервера
                    // tasksPakageGuidValue больше не нужно передавать, вместо нее tasksPakageGuidField
                    int taskPackageCount = await TasksFromKeysToQueue(tasksPackageGuidField, tasksPackageGuidValue, backServerPrefixGuid);

                    // здесь подходящее место, чтобы определить количество процессов, выполняющих задачи из пакета - в зависимости от количества задач, но не более максимума из константы
                    // PrefixProcessAdd - префикс ключа (+ backServerGuid) управления добавлением процессов
                    // PrefixProcessCancel - префикс ключа (+ backServerGuid) управления удалением процессов
                    // в значение положить требуемое количество процессов
                    // имя поля должно быть общим для считывания значения
                    // PrefixProcessCount -
                    // не забыть обнулить (или удалить) ключ после считывания и добавления процессов - можно и не удалять, всё равно, пока его не перепишут, он больше никого не интересует
                    // можно в качестве поля использовать гуид пакета задач, но, наверное, это лишние сложности, всё равно процессы общие
                    int addProcessesCount = await AddProcessesToPerformingTasks(eventKeysSet, taskPackageCount);

                    // тут ждать, пока не будут посчитаны всё задачи пакета
                    int completionPercentage = 1; // await CheckingAllTasksCompletion(tasksPakageGuidField);
                    // если проценты не сто, то какая-то задача осталась невыполненной, надо сообщить на подписку диспетчеру (потом)
                    int hundredPercents = 100;    // from constants
                    if (completionPercentage < hundredPercents)
                    {
                        await _cache.SetHashedAsync("dispatcherSubscribe:thisServerGuid", "thisTasksPackageKey", completionPercentage); // TimeSpan.FromDays - !!! как-то так
                    }
                    // тут удалить все процессы (потом)
                    int cancelExistingProcesses = await CancelExistingProcesses(eventKeysSet, addProcessesCount, completionPercentage);

                    // выйти из цикла можем только когда не останется задач в ключе кафе
                }
            }

            // пока что сюда никак попасть не может, но надо предусмотреть, что все задачи исчерпались, а никого не поймали
            // скажем, ключ вообще исчез и ловить больше нечего
            // теперь сюда попадём, если ключ eventKeyFrontGivesTask исчез и задачу не захватить
            // надо сделать возврат в исходное состояние ожидания вброса ключа
            // побочный эффект - можно смело брать последнюю задачу и не опасаться, что ключ eventKeyFrontGivesTask исчезнет
            _logger.LogInformation(481, "This BackServer cannot catch the task.");

            // возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке
            _logger.LogInformation(491, "This BackServer goes over to the subscribe event awaiting.");
            // восстанавливаем условие разрешения обработки подписки
            return(false); // надо true
        }
Example #16
0
        private async Task BackgroundProcessing(CancellationToken stoppingToken)
        {
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();

            //string backServerGuid = _guid ?? throw new ArgumentNullException(nameof(_guid));
            //eventKeysSet.BackServerGuid = backServerGuid;
            //string backServerPrefixGuid = $"{eventKeysSet.PrefixBackServer}:{backServerGuid}";
            //eventKeysSet.BackServerPrefixGuid = backServerPrefixGuid;

            //string eventKey = "task:add";
            string cancelKey             = "task:del";
            int    createdProcessesCount = 0;
            string backServerGuid        = $"{eventKeysSet.PrefixBackServer}:{_guid}"; // backserver:(this server guid)

            _logger.LogInformation(1101, "INIT No: {0} - guid of This Server was fetched in QueuedHostedService.", backServerGuid);
            // создать ключ для подписки из констант
            string prefixProcessAdd   = eventKeysSet.PrefixProcessAdd; // process:add
            string eventKeyProcessAdd = $"{prefixProcessAdd}:{_guid}"; // process:add:(this server guid)
            // поле-пустышка, но одинаковое с тем, что создаётся в основном методе - чтобы достать значение
            string eventFieldBack = eventKeysSet.EventFieldBack;

            _logger.LogInformation(1103, "Processes creation on This Server was subscribed on key {0} / field {1}.", eventKeyProcessAdd, eventFieldBack);
            // подписка на ключ добавления бэкграунд процессов(поле без разницы), в значении можно было бы ставить количество необходимых процессов
            // типовая блокировка множественной подписки до специального разрешения повторной подписки
            bool flagToBlockEventAdd = true;

            _keyEvents.Subscribe(eventKeyProcessAdd, async(string key, KeyEvent cmd) =>
            {
                if (cmd == KeyEvent.HashSet && flagToBlockEventAdd)
                {
                    // временная защёлка, чтобы подписка выполнялась один раз
                    flagToBlockEventAdd = false;
                    _logger.LogInformation(1111, "Received key {0} with command {1}", eventKeyProcessAdd, cmd);
                    // название поля тоже можно создать здесь и передать в метод
                    // ещё лучше - достать нужное значение заранее и передать только его, тогда метод будет синхронный (наверное)
                    // не лучше
                    // лучше
                    int requiredProcessesCount = await _cache.GetHashedAsync <int>(eventKeyProcessAdd, eventFieldBack);
                    if (requiredProcessesCount > 0)
                    {
                        createdProcessesCount = await AddProcessesToPerformingTasks(stoppingToken, requiredProcessesCount);
                        _logger.LogInformation(1131, "AddProcessesToPerformingTasks created processes count {0}", createdProcessesCount);

                        if (createdProcessesCount > 0)
                        {
                            flagToBlockEventAdd = true;
                        }
                    }
                    // если вызвали с неправильным значением в ключе, подписка навсегда останется заблокированной, где-то тут ее надо разблокировать
                }
            });

            string eventKeyCommand = $"Key {eventKeyProcessAdd}, HashSet command";

            _logger.LogInformation(1311, "You subscribed on event - {EventKey}.", eventKeyCommand);

            _keyEvents.Subscribe(cancelKey, (string key, KeyEvent cmd) =>
            {
                if (cmd == KeyEvent.HashSet)
                {
                    _logger.LogInformation("key {0} - command {1}", key, cmd);
                    if (createdProcessesCount > 0)
                    {
                        // останавливаем процесс
                        var cts = completingTasksProcesses[createdProcessesCount - 1].CancellationTaskToken;
                        cts.Cancel();

                        completingTasksProcesses.RemoveAt(createdProcessesCount - 1);
                        createdProcessesCount--;
                        _logger.LogInformation("One Task for Background Processes was removed, total count left {Count}", createdProcessesCount);
                    }
                    else
                    {
                        _logger.LogInformation("Task for Background Processes cannot be removed for some reason, total count is {Count}", createdProcessesCount);
                    }
                }
            });

            List <Task> processingTask = completingTasksProcesses.Select(t => t.ProcessingTask).ToList();

            await Task.WhenAll(processingTask);

            _logger.LogInformation("All Background Processes were finished, total count was {Count}", processingTask.Count);
        }