Child process executor
[csharp
|
rpc
]
Abstract
Механизм запуска задач в дочернем процессе, локальном либо удалённом. Позволяет освободить основные сборки решения от нативных зависимостей и даёт возможность параллельного исполнения.
Introduction
Есть крупный сервис обработки данных, разной обработки разных данных. В основном обработка производится с помощью сторонних библиотек, большая часть из которых нативная (C, C++, Pascal), а некоторые защищены железными ключами (HASP).
Основной процесс сервиса x64, соответственно большая часть сборок либо x64, либо AnyCPU. Но часть библиотек не имеет x64 версии, а отказаться от них просто так нельзя; часть задач необходимо выполнять на конкретных машинах, потому что в них воткнуты ключи с лицензиями.
Возникла потребность выполнения определённых задач вне основного процесса, в том числе удалённо.
Основной сценарий работы с библиотеками состоит из двух этапов: инициализация и обработка; в большинстве случаев время обработки многократно превышает время инициализации.
Methods
Всю логику запуска определённой задачи в дочернем процессе, на локальной или удалённой машине вынесена в отдельный проект (на гитхабе).
Состав решения:
-
MasterProcess (x64) - используется главным процессом сервиса, запускает задачу, получает результат.
-
CommonAssembly (AnyCPU) - общая для всех сборка; содержит классы для передачи параметров запуска и обработки задачи.
-
ChildProcessExecutor (x86 и x64) - логика запуска и выполнения указанной задачи.
-
TaskAssembly (x86 и x64) - сборка задачи; логика работы с конкретной библиотекой.
Ограничения зависимостей:
-
Сборки главного процесса (в том числе MasterProcess) не должны иметь нативных зависимостей или ссылок на сборки с жёсткой привязкой к x86. Они имеют ссылки только на CommonAssembly и TaskAssembly, но не инстанциируют никаких типов из последней!
-
Сборка CommonAssembly также не должна иметь нативных зависимостей или ссылок на сборки с жёсткой привязкой к x86.
-
Сборка ChildProcessExecutor собирается в разные битности в зависимости от битности сборок задач. Обычно на каждой машине лежит две этих сборки с разными битностями.
-
Сборок TaskAssembly может и будет много: на каждый тип задачи (со своими нативными зависимостями) своя.
Как это всё работает:
-
Головной процесс запускает executor, либо напрямую (если тот расположен локально), либо через утилиту PsExec из пакета SysInternals (если удалённо).
-
Executor запускается, получая в качестве аргументов режим запуска и параметры канала связи (net.pipe либо http) с головным процессом.
-
Executor запрашивает у головного процесса параметры запуска задачи (в том числе путь до сборки задачи плюс параметры инициализации библиотеки).
-
Executor загружает сборку задачи (с зависимостями) и инициализирует её. Сборка может быть в другом месте файловой системы, нежели чем сам executor, но обязательно на той же машине.
-
Executor запускает обработку с указанными параметрами и отправляет результат головному процессу.
Results
Что получилось в итоге: описанный выше механизм успешно используется в коммерческом продукте, гибко настраивается и хорошо масштабируется.
Код на гитхабе.