Добро пожаловать на домашнюю страницу языка программирования MC#!
Проект MC#
Главная страница 
Язык MC# 
Документация 
Публикации 
 Примеры 
FAQ 

 
Дистрибутивы
Система программирования MC# 

 
Ссылки
Parallel C# 
Polyphonic C# 
Проект SKIF 

 
Контакты
 Контакты 


Mono powered

Microsoft .Net powered


 
   1

ОПИСАНИЕ РАБОТЫ RUNTIME-СИСТЕМЫ (LINUX-ВЕРСИИ)

ЯЗЫКА ПРОГРАММИРОВАНИЯ MC#

 

Вадим Б. Гузев

Юрий П. Сердюк

2005

1      Общая архитектура.. 2

1.1        Менеджер распределения ресурсов – resman.. 2

1.1.1     Утилита инсталляции менеджера распределения ресурсов rminstall 3

1.2        Рабочий узел – wn.. 3

1.3        Утилита daemonize. 4

1.4        Утилита mcsboot.. 4

1.5        Утилита mcshalt.. 6

1.6        Утилита mcsnodes. 7

1.7        Утилита do-all. 7

2      Внутренние механизмы работы Runtime-системы... 8

2.1        Протокол передачи сообщений.. 8

2.2        Инициализация вычислительной сессии.. 9

2.3        Запуск movable-функции.. 13

2.4        Передача канального сообщения.. 14

2.5        Сбор статистики.. 15

2.6        Завершение вычислительной сессии.. 15

 

1         Общая архитектура

 

В архитектуре MC# можно выделить следующие компоненты:

1) Клиентская программа (client) – программа, написанная на языке MC#, которая должна исполниться на кластере.

2) Менеджер распределения ресурсов (resman) – как видно из названия, назначение этого сервиса – следить за состоянием кластера и распределять movable-вызовы на наименее загруженные узлы кластера.

3) Рабочий узел (wn) – назначение этого сервиса – принимать команды от менеджера распределения ресурсов на исполнение/приостановку определённых процессов.

4) Копии клиентской программы на узлах кластера. Именно эти копии и производят основные вычисления, за счёт чего достигается ускорение.

 

Основные требования к кластеру:

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

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

В типичных конфигурациях кластеров папка /home доступна на всех узлах кластера.

- На узлах кластера и на фронтальной машине должен быть настроен SSH (RSH или любой из его аналогов), чтобы «переход» между узлами производился прозрачно, без запрашивания пароля пользователя.

 

1.1      Менеджер распределения ресурсов – resman

 

Расположение основных исходных файлов: “lib/ResourceManager.cs, lib/ResourceManagerHandler.cs, bin/resman”.

Менеджер распределения ресурсов занимается распределением нагрузки кластера, а также участвует в процессах инициализации и завершения сессий, а также загрузки и остановки Runtime-системы на кластере. Запускается он с помощью утилиты mcsboot, которая будет описана в секции “Утилита mcsboot”.

После запуска первым делом проверяется наличие файла $HOME/.mcsharp/.rmport и производится попытка считать из него номер порта, на котором должен быть запущен менеджер распределения ресурсов (по умолчанию, если файл не найден или происходит ошибка во время чтения из файла, менеджер распределения ресурсов запускается на порту 8888). Номер выбранного порта записывается в переменную ResourceManager.RMPort.

Далее производится попытка узнать, на каком порту должны запускаться рабочие узлы (wn) путём прочтения файла $HOME/.mcsharp/.wnport. Если файл не существует, то предполагается, что используется порт 9999. Номер порта записывается в переменную ResourceManager.WNPort.

Далее производится проверка – не занят ли порт под номером ResourceManager.RMPort. Если он занят, то выдаётся соответствующее предупреждение и менеджер завершает свою работу.

Если же он не занят, то на нём запускается сервер, обслуживающий поступающие команды, описанные подробно в других секциях ниже. Завершение работы менеджера распределения ресурсов производится при получении команд “halt” или “stopruntime” от утилиты mcshalt. Подробнее об этом см. в секции “Утилита mcshalt” .

Основные поля класса ResourceManager:

-         Port – номер порта, на котором запущен менеджер;

-         HostIP-адрес, используемый менеджером распределения ресурсов;

-         WNPort – номер порта, используемый wn-ами;

-         Nodes – список всех узлов, доступных в кластере;

-         Applications – список конфигураций, действующих на данный момент сессий;

-         CurrentApplication – счётчик приложений – увеличивается на 1, при запуске нового приложения. Служит для генерации идентификаторов сессий.

 

1.1.1      Утилита инсталляции менеджера распределения ресурсов rminstall

 

Расположение основных исходных файлов: “lib/rminstall.cs”.

Утилита инсталляции менеджера распределения ресурсов и сервиса «рабочего узла» запускается лишь один раз – при инсталляции пакета. При запуске она генерирует два свободных порта в системе в промежутке от 10000 до 30000 (которые будут в дальнейшем использоваться менеджером распределения ресурсов на фронтенде и сервисами «рабочих узлов» на узлах кластера) и записывает их соответственно в файлы “$HOME/.mcsharp/.rmport” и “$HOME/.mcsharp/.wnport”. Эта же утилита создаёт папку, где будут лежать лог-файлы $HOME/.mcsharp/logs.

 

1.2      Рабочий узел – wn

 

Расположение основных исходных файлов: “lib/WorkNode.cs”.

Процесс, занимающийся принятием команд от менеджера распределения ресурсов на старт/остановку процессов.

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

wn /rm:rmhost:rmport:host

При запуске первым делом проверяется файл $HOME/.mcsharp/.wnport и происходит попытка прочитать номер порта, на котором должен быть запущен Wn. Если файл не существует, то выдаётся предупреждение и используется порт 9999.

Для правильного функционирования WN необходимо, чтобы на узле была проставлена переменная $MCSHARPPATH, которая должна содержать путь до папки, в которую был установлен дистрибутив MC#.

Далее производится проверка, не занят ли нужный нам порт. Если порт не занят, то выдаётся ошибка и приложение завершает свою работу.

WN может принимать всего две команды – start и stop: для запуска приложения и завершения работы.

Формат команды start:

“start applicationID\n

commandLine\n

currentDirectory\n”

где applicationID – идентификатор приложения, исполняющегося на кластере

commandLine – командная строка, с помощью которой было запущено пользовательское приложение

currentDirectory – директория, с которой было запущено пользовательское приложение.

При получении этой команды запускается приложение:

$MCSHARPPATH/bin/starter $HOME/.mcsharp/logs/app_{guid}_{host}.log mono {commandLine} /rm:{rmhost}:{rmport}:{applicationID}:{host}

Запуск производится из директории {currentDirectory}.

Команда starter занимается лишь тем, что перенаправляет вывод потока в указанный файл ($HOME/.mcsharp/logs/app_{guid}_{host}.log).

Формат команды stop: “stop\n”. При получении этой команды вызывается System.Environment.Exit(0) и wn завершает свою работу.

 

1.3      Утилита daemonize

 

Расположение основных исходных файлов: “lib/daemonize.c”.

Утилита, запускающая любую исполняемую программу (даже с параметрами) как демон-процесс. Эта утилита была разработана на языке C++ Брайаном Клэппером (Brian Clapper, bmc@clapper.org) и доступна в Интернете (лицензия GPL). Программы, запущенные с помощью этой утилиты продолжают свою работу, даже если терминал, с которого была запущена программа, закрывается.

 

1.4      Утилита mcsboot

 

Расположение основных исходных файлов: “lib/mcsboot.cs, bin/mcsboot”.

Утилита для инициализации кластера. В качестве параметра может принимать файл, содержащий имена узлов, на которых необходимо проинициализировать Runtime-систему.

Если параметр не указан, то список узлов считывается из файла “$MCSHARPPATH/bin/nodes”. При этом если у узла существует несколько IP-адресов, то будет использоваться первый адрес, который возвращает система. Если же необходимо использовать конкретный IP-адрес, то нужно указать его, а не имя узла.

При запуске утилиты сначала проверяется наличие проставленной переменной $MCSHARPPATH в системе. Если эта переменная не доступна, то утилита завершает свою работу с выдачей соответствующей ошибки. Если переменная считана нормально, то далее считывается файл “$HOME/.mcsharp/.rmport”, в котором находится номер порта, на котором должен быть запущен менеджер распределения ресурсов. Если этого файла не существует, то утилита завершает свою работу с выдачей соответствующего предупреждения.

После этого считывается файл “$HOME/.mcsharp/.wnport”, в котором находится номер порта, на котором должны быть запущены «рабочие узлы» на узлах кластера. На каждом из узлов может быть запущен только один экземпляр «рабочего узла» (т.к. на один порт не может быть использован дважды), однако «рабочий узел» может запускать сколько угодно экземпляров клиентской программы на одном узле. Если этого файла не существует, то утилита завершает свою работу с выдачей соответствующего предупреждения.

Если оба файла были найдены и номера портов были успешно прочитаны, то дальше производится проверка, не занят ли номер порта, на котором должен быть запущен менеджер распределения ресурсов (т.к. он должен быть обязательно запущен на том же узле, где запускается утилита mcsboot). Если порт уже занят, то это означает, что Runtime-система уже была запущена, или какой-то другой процесс занял этот порт (и нужно сменить его в настройках, в файле $HOME/.mcsharp/.rmport). Если порт уже занят, то выдаётся соответствующее предупреждение и утилита завершает свою работу. Если же удалось создать серверный сокет на этом порту, то он тут же освобождается и с помощью утилиты daemonize запускается менеджер распределения ресурсов:

 

ProcessStartInfo psi2 = new ProcessStartInfo();

psi2.FileName = mcsharpPath + "bin/daemonize";

psi2.UseShellExecute = false;

psi2.Arguments = " " + mcsharpPath + "bin/starter \"" + home + Path.DirectorySeparatorChar +

".mcsharp" + Path.DirectorySeparatorChar + "logs" + Path.DirectorySeparatorChar + "rm.log\"" +  " \"" + mcsharpPath + "bin/resman\" \"" + fileName + "\"";

Process p = Process.Start( psi2 );

p.WaitForExit();

 

Где starter – небольшая вспомогательная утилита, находящаяся в папке $MCSHARPPATH/bin и занимающаяся только тем, что перенаправляет вывод запущенного потока в определённый файл. Вот как она выглядит:

#!/bin/sh

LOGFILE=$1

shift 1

PROGRAMANDPARAMETERS=$@

mkdir -p ~/.mcsharp/logs

touch $LOGFILE

$PROGRAMANDPARAMETERS >$LOGFILE &

 

Фактически, весь вывод утилиты resman будет перенаправляться в файл “$HOME/.mcsharp/logs/rm.log”.

После того, как менеджер распределения был запущен, на порт, считанный из файла “$HOME/.mcsharp/.rmport” отправляется сообщение “ping\n” для проверки соединения с менеджером. Если проверка проваливается, то выдаётся соответствующее предупреждение и утилита завершает свою работу. Если же сообщение нормально отправляется, то происходит инициализация сервисов «рабочих узлов» на узлах кластера, указанных в файле, переданном в качестве параметров утилиты mcsboot.

При этом сначала утилита пытается считать из файла “$HOME/.mcsharp/.accesstype” название команды, с помощью которой должны запускаться программы на узлах кластера (rsh/ssh/…). Если этот файл не существует, то используется RSH.

Все строчки в файле, содержащем названия узлов, начинающиеся с символа “#” считаются закомментированными и пропускаются утилитой.

Для каждого узла host в указанном файле производится запуск процесса «рабочего узла» (wn):

 

ProcessStartInfo psi3 = new ProcessStartInfo();

psi3.FileName = accessType;

psi3.UseShellExecute = false;

psi3.Arguments = " " + host + " \"" + mcsharpPath +

"bin/daemonize\" \"" + mcsharpPath + "bin/starter\" \""  +

home + Path.DirectorySeparatorChar + ".mcsharp" + Path.DirectorySeparatorChar +

"logs" + Path.DirectorySeparatorChar + "wn_" + host + ".log\" \"" + mcsharpPath +

"bin/wn\" /rm:" + rmHost + ":" + rmPort;

Process p2 = Process.Start( psi3 );

p2.WaitForExit();

 

В этом случае вывод каждого процесса, запущенном на узле host, будет перенаправляться в файлы $HOME/.mcsharp/logs/wn_host.log. После запуска процесса на узле host производится проверка соединения с этим узлом.

В случае если у фронтенда существует несколько IP-адресов, и необходимо указать какой именно IP-адрес должен использоваться менеджером распределения ресурсов, то можно указать этот адрес с помощью флага /ip. Например, такая ситуация возможна если на каждом узле кластера поддерживается один IP-адрес для обычных TCP соединений, а второй для TCP over SCI.

После запуска всех сервисов «рабочих узлов» менеджеру распределения ресурсов отправляется список узлов, которые были успешно проинициализированы. Отправка сообщения происходит в следующем формате:

wns <количество проинициализированных узлов>\n

host1\n

host2\n

hostn\n

Менеджер распределения ресурсов записывает у себя этот список узлов в коллекцию ResourceManager.Nodes.

После этого кластер считается проинициализированным.

 

1.5      Утилита mcshalt

 

Расположение основных исходных файлов: “lib/mcshalt.cs, bin/mcshalt”.

Утилита для завершения работы кластера и освобождения задействованных ресурсов.

При запуске утилиты считывается файл “$HOME/.mcsharp/.rmport”, в котором должен быть записан номер порта, на котором запущен менеджер распределения ресурсов. Если этот файл не существует, или номер порта указан в неправильном формате, то выдаётся соответствующее предупреждение и утилита завершает своё исполнение.

Если же номер порта считан нормально, то считывается номер порта, на котором должны быть запущены сервисы «рабочих узлов» на узлах кластера (из файла “$HOME/.mcsharp/.wn”). Если этот файл не существует, или номер порта указан в неправильном формате, то выдаётся соответствующее предупреждение и утилита завершает своё исполнение.

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

Если соединиться не удалось, то выдаётся соответствующее предупреждение и утилита прекращает своё исполнение. В противном же случае менеджеру распределения ресурсов отправляется сообщение “getnodeslist\n” по первому открытому сокету. Менеджер распределения ресурсов в ответ выдаёт список узлов, находящийся в коллекции ResourceManager.Nodes (по одному названию узла в каждой строке, строки разделены символом ‘\n’). После этого первый открытый сокет закрывается.

Затем менеджеру распределения ресурсов отправляется команда “halt\n”. Менеджер смотрит, есть ли исполняющиеся приложения на кластере, и если нет, то завершает свою работу. Если же на кластере есть хотя бы одно исполняющееся приложение, то утилите mcshalt возвращается строка “1\n” и менеджер не завершает своей работы. В свою очередь, при получении такого сообщения от менеджера распределения ресурсов, утилита mcshalt выдаёт сообщение, что есть незавершившиеся приложения на кластере и завершает свою работу.

Если необходимо приостановить Runtime-систему даже когда есть исполняющиеся приложения, то необходимо использовать ключ “-force”. В этом случае, вместо команды “halt\n” будет отправлена команда “stopruntime parameters\n”, где parameters – параметры, переданные утилите mcshalt. В качестве ответа на такое сообщение, менеджер распределения ресурсов отправляет количество запущенных приложений и список этих приложений (команд) со статусом завершения (Stopped или Cant connect).

Причём каждая строка появляется только после завершения попытки остановить соответствующее приложение:

 

mcshalt: stopruntime –force\n

resman: 3\n

fib.exe 35 /np 1 (Stopped)\n

fib.exe 40 /np 10 (Can’t connect)\n

fib.exe 35 /np 3 (Stopped)\n

 

(Между выводом каждой из строк может быть небольшая пауза)

После этого производится остановка «рабочих узлов» на узлах кластера.

При этом на каждый узел из списка узлов, полученного с помощью команды “getnodeslist\n”, отправляется команда “stop\n”. Если отправка команды удалась, то утилита выдаёт в консоль статус Stopped, в противном случае Cant connect or node is not responding.

 

1.6      Утилита mcsnodes

 

Расположение основных исходных файлов: “lib/mcsnodes.cs, bin/mcsnodes”.

Утилита, возвращающая список всех проинициализированных узлов в кластере. При запуске эта утилита смотрит файл “$HOME/.mcsharp/.rmport”, считывает из него номер порта, на котором запущен менеджер распределения ресурсов и отправляет на него сообщение “getnodeslist\n”. Если этот файл не найден или содержит номер порта не в правильном формате, то выдаётся соответствующее предупреждение и утилита завершает свою работу. При получении сообщения “getnodeslist\n” менеджер распределения ресурсов должен, в свою очередь, вернуть список проинициализированных узлов кластера, который и распечатывает mcsnodes (или ошибку, если что-то пошло не так).

 

1.7      Утилита do-all

 

Расположение основных исходных файлов: “lib/do-all.cs, bin/do-all”.

Утилита, исполняющая одну и ту же команду на всех узлах кластера. Эта утилита считывает список узлов из файла $MCSHARPPATH/bin/nodes и исполняет на них одно и то же действие, переданное в качестве параметров утилите. При этом учитывается тип доступа к узлам кластера (rsh/ssh/…), который можно сконфигурировать в файле $HOME/.mcsharp/.accesstype. Если этот файл не существует, то используется RSH.


 

2         Внутренние механизмы работы Runtime-системы

 

2.1      Протокол передачи сообщений

 

Расположение основных исходных файлов: “lib/TCPBase.cs, lib/TCP.cs ”.

Все компоненты системы, будь то менеджер распределения ресурсов, «рабочий узел», или клиентская программа, запущенная на фронтальной машине или любой другой машине кластера обмениваются между собой сообщениями, удовлетворяющими определённым правилам.

Все сообщения в системе состоят из заголовка и тела сообщения. Заголовок сообщения может содержать несколько строк, разделённые между собой символом перевода строки “\n”. Первая строка заголовка должна начинаться с уникального слова, по которому принимающая сторона может распознать поступившую команду, например, это может быть “stop”, “sessioninitialized”, “halt” и т.д. Тело сообщения может отсутствовать или же наоборот содержать какие-либо большие бинарные данные (например, вызов movable-метода в сериализованном виде или канальное сообщение).

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

Все базовые методы, связанные с отправкой и получением сообщений находятся в файле “lib/TCPBase.cs”. Методы, находящиеся в этом файле:

 

SendCommandAndObject( string host, int port, string header, object o ) – отправляет сообщение на узел host:port c заголовком “header\n” и сериализованным объектом o в теле сообщения.

 

SendCommandAndObjectAsync( string host, int port, string header, object o ) – тоже, что и предыдущий метод, но отправка производится асинхронно, т.е. в отдельном потоке.

 

SendMessage( Socket s, string message ) – отправляет сообщение message, через уже открытый сокет s

 

SendMessageAsync( Socket s, string message ) – тоже, что и в предыдущем методе, только отправка производится асинхронно, т.е. в отдельном потоке.

 

SendMessage( Socket s, byte[] buf, int len ) – отправляет первые len байтов из массива байтов buf. Значение len должно быть не больше, чем длина массива buf.

 

SendMessageAsync( Socket s, byte[] buf, int len ) – тоже, что и в предыдущем методе, но отправка производится асинхронно, т.е. в отдельном потоке.

 

SendMessage( string host, int port, string message ) – отправляет сообщение message на узел host:port

 

SendMessageAsync( string host, int port, string message ) – тоже, что и в предыдущем методе, но отправка производится асинхронно, т.е. в отдельном потоке.

 

ReadDataToStream( Socket s, MemoryStream ms ) – читает все поступившие данные из сокета s в ms.

 

ReadLine( Socket s ) – читает одну строку из сокета s. Строки должны быть разделены символом \n.

 

CreateClientSocket( string host, int port ) – пытается соединиться к узлу host:port. По-умолчанию делается 400 попыток соединения (которое можно изменить в TCPBase.MAX_NUMBER_OF_TRIES).

 

CreateClientSocket( string host, int port, int numberOfTries ) – пытается соединиться к узлу host:port используя numberOfTries попыток.

 

CreateServerSocket( int from, int to, out int port ) – Создаёт серверный сокет, открытый на одном из свободных портов в промежутке от from до to.

 

SendCommandAndArray( string host, int port, string header, byte[] array, int offset, int count ) – быстрая отправка массива данных на узел host:port с заголовком header, смещением в массиве – offset, количество передаваемых байтов – count.

 

SendMessage( Socket s, byte[] buf, int offset, int count ) – отправляет count байтов массива buf начиная с offset-ного байта по открытому соединению s.

 

2.2      Инициализация вычислительной сессии

 

Расположение основных исходных файлов: “lib/Session.cs”, “lib/TCP.cs”, “lib/CommunicatorHandler.cs”.

При трансляции программ с языка MC# в язык C#, компилятор добавляет следующую строчку в каждый метод Main, присутствующий в программе:

 

if ( ! MCSharp.Session.Init ( args ) ) return;

 

Результат выполнения функции Init отличается в зависимости от того, на каком узле исполняется в данный момент программа (либо это узел кластера, либо фронтальная машина). При запуске на фронтальной машине эта функция завершает своё выполнение, как только сессия будет проинициализирована (либо произойдёт ошибка). При этом если сессия была проинициализирована нормально, то возвращается true и программа продолжает своё выполнение. Если же произошли какие-либо ошибки, то они выводятся на экран, функция возвращает false и программа завершает своё выполнение.

При запуске на одном из узлов кластера эта функция будет выполняться на протяжении всего жизненного цикла работы программы (до тех пор, пока в главном экземпляре программы, на фронтенде, не будет вызвана функция MCSharp.Session.FinalizeIt). Эта функция никогда ничего не вернёт (выход из программы будет произведён с помощью вызова функции System.Environment.Exit при получении соответствующего сигнала от менеджера распределения ресурсов).

При запуске функции Init на фронтальной машине первым делом замеряется время старта инициализации сессии. Это делается с помощью вызова функции ComputationDuration.SetInitDate(). После этого создаётся объект MCStatistics, в котором будет собираться статистика, и запоминается в MCStatistics.CurrentStatistics.

По умолчанию флаг Session.Distributed проставляется в false, что означает, что сессия не распределённая.

Функции Init в качестве параметров передаются все параметры, переданные главной функции Main. Эти параметры разбираются в методе Init. На данный момент функция Init может принимать следующие параметры: rm, np, showstats, withdebug, completeboot. Каждый параметр должен начинаться либо с символа ‘/’, либо с символа ‘–’.

/np – количество процессоров, которое должно быть использовано (запоминается во временной переменной np). Если указан этот параметр, то переменная Session.Distributed выставляется в true.

/showstats – флаг, указывающий, что нужно собрать дополнительную статистику (например, показать гистограмму распределения movable-методов по узлам кластера). Сохраняется в Globals.ShowStatistics.

/withdebug – флаг, указывающий, что необходимо выводить всю отладочную информацию (сохраняется в Globals.IsDebug).

/completeboot – флаг, указывающий, что необходимо дождаться загрузки всех задействованных узлов кластера, прежде чем начинать вычисления (сохраняется в Globals.CompleteBoot).

/rm – этот параметр добавляется программой WN автоматически при запуске пользовательской программы на узлах кластера. В нём должны быть переданы следующие параметры (через символ ‘:”):

- rmHost адрес узла, на котором запущен менеджер распределения ресурсов (сохраняется в Communicator.RMHost);

- rmPort – номер порта, на котором запущен менеджер распределения ресурсов (сохраняется в Communicator.RMPort);

- guid – уникальный идентификатор текущей вычислительной сессии (сохраняется в Session.Guid);

- hostnameIP-адрес, который должен быть использован на этом узле – (например, это необходимо для поддержки SCI – когда на одном узле могут использоваться несколько IP-адресов: один для обычных Ethernet соединений, другой для TCP over SCI соединений). Запоминается во временной переменной hostName.

В случае если указан параметр /rm, то выставляются следующие значения переменных:

 

Session.Distributed = true;

Communicator.IsOnClusterNode = true;

Communicator.IsOnClientNode = false;

 

Если в результате разбора параметров переменная Session.Distributed оказалась равной true, то вызывается функция CommExec.Init(). Всё что делается в этом методе – это запускается дополнительный поток, который будет обслуживать в дальнейшем очередь канальных сообщений.

 

Далее, если производится проверка, проинициализирована ли переменная hostName. Если нет, то в эту переменную записывается первый IP-адрес машины из списка адресов записанных в DNS:

 

hostName = Dns.Resolve( Dns.GetHostName() ).AddressList [0].ToString();

 

После этого производится проверка: исполняется ли текущий экземпляр программы на фронтальной машине или же на одном из узлов кластера (Communicator.IsOnClusterNode && Session.Distributed). Если программа исполняется на одном из узлов кластера, то производится запуск коммуникатора (с помощью запуска функции Communicator.Init) и менеджеру распределения ресурсов отправляется команда “initiated”, что означает, что узел был проинициализирован и готов к исполнению дальнейших команд:

 

string toSend = "initiated " + Session.Guid + " " + Communicator.Host + " " + Communicator.Port + "\n";

TCPBase.SendMessage( Communicator.RMHost, Communicator.RMPort, toSend );

 

Что происходит в менеджере распределения ресурсов:

 

При получении данного сообщения менеджер распределёния ресурсов разыскивает в списке запущенных приложений объект типа ApplicationConfig (поиск производится по Guid’у приложения) и добавляет узел, с которого пришла эта команда в список «проинициализированных» узлов:

 

ApplicationConfig ac =

   (ApplicationConfig) ResourceManager.Applications [guid];

ac.AddNode( new Node( host, port ) );

 

Если этот узел был первым из проинициализированных узлов данного приложения, то клиенту отправляется сообщение “initstarted\n”, что означает, что клиент в принципе уже может начать рассылать задания на исполнение movable функций:

 

if ( !ac.OneNodeInitiated ) {

 ac.OneNodeInitiated = true;

 TCPBase.SendMessage( ac.ClientSocket, "initstarted\n" + guid + "\n" );

 ac.ClientSocket.Close();

}

 

Если же этот узел был последним из списка узлов, которые должны были быть проинициализированы для этого приложения, то каждому узлу из списка «проинициализированных» узлов отправляется команда “applicationinitiated” вместе со списком «проинициализированных» узлов в кластере. После чего клиенту отправляется сообщение “sessioninitiated” (также с тем же списком всех узлов в кластере), что означает, что сессия полностью проинициализирована:

 

if ( ac.NumberOfLoadedNodes == ac.NumberOfProcessors ) {

 string toSend = ac.NumberOfLoadedNodes + "\n" + ac.ClientHost + " " +

  ac.ClientPort + "\n";

 foreach ( Node n in ac.Nodes )

  toSend += n.Host + " " + n.Port + "\n";

 

 foreach ( Node n in ac.Nodes )

  TCPBase.SendMessage( n.Host, n.Port, "applicationinitiated\n" + toSend );

 

 TCPBase.SendMessage( ac.ClientHost, ac.ClientPort, "sessioninitiated\n" +

  toSend );

}

 

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

После отправки данного сообщения главный поток засыпает до завершения работы приложения, т.е. дальше выполнение функции Session.Init (и собственно остального кода функции Main программы) на узлах кластера не продолжается.

 

Далее возможны две ситуации:

a) Текущая сессия является распределённой (Session.Distributed == true), то на главном узле производится считывание номера порта, на котором должен быть запущен менеджер распределения ресурсов из файла $HOME/.mcsharp/.rmport. После чего запускается Communicator.Init (который запускает специальный поток, который будет принимать команды от других узлов и исполнять их) и производится попытка связаться с менеджером распределения ресурсов. Если происходит ошибка при попытке соединения, то выдаётся соответствующее предупреждение и программа завершает свою работу, перед этим завершив работу коммуникатора и CommExec’а.

Менеджеру распределения ресурсов отправляется команда “init” на инициализацию приложения:

 

string line = "init " + np + " " + addressList [0] +

 " " + Communicator.Port + "\n" + Environment.CommandLine + "\n" +

 Environment.CurrentDirectory + "\n";

 

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

 

Что происходит в менеджере распределения ресурсов:

 

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

Далее он рассылает np узлам, выбранным из списка всех доступных узлов, команду “start”, при этом генерируя новый guid для вновь запущенной пользовательской программы:

 

for ( int i = 0; i < numberOfProcessors; i++ ) {

 string host =

  (string) ResourceManager.Nodes [i % ResourceManager.Nodes.Count];

 string message = "start " + guid + "\n" + ac.CommandLine + "\n" +

  ac.CurrentDirectory + "\n";

 if ( ac.AssignedNodes [host] == null ) ac.AssignedNodes [host] = 1;

 else ac.AssignedNodes [host] = (int) ac.AssignedNodes [host] + 1;

 TCPBase.SendMessage( host, ResourceManager.WNPort, message );

}

 

Как видно, менеджер распределения ресурсов также подсчитывает, сколько экземпляров пользовательского приложения запущено на каждом из физических узлов. Это необходимо для вывода гистограммы распределения movable методов по узлам кластера.

 

Что происходит при получении команды “start” на рабочих узлах подробно описано в секции Рабочий узел – wn.

 

После отправки команды init клиентская программа дожидается ответа от менеджера распределения ресурсов. В качестве ответа менеджер распределения ресурсов может вернуть либо назначенный guid приложения, либо строку “error”, с кратким описанием ошибки. Если произошла ошибка, то она выводится на экран, после чего программа завершает своё исполнение. Если же менеджер вернул вновь назначенный идентификационный номер приложения, то он записывается в Session.Guid и он выводится на экран. Менеджер распределения ресурсов возвращает guid приложения только после того, как хотя бы на одном из узлов кластера было проинициализировано данное пользовательское приложение.

 

b) Сессия не является распределённой (Session.Distributed == false), то просто инициализируется CommWorld с одним текущим узлом (при этом Communicator не инициализируется). Это делается для совместимости локального и распределённого узлов (чтобы CommWorld был доступен и в локальном режиме).

 

После завершения шагов a) или b) производится проверка: установлен ли флаг Globals.CompleteBoot. Если он установлен, то программа дожидается до тех пор, пока менеджер распределения ресурсов не пришлёт команду “sessioninitialized”. Это будет означать, что приложение было проинициализировано на всех необходимых узлах кластера (при получении этой команды выставляется в true флаг CommWorld.Initialized).

Далее выставляются флаги:

 

Session.Initiated = true;

Session.Finalized = false;

 

И запоминается время инициализации сессии:

 

MCStatistics.CurrentStatistics.SessionInitializationTime =

  DateTime.Now.Subtract( startSessionDate );

 

После этого сессия считается проинициализированной и функция Init возвращает true.

 

2.3      Запуск movable-функции

 

Расположение основных исходных файлов: “lib/TCP.cs”, “lib/CommunicatorHandler.cs”, “lib/Agent.cs”, “lib/MovableMethodHandler.cs”.

Компилятор заменяет вызовы movable-функций на вызов метода TCP.DeliverMovableMethod, имеющей следующие параметры:

- объект, у которого была вызвана movable функция;

- название вызываемой movable функции;

- параметры movable-функции.

1. Если сессия распределённая, то менеджеру распределения ресурсов отправляется запрос “getfreenode Session.Guid\n”, где Session.Guid – идентификатор запущенного приложения. В ответ на этот запрос менеджер распределения ресурсов возвращает адрес узла и номер порта, на который необходимо передать данную movable функцию для вычисления. Реализация процесса выборки «свободного» узла менеджером распределения ресурсов может отличаться в разных версиях Runtime-системы. Самый простейший вариант – “выборка узлов по кругу”, т.е. movable-методы распределяются равномерно и по очереди по всем узлам кластера, вне зависимости от текущей загруженности узлов.

2a. В случае если сессия нераспределённая или же функция должна быть исполнена на текущем узле (по решению менеджера распределения ресурсов), то создаётся копия объектов, переданных в качестве параметров функции DeliverMovableMethod. Копии объектов создаются с помощью механизма сериализации/десериализации. Далее создаётся специальный объект типа MovableMethodHandler, параметром которому передаются созданные копии параметров, упакованные в объект типа Agent, и у этого объекта запускается метод StartInNewThread. В этом методе с помощью механизмов рефлекции вызывается необходимый movable метод:

 

Type t = agent.Obj.GetType();

MethodInfo mi = t.GetMethod( agent.MethodName );

mi.Invoke( agent.Obj, agent.Parameters );

 

2b. Если же сессия является распределённой и функция должна быть исполнена на другом узле (также по решению менеджера распределения ресурсов), то на узел где должна исполниться movable функция отправляется команда “movablecall\n{Serialized Agent}”, где {Serialized Agent} – параметры функции DeliverMovableMethod, упакованные в MCSharp.Agent. При получении данного сообщения коммуникатор выполняет действия, указанные в пункте 2a.

 

2.4      Передача канального сообщения

 

Расположение основных исходных файлов: “lib/TCP.cs”, “lib/CommunicatorHandler.cs”, “lib/Agent.cs”, “lib/Channel.cs”, “lib/ChannelMessageHandler.cs”.

Каждый объект, у которого существует хотя бы один канал, регистрируется в списке объектов в CommExec.Objects. Необходимый код для этого генерирует компилятор MC# и выглядит он следующим образом:

 

public void mcsharp_initiate ( ) {

 Monitor.Enter ( CommExec.Objects );

 int mcObjectNumber = CommExec.Objects.Count + 1;

 CommExec.Objects.Add ( mcObjectNumber, this );

 Monitor.Exit ( CommExec.Objects );

 c1 = new Channel ( this, mcObjectNumber, "mcsharpChannelc1" );

 c2 = new Channel ( this, mcObjectNumber, "mcsharpChannelc2" );

 mcsharpQueueGet2 = new MCSharp.ThreadQ();

}

 

Функция mcsharp_initiate вызывается в начале всех конструкторов объекта содержащего каналы. Как видно из приведённого выше отрезка кода, каждый объект типа Channel запоминает номер объекта, к которому он принадлежит в списке объектов. Также канал всегда «помнит», на каком узле он был первоначально создан и номер порта коммуникатора на том узле. Эти значения он «таскает» за собой при миграции с одного узла на другой.

При вызове метода Send( params object[] parameters) у некоторого канала c1 вызывается функция MCSharp.TCP.DeliverChannelMessage( this, parameters ).

1. Если сессия не является распределённой (можно узнать с помощью переменной Session.Distributed), или же канал первоначально был создан на текущем узле, то канал c1 и parameters упаковываются в объект типа Agent, и передаются объекту типа ChannelMessageHandler:

 

Agent agent = new Agent( c, parameters );

if ( !Session.Distributed )

{

 new ChannelMessageHandler( agent ).Start();

 return;

}

 

При вызове метода Start происходит следующее:

 

Type t = CommExec.Objects [agent.ObjectNumber].GetType();

MethodInfo mi = t.GetMethod( agent.MethodName );

mi.Invoke( CommExec.Objects [agent.ObjectNumber], agent.Parameters );

 

Т.е. фактически отыскивается объект, к которому принадлежит этот канал и у него уже вызывается метод, отвечающий за обслуживание канального сообщения (генерируется компилятором MC#).

2. Если же сессия является распределённой, и канал был первоначально создан на другом узле, то на узел первоначального создания канала отправляется сообщение в формате “channelmessage\n{Serialized Agent}”, где {Serialized Agent} – упакованные параметры функции TCP.DeliverChannelMessage в MCSharp.Agent. При получении данного сообщения коммуникатор выполняет действия, указанные в пункте 1.

 

2.5      Сбор статистики

 

Расположение основных исходных файлов: “lib/MCStatistics.cs”, “lib/TCP.cs”.

По-умолчанию подсчитываются следующие характеристики:

- количество вызванных movable методов;

- количество переданных канальных сообщений;

- количество movable методов, которые пришлось пересылать по сети;

- количество канальных сообщений, которые пришлось пересылать по сети;

- общий размер (в байтах) вызванных movable методов, переданных по сети;

- общий размер (в байтах) переданных канальных сообщений, переданных по сети;

- общее время, затраченное на пересылку movable методов по сети;

- общее время, затраченное на пересылку канальных сообщений по сети;

- суммарное количество байт отправленное по сети;

- суммарное время, затраченное на отправку сообщений по сети;

- время инициализации сессии.

 

Все эти характеристики инкрементируются, когда вызываются соответствующие методы из класса TCP. При завершении вычислительной сессии все узлы скидывают накопленную статистику менеджеру распределения ресурсов, который её суммирует и отправляет на клиентский узел.

 

Время инициализации сессии доступно только на клиентской программе и инициализируется в функции Session.Init.

 

Если при запуске пользовательского приложения указан ключ /showstats, то дополнительно к указанной статистике выводится гистограмма распределения movable методов по узлам кластеров. Данная статистика накапливается на самом менеджере распределения ресурсов в отличие от предыдущих видов статистики, которые собираются на узлах кластера.

 

2.6      Завершение вычислительной сессии

 

Расположение основных исходных файлов: “lib/Session.cs”, “lib/ResourceManager.cs”, “lib/MCStatistics.cs”, “lib/TCP.cs”.

Завершение сессии начинается с вызова функции Session.FinalizeIt(). Вызов этой функции автоматически вставляется транслятором языка MC# сразу после завершения тела функции Main. При запуске этой функции первым делом проверяется, не остановлена ли вычислительная сессия на данный момент и была ли она вообще проинициализирована:

 

if ( !Initiated || Finalized )  return;

Далее проставляется флаг Session.Finalized в False и замеряется временная метка - дата завершения сессии (с помощью вызова функции ComputationDuration.SetEndDate()).

После этого, если текущая сессия является распределённой, то менеджеру распределения ресурсов отправляется сигнал “stop <SessionID>”, где SessionID – идентификатор текущей сессии.

 

Что происходит в менеджере распределения ресурсов:

 

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

Если сессия найдена, создаётся специальный объект statistics (объект типа MCStatistics) и для каждого узла (точнее – коммуникатора), задействованного в нужной нам сессии, отправляется сообщение “stop\n”. В ответ на это сообщение каждый узел присылает накопленную статистику в виде 10 строк, разделённых символом ‘\n’, содержащих 10 чисел:

- количество movable вызовов;

- количество канальных сообщений;

- количество movable вызовов произведённых через сеть;

- количество переданных канальных сообщений через сеть;

- общий объём (в байтах) переданных через сеть movable вызовов;

- общий объём (в байтах) переданных через сеть канальных сообщений;

- общее время, затраченное на передачу movable вызовов через сеть;

- общее время, затраченное на передачу канальных сообщений через сеть;

- общее количество переданных байтов по сети;

- общее время, затраченное на обмен сообщениями с другими узлами.

После получения данного сообщения соединение с узлом закрывается, и узел завершает свою работу.

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

Если при запуске приложения был указан флаг /showstats (запись об этом есть у менеджера распределения ресурсов – см. ApplicationConfig.ShowStatistics), то формируется строка, содержащая следующие данные:

- суммарное количество узлов в кластере;

- количество узлов, задействованных в данной сессии;

- для каждого задействованного узла записывается количество назначенных на этот узел копий приложения, или количество виртуальных узлов. Они записываются в виде: “number x nodeName”, где number – количество копий.

- сразу после каждой строки вида “number x nodeName” идёт строка, содержащая количество movable вызовов, которые были назначены на узел nodeName.

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

 

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

После чего завершает свою работу коммуникатор, CommExec и само приложение

 

Communicator.FinalizeIt();

CommExec.FinalizeIt();

Environment.Exit( 0 );

 


Весь Переславль