GLADE User Guide


GLADE, GNAT Library for Ada Distributed Environment
GLADE Version 3.15pff
Date: 28 May 2002

Laurent Pautet, Samuel Tardieu

Перевод Copyright (C) 2003 А.Гавва.
Коммерческое распространение перевода, без разрешения автора перевода, запрещено.


GLADE - это GNAT-реализация дополнения стандарта Ada95 для распределенных систем (Ada95 Distributed Systems Annex).


Содержание


Об этом руководстве

Что содержит это руководство

Это руководство содержит следующие главы:


Введение в распределенные системы

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


Использование сетевых сервисов операционной системы

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

Конечно, подобный код взаимодействия, с целью упрощения, может быть инкапсулирован в "обертки" (wrappers), однако очевидно, что большая его часть может генерироваться автоматически. Следует заметить, что непосредственное программирование передачи сообщений отвлекает внимание разработчика от предметной области приложения. Сценарий запрос/ответ - является классической схемой, которая применяется в распределенных приложениях. Использование передачи сообщений, в этой схеме, можно сравнить с использованием механизма goto в нераспределенных приложениях. С точки зрения современной инженерии программирования подобная методология неприменима. Более качественный дизайн требует использование структурированного подхода, который основывается на вызовах подпрограмм.

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


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

Промежуточное окружение должно предусматривать высокоуровневые абстракции, облегчающие разработку приложений пользователя. Окружения подобные CORBA или окружению распределенных вычислений DCE (Distributed Computing Environment) предусматривают среду разработки клиент-серверных приложений, которая основывается на модели вызова удаленных подпрограмм RPC (Remote Procedure Call). Модель RPC является производной от схемы запрос/ответ. Передача аргументов и дополнительных данных, определяющих удаленную процедуру, которую необходимо выполнить, в поток, является грубой аналогией вызова обычной процедуры. Поток передается к серверу по сети. Сервер декодирует поток, выполняет локальный вызов обычной подпрограммы, а затем помещает выходные параметры в другой поток, наряду с исключением, если оно было возбуждено в процессе выполнения подпрограммы. После этого сервер посылает поток обратно к клиенту вызвавшему подпрограмму. Клиент декодирует поток и, при необходимости, локально возбуждает исключение.

CORBA предусматривает такие же расширения для модели удаленных процедур, какие объектно-ориентированные языки предусматривают для классических процедурных языков. Такие расширения включают инкапсуляцию, наследование, проверку типа и исключения. Эти средства обеспечиваются с помощью языка описания интерфейсов IDL (Interface Definition Language).

Промежуточная среда взаимодействия предусматривает все механизмы для прозрачного осуществления вызовов удаленных процедур и/или методов удаленных объектов. Например, каждый интерфейс CORBA взаимодействует с каким-либо Object Request Broker (ORB). Задача подсистемы взаимодействия, подобной ORB, заключается в том, чтобы избавить приложения использующие удаленные объекты от непосредственного использования низкоуровневых механизмов, реализующих передачу сообщений. Дополнительно, при разработке распределенного приложения, пользователю могут понадобиться некоторые более сложные сервисы. Некоторые из таких сервисов могут оказаться крайне необходимыми, например, сервис расположения, который позволяет клиентам обращаться к удаленным сервисам посредством использования высокоуровневых имен вместо традиционной схемы адресации удаленных сервисов, которая использует IP адреса и номера портов. Другие сервисы могут предусматривать интерфейсы не зависящие от доменов, которые часто используются в распределенных системах.

Если мы вернемся к сравнению с многопоточным/многонитиевым программированием, то обнаружим, что промежуточная среда предусматривает средства, которые подобны средствам, предусматриваемым библиотекой POSIX или языком подобным Esterel1, для разработки многопоточных/многонитиевых приложений. Промежуточная среда, такая как DCE, подобна библиотеке POSIX с точки зрения уровней абстракции. Ее функциональность очень низкоуровнева и сложна. CORBA, с точки зрения процесса разработки, ближе к Esterel. Управляющая часть приложения может быть определена с помощью языка описания. После чего, для построения вычислительной части приложения, разработчик заполняет автоматически сгенерированный код шаблонов ("заглушки" stub и "скелета" skeleton). Распределение является пре-компиляционным процессом и границы распределения всегда являются явными. При использовании CORBA, распределенная часть пишется на IDL, а ядро приложения - на хост-языке, таком как C++.


Использование распределенного языка

Более удачной альтернативой, по сравнению с определением нового языка, такого как CORBA IDL, является расширение какого-либо существующего языка программирования дополнительными средствами поддержки программирования распределенных систем. Парадигма распределенных объектов предусматривает объектно-ориентированный подход программирования распределенных систем. Понятие распределенного объекта является расширением абстрактного типа данных, которое позволяет осуществлять вызовы сервисов, предусматриваемых в интерфейсе типа, вне зависимости от того где фактический сервис будет выполняться. При комбинировании с объестно-ориентированными свойствами, такими как наследование и полиморфизм, распределенные объекты предоставляют более динамичное и структурированное вычислительное окружение для распределенных приложений.

Дополнение стандарта Ada95 для распределенных систем (DSA) определяет некоторые расширения с помощью которых можно строить распределенные системы полностью написанные на языке программирования Ада. Все типы распределенных объектов, сервисы, которые они предусматривают, и тела удаленных методов согласованно описываются в пакетах Ады. Модель Ada95 аналогична модели Java/RMI. В обоих языках, язык IDL заменяется хорошо определенными языковыми конструкциями. Следовательно, язык прозрачно поддерживает вызовы удаленных процедур и вызовы методов удаленных объектов, причем, семантика распределения согласована с остальными элементами языка.

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

По сравнению с многопоточным/многонитиевым программированием, решение, которое основано на языковом расширении, эквивалентно решению, которое адаптировано к средствам поддержки многозадачности Ады. Написание распределенного приложения является таким же простым как и написание многозадачного приложения: при этом не рассматривается связывание с внешними библиотеками и отсутствует код "оберток" (wrapper). Большая часть низкоуровневой работы возлагается на язык программирования и его систему времени выполнения, что позволяет не отвлекать внимание программиста от прикладных задач приложения.


Дополнение стандарта Ada95 для распределенных систем

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

Простейший путь начать разработку программы с учетом дополнения к стандарту Ada95 для распределенных систем - это начать разрабатывать приложение для не распределенной системы. Естественно, проект приложения должен учитывать то, что некоторые модули приложения будут доступны удаленно. В случае написания распределенной Ада-программы, пользователю необходимо учитывать, что некоторые компилируемые модули приложения уровня библиотеки будут отмечены директивами категорирования. Как правило, категорированию подлежат удаленно вызываемые модули и модули, которые предусматривают типы данных для удаленного доступа.

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

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

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

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

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


Архитектура распределенного приложения Ada95

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

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

Для удаленно вызываемых подпрограмм, определенных в библиотечном модуле, который категорирован как интерфейс удаленного вызова (RCI - Remote Call Interface) или как удаленные типы (RT - Remote Types) может осуществляться как статическое, так и динамическое связывание подпрограмм. Раздел распределенной программы на котором выполняется статически связанная удаленная подпрограмма может быть определен до осуществления вызова. Такой вызов называют статическим удаленным вызовом подпрограммы. В отличие от этого, удаленный метод или разыменование ссылки на удаленную подпрограмму являются динамически связанными удаленными вызовами поскольку раздел распределенной программы, на котором будет выполняться удаленная программа, определяется во время выполнения, в процессе осуществления фактического вызова.

В показанном на рисунке примере модули Data_1 и Data_2 являются общими пассивными (SP) библиотечными модулями. Модуль Data_1 сконфигурирован на пассивном разделе распределенной программы, который отображается на узел хранения данных. Разделы Partition_1 и Partition_2 являются активными разделами распределенной программы. Примечательно, что в некоторых случаях какой-либо раздел, например раздел Partition_2, может дублироваться. Для дублирования, модули Unit_2 и Unit_3, которые сконфигурированы на разделе Partition_2 должны предусматривать только динамически связываемые удаленные подпрограммы. В противном случае, какой-либо раздел, вызывающий удаленную подпрограмму модуля Unit_2, не сможет статически определить как осуществить удаленный вызов к двум экземплярам модуля Unit_2.

xe-arch.fig.jpg


Директивы категорирования

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

Последующие параграфы не представляют все детали семантики этих директив (все формальные подробности находятся в справочном руководстве по языку программирования Ada95 - Ada95 Reference Manual). Цель этих параграфов - дать интуитивное представление о назначении этих директив. Не категорированный библиотечный модуль называют обычным библиотечным модулем, и в распределенном приложении такой модуль не играет никакой специальной роли. Подобные модули дублируются в любом разделе распределенной программы, в котором они указаны.

Небольшое замечание: во избежание необходимости использования специфических библиотек времени выполнения для дополнения к стандарту Ada95 для распределенных систем (DSA), понятие удаленных рандеву не вводится в Ada95, задача одного раздела распределенной программы не может быть непосредственно вызвана из другого раздела. Следовательно, описания типов задач и общих защищенных типов с входами в категорированных библиотечных модулях Ады не допускается.


Директива объявления Pure

Эта директива не является специфичной для дополнения к стандарту Ada95 для распределенных систем (DSA) и может использоваться как в контексте категорированных пакетов, так и в контексте не категорированных пакетов. Пакеты Pure ("чистые") являются пре-элаборируемыми пакетами, которые не содержат описания каких-либо переменных или именованных ссылочных типов. Такие пакеты удобно использовать для описания типов, констант и подпрограмм, общих для некоторых категорированных пакетов. В противоположность этому, обычные пакеты не могут встречаться в контексте описаний категорированных пакетов. Поскольку "чистые" пакеты не имеют состояния, они могут дублироваться в нескольких разделах распределенной программы.


Директива Remote_Call_Interface

Обзор директивы Remote_Call_Interface

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

Динамическое связывание вызовов подпрограмм предусматривается с помощью двух механизмов:

Удаленный ссылочный тип (RAS или RCI) можно рассматривать как "полный" указатель (fat pointer), который является структурой состоящей из удаленного адреса и локального адреса (подобно URL: <protocol>://<remote-machine>/<local-directory>). Удаленный адрес должет обозначать хост (узез в сети) раздела распределенной задачи на котором создана соответствующая сущность, а локальный адрес описывает локальный адрес памяти на этом хосте.

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


Обычные удаленные подпрограммы (RCI)

В показанном ниже примере, RCI_Bank предоставляет несколько удаленных сервисов: Balance, Transfert, Deposit и Withdraw. С вызывающей стороны, клиенты банка используют файлы "заглушек" (stub files) модуля RCI_Bank. На приемной стороне, банк-приемник использует файлы "скелетов" (skeleton files) модуля RCI_Bank, включающие тело этого пакета.

    
    
    package Types is
       pragma Pure;
    
       type Customer_Type is new String;
       type Password_Type is new String;
    end Types;
    

    
    
    with Types; use Types;
    package RCI_Bank is
       pragma Remote_Call_Interface;
    
       function Balance
         (Customer : in Customer_Type;
          Password : in Password_Type)
          return Integer;
    
       procedure Transfer
         (Payer    : in Customer_Type;
          Password : in Password_Type;
          Amount   : in Positive;
          Payee    : in Customer_Type);
    
       procedure Deposit
         (Customer : in Customer_Type;
          Amount   : in Positive);
    
       procedure Withdraw
         (Customer : in Customer_Type;
          Password : in Password_Type;
          Amount   : in out Positive);
    end RCI_Bank;
    

    
    
    with Types; use Types;
    with RCI_Bank; use RCI_Bank;
    procedure RCI_Client is
       B : Integer;
       C : Customer_Type := "rich";
       P : Password_Type := "xxxx";
    begin
       B := Balance (C, P);
    end RCI_Client;
    


Ссылки на удаленные подпрограммы (RAS)

В следующем примере несколько "зеркальных" банков предлагают свои сервисы посредством одной и той же базы данных. Каждый банк регистрирует ссылку на каждый свой сервис в центральном банке. Клиент центрального банка запрашивает сервис у одного из "зеркальных" банков. Для удовлетворения запросов, модуль RCI RAS_Bank описывает тип Balance_Type, для доступа к удаленным подпрограммам (следует напомнить, что ссылочный тип, описываемый в удаленном модуле, может быть или ссылочным типом для подпрограмм, или надклассовым ссылочным типом).

Необходимо отметить, что подпрограмма, которая предоставляет удаленную ссылку, должна быть также удаленной. Следовательно, модуль Mirror_Bank является библиотечным модулем RCI.

    
    
    with Types; use Types;
    package RAS_Bank is
       pragma Remote_Call_Interface;
    
       type Balance_Type is access function
         (Customer : in Customer_Type;
          Password : in Password_Type)
          return Integer;
    
       procedure Register
          (Balance : in Balance_Type);
    
       function Get_Balance
          return Balance_Type;
    
       --  [...] Другие сервисы
    end RAS_Bank;
    

Показанный ниже код демонстрирует как "зеркальный" банк регестрирует свои сервисы в центральном банке.

    
    
    with Types; use Types;
    package Mirror_Bank is
       pragma Remote_Call_Interface;
    
       function Balance
         (Customer : in Customer_Type;
          Password : in Password_Type)
          return Integer;
    
       --  [...] Другие сервисы
    end Mirror_Bank;
    

    
    
    with RAS_Bank, Types; use RAS_Bank, Types;
    package body Mirror_Bank is
    
       function Balance
         (Customer : in Customer_Type;
          Password : in Password_Type)
          return Integer is
       begin
          return Something;
       end Balance;
    
    begin
       --  Регистрация динамически связываемой удаленной подпрограммы (Balance)
       --  с помощью статически связанной удаленной подпрограммы (Register)
       Register (Balance'Access);
       --  [...] Регистрация других сервисов
    end Mirror_Bank;
    

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

    
    
    with Types; use Types;
    with RAS_Bank; use RAS_Bank;
    procedure Bank_Client is
       B : Integer;
       C : Customer_Type := "rich";
       P : Password_Type := "xxxx";
    begin
       --  Получиение динамически связанной удаленной подпрограммы
       --  с помощью статически связанной удаленной подпрограммы (Get_Balance).
       --  Разыменование для осуществления динамического вызова.
       B := Get_Balance.all (C, P);
    end Bank_Client;
    


Ссылки на удаленные надклассовые типы (RACW)

Теперь предположим, что клиент банка подключен к банку через терминал. Банк хочет уведомить подключенного клиента о том, что другой клиент пересылает какую-либо сумму денег на его счет с помощью посылки сообщения на терминал. В следующем примере, терминал спроектирован как распределенный объект. Каждый клиент банка будет регистрировать свой терминалльный объект на сервере банка для последующего использования. В показанном ниже коде, тип Term_Type является корневым типом иерархии распределенных терминалов.

    
    
    with Types; use Types;
    package Terminal is
       pragma Pure;
    
       type Term_Type is abstract tagged limited private;
    
       procedure Notify
         (My_Term   : access Term_Type;
          Payer    : in Customer_Type;
          Amount   : in Integer) is abstract;
    
    private
       type Term_Type is abstract tagged limited null record;
    end Terminal;
    

В показанном ниже коде, RCI-модуль RACW_Bank описывает удаленный надклассовый ссылочный тип Term_Access. Тип Term_Access будет ссылаться на распределенный объект. В следующей секции мы рассмотрим как можно описать производный тип, чтобы расширить тип Term_Type, как создать распределенный объект и как использовать ссылку на него.

    
    
    with Terminal, Types; use Terminal, Types;
    package RACW_Bank is
       pragma Remote_Call_Interface;
    
       type Term_Access is access all Term_Type'Class;
    
       procedure Register
         (My_Term   : in Term_Access;
          Customer : in Customer_Type;
          Password : in Password_Type);
    
       --  [...] Другие сервисы
    end RACW_Bank;
    


Резюме по директиве Remote_Call_Interface

Модули вызова удаленных интерфейсов (модули RCI):


Директива Remote_Types

Обзор директивы Remote_Types

В отличие от модулей RCI, библиотечные модули, которые категорированы с помощью этой директивы, могут описывать распределенные объекты и их удаленные методы. Модули RCI и RT могут описывать удаленные ссылочные типы, как это было показано выше (для RACW). Какая-либо самостоятельная подпрограмма, описанная в модуле RT, не является удаленной подпрограммой. В отличие от модулей RCI, модули RT могут дублироваться на нескольких разделах распределенной программы. В таком случае, все сущности, которые описаны внутри этих модулей, будут отличаться друг от друга. Экземпляры этих модулей на каждом разделе распределенной программы, для которого они определены, - отличаются.


Распределенный объект

Если мы хотим реализовать средство уведомления, которое было предложено в предыдущей секции, нам необходимо описать тип, производный от типа Term_Type. Это можно выполнить с помощью модуля удаленных типов (RT), подобного модулю New_Terminal (см. ниже). Любой объект типа New_Term_Type будет распределенным объектом и любая ссылка на подобный объект будет "полным" указателем (fat pointer) или ссылкой на распределенный объект (см. описание Term_Access в Ссылки на удаленные надклассовые типы (RACW)).

    
    
    with Types, Terminal; use Types, Terminal;
    package New_Terminal is
       pragma Remote_Types;
    
       type New_Term_Type is
          new Term_Type with null record;
    
       procedure Notify
         (My_Term   : access New_Term_Type;
          Payer    : in Customer_Type;
          Amount   : in Integer);
    
       function Current return Term_Access;
    end New_Terminal;
    

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

    
    
    with New_Terminal, RACW_Bank, Types; use New_Terminal, RACW_Bank, Types;
    procedure Term1_Client is
       My_Term   : Term_Access   := Current;
       Customer : Customer_Type := "poor";
       Password : Password_Type := "yyyy";
    begin
       Register (My_Term, Customer, Password);
       --  [...] Выполнение других действий
    end Term1_Client;
    

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

    
    
    with New_Terminal, RACW_Bank, Types; use New_Terminal, RACW_Bank, Types;
    procedure Term2_Client is
       My_Term   : Term_Access   := Current;
       Payer    : Customer_Type := "rich";
       Password : Password_Type := "xxxx";
       Payee    : Customer_Type := "poor";
    begin
       Register (My_Term, Payer, Password);
       Transfer (Payer, Password, 100, Payee);
    end Term2_Client;
    

В показанном ниже коде, мы описываем общий дизайн процедуры Transfer. Сначала выполняются классические операции Withdraw и Deposit Затем RACW_Bank получает терминал получателя денег (если он есть) и вызывает диспетчеризуемый вызов, разыменовывая распределенный объект Term. Ссылка проверяется во время выполнения, и выполнение этой операции осуществляется на том разделе распределенной программы, на котором располагается распределенный объект.

    
    
    with Types; use Types;
    package body RACW_Bank is
       procedure Register
         (My_Term   : in Term_Access;
          Customer : in Customer_Type;
          Password : in Password_Type) is
       begin
          Insert_In_Local_Table (My_Term, Customer);
       end Register;
    
       procedure Transfer
         (Payer    : in Customer_Type;
          Password : in Password_Type;
          Amount   : in Positive;
          Payee    : in Customer_Type)
       is
          --  Поиск терминала заказчика.
          Term : Term_Access := Find_In_Local_Table (Payee);
       begin
          Withdraw (Payer, Amount);
          Deposit  (Payee, Amount);
          if Term /= null then
             --  Уведомление терминала получателя.
             Notify (Term, Payer, Amount);
          end if;
       end Transfer;
    
       --  [...] Другие сервисы
    end RACW_Bank;
    


Передача динамических структур данных

    
    
    with Ada.Streams; use Ada.Streams;
    package String_Array_Stream is
       pragma Remote_Types;
    
       type List is private;
       procedure Append (L : access List; O : in String);
       function  Delete (L : access List) return String;
    
    private
       type String_Access is access String;
    
       type Node;
       type List is access Node;
    
       type Node is record
          Content : String_Access;
          Next    : List;
       end record;
    
       procedure Read
         (S : access Root_Stream_Type'Class;
          L : out List);
       procedure Write
         (S : access Root_Stream_Type'Class;
          L : in List);
       for List'Read use Read;
       for List'Write use Write;
    end String_Array_Stream;
    

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

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

    
    
    package body String_Array_Stream is
       procedure Read
         (S : access Root_Stream_Type'Class;
          L : out List) is
       begin
          if Boolean'Input (S) then
             L := new Node;
             L.Content := new String'(String'Input (S));
             List'Read (S, L.Next);
          else
             L := null;
          end if;
       end Read;
    
       procedure Write
         (S : access Root_Stream_Type'Class;
          L : in List) is
       begin
          if L = null then
             Boolean'Output (S, False);
          else
             Boolean'Output (S, True);
             String'Output (S, L.Content.all);
             List'Write (S, L.Next);
          end if;
       end Write;
    
       --  [...] Другие сервисы
    end String_Array_Stream;
    


Резюме для модулей удаленных типов (RT)

Модули удаленных типов (RT):


Директива Shared_Passive

Обзор директивы Shared_Passive

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


Общие и защищенные объекты

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

    
    
    package Shared_Objects is
       pragma Shared_Passive;
    
       Max : Positive := 10;
       type Index_Type is range 1 .. Max;
       type Rate_Type is new Float;
    
       type Rates_Type is array (Index_Type) of Rate_Type;
    
       External_Synchronization : Rates_Type;
    
       protected Internal_Synchronization is
          procedure Set
            (Index : in Index_Type;
             Rate  : in Rate_Type);
    
          procedure Get
            (Index : in Index_Type;
             Rate  : out Rate_Type);
       private
          Rates : Rates_Type;
       end Internal_Synchronization;
    end Shared_Objects;
    


Резюме по директиве Shared_Passive


Дополнительные сведения о директивах категорирования

Переменные и не-удаленные ссылочные типы

В описаниях пакетов RT или RCI, запрещается описывать переменные, а не-удаленные ссылочные типы могут быть описаны наряду с явним определением подпрограмм передачи (marshaling subprograms). (см. Передача динамических структур данных)..


Ошибки RPC

Вызовы выполняются хотя бы один раз: они выполняются однократно или терпят неудачу, возбуждая исключение. При возникновении ошибки взаимодействия (communication error), возбуждается исключение System.RPC.Communication_Error.


Исключения

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

    
    
    package Internal is
       Exc : exception;
    end Internal;
    

    
    
    package Rem_Pkg2 is
       pragma Remote_Call_Interface;
    
       procedure Subprogram;
    end Rem_Pkg2;
    

    
    
    package Rem_Pkg1 is
       pragma Remote_Call_Interface;
    
       procedure Subprogram;
    end Rem_Pkg1;
    

Предположим, что пакеты Rem_Pkg2, Internal и Rem_Exc_Main расположены на разделе Partition_1, а пакет Rem_Pkg1 - на разделе Partition_2.

    
    
    with Rem_Pkg1, Ada.Exceptions; use Ada.Exceptions;
    package body Rem_Pkg2 is
       procedure Subprogram is
       begin
          Rem_Pkg1.Subprogram;
       exception when E : others =>
          Raise_Exception (Exception_Identity (E), Exception_Message (E));
       end Subprogram;
    end Rem_Pkg2;
    

    
    
    with Internal, Ada.Exceptions; use Ada.Exceptions;
    package body Rem_Pkg1 is
       procedure Subprogram is
       begin
          Raise_Exception (Internal.Exc'Identity, "Message");
       end Subprogram;
    end Rem_Pkg1;
    

    
    
    with Ada.Text_IO, Ada.Exceptions; use Ada.Text_IO, Ada.Exceptions;
    with Rem_Pkg2, Internal;
    procedure Rem_Exc_Main is
    begin
       Rem_Pkg2.Subprogram;
    exception when E : Internal.Exc =>
       Put_Line (Exception_Message (E)); -- Вывод "Message"
    end Rem_Exc_Main;
    

Кргда подпрограмма Rem_Pkg1.Subprogram на разделе Partition_1 возбуждает исключение Internal.Exc, это исключение распространяется обратно на раздел Partition_2 Поскольку исключение Internal.Exc не определено на разделе Partition_2, то возможность "отловить" и обработать это исключение без обработчика when others - отсутствует. Когда это исключение повторно возбуждается в подпрограмме Rem_Pkg1.Subprogram, оно распространяется на раздел Partition_1. В этой ситуации, исключение Internal.Exc является видимым, и его можно обработать также как это делается в обычной Ада-программе (программе, которая состоит из одного раздела). Естественно, что сообщение исключения также сохраняется.


Директива Asynchronous

По умолчанию, удаленный вызов является блокирующим: вызывающий клиент ожидает завершения обработки вызова и приема выходного потока. В противоположность этому, удаленная подпрограмма, отмеченная директивой Asynchronous, позволяет осуществлять асинхронное выполнение статически и динамически связанных удаленных вызовов. Вызов асинхронной процедуры не ожидает завершение удаленного вызова, и позволяет вызывающему клиенту продолжать свое выполнение. Такая удаленная процедура должна иметь только in-параметры, и любое исключение, возбуждаемое в процессе выполнения удаленной процедуры, будет утеряно.

Когда директива Asynchronous применяется для обычной подпрограммы с in-параметрами, любой вызов этой подпрограммы будет осуществляться асинхронно. Следующий пример демонстрирует описание Asynchronous_RCI.Asynchronous.

    
    
    package Asynchronous_RCI is
       pragma Remote_Call_Interface;
    
       procedure Asynchronous (X : Integer);
       pragma Asynchronous (Asynchronous);
    
       procedure Synchronous  (X : Integer);
    
       type Asynchronous_RAS is access procedure (X : Integer);
       pragma Asynchronous (Asynchronous_RAS);
    end Asynchronous_RCI;
    

    
    
    package Asynchronous_RT is
       pragma Remote_Types;
    
       type Object is tagged limited private;
    
       type Asynchronous_RACW is access all Object'Class;
       pragma Asynchronous (Asynchronous_RACW);
    
       procedure Asynchronous (X : Object);
       procedure Synchronous  (X : in out Object);
       function Create return Asynchronous_RACW;
    
    private
       type Object is tagged limited null record;
    end Asynchronous_RT;
    

Директива Asynchronous применяется к удаленной ссылке на подпрограмму (RAS). Асинхронаая ссылка на подпрограмму может быть как асинхронной, так и синхронной, в зависимости от фактически обозначаемой подпрограммы с помощью этого ссылочного значения. Например, в показанном ниже коде, удаленный вызов (1) является асинхронным, а удаленный вызов (2) - синхронным.

Директива Asynchronous может также применяться для ссылок на удаленные надклассовые типы (RACW). В этом случае, вызов любого метода с in-параметрами всегда будет осуществляться асинхронно. Таким образом, в показанном ниже коде, вызов удаленного метода (3) является асинхронным, а вызов удаленного метода (4) - синхронным.

    
    
    with Asynchronous_RCI, Asynchronous_RT;
    use Asynchronous_RCI, Asynchronous_RT;
    procedure AsynchronousMain is
       RAS  : Asynchronous_RAS;
       RACW : Asynchronous_RACW := Create;
    begin
       --  Асинхронный динамически связываемый удаленный вызов (1)
       RAS := Asynchronous_RCI.Asynchronous'Access;
       RAS (0);  --  Abbrev for RAS.all (0)
       --  Синхронный динамически связываемый удаленный вызов (2)
       RAS := Asynchronous_RCI.Synchronous'Access;
       RAS (0);
       --  Асинхронный динамически связываемый удаленный вызов (3)
       Asynchronous (RACW.all);
       --  Синхронный динамически связываемый удаленный вызов (4)
       Synchronous (RACW.all);
    end AsynchronousMain;
    

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

Для иллюстрации последнего, рассмотрим следующий пример:

    
    
    package Node_2 is
       pragma Remote_Call_Interface;
    
       procedure Send (X : Integer);
       pragma Asynchronous (Send);
    end Node_2;
    

    
    
    package body Node_2 is
       V : Integer := 0;
       procedure Send (X : Integer) is
       begin
          V := X;
       end Send;
    end Node_2;
    

    
    
    package Node_1 is
       pragma Remote_Call_Interface;
    
       procedure Send (X : Integer);
       pragma Asynchronous (Send);
    end Node_1;
    

    
    
    with Node_2;
    package body Node_1 is
       procedure Send (X : Integer) is
       begin
          Node_2.Send (X);
       end Send;
    end Node_1;
    

    
    
    with Node_1, Node_2;
    procedure Non_Deterministic is
    begin
       Node_1.Send (1);
       Node_2.Send (2);
    end Non_Deterministic;
    

Предположим, что выбрана следующая конфигурация: Main располагается на разделе Partition_0, Node_1 - на разделе Partition_1, и Node_2 - на разделе Partition_2 Если процедуры Node_1.Send и Node_2.Send были синхронными или если не было определено времени ожидания для сетевого взаимодействия, то мы получим следующий порядок RPC: Main удаленно вызывает Node_1.Send, которая удаленно вызывает Node_2.Send, что установит V в 1. Затем, Main удаленно вызывает Node_2.Send и установливает V в 2.

Теперь, предположим, что обе процедуры Send являются асинхронными, и что соединение взаимодействия между разделами Partition_1 и Partition_2 очень медленное. В такой ситуации, достаточно часто возникает следующий сценарий. Main удаленно вызывает Node_1.Send и не блокируется. Немедленно, после этого вызова, Main удаленно вызывает Node_2.Send и установливает V в 2. Как только это выполнено, удаленный вызов Node_1.Send завершается на разделе Partition_1 и удаленно вызывается Node_2.Send, который устанавливает V в 1.


Директива All_Calls_Remote

Директива All_Calls_Remote в модуле RCI принудительно направляет вызовы удаленных процедур через подсистему коммуникации (взаимодействия), даже для локальных вызовов. Это облегчает отладку приложения в не-распределенном окружении, которое подобно распределенному, поскольку подсистема взаимодействия (включая процедуры передачи (marshalling и unmarshalling)) может быть оттестирована на одиночном узле.

В некоторых случаях, поведение не-распределенного приложения может отличаться от распределенного приложения, размещенного на одном разделе распределенной программы. Это может произойти при одновременном использовании директив All_Calls_Remote и Asynchronous (для примера см. Директива Asynchronous). Другим случаем является ситуация, когда операции передачи (marshalling) возбуждают исключения. В показанном ниже примере, когда модуль ACR_RCI является пакетом All_Calls_Remote, программа возбуждает исключение Program_Error. Когда модуль ACR_RCI не является пакетом All_Calls_Remote, программа выполняется без сообщений об ошибках.

    
    
    with Ada.Streams; use Ada.Streams;
    package ACR_RT is
       pragma Remote_Types;
       type T is private;
    private
       type T is new Integer;
       procedure Read
         (S : access Root_Stream_Type'Class;
          X : out T);
       procedure Write
         (S : access Root_Stream_Type'Class;
          X : in T);
       for T'Read  use Read;
       for T'Write use Write;
    end ACR_RT;
    

    
    
    package body ACR_RT is
       procedure Read
         (S : access Root_Stream_Type'Class;
          X : out T) is
       begin
          raise Program_Error;
       end Read;
    
       procedure Write
         (S : access Root_Stream_Type'Class;
          X : in T) is
       begin
          raise Program_Error;
       end Write;
    end ACR_RT;
    

    
    
    with ACR_RT; use ACR_RT;
    package ACR_RCI is
       pragma Remote_Call_Interface;
       pragma All_Calls_Remote;
    
       procedure P (X : T);
    end ACR_RCI;
    

    
    
    package body ACR_RCI is
       procedure P (X : T) is
       begin
          null;
       end P;
    end ACR_RCI;
    

    
    
    with ACR_RCI, ACR_RT;
    procedure ACR_Main is
       X : ACR_RT.T;
    begin
       ACR_RCI.P (X);
    end ACR_Main;
    


Настраиваемые категорированные модули

    
    
    generic
    package Generic_RCI is
       pragma Remote_Call_Interface;
    
       procedure P;
    end Generic_RCI;
    

    
    
    with Generic_RCI;
    package RCI_Instantiation is new Generic_RCI;
    pragma Remote_Call_Interface (RCI_Instantiation);
    

    
    
    with Generic_RCI;
    package Normal_Instantiation is new Generic_RCI;
    

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


Зависимости категорированных модулей

Каждая директива категорирования обладает специфическими правилами видимости. Общим правилом является следующее: RCI > RT > SP > Pure, где сравнение показывает допустимую семантическую зависимость. Это подразумевает, что пакет Remote_Types может сделать видимыми в своей спецификации только модули: Remote_Types, Shared_Passive и Pure.


Подсистема взаимодействия разделов

Операции пересылки (marshalling и unmarshalling)

Подсистема взаимодействия разделов (PCS - Partition Communication Subsystem) осуществляет передачу и прием (marshall и unmarshall) данных вызывающего клиента и сервера через поток типа System.RPC.Params_Stream_Type:

    
    
    type Params_Stream_Type
      (Initial_Size : Ada.Streams.Stream_Element_Count) is new
        Ada.Streams.Root_Stream_Type with private;
    

Этот тип является контейнером передачи данных между разделами распределенной программы. Корневым типом является тип Root_Stream_Type, который определяет базовый тип потока и две абстрактные операции: Write, для помещения в поток, и Read, для извлечения из потока, объектов типа Stream_Element_Array, которые являются массивом байтов представляющих индивидуальные данные.

Чтение и запись потоков осуществляется с помощью использования четырех атрибутов:

Любой Ада-компилятор предусматривает, принимаемые по умолчанию, операции 'Read и 'Write. Однако, ответственность за обеспечение принимаемых по умолчанию операций 'Read и 'Write, которые будут надежно работать в гетерогенной архитектуре, возлагается на реализацию подсистемы взаимодействия разделов (см. Гетерогенная система).

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

    
    
    with Ada.Streams; use Ada.Streams;
    package New_Integers is
       pragma Pure;
    
       type New_Integer is new Integer;
    
       procedure Read
         (S : access Root_Stream_Type'Class;
          V : out New_Integer);
       procedure Write
         (S : access Root_Stream_Type'Class;
          V : in New_Integer);
    
       for New_Integer'Read  use Read;
       for New_Integer'Write use Write;
    end New_Integers;
    

    
    
    package body New_Integers is
       procedure Read
         (S : access Root_Stream_Type'Class;
          V : out New_Integer)
       is
          B : String := String'Input (S);
       begin
          V := New_Integer'Value (B);
       end Read;
    
       procedure Write
         (S : access Root_Stream_Type'Class;
          V : in New_Integer)
       is
       begin
          String'Output (S, New_Integer'Image (V));
       end Write;
    end New_Integers;
    

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


Неверная удаленная диспетчеризация

Когда удаленная подпрограмма принимает параметр надклассового типа существует риск использования объекта производного типа, который нельзя переслать. В качестве примера, рассмотрим следующую ситуацию. Предположим, что существует тип Root_Type. Предположим также, что существует удаленная процедура, которая принимает параметр типа Root_Type'Class. В этом случае, пользователь может вызвать эту процедуру и передать ей как аргумент экземпляр типа Derived_Type, который является типом, производным от типа Root_Type, причем, Derived_Type расширяет тип Root_Type дополнительным полем, которое содержит тип задачи. В результате, это приводит к попытке передачи, между разделами распределенной программы, непересылаемого типа данных.

Для предотвращения подобных ситуаций, параграф E.4(18) руководства по языку программирования Ada95 объясняет, что любой фактический тип используемый как параметр удаленного вызова, чей формальный тип является надклассовым типом, должен быть описан в видимой части пакета, который определен как Pure или Remote_Types пакет. Это требование сохраняется также для удаленных функций, которые возвращают значения надклассового типа. Таким образом, фактически используемый тип должен быть приемлемым для непосредственного указания в том месте, в котором указан корневой тип. Если удаленной подпрограмме передан "плохой" объект, то в точке вызова подпрограммы возбуждается исключение Program_Error.


Идентификаторы разделов

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

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

    
    
    with RCI;
    with Ada.Text_IO;
    procedure Check_PID is
    begin
       if RCI'Partition_ID = Check_PID'Partition_ID then
          Ada.Text_IO.Put_Line ("package RCI is configured locally");
       else
          Ada.Text_IO.Put_Line ("package RCI is configured remotely");
       end if;
    end Check_PID;
    


Одновременные удаленные вызовы

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


Согласованность и элаборация

Библиотечный модуль является согласованным, если одна и та же версия его описаний используется всеми модулями, которые от него зависят. Такое же требование предъявляется к модулю, на который ссылаются несколько разделов распределенной программы. Если модуль U - это общий пассивный библиотечный модуль или библиотечный модуль RCI, и он включен в какой-либо раздел P распределенной программы, то при элаборации другого раздела P1 этой же распределенной программы зависящего от модуля U, версия которого отличается, произойдет ошибка связывания. В результате этой ошибки, в процессе элаборации, в одном или обоих разделах распределенной программы может быть возбуждено исключение Program_Error.

Атрибут U'Version выдает строку, которая идентифицирует версию описания модуля U и любого описания модуля от которого он зависит. Атрибут U'Version_Body выдает строку, которая идентифицирует версию тела модуля U. Эти атрибуты используются PCS для проверки согласованности приложения.

После элаборации библиотечных модулей, но перед вызовом головной подпрограммы распределенной программы, PCS проверяет версии RCI-модулей, и только после этого принимает любые входящие RPC. Чтобы гарантировать безопасность вызова принятых "заглушек", любой поступивший RPC остается в ожидании до тех пор, пока раздел распределенной программы не завершит процесс элаборации.


Принудительное завершение (abortion) и прекращение (termination)

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

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


Основные свойства в одном примере

Пример, который показан на рисунке ниже, демонстрирует основные свойства дополнения к стандарту Ada95 для распределенных систем (DSA). Система основана на наборе "фабрик" (factory), "работников" (worker) и одного "хранилища" (storage). Каждая сущность является самостоятельным разделом. "Фабрика" нанимает "работника" из пула работников (hire - 1) и назначает для работника работу (query - 2). "Работник" выполняет работу и сохраняет результат (reply - 3) в общем для всех фабрик хранилище. После этого, "работник" уведомляет "фабрику" о завершении своей работы (notify - 4).

full-ex.fig.jpg

Когда "работник" завершает свою работу, результат работы должен быть сохранен в общем хранилище. Для выполнения этого, мы определяем защищенную область в SP-пакете Storage (см. код ниже). Защищенный объект без входов гарантирует атомарность доступа к этой области.

    
    
    package Storage is
       pragma Shared_Passive;
    
       protected Queue is
          procedure Insert (Q, R : Integer);
          procedure Remove
            (Q : in Integer;
             R : out Integer);
       private
          --  Other declarations
       end Queue;
    end Storage;
    

Общим пакетом является Remote_Types-пакет, который определяет большинство удаленных сервисов показанной системы (см. код примера ниже). Во-первых, мы определяем способ с помощью которого "работники" сигнализируют о завершении своей работы. Этот механизм обратного вызова (callback) реализуется с помощью RAS Notify.

    
    
    with Storage; use Storage;
    package Common is
       pragma Remote_Types;
    
       type Notify is
          access procedure (Q : Integer);
       pragma Asynchronous (Notify);
    
       type Worker is
          abstract tagged limited private;
       procedure Assign
         (W : access Worker;
          Q : in Integer;
          N : in Notify) is abstract;
    
       type Any_Worker is
          access all Worker'Class;
       pragma Asynchronous (Any_Worker);
    
    private
       type Worker is abstract tagged limited null record;
    end Common;
    

Мы описываем абстрактный тэговый тип Worker, который необходим как корневой тип иерархии всех распределенных объектов. Процедура Assign позволяет какой-либо "фабрике" указывать для "работника" работу и способ сигнализирования об окончании работы. Тип Any_Worker - это удаленный надклассовый ссылочный тип (RACW). Другими словами, это ссылка на распределенный объект любого типа, который принадлежит к классу Worker (т.е. производного от типа Worker). Примечательно, что удаленные ссылочные типы (Any_Worker и Notify) описаны как асинхронные. Следовательно, любая переопределенная процедура Assign будет выполняться асинхронно. Чтобы быть асинхронным, какой-либо объект типа Notify должен быть ссылкой на асинхронную процедуру.

Тип New_Worker является производным типом от типа Worker и его процедура Assign - переопределена.

    
    
    with Common, Storage; use Common, Storage;
    package New_Workers is
       pragma Remote_Types;
    
       type New_Worker is new Worker with private;
    
       procedure Assign
         (W : access New_Worker;
          Q : Integer;
          N : Notify);
    private
       type New_Worker is new Worker with record
          NewField : Field_Type; --  [...] Другие поля
       end record;
    end New_Workers;
    

Следующий код демонстрирует как описать следующее поколение "работников" New_New_Worker от первого поколения New_Worker. Как говорилось ранее, этот RT-пакет может дублироваться на различных разделах распределенного приложения для получения нескольких типов "работников", а также нескольких удаленных "работников".

    
    
    with Common, Storage, New_Workers; use Common, Storage, New_Workers;
    package New_New_Workers is
       pragma Remote_Types;
    
       type New_New_Worker is new New_Worker with private;
    
       procedure Assign
         (W : access New_New_Worker;
          Q : Integer;
          N : Notify);
    private
       type New_New_Worker is new New_Worker with record
          NewField : Field_Type; --  [...] Другие поля
       end record;
    end New_New_Workers;
    

В следующем коде, мы описываем уникальное место, в котором "работники" ожидают работу. Пакет Worker_City является пакетом Remote_Call_Interface, который обслуживает "наем" и "увольнение" "работников". В отличие от пакетов Remote_Types, пакеты Remote_Call_Interface не могут дублироваться и назначаются на один определенный раздел распределенной программы.

    
    
    with Common; use Common;
    package Worker_City is
       pragma Remote_Call_Interface;
    
       procedure Insert (W : in  Any_Worker);
       procedure Remove (W : out Any_Worker);
    end Worker_City;
    

Для того чтобы использовать еще больше возможностей, предоставляемых дополнением к стандарту Ada95 для распределенных систем (DSA), пакет Factory можно описать как настраиваемый RCI-пакет. В этом случае, каждая конкретизация настраиваемого пакета будет определять новую "фабрику" (см. пример ниже). Чтобы быть RCI-пакетом, экземпляр настроенного модуля, который получен в результате конкретизации, должен быть категорирован.

    
    
    with Storage; use Storage;
    generic
    package Factory is
       pragma Remote_Call_Interface;
    
       procedure Notify (Q : Integer);
       pragma Asynchronous (Notify);
    end Factory;
    

    
    
    with Factory;
    package NewFactory is new Factory;
    pragma Remote_Call_Interface (NewFactory);
    


Основы GLADE

Эта глава описывает обычный способ использования GLADE для компиляции распределенных Ада-программ.


Знакомство с GLADE

Распределенное приложение Ada95 состоит из нескольких разделов, которые могут одновременно выполняться на одном и том же компьютере, или даже могут быть распределены между компьютерами сети. Способ взаимодействия разделов описывается в приложении E руководства по языку программирования Ada95 (Ada 95 Reference Manual, Annex E).

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

Дополнение стандарта Ada95 для распределенных систем (DSA - Distributed Systems Annex) не описывает способ конфигурирования распределенного приложения. Таким образом, определение содержимого разделов распределенной программы и указание компьютеров, на которых эти разделы будут исполняться, возлагается на пользователя.

Инструментальная утилита gnatdist и ее язык конфигурирования позволяют пользователю разделить свою программу на разделы и указать компьютеры на которых будут исполняться индивидуальные разделы распределенного приложения.

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


Как конфигурировать распределенное приложение


Опции командной строки gnatdist

    
    
    gnatdist [опции] configuration-file [list-of-partitions]
    

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

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

При указании списка разделов в командной строке запуска gnatdist осуществляется построение только тех разделов, которые указаны в командной строке. Следующий пример демонстрирует, что пользователь может напечатать:

    
    
    gnatdist <configuration> <partition_2> <partition_3>
    


Действия выполняемые gnatdist

Ниже перечислены действия выполняемые gnatdist в процессе построения распределенного приложения:


Язык конфигурирования

Язык конфигурирования является Ада-образным языком, который развивается по мере развития возможностей GLADE. Большинство атрибутов и директив могут быть переопределены во время выполнения с помощью использования аргументов командной строки или переменных окружения.


Зарезервированные слова

Все зарезервированные (ключевые) слова Ады являются также зарезервированными словами языка конфигурирования GLADE. Утилита gnatdist генерирует полноценный Ада-код, который необходим для построения различных исполняемых файлов. С целью предотвращения конфликтов именования, между Адой и языком конфигурирования GLADE, зарезервированы все ключевые слова Ады, даже если эти слова непосредственно не используются языком конфигурирования GLADE.

Кроме того, существуют три новых зарезервированных слова:


Директивы и спецификаторы представления

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

    
    
    PRAGMA ::=
       pragma PRAGMA_NAME [(PRAGMA_ARGUMENTS)];
    

Также, существует возможность изменения принимаемого по умолчанию поведения всех разделов (или каналов) путем указания атрибутов, которые относятся к предопределенному типу Partition (или Channel).

    
    
    REPRESENTATION_CLAUSE ::=
       for Partition'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS;
     | for Channel'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS;
    

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

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS;
    

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


Описание конфигурации

Распределение одной или нескольких Ада-программ описывается в одном модуле конфигурации. Этот модуль конфигурации содержит часть спецификации и необязательную часть тела. Модуль конфигурации описывается подобно процедуре Ады. Для этого используется зарезервированное слово configuration.

    
    
    CONFIGURATION_UNIT ::=
       configuration IDENTIFIER is
          DECLARATIVE_PART
       [begin
          SEQUENCE_OF_STATEMENTS]
       end [IDENTIFIER];
    


Описание раздела

В части описаний пользователь описывает разделы своего приложения, и может изменить их поведение по умолчанию. Утилита gnatdist предусматривает предопределенный тип Partition. Пользователь может описать список разделов, и может также инициализировать эти разделы начальным списком Ада-модулей.

    
    
    DECLARATIVE_PART ::= {DECLARATIVE_ITEM}
    
    DECLARATIVE_ITEM ::=
       PARTITION_DECLARATION
     | CHANNEL_DECLARATION
     | REPRESENTATION_CLAUSE
     | SUBPROGRAM_DECLARATION
     | PRAGMA
    
    SUBPROGRAM_DECLARATION ::=
       MAIN_PROCEDURE_DECLARATION
     | PROCEDURE_DECLARATION
     | FUNCTION_DECLARATION
    
    PARTITION_DECLARATION ::=
       DEFINING_IDENTIFIER_LIST : Partition
          [:= ENUMERATION_OF_ADA_UNITS];
    
    DEFINING_IDENTIFIER_LIST ::=
       DEFINING_IDENTIFIER {, DEFINING_IDENTIFIER}
    
    STATEMENT ::=
       IDENTIFIER := ENUMERATION_OF_ADA_UNITS;
    
    SEQUENCE_OF_STATEMENTS ::=
       STATEMENT {STATEMENT}
    

После описания раздел содержит пустой список Ада-модулей. Знак операции ":=" добавляет список Ада-модулей, указанных справа, к текущему списку Ада-модулей, который уже отображен на раздел распределенного приложения. Эта операция не является деструктивной. Проверка того, что какой-либо модуль является релевантным (актуальным) Ада-модулем или нет осуществляется позже, back-end-ом gnatdist. Подобные присваивания могут осуществляться как в части описаний, так и в части тела.

    
    
    ENUMERATION_OF_ADA_UNITS ::= ({ADA_UNIT {, ADA_UNIT}});
    


Описание расположения

В языке конфигурации GLADE существует несколько видов размещений (location) Мы рассмотрим их далее, а здесь представим краткий обзор этих размещений:

Размещение (location) состоит из имени поддержки (support name) и определенных для этой поддержки данных. Например, сетевое размещение (network location) состоит из имени протокола, подобного tcp, и данных протокола, подобных <machine>:<port>. Размещение накопителя данных (storage location) состоит из имени поддержки накопителя, подобного dfs (для Distributed File System - распределенная файловая система), и данных поддержки накопителя, подобных имени каталога /dfs/glade.

    
    
    LOCATION      ::= ([Support_Name =>] STRING_LITTERAL,
                       [Support_Data =>] STRING_LITTERAL)
    
    LOCATION_LIST ::= (LOCATION [,LOCATION)])
    

Примечательно, что размещение может иметь неопределенные или не полные данные поддержки. В этом случае, поддержка может вычислить данные поддержки. Например, ("tcp", "") указывает используемый протокол, но данные протокола <machine>:<port> должны быть определены протоколом самостоятельно.

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

Если одному разделу необходимо взаимодействие с другим разделом, как только список размещений последнего известен, вызывающий раздел будет использовать первое расположение вызываемого раздела, чей протокол локально доступен. Например, если вызываемый раздел экспортирует три размещения: ("N1", "D1"), ("N2", "D2") и ("N3", "D3"), - то вызывающий раздел с локально доступными протоколами N2 и N3 будет пытаться взаимодействовать с вызываемым разделом используя протокол с именем N2 и определенными данными D2.


Атрибут раздела 'Main

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

В не-распределенном варианте, пользователь исполняет только один главный исполняемый модуль, имя которого, возможно, соответствует имени главного модуля приложения пользователя. При использовании gnatdist, в распределенном варианте, главный исполняемый модуль, с именем, которое соответствует имени главного модуля приложения пользователя, отвечает за запуск всего распределенного приложения. Следовательно, пользователь может запустить свое приложение таким же способом который используется для запуска не-распределенного варианта приложения.

Исходя из этого, язык конфигурации предусматривает способ описания главной процедуры не-распределенного приложения.

    
    
    MAIN_PROCEDURE_DECLARATION ::=
       procedure MAIN_PROCEDURE_IDENTIFER is in PARTITION_IDENTIFIER;
    

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

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

    
    
    PROCEDURE_DECLARATION ::=
       procedure PROCEDURE_IDENTIFIER;
    
    REPRESENTATION_CLAUSE :=
       for PARTITION_IDENTIFIER'Main use PROCEDURE_IDENTIFIER;
    


Директива Starter

По умолчанию, главный исполняемый модуль является полной стартерной процедурой Ады. Это подразумевает, что такая процедура стартует (запускает) все остальные разделы из Ада-программы. Директива Starter позволяет пользователю требовать использование того или иного стартера. Когда хост раздела не определен статически (см. Атрибут раздела 'Host), подпрограмма-стартер, во время выполнения, будет интерактивно запрашивать указание хоста раздела.

    
    
    CONVENTION_LITERAL ::= Ada   |
                           Shell |
                           None
    
    PRAGMA ::=
       pragma Starter ([Convention =>] CONVENTION_LITERAL);
    


Директива Boot_Location

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

    
    
    PRAGMA ::=
       PRAGMA_WITH_NAME_AND_DATA
     | PRAGMA_WITH_LOCATION
     | PRAGMA_WITH_LOCATION_LIST
    
    PRAGMA_WITH_NAME_AND_DATA ::=
       pragma Boot_Location
         ([Protocol_Name =>] STRING_LITERAL,
          [Protocol_Data =>] STRING_LITERAL);
    
    PRAGMA_WITH_LOCATION ::=
       pragma Boot_Location ([Location =>] LOCATION);
    
    PRAGMA_WITH_LOCATION_LIST ::=
       pragma Boot_Location ([Locations =>] LOCATION_LIST);
    

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

Примечание:
В настоящее время директива Boot_Server считается устаревшей. Вместо нее рекомендуется использоват директиву Boot_Location. Такая редакция признана более согласованной с остальной частью языка конфигурации (см, Self_Location Опция раздела self_location и Data_Location Опция раздела data_location).


Атрибут раздела 'Self_Location

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

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Self_Location use LOCATION;
     | for PARTITION_IDENTIFIER'Self_Location use LOCATION_LIST;
    

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

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


Атрибут раздела 'Passive

По умолчанию, какой-либо раздел считается активным разделом. Атрибут 'Passive позволяет указывать пассивный раздел. В таком случае, gnatdist будет проверять, что на этот раздел отображаются только пассивные общие модули. Поскольку такой раздел не может себя зарегистрировать, его расположение жестко кодируется во всех разделах, которые зависят от его общих пассивных модулей.

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Passive use BOOLEAN_LITERAL;
    


Атрибут раздела 'Data_Location

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

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Data_Location use LOCATION;
     | for PARTITION_IDENTIFIER'Data_Location use LOCATION_LIST;
    

Когда атрибут 'Data_Location указывается для определенного раздела, модули поддержки накопителя данных, которые необходимы для этого раздела, будут компоноваться в результирующий исполняемый модуль раздела. По умолчанию, когда атрибут 'Data_Location не переопределяется, используется поддержка dfs, которая компонуется в результирующий исполняемый модуль раздела. Распределенная файловая система dfs (Distributed File System) является доступной поддержкой накопителя поскольку разделы могут использовать общие файлы.

Возможность отображения различных общих пассивных модулей данного раздела на другое размещение накопителя данных отсутствует. GLADE требует, чтобы все общие пассивные модули данного раздела отображались на одну и ту же поддержку накопителя. Когда атрибут 'Data_Location, указанный для какого-либо раздела, является списком размещений, все модули поддержки накопителя данных, которые необходимы для этого раздела, будут компоноваться в результирующий исполняемый модуль раздела. По умолчанию, осуществляется активация только первого указанного размещения. Пользователь может изменить выбор активированной поддержки на какое-либо другое размещение, которое указано в списке размещений. Это может быть выполнено с помощью использования опции раздела Data_Location (см. Опция раздела data_location).

Поскольку пассивный раздел не может быть активирован, то возможность указания списка размещений как атрибута 'Data_Location отсутствует. Также, отсутствует возможность динамического изменения расположения пассивного раздела.


Атрибут раздела 'Host

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

    
    
    FUNCTION_DECLARATION ::=
       function FUNCTION_IDENTIFIER
         (PARAMETER_IDENTIFIER : [in] String)
          return String;
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Host use STRING_LITERAL;
     | for PARTITION_IDENTIFIER'Host use FUNCTION_IDENTIFIER;
    

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

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


Директива Import

Язык конфигурации GLADE позволяет использовать две разновидности подпрограмм. Главная процедура используется как атрибут раздела 'Main, а функция используется как атрибут раздела 'Host

    
    
    SUBPROGRAM_DECLARATION ::=
         procedure MAIN_PROCEDURE_IDENTIFIER is in PARTITION_NAME;
       | procedure PROCEDURE_IDENTIFIER;
       | function FUNCTION_IDENTIFIER
            (PARAMETER_IDENTIFIER : [in] String)
             return String;
    

Функция может быть обычной функцией Ады (по умолчанию) или скриптом командного интерпретатора shell. Для импорта скрипта командного интерпретатора shell должна использоваться директива Import:

    
    
    PRAGMA ::=
       pragma Import
          ([Entity        =>] FUNCTION_IDENTIFIER,
           [Convention    =>] CONVENTION_LITERAL,
           [External_Name =>] STRING_LITERAL);
    
    pragma Import (Best_Node, Shell, "best-node");
    

В этом случае, GLADE PCS вызывает скрипт командного интерпретатора shell, указывая имя раздела в качестве аргумента командной строки. При этом подразумевается, что скрипт командного интерпретатора shell возвратит имя хоста раздела (см. Атрибут раздела 'Host).


Атрибут раздела 'Directory

Атрибут 'Directory позволяет пользователю указать в каком каталоге располагается исполняемый модуль раздела. Этот атрибут может быть полезен при построении гетерогенных систем, когда пользователю необходимо хранить исполняемые модули, предназначенные для определенной целевой платформы, в едином каталоге. Кроме того, указание каталога может оказаться полезным когда исполняемый модуль раздела не является непосредственно доступным в рабочем окружении пользователя. Например, когда вызывается удаленная команда, подобная rsh, исполняемый модуль раздела должен быть доступен для пользователя посредством переменной окружения PATH. В результате указания атрибута 'Directory, будет использоваться полное имя исполняемого модуля раздела.

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Directory use STRING_LITERAL;
    


Атрибут раздела 'Command_Line

Пользователю может понадобиться возможность передачи аргументов в командной строке запуска исполняемого модуля раздела. Однако, когда раздел запускается автоматически, посредством запуска из главного раздела, командная строка запуска содержит только аргументы, которые необходимы GLADE. Для указания дополнительных аргументов командной строки пользователь может использовать атрибут 'Command_Line.

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTIITON_IDENTIFIER'Command_Line use STRING_LITERAL;
    


Атрибут 'Termination для разделов

Руководство по языку программирования Ada95 (The Ada95 Reference Manual) не предусматривает какое-либо правило обработки глобального завершения распределенного приложения (см. Принудительное завершение (abortion) и прекращение (termination)).

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

    
    
    TERMINATION_LITTERAL ::= Global_Termination |
                             Local_Termination  |
                             Defered_Termination
    
    REPRESENTATION_CLAUSE ::=
       for PARTIITON_IDENTIFIER'Termination use TERMINATION_LITERAL;
    

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


Атрибут раздела 'Reconnection

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

Когда раздел "разрушается" или останавливается, кому-либо может понадобиться перестартовать этот раздел и, возможно, восстановить его состояние (например для пакетов Shared_Passive). В подобном случае, этот раздел уже известен другим разделам и, возможно, отмечен как "мертвый" раздел. Может быть выбрана различная политика переподключения:

    
    
    RECONNECTION_LITTERAL ::= Reject_On_Restart  |
                              Fail_Until_Restart |
                              Wait_Until_Restart
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Reconnection use RECONNECTION_LITTERAL;
    


Описание канала

Язык конфигурации позволяет описывать не только разделы распределенного приложения, но и соединения между ними. Такие соединения называют каналами (Channel), которые предоставляют двунаправленное соединение между двумя разделами.

    
    
    CHANNEL_DECLARATION ::=
       CHANNEL_IDENTIFIER : Channel
          [:= PARTITION_PEER];
    
    PARTITION_PEER ::= (PARTITION_IDENTIFIER, PARTITION_IDENTIFIER);
    

Соединение разделов является парой различных имен разделов. Порядок перечисления не имеет значения. Естественно, что описание разделов должно предшествовать описанию каналов.

    
    
    A_Channel : Channel := (Partition_1, Partition_2);
    

Показанный выше пример описывает имя A_Channel как соединение между разделами Partition_1 и Partition_2. Следует заметить, что для двух одних и тех же разделов нельзя описать более одного канала взаимодействия.


Атрибут 'Filter для раздела и канала

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

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

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

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

    
    
    A_Channel : Channel := (Partition_1, Partition_2);
    
    for A_Channel'Filter use "ZIP";
    

Показанный пример указывает, что все пересылаемые через соединительный канал данные должны быть преобразованы с помощью фильтра This specifies that all data sent over this channel should be transformed by the filter named ZIP (Фильтр с именем ZIP должен существовать, а его реализация должна присутствовать в пакете System.Garlic.Filters.Zip).

Может оказаться полезной возможность указания того, что раздел должен использовать какой-либо определенный фильтр для выполнения всех удаленных вызовов, вне зависимости от канала (например, вне зависимости от раздела, который принимает удаленный вызов). Это можно осуществить используя атрибут 'Filter для раздела:

    
    
    for Partition_1'Filter use "ZIP";
    

или

    
    
    for Partition'Filter use "ZIP";
    

Позднее, можно установить фильтр по умолчанию для всех разделов приложения. Первоначально фильтр по умолчанию установлен только для раздела Partition_1. Также, существует возможность применения фильтра по умолчанию с последующим переопределением фильтра по умолчанию для конкретного канала:

    
    
    My_Channel : Channel := (Partition_1, Partition_2);
    
    for My_Channel'Filter  use "ZIP";
    for Partition_1'Filter use "Some_Other_Filter";
    

Это указывает, что раздел Partition_1 использует фильтр Some_Other_Filter для всех удаленных вызовов за исключением любого взаимодействия с разделом Partition_2, где должен использоваться фильтр ZIP.

Утилита gnatdist заботится о проверке согласованности определений фильтров. Например, множество определений фильтров для одного и того же канала не допускается. Фильтрация активируется только в результате явного указания в конфигурационном файле.

    
    
    REPRESENTATION_CLAUSE ::=
       for CHANNEL_IDENTIFIER'Filter use STRING_LITERAL;
     | for PARTITION_IDENTIFIER'Filter use STRING_LITERAL;
    


Директива Registration_Filter

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

    
    
    PRAGMA ::=
       pragma Registration_Filter ([Filter =>] STRING_LITERAL);
    


Директива Version

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

    
    
    PRAGMA ::=
       pragma Version ([Check =>] BOOLEAN_LITERAL);
    


Атрибут раздела 'Task_Pool

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

    
    
    REPRESENTATION_CLAUSE ::=
       for PARTITION_IDENTIFIER'Task_Pool use TASK_POOL_SIZE_ARRAY;
    
    TASK_POOL_SIZE_ARRAY ::=
      (NATURAL_LITERAL,  --  Task Pool Minimum Size
       NATURAL_LITERAL,  --  Task Pool High Size
       NATURAL_LITERAL); --  Task Pool Maximum Size
    

Чтобы иметь в пуле задач одновременно только один активный удаленный вызов, пул задач должен быть сконфигурирован следующим образом:

    
    
    for Partition'Task_Pool use (0, 0, 1);
    


Полный пример

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

    
    
    01 configuration MyConfig is
    02
    03   Partition_1 : Partition := ();
    04   procedure Master_Procedure is in Partition_1;
    05
    06   Partition_2, Partition_3 : Partition;
    07
    08   for Partition_2'Host use "foo.bar.com";
    09
    10   function Best_Node (Partition_Name : String) return String;
    11   pragma Import (Shell, Best_Node, "best-node");
    12   for Partition_3'Host use Best_Node;
    13
    14   Partition_4 : Partition := (RCI_B5);
    15
    16   for Partition_1'Directory use "/usr/you/test/bin";
    17   for Partition'Directory use "bin";
    18
    19   procedure Another_Main;
    20   for Partition_3'Main use Another_Main;
    21
    22   for Partition_3'Reconnection use Block_Until_Restart;
    23   for Partition_4'Command_Line use "-v";
    24   for Partition_4'Termination use Local_Termination;
    25
    26   pragma Starter (Method => Ada);
    27
    28   pragma Boot_Server
    29     (Protocol_Name => "tcp",
    30      Protocol_Data => "`hostname`:`unused-port`");
    31
    32   pragma Version (False);
    33
    34   Channel_1 : Channel := (Partition_1, Partition_4);
    35   Channel_2 : Channel := (Partition_2, Partition_3);
    36
    37   for Channel_1'Filter use "ZIP";
    38   for Channel_2'Filter use "My_Own_Filter";
    39   for Partition'Filter use "ZIP";
    40
    41   pragma Registration_Filter ("Some_Filter");
    42
    43 begin
    44    Partition_2 := (RCI_B2, RCI_B4, Normal);
    45    Partition_3 := (RCI_B3);
    46 end MyConfig;
    
  1. Строка 01 Обычно после создания файла конфигурации пользователь выполняет команду:

      
      
      gnatdist myconfig.cfg
      

    Если пользователю необходимо построение только некоторых разделов, то он может указать их в командной строке gnatdist следующим образом:

      
      
      gnatdist myconfig.cfg partition_2 partition_3
      

    Префикс имени файла должен быть таким же как имя модуля конфигурации. Для данного примера - myconfig.cfg. Суффикс имени файла должен быть cfg. Для данного распределенного приложения пользователь может иметь столько файлов конфигурации, сколько ему необходимо.

  2. Строка 04 Раздел Partition_1 не содержит пакетов RCI. Однако, он будет содержать главную процедуру распределенного приложения, которая, для данного примера, будет называться Master_Procedure. Если строка "procedure Master_Procedure is in Partition_1;" была опущена, то раздел Partition_1 будет полностью пустым. В реальности, это запрещено, поскольку раздел должен содержать хотя бы один библиотечный модуль.

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

  3. Строка 08 Указывает хост на котором будет выполняться раздел Partition_2.
  4. Строка 12 Использование значения возвращаемого программой для получения, во время выполнения, имени хоста на котором будет выполняться раздел Partition_3. Например, скрипт best-node, командного интерпретатора shell, который принимает имя раздела как параметр, и возвращает строку с именем машины на которой будет запущен раздел Partition_3.
  5. Строка 14 Для раздела Partition_4, который содержит один пакет RCI (пакет RCI_B5), не определен хост. При запуске, скрипт запуска будет интерактивно запрашивать имя хоста для этого раздела.
  6. Строка 16 Указывает каталог в котором хранится исполняемый модуль раздела Partition_1.
  7. Строка 17 Указывает каталог в котором храняться исполняемые модули разделов (кроме Partition_1, см. Директивы и спецификаторы представления). По умолчанию - текущий каталог.
  8. Строка 20 Указывает подпрограмму, которая должна быть использована как главная подпрограмма данного раздела.
  9. Строка 22 Указывает политику переподключения в случае разрушения раздела Partition_3. Любая попытка переподключения к разделу Partition_3, когда этот раздел "умер", будет блокироваться до рестарта раздела Partition_3. По умолчанию, любой рестарт отбрасывается (Reject_On_Restart). Другая политика заключается в возбуждении исключения Communication_Error при попытке переподключения до рестарта раздела Partition_3.
  10. Строка 23 Указывает дополнительные аргументы командной строки, которые передаются при запуске раздела.
  11. Строка 24 Указывает механизм завершения выполнения раздела Partition_4. По умолчанию, вычисляется условие глобального распределенного завершения. При указании Local_Termination, раздел завершает выполнение как только обнаружено условие локального завершения (стандартное завершение Ада-программы).
  12. Строка 26 Указывает необходимый пользователю вид метода запуска. Существует три разновидности: Shell, Ada и None. Указание Shell - строит соответствующий скрипт командного интерпретатора. Все разделы будут запускаться из скрипта командного интерпретатора. При выборе Ada, для запуска различных разделов будет использоваться главная Ада-процедура. При выборе None, ответственность за запуск разделов возлагается на пользователя и пользователь должен запускать каждый раздел вручную.

    Ести указание стартера отсутствует, то будет использоваться Ада-стартер.

    В этом примере, разделы Partition_2, Partition_3 и Partition_4 будут запускаться (стартовать) разделом Partition_1 (например, из Ада-процедуры Master_Procedure).

  13. Строка 30 Указывает использование какого-либо частного сервера загрузки. Это может быть полезно когда порт по умолчанию, который используется GLADE PCS (случайно выбранный при инсталляции GLADE) уже назначен и используется для других целей.
  14. Строка 32 Существует ошибка элаборации раздела распределенной программы, которая содержит компилируемый модуль зависящий от отличной версии описаний библиотечного модуля RCI, который включен в раздел к которому назначен библиотечный модуль RCI. Когда директива Version установлена в False, такая проверка согласованности не осуществляется.
  15. Строка 35 Определяет два канала. Другие каналы взаимодействия между разделами остаются неизвестными.
  16. Строка 37 Использовать прозрачной компрессии/декомпрессии для аргументов и возвращаемых результатов, при любых удаленных вызовах на канале Channel_1, то есть, между разделами Partition_1 и Partition_4.
  17. Строка 38 Использовать фильтр My_Own_Filter на любом описываемом канале, то есть, на каналах Channel_1 и Channel_2. Поскольку атрибут фильтра для канала Channel_1 уже назначен, то это распространяется только на канал Channel_2. Этот фильтр должен быть реализован в пакете System.Garlic.Filters.My_Own_Filter.
  18. Строка 39 Для всех данных взаимодействия, пересылаемых между разделами, использовать фильтр ZIP (то есть, для принимаемых удаленных вызовов и для вызовов осуществляемых разделом).
  19. Строка 41 Фильтр Some_Filter будет использоваться для обмена параметрами фильтров между двумя разделами. Сам фильтр Some_Filter должен представлять алгоритм, который не нуждается в дополнительных параметрах для повторного фильтрования. Этот фильтр должен быть реализован в пакете System.Garlic.Filters.Some_Filter.
  20. Строка 43 Тело конфигурации - не обязательно. Пользователь может иметь полное описание своей конфигурации, размещенное только в описательной части.
  21. Строка 44 Раздел Partition_2 содержит два пакета RCI (RCI_B2 и RCI_B4) и один обычный пакет. Обычный пакет не категорирован.
  22. Строка 45 Раздел Partition_3 содержит один пакет RCI (RCI_B3).


Опции командной строки для раздела распределенной программы

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

Переменная окружения (AA_BB_CC) может быть установлена в значение ожидаемого типа. При запуске приложения в командном интерпретаторе, значение, которое назначено в файле конфигурации, заменяется значением переменной окружения. Когда командным интерпретатором пользователя является sh, bash или zsh, для установки переменной окружения (AA_BB_CC) можно напечатать команду:

    
    
    AA_BB_CC=<x>
    export AA_BB_CC
    

В случае, когда пользователь использует командный интерпретатор csh или tcsh, можно напечатать команду:

    
    
    setenv AA_BB_CC <x>
    

В обоих примерах, <x> является значением ожидаемого типа.

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

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

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

Размещение может быть собрано в единую строку с целью его использования как опции командной строки или как переменной окружения. Форматированные строки должны соответствовать виду: <support_name>://<support_data>. Наиболее общим видом строки сетевого размещения является tcp://<machine>:<port>, что подразумевает, что именем протокола будет tcp, данными протокола, которые специфичны для имени протокола, является <machine>:<port>.

Следует заметить, что список размещений также может быть собран в единую строку. В этом случае, строки размещения разделяются символами пробела. Для обеспечения возможности использования списка размещений как опции командной строки, строка списка может быть заключена в кавычки. Наиболее общим случаем строки сетевых размещений является "tcp://<machine>:<port1> tcp://<machine>:<port2>".


Опция раздела boot_location

Эта опция устанавливает размещение сервера загрузки (см. Директива Boot_Location).

Переменная окружения Опция командной строки Тип
BOOT_LOCATION --boot_location Formatted String

Форматированная строка должна соответствовать нотации размещения (см. Описание расположения и Опции командной строки для раздела распределенной программы). Наиболее часто, она будет иметь вид tcp://<machine>:<port>.


Опция раздела self_location

Эта опция устанавливает текущее размещение раздела (см. Атрибут раздела 'Self_Location).

Переменная окружения Опция командной строки Тип
SELF_LOCATION --self_location Formatted String

Переменная окружения Опция командной строки Тип
SELF_LOCATION --self_location Formatted String

Форматированная строка должна соответствовать нотации размещения (см. Описание расположения и Опции командной строки для раздела распределенной программы). Наиболее часто, она будет иметь вид tcp://<machine>:<port>.


Опция раздела data_location

Эта опция устанавливает размещение накопителя данных на который отображаются пассивные общие модули текущего раздела (см. Атрибут раздела 'Data_Location). Это размещение должно быть совместимым с одним из размещений предусмотренных в файле конфигурации. Это подразумевает, что опция раздела data_location должна использовать имя одного из размещений накопителя данных, которые указаны в файле конфигурации.

Переменная окружения Опция командной строки Тип
DATA_LOCATION --data_location Formatted String

Переменная окружения Опция командной строки Тип
DATA_LOCATION --DATA_location Formatted String

Форматированная строка должна соответствовать нотации размещения (см. Описание расположения и Опции командной строки для раздела распределенной программы). Наиболее часто, она будет иметь вид dfs://<directory>.


Опция раздела nolaunch

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

Переменная окружения Опция командной строки Тип
NOLAUNCH --nolaunch None


Опция раздела detach

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

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

Переменная окружения Опция командной строки Тип
DETACH --detach None


Опция раздела slave

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

Переменная окружения Опция командной строки Тип
SLAVE --slave None


Опция раздела boot_mirror

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

Переменная окружения Опция командной строки Тип
BOOT_MIRROR --boot_mirror None


Опция раздела mirror_expected

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

Переменная окружения Опция командной строки Тип
MIRROR_EXPECTED --mirror_expected None


Опция раздела connection_hits

Эта опция устанавливает для раздела количество попыток подключения к серверу загрузки, после достижения которого (при неудаче) будет возбуждено исключение Communication_Error.

Переменная окружения Опция командной строки Тип
CONNECTION_HITS --connection_hits Natural


Опция раздела reconnection

Эта опция устанавливает политику переподключения (см. Атрибут раздела 'Reconnection).

Переменная окружения Опция командной строки Тип
RECONNECTION --reconnection Reconnection_Type


Опция раздела termination

Эта опция устанавливает политику завершения (см. Атрибут 'Termination для разделов).

Переменная окружения Опция командной строки Тип
TERMINATION --termination Termination_Type


Опция раздела trace

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

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

По умолчанию, имя файла трассировки соответствует имени исполняемого модуля раздела (то есть, строка, возвращаемая стандартной подпрограммой Ada.Command_Line.Command_Name) с суффиксом .ptf (Partition Trace File). Файл трассировки будет содержать все входящие сообщения, которые переданы разделу. Имя файла трассировки может быть изменено с помощью использования опции командной строки --trace_file <othername>.

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


Опция раздела replay

Для того, чтобы повторно проиграть выполнение раздела, которое первоначально было оттрассировано, необходимо использовать в командной строке аргумент --replay. Дополнительно, необходимо указать специальное размещение сервера загрузки "replay://", то есть использовать дополнительный аргумент командной строки --boot_location replay://.

Для повторного проигрыша оттрассированного выполнения раздела, чей исполняемый модуль имеет имя part, мы используем следующую команду запуска:

    
    
    % part [--nolaunch] [--slave] --replay --boot_location replay://
    

При этом возможно выполнение раздела под управлением отладчика (такого как gdb).

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

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


Средства отладки

Для трассировки работы своего приложения, пользователь может установить в true две переменные окружения. Переменная окружения S_RPC предусматривает информацию о том, что происходит в процессе выполнения удаленных вызовов процедур (рассматривается в System.RPC - s-rpc.adb). Переменная окружения S_PARINT предусматривает информацию о разделах и состоянии модулей (рассматривается в System.Partition_Interface - s-parint.adb). Например, используя командный интерпретатор sh, bash или zsh, для установки этих переменных окружения в true можно использовать следующее:

    
    
    S_RPC=true;    export S_RPC
    S_PARINT=true; export S_PARINT
    


Иерархия файлов GLADE

Все промежуточные файлы GLADE (объектные файлы, ...) храняться в общем каталоге с именем "dsa". Потлователь может удалить весь этот каталог и все его содержимое, если он не предполагает осуществление пересборки своих распределенных приложений.


Внутренняя организация GLADE

GLADE PCS (Partition Communication Subsystem - подсистема взаимодействия разделов) называется GARLIC (Generic Ada Reusable Library for Interpartition Communication - общая многократно используемая Ада-библиотека для взаимодействия между разделами). Большинство рассмотренных ранее свойств, таких как фильтрация, трассировка / повторный проигрыш, завершение, переподключение, согласованность версий и удаленный вызов, обеспечиваются с помощью специфических свойств gnatdist. Некоторые из этих свойств являются для пользователя неконфигурируемыми.


Архитектура GLADE PCS

Одними из первых шагов, при запуске раздела на выполнение, являются регистрация сервера идентификаторов разделов и регистрация сервера имен RCI. Оба этих сервера располагаются на сервере загрузки (boot server).

Сервер идентификаторов разделов используется для выделения уникального идентификатора раздела при регистрации нового раздела. Кроме того, этот сервер отвечает на информационные запросы от других разделов. Такая информация включает IP-адрес, номер порта на котором раздел ожидает запросы и все параметры конфигурации (политика завершения, политика переподключения, фильтры, ...).

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

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

Сервер загрузки является первым зеркалом загрузки системы. Новый раздел, который описан как зеркало загрузки, включается в группу зеркал загрузки. Группа зеркал загрузки работает как кольцо с маркером (token ring): любой запрос от нового раздела к зеркалу загрузки посылается в кольцо через маркер. Запрос может однократно или двукратно пересечь кольцо перед тем как он будет воспринят всеми зеркалами загрузки.

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


Гетерогенная система

Окружение GNAT предусматривает потоковые атрибуты по умолчанию, исключая не-удаленные ссылочные типы (см. Передача динамических структур данных и Операции пересылки (marshalling и unmarshalling)). Реализация потоковых атрибутов по умолчанию для предопределенных типов находится в System.Stream_Attributes (s-stratt.adb).

Реализация GLADE переопределяет установленные по умолчанию подпрограммы пересылки (marshalling и unmarshalling) GNAT своими собственными подпрограммами, которые форматируют данные в соответствии с протоколом подобным протоколу XDR. Таким образом, любое приложение GLADE будет способно работать в гетерогенном окружении.

Если пользователь желает сохранить использование атрибутов по умолчанию GNAT, в целях повышения производительности, или желает использовать другой протокол передачи предопределенных типов, он может заменить s-stratt.adb на более подходящую реализацию.


Аллокация ID для разделов

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


Выполнение одновременных удаленных вызовов

Когда на одном разделе происходит множество вызовов удаленных подпрограмм они обрабатываются множеством анонимных задач. Число задач, в пуле анонимных задач, может быть указано тремя числами (см. Атрибут раздела 'Task_Pool). Таким образом, пользователь должен осуществлять синхронизацию доступа к глобальным данным в модулях Remote Call Interface (RCI) или Remote Types (RT) для предотвращения одновременного доступа к данным. Если пользователь желает подавить свойства множесма запросов, он может принудительно установить конфигурацию пула анонимных задач в (0 | 1, 0 | 1, 1). Это подразумевает выполнение не более одной анонимной задачи одновременно.


Наследование приоритетов

Сохранение приоритета вызывающего клиента, во время удаленного вызова процедуры, зависит от компилятора. Фактически, зависимость от приоритетов может быть не надежна, поскольку два раздела могут иметь различные диапазоны и политики приоритетов. Тем не менее, GLADE сохраняет приоритет вызывающего клиента. Этот приоритет передается (marshall и unmarshall) во время удаленного вызова процедуры, и приоритет анонимной задачи, запущенной на сервере, устанавливается в приоритет вызывающего клиента. В настоящий момент блокировка этого свойства отсутствует.


Принудительное завершение удаленного вызова

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


Реализация фильтра пользователя

Как в кратце упоминалось ранее, фильтр с именем "NAME" должен быть реализован в пакете с названием System.Garlic.Filters.Name. Пользователь может написать свои собственные фильтры, которые должны реализовывать фильтрацию данных в примитивных операциях типов, производных от типа System.Garlic.Filters.Filter_Type. После этого, пакет фильтров пользователя должен зарегестрировать в GLADE экземпляр своего вновь определенного типа путем вызова System.Garlic.Filters.Register. После этого, фильтр готов к непосредственному использованию.

Для получения более подробной информации о том как писать пакеты фильтров следует обратиться к реализации фильтра "ZIP" (файлы s-gafizi.ad[bs]), который поставляется в составе дистрибутива GLADE. Кроме того, в дистрибутиве GLADE, можно посмотреть пример в подкаталоге Filtering.


Примечания для удаленного командного интерпретатора shell

Для запуска раздела главный раздел запускает удаленный командный интерпретатор, кроме случаев, когда распределенное приложение построено с указанием стартера None (без стартера). Таким образом, пользователь должен убедиться в том, что он обладает правами запуска удаленного командного интерпретатора на удаленной машине. В этом случае, необходимо добавить в файл $HOME/.rhosts строку вида:

    
    
    <remote-machine> <user-name>
    

Если пользователь не авторизован, он может пропустить показанный шаг. При этом ему необходимо выполнить следующее:


DSA и CORBA

Архитектура CORBA

CORBA является индустриально финансируемым проектом стандартизации парадигмы распределенных объектов, описываемых с помощью языка описания интерфейсов (CORBA Interface Definition Language, или сокращенно - IDL). Использование IDL делает CORBA более самоописываемой чем какие-либо другие клиент/серверные промежуточные среды. The use of IDL makes CORBA more self-describing than any other client/server middleware. Common Object Request Broker: Architecture and Specification, revision 2.2 описывает основные свойства CORBA, которыми являются: Interface Definition Language, Language Mappings, Stubs, Skeletons, а также Object Adapters, ORB, Interface Repository, Dynamic Invocation, ORB протоколы и CORBA сервисы.

corba-arch.fig.jpg

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

Транслятор IDL генерирует "заглушки" клиента и "скелеты" сервера на хост-языке (C++, C, Java, Smalltalk, Ada95). Отображение языка определяет то, как сущности IDL реализованы на хост-языке. В зависимости от свойств/средств, которые доступны в хост-языке, отображение может быть более или менее простым. Когда свойство IDL не определено в хост-языке, отображение предусматривает стандартизированный, но более сложный способ симуляции отсутствующего свойства. Ходя в результате пользователь работает со сгенерированным кодом, достаточно часто требуется хорошее понимание межязыкового отображения.

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

Транслятор IDL генерирует несколько файлов с исходным текстом на хост-языке, в зависимости от языкового отображения: файлы клиента, называемые "заглушками", и файлы сервера, называемые "скелетами". Эти файлы зависят от поставщика (vendor) и продукта, поскольку они осуществляют вызовы к характерным коммуникационным подсистемам, однако подразумевается, что их структура и интерфейс соответствуют стандарту. "Заглушки" клиента преобразуют запросы пользователя в запросы ORB, которые передают запросы пользователя, через объектный адаптер (object adapter), к "скелету" сервера.


Язык описания интерфейсов (IDL)

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

В CORBA, IDL является языком описаний; он поддерживает синтаксис C++ для описания констант, типов и операций. Из IDL-описаний, транслятор может непосредственно генерировать клиентские файлы заголовков и реализацию серверных "скелетов".

IDL-файл может начинаться описанием модуля. Это предусматривает пространство имен, содержащее набор интерфейсов, и является способом введения уровня иерархии (<module>::<interface>::<operation>). Связывание (binding) Ada95 отображает такой элемент в (дочерний) пакет. Директива #include делает любое другое пространство имен видимым.

Модуль может описывать интерфейсы. Какой-либо интерфейс описывает набор методов, которые клиент может вызвать для манипуляции над объектом. Интерфейс может также описывать исключения и атрибуты. Какое-либо исключение является подобным исключению C++: к нему может быть прицеплен какой-либо компонент данных. Какой-либо атрибут является полем компонента (component field). Для каждого атрибута Attribute реализация автоматически создает подпрограммы Get_Attribute и Set_Attribute Для атрибутов, которые доступны только на чтение, предусматриваются только подпрограммы Get_. Интерфейс может быть производным от одного или более интерфейсов (множественное наследование).

Связывание Ada95 отображает этот элемент в пакет или дочерний пакет. Для "заглушки" клиента, реализация будет автоматически создавать тэговый тип называемый Ref (который является производным от CORBA.Object.Ref или от другого Ref-типа, определенного в другом интерфейсе) в пакете, чье имя совпадает с именем одного из интерфейсов. Для "скелета" сервера, реализация будет автоматически создавать тэговый тип называемый Object (который является производным от определяемого реализацией приватного тэгового типа Object) в пакете с именем Impl, который является дочерним пакетом для пакета именуемого в соответствии с именем интерфейса (<interface>.Impl).

    
    
    module CosNaming {
      typedef string Istring;
      struct NameComponent {
        Istring id;
        Istring kind;
      };
      typedef sequence <NameComponent> Name;
      enum BindingType {nobject, ncontext};
      struct Binding {
        Name binding_name;
        BindingType binding_type;
      };
      typedef sequence <Binding> BindingList;
    
      interface BindingIterator;
    
      interface NamingContext {
          exception CannotProceed {
              NamingContext cxt;
              Name rest_of_name;
            };
          void bind (in Name n, in Object obj)
            raises (CannotProceed);
          void list
            (in unsigned long how_many,
             out BindingList bl,
             out BindingIterator bi);
          // Other declarations not shown
        };
    
      interface BindingIterator {
          boolean next_n
            (in unsigned long how_many,
             out BindingList bl);
          // Other declarations not shown
        };
    };
    

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

Для подпрограмм может применяться атрибут oneway (односторонний), обеспечивающий семантику at-most-once вместо семантики exactly-once, принимаемой по умолчанию. Для метода, это устраняет наличие параметров вывода, возвращаемого значения или возбуждение исключения. Предположение того, что вызывающий клиент возобновляет свое выполнение сразу после передачи параметров ввода (in) считается непереносимым.

Большинство типов данных CORBA просто отображаются на предопределенные типы Ады, за исключением типов any и sequence. Тип данных any, который обозначает любой тип данных CORBA, отображается на тип потока с операциями read и write. Тип sequence содержит последовательность элементов данного типа и представляется в Аде с помощью использования пары длинных настраиваемых пакетов. Кто-то может заметить, что используемый в CORBA тип данных string отображается на тип Unbounded_String Ada95. Язык IDL не предусматривает тип, эквивалентный неограниченным массивам.

Отображение Ada95 предусматривает специальные механизмы для реализации двух сложно-отображаемых свойств CORBA. Во-первых, предусмотрена трансляция множественного наследования. Как описано выше, пакет Ada95 описывает тип, производный от первого интерфейса, и расширяет список примитивных операций этого типа для получения наследования других интерфейсов. Другое неестественное для Ада-программиста свойство CORBA проявляется в виде опережающих описаний (или ссылок вперед). В Аде, спецификации двух пакетов не могут указывать друг друга в спецификаторе контекста with, но это может встречаться между двумя интерфейсами IDL. Для разрешения этой проблемы, языковое отображение может создать "опережающие" пакеты. Это может привести к весьма не-интуитивной ситуации, когда, в спецификаторе контекста with, "заглушка" клиента не указывает свой обычный пакет интерферфейса, а, вместо него, указывает "опережающие" пакеты.

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

Интерфейсная информация IDL может быть сохранена в онлайновой базе данных, называемой репозиторием интерфейсов (Interface Repository, сокращенно - IR). Спецификация CORBA описывает организацию репозитория интерфейсов и способ получения информации из него. Читатель может обнаружить, что эта информация очень похожа на информацию, которую способен предоставить Ada Semantic Interface Specification (ASIS).

Репозиторий интерфейсов позволяет клиенту распознавать сигнатуру метода, которая не известна на этапе компиляции. Впоследствии, клиент может использовать эту информацию вместе со значениями параметров методов для построения полного запроса и вызова метода Множество функций, которые позволяют построить запрос вызова метода во время выполнения, образуют интерфейс динамического вызова (Dynamic Invocation Interface, сокращенно DII).

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

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

DII обладает противоположной частью на стороне сервера, которая называется динамическим скелетным интерфейсом (Dynamic Skeleton Interface, сокращенно DSI). Оба механизма являются весьма мощными, но очень сложными и запутанными для непосредственного использования. Следует также заметить, что в некоторых отношениях эти механизмы (DII и DSI) нарушают философию Ada95, поскольку не сохраняют строгую типизацию данных. Большинство пользователей будут использовать в своей работе статические вызовы.


Подсистема сетевого взаимодействия

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

В некоторых случаях, достаточно сложно строго описать границу разделящую подсистему взаимодействия от внешних сервисов. Более того, иногда, что-либо рассматриваемое в CORBA как сервис, может рассматриваться в DSA как полностью внутренний элемент.


DSA PCS

В мире DSA, все, что не выполняется компилятором, с точки зрения распределения приложения, относится к подсистеме взаимодействия разделов (PCS). Например, ответственность представление того, на каком разделе размещается пакет, который будет вызываться удаленно, возлагается на PCS.

Точки входа PCS хорошо определены в DSA, и описываются в пакете System.RPC. При рассмотрении этого пакета, можно заметить, что в нем не существует ничего относящегося к принудительному завершению вызова удаленной подпрограммы, хотя дополнение констатирует, что если подобный вызов завершается принудительно, сообщение принудительного завершения должно быть послано удаленному разделу для отмены удаленной обработки. Это подразумевает, что PCS заботится об обнаружении того, что вызов одной из ее точек входа может быть принудительно завершен, и должно послать, в этом случае, сообщение о принудительном завершении без какой-либо дополнительной помощи со стороны компилятора.

Другой интересной характеристикой PCS является ее поведение в зависимости возникновения от неизвестного исключения. При возбуждении какого-либо исключения, в результате вызова удаленной подпрограммы, оно (исключение) распространяется обратно к вызывающему клиенту. Однако, вызывающий клиент может "не видеть" описания исключения, но может "отловить" его с помощью использования when others. Кроме того, когда вызывающий клиент "не отлавливает" исключение и позволяет ему распространиться далее, вверх по иерархии вызовов подпрограмм (возможно: к другому разделу), и вышестоящий вызов "видит" описание исключения, то он (вышестоящий вызов) может "отловить" исключение, используя имя исключения. Это подразумевает, что PCS обязана распознавать то, что первоначально неизвестное исключение отображается на исключение, которое известно локально. Например, для обеспечения возможности динамической регистрации нового исключения в системе времени выполнения.


CORBA ORB

В CORBA, был адаптирован более фрагментированный подход к сервисам взаимодействия (коммуникации): по существу, они определены как внешние. Например, сервис имен (который лтображает имена объектов на объектные ссылки) является распределенным объектом со стандартным IDL-интерфейсом.

В случае, когда этот сервис выглядит более чисто, он страдает недостатком производительности. Будучи самостоятельным распределенным объектом, сервис имен не может быть оптимизирован под нужды определенного ORB. Специальный случай также требует от ORB возможности определения расположения самого сервиса имен (проблема курицы и яйца): для того, чтобы получить ссылку на начальный распределенный объект (IOR, Interface Object Reference), программисту необходимо иметь IOR для сервиса имен. Такая IOR может быть получена из командной строки, из файла или путем вызова интерфейса ORB, в зависимости от версии CORBA.

Относительно распространения исключений, ORB не может распространять исключения, которые не определены в IDL-интерфейсе. Это ограничение, хоть и досадно, поскольку ограничивает использование исключений, но является вполне понятной платой за многоязыковый подход CORBA: что должно произойти, например, когда исключение C++ достигает вызывающего клиента написанного на Аде? Примечательно, что какая-либо реализация может предусматривать больше информации в сообщении исключения CORBA, подобно имени исключения C++ или Ады.


Разработка распределенных приложений

Разработка приложений DSA

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

GLADE предусматривает средства конфигурирования и систему взаимодействия разделов, которые позволяют построить распределенное приложение. Средство gnatdist и его язык конфигурации специально разработаны для того, чтобы позволить пользователю разделить прложение на разделы и указать машины на которых будут выполняться индивидуальные разделы распределенного приложения. Библиотека Generic Ada Reusable Library for Interpartition Communication (GARLIC) является высокоуровневой библиотекой взаимодействия (коммуникации), которая реализует интерфейс между Partition Communication Subsystem, определенной в руководстве по языку программирования Ада, и уровнем сетевого взаимодействия с объектно-орентированной технологией.


Разработка приложений CORBA

ORB предоставляет ядро базовых сервисов. Все другие сервисы предусматриваются объектами с IDL. OMG стандартизировала множество полезных сервисов, таких как: Naming, Trading, Events, Licensing, Life Cycle, Events, ... Поставщик CORBA свободен от обязанности предоставления реализации этих сервисов.

Сервис имен позволяет ассоциировать (связывать) объектную ссылку с удобным для пользователя именем. Связывание имен всегда определяется относительно контекста именования (naming context), который должен быть уникальным. Контекст именования сам является объектом, и, таким образом, может быть заключен в имя другого контекста именования. Таким образом, может быть построен граф именования (naming graph) - направленный граф с контекстами именования, изображаемыми как ребра графа, и именами, изображаемыми в узлах. В данном контексте графа именования, указав последовательность имен, можно, таким образом, сослаться на объект. Это очень похлже на иерархию имен, которая существует в системе доменных имен (Domain Name System) и в файловой системе UNIX. Обычный сценарий начала работы с сервисом имен состоит в предоставлении хорощо известных удаленных ссылок, которые определяют корень иерархии именования и контекста именования. Затем, на этой иерархии можно выполнить множество операций именования. Сервис Trading Service предусматривает более высокий уровень абстракции по сравнению с сервисом Naming Service. Если сервис именования (Naming Service) можно сравнить с White Pages, то сервис Trading Service можно сравнить с Yellow Pages.

Для серверов и клиентов, сервис событий (Events Service) предусматривает способ взаимодействия посредством асинхронных событий между анонимными объектами. Поставщик генерирует события, а потребитель получает уведомление и данные. Какой-либо канал событий является посредником между поставщиками и потребителями. Ответственность за предоставление посредников (proxies), которые позволят поставщикам и потребителям получить доступ к каналу событий, возлагается на администратора поставщиков и администратора потребителей. Поставщики и потребители генерируют и получают сообщения через ассоциированных с ними посредников. С точки зрения канала сообщений, какой-либо посредник поставщика (или посредник потребителя) выглядит как поставщик (или потребитель). Следовательно, посредник поставщика (или посредник потребителя) является расширенным интерфейсом поставщика (или потребителя). Сервис событий, для обмена событиями, определяет методы push и pull. Это позволяет описать четыре модели обмена сообщениями и данными.


Сравнение некоторых элементов

CORBA предусматривает мощную и достаточно популярную технологию. Синтаксис IDL подобен синтаксису C++. Объектная модель подобна Java: CORBA описывает только распределенные объекты. Более того, при использовании отображения на Аду, генерируемый код для "заглушек" и "скелетов" похож на Java с двумя корневыми классами: Ref - для клиентов, и Object - для серверов.

DSA предусматривает более общую модель, которая включает распределенные объекты, а также обычные удаленные подпрограммы и ссылки на удаленные подпрограммы. Общие пассивные пакеты могут быть определены как абстракция для общей (распределенной) памяти, для поддержки устойчивости (persistency support) или для базы данных. В основном, IDL является подмножеством Ada95 и удаленные сервисы описываются в пакетах, которые категорируются тремя видами директив (RCI, RT, SP). Границы распределенности приложения более прозрачны поскольку приложение не разделяется на исходные тексты IDL и хост языка.

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

Пользователь DSA может разрабатывать, реализовывать и тестировать свое приложение в не-распределенном окружении, а позднее перейти в распределенное окружение. Используя способ двух-фазной разработки, пользователь всегда работает в своем привычном окружении Ada95. Использование директивы All_Calls_Remote также облегчает отладку распределенного приложения в не-распределенном контексте (окружении).

При работе над "заглушками" клиентов или "скелетами" серверов, пользователь CORBA вынужден использовать генерируемый код. В любом случае, понимание отображения хост языка всегда очень полезно. Это может потребоваться для некоторых языков программирования подобных C++ Ада-программист должен избегать использования опережающих описаний или множественного наследования (и в некоторых отношениях, их последствий).

Пользователь CORBA вынужден подстраивать свой код под код, который генерируется транслятором из IDL-файла, при каждой модификации IDL-файла. Он также должен использовать предопределенные типы CORBA вместо стандартных типов Ады и вызывать функции ORB или сервиса имен, для получения ссылок на удаленные объекты.

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

Стандарт языка Ада не требует от реализации DSA обеспечения возможности работы в гетерогенных системах, однако GLADE, как и любая другая осмысленная реализация, предусматривает используемые по умолчанию, XDR-подобные операции передачи (marshalling operations). Это свойство может быть заблокировано в целях повышения производительности. ORB, для гарантии безопасного взаимодействия между гетерогенными системами, требует наличия реализации общего представления данных (Common Data Representation, сокращенно - CDR).

CORBA является весьма богатым, но достаточно сложным стандартом. Его недостатки включают необходимость длительного обучения, для способности эффективной разработки и управления приложениями CORBA, ограничения производительности, а также недостатки обеспечения переносимости и безопасности. Такие недостатки являются платой за возможность межязыкового взаимодействия, что является свойством, которое не прдусматривается DSA (ориентированным на Ada95).

DSA пока не обеспечивает возможность взаимодействия между различными компиляторами, поскольку в настоящий момент доступна только одна реализация DSA (GLADE). Однако, сертификационным требованием является обеспечение для пользователя возможности заменить текущую PCS на какую-либо другую PCS из третьих рук. Необходимо заметить, что это не было разрешено в CORBA вплоть до revision 2.2. В этом же смысле, мы можем ожидать, что будущие реализации DSA будут гарантировать совместимость PCS.

Используя свой IDL, OMG описала перечень общих объектных сервисов (Common Object Services, сокращенно - COS), которые часто необходимы в распределенных системах. К несчастью, эти спецификации ограничены описаниями IDL, и большая часть семантики отдана на попечение поставщикам. В DSA отсутствуют подобные библиотеки пользовательского уровня, включая базовые компоненты распределенного программного обеспечения. В общем случае, отсутствие библиотек компонентов является распространенной проблемой для Ады.

Реализация сервисов CORBA как обычных распределенных объектов Ada95, использует преимущества стандартных свойств языка, и предоставляет простые, легко понимаемые и используемые спецификации. В настоящий момент мы уже реализовали сервис имен, сервис событий и сервис, который подобен параллельности в DSA. При разработке сервисов CORBA был получен интересный опыт. Мы представили, что хотя такие сервисы являются хорошо описанными в IDL-файле, их семантика достаточно смутная, в результате чего довольно сложно (и даже не возможно) обеспечить переносимость. Эта работа будет описана в будущих документах.

Другой важной задачей группы разработчиков GLADE является экспорт сервисов DSA в мир CORBA.

Идея состоит в том, чтобы транслировать все свойства DSA в эквивалентные свойства IDL, используя для этого ASIS. Это должно позволить пользователям DSA подключить свой сервер DSA к ORB. Это также должно позволить приложениям, которые написаны на других языках программирования, использовать свойства DSA. Мы также исследуем использование этого подхода, чтобы предоставить механизм DII для DSA.


Сноски

  1. ESTEREL императивный синхронный язык, который разработан для написания спецификаций и разработки реактивных систем.