22 мая 2021 г.

Смена хостинга для нашего сайта

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

https:// версия сайта тоже доступна, но пока не сделана основной. Надо еще поискать по коду все ссылки на незащищенный протокол и поменять их.

Если заметите ошибки в работе сайта, будем признательны обратной связи на адрес support[at]gsbelarus.com.

28 апр. 2021 г.

Охота на утечки памяти

В отладочную версию gedemin.exe и новейшую версию класса TCreator добавлено расширенное логирование:

  1. Транзакции. Логируется старт и завершение транзакции. Показывается количествоактивных транзакций в данный момент времени. 
  2. IBSQL. Логируется выполнение запроса. Для SELECT запросов логируется закрытие. Показывается количество открытых на чтение запросов.
  3. Designer. Логируется создание и удаление объекта. Показывается количество объектов в памяти.
  4. TCreator. Логируется создание и удаление экземпляра класса. Показывается количество TCreator  в памяти.

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

27 апр. 2021 г.

NB! Об использовании TCreator и других VB классов

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

Особенности классов VBScript

  1. Событие Terminate вызывается только в процессе удаления из памяти последнего экземпляра данного класса.
  2. Наличие кольцевых ссылок, в т.ч. цепочек кольцевых зависимостей любой длины, приведет к тому, что ни один из экземпляров не будет удален сборщиком мусора и останется в памяти до конца работы программы.

Как мы столкнулись с проблемой неудаления объектов из памяти

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

Возникает вопрос, как бороться с вышеуказанной частной ситуацией и как правильно работать с классами VBScript, чтобы максимально обезопасить себя от утечек памяти?

Использовать Designer.CreateObject -- Designer.DestroyObject

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

Создать копию класса TCreator

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

Следует создать полную копию класса TCreator в рамках своей подсистемы и использовать ее для выделения ресурсов.

А надо ли каждый раз выделять-уничтожать ресуры?

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

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

Принудительно уничтожить ресурсы в TCreator

В крайнем случае, ресуры выделенные через TCreator можно уничтожить принудительно, вызвав метод DestroyAllObjects. Теперь, даже если сборщик мусора не сможет удалить экземпляр TCreator, то в памяти останется только он, но не связанные с ним ресурсы.

Уничтожать TCreator как можно раньше

Стандартная практика -- TCreator уничтожается по завершении функции или процедуры. Если код процедуры или функции объемный и ресурсы нужны только в одной его части, то рекомендуется уничтожать экземпляр TCreator вручную, присваивая переменной значение Nothing.

...
Dim Creator
Set Creator = New TCreator
...
здесь работаем с ресурсами
...
больше нам выделенные ресурсы не нужны
Set Creator = Nothing
...
продолжается код функции/процедуры
...

22 апр. 2021 г.

Удаление объектов в VBScript и кольцевые ссылки

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

При наличии малейших сомнений, объект останется в памяти до завершения работы Гедымина.

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

Создадим вспомогательный класс с деструктором:

Class Foo
  Public Name
  Public Other
 
  Public Default Function Init(N)
    Name = N
    Set Init = me
  End Function

  Private Sub Class_Terminate
    MsgBox Name & ": Goodbye, world!"
  End Sub
End Class

Пока кольцевых ссылок нет, код работает корректно и созданные экземпляры удаляются по завершении процедуры:

Sub Macros
  Dim f, f2, f3

  Set f = (New Foo)("f")
  Set f2 = (New Foo)("f2")
  Set f3 = (New Foo)("f3")
End Sub

Но,  стоит появиться кольцевой ссылке и объекты останутся в памяти до завершения работы Гедымина:

Sub Macros
  Dim f, f2, f3

  Set f = (New Foo)("f")
  Set f2 = (New Foo)("f2")
  Set f3 = (New Foo)("f3")

  Set f.Other = f2
  Set f2.Other = f3
  Set f3.Other = f
End Sub

Как избежать такой ситуации? Принудительно присваивать объектной переменной значение Nothing после окончания ее использования.

Много полезной информации по внутреннему устройству интерпретатора VBScript можно найти в блоге разработчика Eric Lippert.

Новый сервер компиляции gedemin.exe

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

Процесс запускался по расписанию один раз в сутки, а не в ответ на изменения в исходном коде платформы и решений.

Все бы ничего, пока строго один gedemin.exe "выпекался" из текущих исходников ветки master в репозитории. Несколько лет назад на крупных предприятиях значение генератора GD_G_UNIQUE стало подбираться к верхнему пределу доступных положительных 32-х битных целочисленных значений. В качестве быстрого решения проблемы мы ввели механизм поиска доступных интервалов в последовательности идентификаторов во всех таблицах базы и записи их в специальную таблицу, откуда Гедымин берет новые идентификаторы по мере необходимости. И... начали думать над долговременным решением, которым должно стать использование 64-х битных целочисленных идентификаторов записей.

Надо сказать, что 20 лет назад никто не задумывался над проблемой исчерпания идентификаторов и внутри исходного кода разработчики сплошь и рядом использовали тип Integer или полагались на длину строго в 4 байта при записи и чтении из буфера. Что еще хуже, Delphi 5 не поддерживает тип Int64 в библиотеке типов COM, через которую объекты Гедымина взаимодействуют с программным кодом на VBScript.

Первым шагом трансформации исходного кода стало введение специального типа для идентификаторов TID и набора функций для преобразования идентификаторов в/из строк, целых чисел, чисел с плавающей точкой и т.п. Разумеется, что замена типа затронула большинство из файлов проекта. Для справки, всего Гедымин насчитывает 5164 .pas файла, если считать вместе со сторонними библиотеками. В зависимости от символа условной компиляции ID64 тип TID превращается либо в 32-х битный Integer, либо в Int64. 

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

Таким образом, у нас появляются три версии выполняемого файла:

  1. стабильный, проверенный временем gedemin.exe c 32-х битными идентификаторами.
  2. gedemin.exe на основе новых исходников с 32-х битными идентификаторами.
  3. gedemin.exe c 64-х битными идентификаторами.

Поддерживать такое хозяйство с помощью устаревшей технологии bat файлов и остаться при этом в здравом рассудке не представляется возможным. Пару месяцев назад мы начали проект gbuilder -- сервер компиляции Гедымина и прикладных решений. В настоящее время готова первая очередь. Из специальной ветки с именем india компилируется gedemin.exe на основе стабильных исходников. Прикладные решения формируются из ветки master репозитория gedemin-apps. Сервер компиляции предоставляет разработчику интерфейс через чат-бот в телеграме @gbuilderbot. Компиляция активируется через github webhooks по мере поступления на сервер изменений в исходный код.

В скором времени будет добавлена компиляция бета версий gedemin.exe из новейших исходников с 32-х битными и 64-х битными идентификаторами.

gbuilder реализован на платформе NodeJS. Язык программирования Typescript. Скомпилированные файлы можно скачать из соответствующего раздела нашего сайта.


27 янв. 2021 г.

 Нам 27!



29 дек. 2018 г.

14 мар. 2018 г.

Использование сторонних библиотек

  • Если библиотека активно развивается и широко используется (например, React), ставим ее через npm/yarn. Периодически обновляем до последней версии и тестируем на совместимость с существующим кодом.
  • Библиотека широко используется, но уже не развивается (например, classnames), действуем аналогично предыдущему пункту.
  • Автор забросил библиотеку, но она активно развивается в одном из форков (найти такой мы можем с помощью Network diagram на сайте github. Пример: jison). Устанавливаем библиотеку из гит репозитория активного форка с помощью npm. Если промежуточные изменения нестабильны -- устанавливаем стабильную ветвь или определенный коммит. Дополнительно, клонируем себе репозиторий в папку Lib и периодически делаем pull.
  • Мы собираемся развивать стороннюю библиотеку. Делаем ее форк в свой аккаунт. Клонируем в папку Lib и ведем разработку в ней. Устанавливаем в проект с помощью npm из гит репозитория своего форка.
  • Папка Lib не входит в проекты.
  • Не используем git submodule для установки сторонних библиотек внутрь своего проекта. По крайней мере на VS Code наличие сабмодулей загромождает интерфейс панели управления git. Да и удаление сабмодуля из проекта дело хлопотное.

5 мар. 2018 г.

Конкретизируем следующий шаг

  1. Есть разбор предложения "покажи всех клиентов из минска". сейчас он рисует дерево, где слова зеленые, а остальные узлы белые.
  2. Наша задача: найти сопоставления для сущностей и, там где найдено, мы будем закрашивать узлы дерева красным цветом.
  3. Для этого нам понадобится логическая ER модель базы данных.

    Текущая реализация строит ее на основе физической структуры, но это не совсем то, что нам надо. например, в физической модели будет присутствовать таблица GD_CONTACT, хотя такой сущности нет в логической модели, где мы имеем семь сущностей: Папка, Группа, Организация, Банк, Подразделение, Физическое лицо и Сотрудник предприятия.

    Все они базируются на одной физической таблице GD_CONTACT.

  4. Предполагается, что мы добавим в БД (в дополнительные таблицы или в существующую таблицу AT_RELATIONS) информацию, которая позволит нам правильно извлекать логическую структуру БД.
  5. Причем, если такая информация для таблицы не задана, то мы берем ее физическую структуру и на основе нее создаем сущность в логической модели.
  6. Возвращаемся к нашему предложению:

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

  7. Переходим к фразе "из минска". Тут возможны несколько вариантов сопоставления.

    а) Мы наделяем слова в нашем словаре семантическими категориями. Например, Минск -- это город, а город -- это место. Ищем в базе данных и находим справочник "Административно-территориальных единиц", а "Административно-территориальная единица" находится в смысловом ряду с городом, местом.

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

  8. Весь анализ должен происходить на клиенте. Т.е. надо подумать о передаче схемы с сервера на клиент, представлении ее в памяти и объектах для работы с ней.
  9. На сервере схема должна читаться однократно и храниться в оперативной памяти. Чтобы последующие обращения (покдлючения) клиентов не приводили к повторному длительному чтению всей структуры БД.

25 февр. 2018 г.

вот, чем собственно сейчас и занимаемся.

17 дек. 2017 г.

Каждой программе по своей "проблеме 2000 года"

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

В Гедымине есть своя "проблема  2000 года", а точнее проблема 32-х битного идентификатора бизнес-объекта. Чтобы быть еще более точным, не самого идентификатора а генератора GD_G_UNIQUE, с помощью которого идентификаторы добываются.

Данный генератор стартует со значения 147 000 000 и увеличивается по мере запроса новых идентификаторов. Причем, для сокращения количества запросов к серверу, увеличиение идет с шагом в 100, а неиспользованный на момент завершения программы интервал сохраняется в системном реестре.

Так как у нас ИД объекта -- это знаковое 32-х битное целое, то всего доступно чуть более 2-х миллиардов идентификаторов (мы не учитываем первые 147 миллионов, которые выделены под системные объекты платформы).

Два миллиарда число большое. Скажем, если непрерывно получать по одному идентификатору в секунду, то такого диапазона хватит на 63 года. Однако сам генератор ничем не защищен от некорректного использования, уже не говоря про то, что его можно просто "подвинуть" вперед вручную на произвольную величину. Нам встречался код, где генератор использовался для упорядочивания записей в выборке. Естественно, каждое перестроение запроса приводило к пустому расходу сотен, если не тысяч значений генератора.

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

Что делать?

Теоретически есть два варианта решения проблемы. Первый -- это сдвинуть, утрамбовать все идентификаторы "вниз" на выявленные пустые пробелы. Затем изменить значение генератора в соответствии с максимальным ИД в базе.

Технически, для этого придется выполнить следующую последовательность шагов:
  1. Сохранить все существующие ИД в некоторой структуре.
  2. Построить таблицу соответствия старый ИД -- новый ИД.
  3. Отключить все внешние и первичные ключи.
  4. Обновить ВСЕ записи в базе данных, заменяя старые идентификаторы на новые.
  5. После предыдущего шага желательно выполнить бэкап-восстановление БД для чистки мусора.
  6. Восстановить все первичные и внешние ключи.
На базах размером свыше 100 Гб мы не представляем как можно выполнить указанную последовательность в доступное нам технологическое окно (обычно 8-10 часов).  И, если где-то в коде, используется привязка к ИД записи, вместо РУИД, то такой код перестанет работать. К тому же, надо будет как-то вычистить из реестров всех компьютеров сохраненные интервалы или одномоментно заменить все экзешники, скорректировав алгоритм кэширования.

Второй вариант:
  1. Создать таблицу для доступных интервалов идентификаторов GD_AVAILABLE_ID.
  2. При обращении к функции gdcBaseManager.GetNextID проверять не приблизились ли мы к опасной черте. 
  3. Если нет, то работать по-старому -- через генератор. 
  4. Если уже пора, то заполняем таблицу и по-мере необходимости берем очередной интервал из нее.
На практике проверено, что заполнение такой таблицы занимает пару часов даже на самой большой, доступной нам базе данных.

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