Про Shared Memory Objects и немного про память, SHMA, SHMM

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

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

И вот пока приложение работает, оно потребляет память в виде переменных, экземпляров классов, массивов (также известных как «внутренние таблицы»). И если посмотреть на стек вызовов, то он упирается во входную точку начала этой программы. Ничего ABAPовского за пределами этой точки не существует. Одно приложение не может получить доступ к данным другого приложения, только через какие-то обходные манёвры, например: через постоянное хранилище (СУБД), через взаимные блокировки и прочие костыли. Как только приложение завершает работу, вся его память высвобождается. А приложения долго не работают, потому что они уже в некоторой степени атомарны (то есть как бы уже не монолит, но ещё не микросервисы).

Есть уже случае чуть посложнее, например: есть веб-сервис, слушающий входящие соединения, или одно «приложение» вызывает другое «приложение» через SUBMIT REPORT . Но подход вы уже уловили, это и называется SAP LUW, то есть Logical Unit of Work. Получается, что стек вызовов и контекст существуют неразрывно только внутри него.

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

Один из известных костылей -оператор IMPORT/EXPORT MEMORY ID,  который позволяет перебрасывать переменные (структуры, массивы) между приложениями, но только если они существуют в одной пользовательской сессии. Например Приложение-А вызывает Приложение-Б, Приложение-Б генерирует данные, закидывает их в ячейку памяти X, после чего завершает работу, управление возвращается в Приложение-А, которое считывает данные из ячейки памяти X, профит!

Разговор был бы не так интересен, если бы мы остановились на этом. Но перепрыгнув забор, мы обнаруживаем ещё один забор!

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

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

HTTP-сессии в ABAP могут быть как stateless, так и stateful. По сути их отличие в том, что этот пресловутый SAP LUW растягивается на всю пользовательскую сессию (stateful) или только на один http-вызов (stateless). В stateful-режиме сервис работает со своими причудами, например: обновлённый код сервиса не подхватывается на лету, чтобы изменения в сервисе вступили в силу нужно завершить сессию. Так же и в обычном ABAPe: пока запущена программа со всеми диалоговыми шагами обновлённый код не подхватывается — необходимо перезайти в транзакцию. Получается в рамках HTTP необходимо сделать log-off, что очень редко случается целенаправленно в жизненных реалиях, гораздо чаще сессия умирает по таймауту.

И вот кривая дорожка сомнительных архитектурных решений привела нас к небольшому челенджу — организовать хранение данных сессии при stateless-сессии HTTP.

Окей, вызов принят. Представим типичный сценарий:

  • Пользователь первый раз заходит на страницу => нет куки сессии => выставляем новый случайный куки (надо его куда-то записать) => рендерим страницу с формой для пользователя => Всё закончилось и очистилось, развязка сезона
  • Ура новый сезон => Пользователь что-то сабмитит => открывается другая страница => есть куки сессии => (надо значения сессии достать) => проверить что пользователь насабмиттил => (положить насабмитенное в сессию) => отрендерить страницу для пользователя, что насабмитилось успешно => снова апокалипсис, миру конец
  • Пользователь чего-то тыкнул => ещё одна страница => есть куки сессии => (надо значения сессии достать) => отрендерить значения на странице => и непонятно, продлит ли пользователь этот бесконечный сериал на следующий сезон или нет

Обыкновенным ABAPом красные места можно тупо реализовать через БД или другой как-мы говорим-у нас-в-деревне «персистент сторэдж», мы же это умеем. Но мы же дошли досюда не за простыми решениями.

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

Шаг первый

Сначала создадим сам объект, который будем помещать в память. В местной терминологии он будет называться «корневой/root».

От обычных классов два отличия: включённый флаг SHARED MEMORY (поддержка общей памяти) и используемый интерфейс IF_SHM_BUILD_INSTANCE

Сделаем простую реализацию:

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

Шаг второй

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

 

Шаг третий

Теперь в транзакции SHMA необходимо создать «область/area», для примера с именем ZCL_SESSION_SHMA. При этом будет сгенерирован ещё один класс с именем области. Там не сложно, но придётся указать немного свойств (корневой класс, срок хранения).

Шаг четвёртый

Напрямую кодить  эти объекты куда надо я бы не стал, поэтому нам понадобится пара обвесок:

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

И для того чтобы записать сессию почти что аналогично:

Теоретически может произойти и на практике обязательно произойдёт блокировка, если кто-то уже держит объект на запись.

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

И уже эти два метода (GET и SET)  можно вызывать из нашего исходного кода.

Шаг пятый

Просмотр сессий и запуск инвалидации протухших сессий — задача вне данного proof-of-concept. А без этого понятно, что список сессий будет расти бесконечно, пока не настигнет нас CX_SHM_OUT_OF_MEMORY.

Стоит отметить

В некоторых источниках говорится, что данный подход в таком контексте совсем не рекомендуется:

General shared memory programming is not possible. The current lock logic does not enable you to set specific locks for the following requirements:
— Many parallel read and write accesses
— Frequent write accesses

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

Так же в документации пишут, что никак нельзя создавать динамические типы, но мне не очень и хочется.

На сегодня всё, до новых встреч.

 

Добавить комментарий

Ваш e-mail не будет опубликован.