Разработки          Услуги          О компании          Контакты  

Материал из биософт-м

Перейти к: навигация, поиск
  !   Данная информация предназначена только только для IT-специалистов по системной интеграции модулей БИОСОФТ-М. (см. Руководства пользователя к программным продуктам)


Здесь описаны технические детали применения nav.gif ParaService для параллельных задач

Концепция

Предположим, что возникла задача, выполнение которой надо распараллелить.

Как поступают те, кому наплевать на надежность продукта в этой ситуации? Они запускают триды в приложении и тонет в фатальном крушении нормальной логики.

Что мы делаем? Юзаем служебный параллельный процесс связи которого с основным приложением автоматизированы механизмом ParaService. И затем наслаждаться отсутствием взаимных блокировок, коррупций глобальных и мемберных данных, фаталов в нереентерабельных библиотеках и необходимости мучительно продумывать все возможные миллионы взаимно перепутанных последовательностей параллельного выполнения операций в методах одного и того же объекта.

ParaService это процесс, который своим поведением максимально эмулирует типовое применение трида.

Он не является самостоятельным приложением в системе а функционирует в подчиненной роли, выполняя фоновые задачи для запустившего его процесса. Он терминируется когда главное приложение перестает нуждаться в данном сервисе.

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

Islib-ParaService это обычный процесс с точки зрения ОС и не имеет никакого отношения к Windows-сервисам.

nav.gif ParaService для параллельных задач

Общие принципы применения

Приложение захотело запустить задачу в параллельном сервисе.

Оно формулирует параметры задачи, заполняет их в проперти коммуникационного объекта и посылает (Post) пакет сервису.

Сервис в этот момент может быть уже запущен данным или другим приложением тогда он начнет отрабатывать задачу сразу.

   [ App ]---Args--->[ Post ]---Args--->[ ParaService Process ]

Если же парасервис не запущен, то он будет запущен библиотекой автоматически. И получит посланные ему параметры когда загрузится и будет готов к работе.

   [ App ]---Args--->[ Post ]---Run---> MyParaService.exe
                     ...
                     [ Post ]---Args--->[ ParaService Process ]

Каждый сервис идентифицируется уникальным строковым идентификатором и в один момент времени в системе может быть только один запущенный парасервис с определенным ID.

Если несколько приложений посылают параметры парасервису с одним и тем же ID, то новых сервисов запускаться не будет, будет использоваться процесс, запущенный первым приложением при самом первом посте. Однако этот процесс получит посты и от всех остальных приложений.

   [ AppA ]---ID_1---
                     |
   [ AppB ]---ID_1---+--->[ ParaService(ID_1) ]
                     |
   [ AppC ]---ID_1---
         |
         '----ID_2------->[ ParaService(ID_2) ]

Но ничто в принципе не мешает (кроме ограничений на ресурсы ЭВМ!) запускать множество сервисов одного типа но в разных процессах выполняющие задачи независимо для разных клиентов. Такие сервисы могут быть запущены из одного и того же экзешника и использовать одни и те же коммуникационные классы, но будут использовать разные ID.

                      --- Multiservice.exe ----------------------------
                     |                                                 |
   [ App ]---ID_1--------> Handler for ParaService(ID_1) in process 1  |
                     |                                                 |
   [ App ]---ID_2--------> Handler for ParaService(ID_2) in process 2  |
                     |                                                 |
                      -------------------------------------------------

Ниже будут подробно расписаны все интерфейсы, правила и процедуры. Для разделения зон ответственности между приложением и системными библиотеками кратко перечислим функции тех и других:

Обязанности приложения в основном процессе вызывающем парасервис

  • Заполнить параметры задачи для параллельного процесса
  • Указать идентификатор парасервиса
  • Указать путь к экзешнику реализующему парасервис с данным идентификатором
  • Не использовать параметров параллельному процессу передаваемых в командной строке
  • Послать эти параметры через интерфейс RunParaServicePost()
  • Посылать обновления параметров когда задача для парасервиса меняется
  • Понимать, что параллельный процесс может рестартовать в любой момент неожиданно и не может помнить предыдущих посылок а только самый последний пересланный ему пост
  • Хранить объект с параметрами (унаследованный от CInterprocPostIfaceGp) постоянно и уничтожить его когда парасервис станет более не нужен
  • Предусмотреть альтернативный не параллельный режим (хотя бы для отладки) в котором вместо запуска парасервиса все операции выполняются в основном процессе
  • Аккуратно инкапсулировать коммуникацию с парасервисом в отдельном пакете

Обязанности приложения в параллельном процессе парасервиса

  • Создать и хранить объект с хандлером onpost
  • Инициализировать хандлер используя TryOpenParaServicePostOffice()
  • Если модуль обслуживает несколько классов парасервисов, то попытаться инициализировать их по очереди и обслуживать первый удачно инициализировавшийся
  • Принимать посты в соответствующем хандлере и запускать выполнение задачи согласно данным полученным от главного приложения
  • Не терминировать себя без надобности, полагаться на автоматическое завершение процесса
  • Не использовать UniConfig а только данные присланные от главного процесса
  • Вместо скрытого содержимого главного окна предусмотреть опциональное отладочное UI, которое можно открыть в любой момент, показывающее чем занимается параллельный процесс

Обязанности системных библиотек

  • Автоматически запускать процесс при посылке ему параметров через RunParaServicePost()
  • Не запускать лишних процессов
  • Гарантировать получение процессом параметров и когда они посланы ДО загрузки процесса и когда посылается их обновление в процессе работы
  • Терминировать параллельный процесс когда он более не требуется клиентским приложениям
  • Обеспечивать терминацию и рестарт при зависаниях параллельного процесса
  • Блокировать файловые операции для UniConfig в параллельном процессе

Запуск ParaService и передача ему параметров

Знакомый интерфейс Interproc-Post

Запуск, отслеживание жизненного цикла процесса и начальные указания парасервису реализованы как расширение интерфейса nav.gif Безопасная посылка данных процессам через Interproc-Post

Нужно изучить его сначала а потом уже вникать в особенности его использования для запуска сервисов.

Отличия Interproc-Post при применении его с ParaService

Как и в обычной межпроцессной коммуникации посылка представлена объектом, унаследованным от CInterprocPostIfaceGp. Но отправка сообщения парасервису реализуется не обычным методом PostBroadcast() а особым RunParaServicePost(). Такая посылка влечет за собой следующие отличия:

1) Данные поста это не последовательные команды а обновление состояния.

2) Посылка последнего поста будет повторена автоматически при рестарте сервиса.

3) Посылка не запущенному сервису будет отложена и выполнена только после его старта.

4) Если делается посылка не запущенному сервису то он автоматически запускается.

5) Объект посылки парасервису (CInterprocPostIfaceGp) должен хранится приложением так как его уничтожение терминирует процесс сервиса (это в отличие от создания временного объекта для обычного поста).

6) Все посылки одному и тому же сервису должны делаться через один и тот же хранимый приложением объект (CInterprocPostIfaceGp).

Посылка обновляет состояние а не дает команду

Это концептуальный момент который надо держать в голове проектируя парасервис а не техническая часть интерфейса.

Важное отличие применения nav.gif Безопасная посылка данных процессам через Interproc-Post к парасервисам в том, что отдельные посылки не ставятся в очередь в качестве команд которые должен отработать параллельный процесс.

Вместо этого каждая посылка определяет ТЕКУЩЕЕ СОСТОЯНИЕ в котором должен находится парасервис. В начальный момент эти данные можно рассматривать как расширенную "командную строку". Затем клиенты могут обновлять содержимое такой "командной строки" посылая измененные посты.

Такая необычная интерпретация постов следует из того, что сервис должен иметь возможность рестартовать в ЛЮБОЙ (!) момент работы и при (ре)старте получить от клиентов всю информацию о том чем он должен заниматься.

Вспоминаем как посты через nav.gif Interproc-Post приложение посылает само и они отправляются однократно и забываются.

В отличие от этого посты парасервисам ПЕРЕПОСЫЛАЮТСЯ АВТОМАТИЧЕСКИ ПРИ СТАРТЕ/реСТАРТЕ ПАРА-ПРОЦЕССА.

Это критический для понимания момент в коммуникации из которого следует все остальное!

Обычный сценарий применения nav.gif Interproc-Post подразумевает, что посты отсылаются когда принимающий(ие) их процесс(ы) уже запущены - посты парасервису могут быть подготовлены до того как процесс запустится.

Новая функция RunParaServicePost() вместо PostBroadcast()

Для отправки постов парасервису используется функция

   rMyPostObject->
       RunParaServicePost(
           sServiceId,
           sInstanceId,
           eParaServiceDataStatus,
           out sError)

вместо обычного PostBroadcast().

RunParaServicePost() может не только послать данные но и она же и запускает парасервис если он еще не запущен.

Соответственно если парасервис уже запущен то он получит пост.

Если на момент вызова RunParaServicePost() парасервис еще не был запущен или еще не загрузился, или процесс загрузился но не инициализировал коммуникацию то пост будет отложен до момента готовности парасервиса принять его.

И если парасервис вдруг рестартует по какой бы то ни было причине то новый экземпляр его процесса по готовности снова получит тот же самый последний пост посланный RunParaServicePost().

Это все гарантирует получение инициализированным парасервисом всех данных, посланных ему последний раз каждым из его клиентов.

И последняя важная особенность RunParaServicePost():

Объекты постов посылаемых парасервису приложение должно хранить все время пока парасервис должен продолжать свою работу. И через один и тот же объект поста можно (и нужно! перепосылать пост многократно меняя данные параметры которые надо передать сервису.

Иначе говоря объект поста (унаследованный от CInterprocPostIfaceGp) в случае применения к нему RunParaServicePost() становится инкапсулятором парасервиса и хранилищем его текущего требуемого состояния с точки зрения клиентского приложения.

Именно срок жизни всех ref<CInterprocPostIfaceGp> посылающих параметры парасервису и определяет срок жизни процесса парасервиса. Как только парасервис обнаружит что все его клиенты уничтожили объекты посылающие ему параметры парасервис тут же автоматически терминируется.

Как убедится что процесс запущен но не слать данные

Иногда (Uport) бывает нужно только запускать процесс парасервиса без передачи ему данных. Или в случае если параметры работы парасервиса меняются редко, а проверять запущен ли процесс хочется часто. Для этого RunParaServicePost() имеет параметр

   EParaServiceDataStatus eParaServiceDataStatus

выбирающий логику:

        enum EParaServiceDataStatus
        {
            // this always forces a post to be queued for sending to the ParaService
            E_ParaServiceDataStatus_SendUpdatedPost,
 
            // this avoids needless posting when no data is changed,
            //   use when you want to just meke sure the ParaService process is running
            E_ParaServiceDataStatus_NoUsefulDataChange,
        };

Параметры запускаемого процесса

Так как RunParaServicePost() не только посылает данные сервису но и запускает его по мере необходимости, то он должен знать какой экзешник ему нужно запускать.

Для этого в объект CInterprocPostIfaceGp добавлен

   ref<CInterprocRunParaServiceIfaceGp> _x_rInterprocRun

(см. подробное описание запуска процессов в nav.gif Запуск и выгрузка параллельных процессов )

Из всех параметров запуска процесса в случае с парасервисом нам нужно указать только путь к .exe файлу:

   rPost->_x_rInterprocRun->x_pathRunExe = ...

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

Для обеспечения лучшей инкапсуляции как правило приложение будет запускать свой собственный экзешник в роли сервиса либо DLL будет запускать свой стартер чтобы экземляр этого DLL отработал задачу в составе параллельного процесса. Поэтому путь к запускаемому парасервису обычно будет

        // restart our whole app in ParaService mode
        rPost->_x_rInterprocRun->x_pathRunExe =
            CProject::GGetGlobalInstance().GetProjectStarterExePath();

Момент отсылки данных поста

Еще один небольшой нюанс отличающий RunParaServicePost() от PostBroadcast()..

Если PostBroadcast() отсылает сообщение немедленно, и оно насколько это возможно оперативно будет принято получателями то RunParaServicePost() на самом деле ВСЕГДА откладывает момент отсылки и делает его по таймеру.

В сочетании с тем фактом что приложение ПОСТОЯННО ХРАНИТ данные поста для RunParaServicePost() получается что оно может изменить отсылаемые данные ПОСЛЕ вызова RunParaServicePost(). Что не возможно при применении PostBroadcast()..

Это сделано специально чтобы логика подготовки данных для поста парасервису срабатывала всегда идентично как в случае если RunParaServicePost() постит в уже запущенный сервис так и в случае если ему нужно сначала запустить этот сервис и только потом послать пост.

Завершение процесса парасервиса

возможно в следующих ситуациях:

все клиентские приложения отказались от его услуг (или просто завершились сами)

(это произойдет в случае если для всех коммуникационных объектов вызовутся деструкторы во всех клиентских процессах)

В этой ситуации процесс парасервиса терминирует сам себя.

парасервис может быть терминирован если клиенты обнаружат факт его зависания

(для этого watch dog timer (WDT) ослеживается автоматически библиотеками)

В этом случае будет запущен новый процесс сервиса.

процесс сервиса может завершить себя через ExitProcess(), PostQuitMessage() или фатальную терминацию.

Однако в этом случае если какой бы то ни было из клиентов выразит желание продолжать иметь соответствующий сервис (обновив его параметры через посылку очередного Post сообщения) то для продолжения работы сервиса запустится новый процессс.

В любой ситуации перезапуска процесс парасервиса получит посылку с параметрами которые клиентский процесс (или процессЫ) посылал(и) ему последний раз. Это может быть достаточно чтобы аосстановить контекст работы и продолжить прерванную задачу.

Реализация самого сервиса и прием посылок

Хандлер посылок, принимаемых ParaService

ParaService принимает обновления своего состояния в виде постов по аналогии с Interproc-Post.

Хандлер надо где то хранить

Объект хандлера onpost нужно хранить все время пока сервис запущен. Его следует создать как мембер класса некоего глобального инкапсулятора парасервиса:

    // This Post handler must adhere to PFN_Handler signature in onpost template<>
    //   This servies as a ParaService handler.
    void Handle<...>Post(
            ref<C<...>Post> rPost);
    onpost<Handle<...>Post> _m_onpost<...>;

Например для парасервиса UportParamod:

    // This Post handler must adhere to PFN_Handler signature in onpost template<>
    //   This servies as a ParaService handler.
    void HandleUportParamodPost(
            ref<CUportParamodPost> rUportParamodPost);
    onpost<HandleUportParamodPost> _m_onpostUportParamod;

Инициализируем хандлер, открывая сервис

Вместо простенькой бессбойной функции Interproc-Post OpenPostOffice() для открытия ParaService используем метод

    onpost::TryOpenParaServicePostOffice(sId, this)

Он вернет нам true если данный процесс запущен именно для обслуживания сервиса с указанным sId. Возвращаемый TryOpenParaServicePostOffice() bool является ключем к принятию решения о том в какой роли дальше будет работать приложение.

Можно так же узнать запущен ли процесс в режиме парасервиса без аллокирования хандлера вызовом

   bool IsParaServiceHandlerProcessFor(sPostIfaceName)

для временного объекта ref<CInterprocParaServiceIfaceGp>().

Функция-хандлер постов

Колбак функция вызываемая при получении приложением постов - 'Handle<...>Post(ref<C<...>Post> rPost); реализуется точно так же как и обычный хандлер Interproc-Post. С той только логической разницей что в посте приходящем парасервису приходят не инкрементные команды а полное описание задачи которую в данный момент должен выполнять сервис.

Функция хандлера постов для парасервиса будет вызываться не только когда клиент сделает посылку, но и обязательно она будет вызвана первый раз сразу после инициализации сервиса. При этом она получит те данные, которые клиент посылал сервису последний раз.

Модифицируем поведение приложения в режиме сервиса

Если TryOpenParaServicePostOffice() вернул true то значит нам надо менять роль нашего экзешника подразумевая что он не самостоятельное приложение а выполняет функции сервиса.

В частности в LoaderImpl::OnStartApplication() или его аналоге для режима парасервиса исключаем инициализацию главного окна. UI сервису не нужно (возможно только специальное скрытое от конечного пользователя отладочное UI).

См. Применение ParaService на практических примерах.

Детектируем сервисный режим процесса

Есть глобальная функция IsParaServiceProcess() выбирать между которой и IsParaServiceHandlerProcessFor() надо осторожно.

  • IsParaServiceProcess() используется когда нужно проверить не обслуживает ли данный процесс какой нибудь парасервис. Для логики одинаковой для любого из парасервисов которые есть или могут появится в будущем в процессе.
  • IsParaServiceHandlerProcessFor(sPostIfaceName) нужно использовать для логики применимой только к конкретному указанному парасервису.

Досадные проблемы будут если подразумевать что модуль может обслуживать только один сервис и использовать эти функции как взаимозаменяемые. Они такими не являются но это не очевидно пока не возникнет нескольких возможных сервисов для одного и того же модуля!

Терминация парасервиса

1) В случае когда процесс парасервиса автоматически завершается при исчезновении его клиентов он это делает сам. Библиотеки отслеживают клиентов по таймеру и делают PostQuitMessage().

В этом случае процесс не будет прерван неожиданно, а только когда отдаст управление своему циклу обработки сообщений.

Под отладчиком этот вид терминации не происходит. Это позволяет запустить парасервис из IDE и затем запустить основной процесс (из второго IDE) и отлаживать их совместную работу. Вовне отладчика можно применить debslot("Interproc.ParaService.LiveWithoutClients?").

2) Если процесс парасервиса завис, то его терминируют клиенты, детектировавашие зависание. Произойдет это если WDT не будет обновлятся библиотеками в парасервисе в течении заданного таймаута. Обновляют они его автоматически по своему внутреннему таймеру. Соотвественно зависанием будет признана ситуация когда код парасервиса долго не возвращает управление циклу обработки сообщений.

Терминация такая может произойти на пол пути выполнения парасервисом какой то неожиданно долгой операции без выполнения обычных процедур завершения процесса, без сохранения UniConfig конфига (который вообще не нужен парасервису).

Естественно если процесс остановлен на брейкпоинте таймаут выйдет и по этому под отладчиком авто-терминация автоматически отменяется. При запуске не под IDE того же эффекта достигает debslot("Interproc.ParaService.NoKillOnStall?").InputBool().

3) Парасервис как и любой процесс может в принципе завершить и себя по PostQuitMessage(). Но это не типовая ситуация. Процесс сервиса как правило должен продолжать работать покуда существуют клиентские объекты запустившие его. Основанием для самостоятельной терминации может быть фатальный сбой после которого процесс сервиса не может продолжать работу и может надеятся что клиенты его перезапустят снова. Более хитрая теоретическая ситуация когда процесс парасервиса выполнив всю свою работу может завершать себя после продолжительного простоя чисто ради того чтобы освободить память и ресурсы системы. В расчете опять же, что будет перезапущен клиентами когда снова понадобится. Естественно такой подход замедлит и добавит латентности при перезапуске сервиса.

4) Если сервис запустился а клиентов у него нет, то он терминируется автоматически. Такой случай может возникать, если клиентское приложение, запустившее сервис закрыли до того как сервис загрузился.

ID парасервисов

Тот же самый глобально уникальный строковый ID который применяется в nav.gif Безопасная посылка данных процессам через Interproc-Post используется и для идентификации парасервиса.

Только один процесс соответствующий InterProc ID может быть запущен в системе в данный момент времени. Поэтому для парасервисов есть еще опциональный параметр sInstanceId передаваемый RunParaServicePost(). Если он не пустой, то добавляется к ID сервиса позволяя запускать несколько процессов одного типа. В инициализации сервиса ничего при этом не меняется. Сервис может узнать свой sInstanceId из GetOpenedInstaceId() если ему нужно знать что он не один обслуживает данный тип сервиса в системе.

Любое количество клиентских процессов главных приложений (C_nMaxClientsForParaService) может использовать парасервисный процесс данного класса и даже с одним и тем же sInstanceId. Важно следить чтобы они не засыпали сервисы конфликтующими командами! Либо всегда добавлять к sInstanceId уникальный для данного клиентского процесса идентификатор чтобы каждый клиент общался только со своими экземплярами сервисных процессов.

На количество независимых парасервисов с разными ID в данный момент ограничений не накладывается.

Примеры приложения такой логики коммуникации

(см. также Применение ParaService на практических примерах)

Для примера рассмотрим задачу, которая идеально и наглядно ложится на данные принципы.

Предположим для приложения требуется в параллельном режиме на втором мониторе выводить заданную анимацию (пример из Stabip).

Содержимое анимации меняется при переходе к следующему этапу приложения.

В данном случае приложение создает и постоянно хранит объект с параметрами поста и посылает новый пост заполнив его информацией о требуемой анимации при переходе к очередному этапу на котором анимацию нужно изменить.

    void HandleNextStage()
    {
        // update service parameters
        m_rPostToAnimator->x_rAnimation = GetCurrentAnimation();
 
        // our .exe handles the service
        m_rPostToAnimator->_x_rInterprocRun->x_pathRunExe = sys::GetExeFilePath();
 
        // send or start-and-send
        m_rPostToAnimator->RunParaServicePost(C_sMyAnimator, out sExeStartError);
    }

В этом случае приложению больше ничего не нужно делать и ни о чем заботится.

Внутренняя реализация ParaService сама автоматически:

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

Другой пример - процесс вычитывающий данные из Unimod. Ему вообще не нужно на входе никаких параметров. Пока он работает - заполняет shared буфера данными. Если клиентам они нужны они эти данные вычитывают. При (ре)старте такой процесс сам знает как детектировать устройство и продолжить процедуру.

Более сложные ситуации с приборными процессами. используемыми потенциально несколькими приложениями отдающими прибору команды и ожидающие поддержание прибора и сервиса в определенном состоянии.

Базовый механизм для такого сервиса здесь реализован а прикладные детали - очереди, контексты, транзакции и конфликты на прикладном уровне должны разрешаться приложением за пределами данного топика.

Альтернативные способы коммуникации с парасервисом

Если схема такой посылки постов кажется сложной или не удачной для вашего конкретного приложения, то данных с этими постами можно и не посылать. Можно посылать ПУСТЫЕ посты парасервису, используя расширенный механизм RunParaServicePost только для того чтобы стартовать парасервис и контролировать время его жизни.

При этом всю остальную коммуникацию можно организовать обычными постами, или через общую память, файлы и т.п. Однако это не избавляет от необходимости помнить что такая коммуникация должна обеспечивать корректное возобновление работы сервиса при прерывании его в любой момент времени.

Совместимость ParaService и Interpoc-Post

Нет никаких конфликтов между одновременным использованием обычных Interproc постов и специальными постами, запускающими парасервис.

Если это необходимо, то можно добавить любые каналы Post-коммуникации между сервисами и их клиентами в любых направлениях. В т.ч. полностью заменив ими пересылку параметров через RunParaServicePost().

Тогда RunParaServicePost() необходимо вызывать только чтобы клиент засвидетельствовал факт того, что он хочет иметь в наличии заданный сервис. Параметров передавать не обязательно.

Однако нужно помнить что PostBroadcast() посланный в момент, когда процесс еще не запустился будет утерян. RunParaServicePost() гарантирует отсылку и повторную переотсылку последнего пакета запущенному и перезапущенному процессу.

Альтернативный не паралельный режим

Рекомендуется сразу, по крайней мере для удобства отладки, реализовывать функции парасервиса не только работающими в отдельном процессе но и опционально в рамках основного.

Будет это вотребовано в релизе или нет, но понадобится сравнительное тестирование того как функции работают в паралель по сравнению с синхронным режимом и может быть востребован аварийный или ограниченый режим когда (в простых случаях?) задачи парасервиса проще, надежней и быстрей выполнить в основном процессе. И естственно отлаживать один процесс проще чем два.

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

  • системные классы управляющие стартом и коммуникаией парасервиса располагаем в своем пакете а прикладные функции в другом(других) пакетах
  • для остального приложения функции парасервиса представляем интерфейсом не подразумевающим способ реализации параллельности (парасервис или таймер)
    • реализуем две версии этого интерфейса - одна инициализирует и посылает посты парасервису а другая сразу выполняет их или вешает на таймер
    • при создании объекта выбираем тот или другой Impl хотя бы на основе debslot.
     // This returns to the main app
     //   - either local implementation residing in the current process
     //   - or parallel implemetation operating via ParaService posting
     //   according to its internal settings.
     //   The app does not need to care about the details!
     ref<CStabipDizzyRenderIface> NewStabipDizzyRender()
             new virtual auto;
     ...
     if (debslot("Dizzy.InProcess?").InputBool())
     {
         // default implementation
         return ref<CStabipDizzyRenderIface>();
     }
     else
     {
         return m_rStabipDizzyRenderAsPoster;
     }
 
 
 // Option 1: As ParaService:
 
 void CStabipDizzyRenderAsPoster::OnStopRendering()
 {
     m_rStabipDizzyPost->x_bEnableRendering = false;
 
     RepostStabipAnimationStateToParaService();
 }
         // ...... in the second process:
         void CStabipDizzyServiceImpl::HandleStabipDizzyPost(
                 ref<CStabipDizzyPost> rStabipDizzyPost)
         {
             if (rStabipDizzyPost->x_bEnableRendering)
             {
                 ...
             }
             else
             {
                 m_rStabipDizzyRender->
                     StopRendering();
             }
         }
 
 
 
 // Option 2: All in the same process:
 
 void CStabipDizzyRenderImpl::OnStopRendering()
 {
     this->x_rRenderingDbStabipStepConfig = null();
     m_bForceSecondMonitor = false;
 
     UpdateRenderWindowSituation();
 }

Прокси стартеры

Механизм ParaService специально спроектирован для гибкой связи между основным и сервисным процессами не зависящей от того какой из процессов изначально кого запустил.

Клиентский процесс не только использует сервисы запущиенные другими процессами (при совпадении ID) но и даже когда сам запускает процесс ParaService не ожидает что запущенный им процесс это будет тот сервис с которым он установит коммуникацию.

Стартер .exe указанный для запуска парасервиса может реально не обслуживать сервис а в свою очередь запускать процесс реализующий сервис. Либо может посылать команду другому процессу, в свою очередь запускающую парасервис.

Главное - чтобы ID указанный в командной строке поступил на вход процессу реализующему в итоге данный сервис. Достаточно чтобы в итоге в системе один из процессов инициализировал коммуникацию, объявив себя требуемым парасервисом.

Возможен даже вариант когда процесс парасервиса запускает процесс который станет его клиентом.

Если парасервис попробует запустить свой собственный сервис, то только установит связь с со своим же собственным процессом.

Для обеспечения такой гибкости реализация процедуры старта ParaService не подразумевает что process ID запущенный ей вообще имеет какое то отношение к сервису. Связь с процессом ParaService устанавливается между ним и запустившим его клиентом как и с другими его клиентами по принципу ослеживания статуса и внутренних ссылок в специальной разделяемой области памяти. Запущенный парасервис после удачной инициализации, убедившись в своей уникальности глобально публикует для клиентов необходимые для коммуникации данные. Клиенты замечают это (отслеживая по таймеру) и понимают, что сервис запустился.

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

Прокси стартер не должен завершаться по крайней мере раньше чем запущенный им парасервисный процесс инициализируется. По ID стартерного процесса отслеживается таймаут перед попыткой повторного рестарта сервиса. Если сервисный процесс не может стартовать вообще, то все клиенты будут пытаться перезапускать его с очень маленьким таймаутом. Это произойдет если стартерный процесс будет завершаться не дождавшись инициализации сервиса.

Конфиг

Для процесса ParaService библиотеки блокируют доступ к файлу конфига UniConfig. По этому при запуске он будет всегда иметь все параметры конфига в значениях по умолчанию.

Сервис должен получать все данные для своей работы от главного приложения и только в главном приложении должны быть все конфиги!

Если парасервис вдруг начнет генерить свои левые конфигурационные файлы в обход UniConfig, то важно помнить чтобы в паралельной среде не возникло конфликтов в том числе одновременного доступа к файлу из нескольких процессов, перезаписи устаревшими данными обновленных файлов, и конфликта между одним и тем же экзешником или DLL в разных экзешниках запущенных а разных парасервисах с разными ID.

Конфиг парасервиса в любом случае не учавствует в процедуре инсталляции и импорта.

Рекомендуемая структура проекта

на примере текущих применений сабжа: nav.gif Применение ParaService на практических примерах

Ограничения

  • Не поддерживается реализация более одного парасервиса одним процессом.

Сейчас если процесс даже и мог бы обслуживать несколько сервисов, то инициализировать их нет возможности и нужно либо разносить разные классы сервисов по разным процессам. Однако один и тот же экзешник может запускаться несоклько раз и тогда обслуживать разные сервисы в разных процессах. Кроме этого можно оборачивать все сервисы процесса в один глобальный если это удобно по логике задачи.

Видится, что потребность объединять сервисы может возникнуть только в целях тонкой оптимизации ресурсов системы и памяти. Если острая потребность возникнет, то можно дать возможность указать в командной строке несколько имен сервисов сразу или дать дозапускать дополнительные сервисы в рамках существующего процесса. Нужно только будет усложнить процедуры отслеживания повторных экземпляров процесса и реализовать глобальный мапинг всех сервисов процесса (класс под такой заготовлен но не используется).

www.biosoft-m.ru



Просмотры
Личные инструменты