redmanmale

if-goto

Child process executor

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 может и будет много: на каждый тип задачи (со своими нативными зависимостями) своя.

Как это всё работает:

  1. Головной процесс запускает executor, либо напрямую (если тот расположен локально), либо через утилиту PsExec из пакета SysInternals (если удалённо).

  2. Executor запускается, получая в качестве аргументов режим запуска и параметры канала связи (net.pipe либо http) с головным процессом.

  3. Executor запрашивает у головного процесса параметры запуска задачи (в том числе путь до сборки задачи плюс параметры инициализации библиотеки).

  4. Executor загружает сборку задачи (с зависимостями) и инициализирует её. Сборка может быть в другом месте файловой системы, нежели чем сам executor, но обязательно на той же машине.

  5. Executor запускает обработку с указанными параметрами и отправляет результат головному процессу.

Results

Что получилось в итоге: описанный выше механизм успешно используется в коммерческом продукте, гибко настраивается и хорошо масштабируется.

Код на гитхабе.