вторник, 1 января 2030 г.

О блоге

Более тридцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). В настоящее время занимаюсь развитием компании по разработке ПО stiffstream, в которой являюсь одним из соучредителей. Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

понедельник, 31 декабря 2029 г.

[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)

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

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

пятница, 3 июля 2026 г.

[prog.eiffel] Сохраню в склерозник пример работы с anchored-типами в Eiffel

Есть очень прикольный язык программирования -- Eiffel. А в нем есть очень прикольная фича -- заякоренные типы (anchored types). Давеча пришлось в одном из разговоров про нее вспомнить и набросать небольшой пример, чтобы проверить те или иные предположения.

У Eiffel-я есть он-лайн компилятор (что-то по типу godbolt, wandbox, ideone), но в нем нельзя просто так сохранить сделанное и поделиться публичной ссылкой. Поэтому помещу под кат код написанного примера, просто на память, а то вдруг еще раз потребуется.

В чем суть примера?

Есть базовый класс MESSAGE.

Есть базовый класс ENVELOPE, который хранит в себе MESSAGE. Но класс ENVELOPE написан с использованием MESSAGE в качестве якорного типа. Что позволяет создать наследника SIGNED_ENVELOPE, который хранит уже не просто MESSAGE, а SIGNED_MESSAGE. Но менять унаследованные из ENVELOPE методы не нужно, Eiffel сам разбирается с тем, что в SIGNED_ENVELOPE методы make и change_content получают уже не MESSAGE, а SIGNED_MESSAGE.

При этом Eiffel что-то может проверить в compile-time. Например, если у нас есть ссылка signed_env с типом SIGNED_ENVELOPE, то в вызов signed_env.change_content мы не может отдать просто ссылку на MESSAGE. Будет ошибка компиляции, компилятор ждет от нас SIGNED_MESSAGE (или наследника SIGNED_MESSAGE).

Но если у нас есть env типа ENVELOPE и мы env присвоили signed_env (т.е. теперь env ссылается на экземпляр SIGNED_ENVELOPE), то компилятор пропустит передачу обычного MESSAGE в env.change_content. Но ошибка будет диагностирована в run-time с выбросом исключения. Т.е. "обмануть" Eiffel не получится: то, что Eiffel не смог поймать в compile-time, он поймает в run-time.

среда, 1 июля 2026 г.

[life.cinema] Очередной кинообзор (2026/06)

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

Фильмы

Грязные деньги (In The Grey, 2026). Тупо, прямолинейно, приторно красиво, но бодренько. За неимением лучшего можно посмотреть, чтобы скоротать вечер.

Мандалорец и Грогу (The Mandalorian and Grogu, 2026). Отличная сказочка для семейного просмотра с детьми младшего школьного возраста. Разве что немного мрачноватая. В любом случае кино абсолютно детское, взрослым к нему всерьез относиться не стоит.

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

Ограбить Лондон (Fuze, 2025). Ничего не ждал от фильма вообще, думал обычная третьесортная поделка. Оказалось на удивление хорошо. Т.е. это далеко не шедевр, уровнем чуть повыше среднего, но на фоне всего остального шлака, прям глоток свежего воздуха.

Охота за тенью (Shadow's Edge, 2025). Очень бодрая сказочка. Джеки Чану мое почтение.

Мусорщик (Wasteman, 2025). Мне показалось, что это неплохая тюремная драма. Но, во-первых, понятия не имею насколько это достоверно. И, во-вторых, смотрел на скорости 1.25, потому что на нормальной скорости было ну как-то совсем уж нудно. Тем не менее, развязка понравилась.

Сериалы

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

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

Бухта вдов (Widow's Bay, первый сезон, 2026). Скучно, что-то реально интересное и удивляющее происходит очень редко. Сама история толком ничем не закончилась, тут нам явно предлагают ждать следующих сезонов. Купился на хвалебные отзывы в Интернете, по итогу не понравилось, жаль потраченного времени.

Красота (The Beauty, первый сезон, 2026). Первые серии хорошие -- тут и экшен, и драйв, и многообещающая завязка. Затем темп повествования потерялся, драйв улетучился. Финала как такового у первого сезона нет. Создатели обрывают происходящее буквально на полуслове. Возможно, когда выйдет второй сезон, и история достойным образом разовьется, то этот сериал можно будет хоть как-то оценить. Но не в его текущем виде.

Не смог досмотреть

Опасные отношения (Over Your Dead Body, 2026). Это ремейк фильма Поездка (I onde dager, 2021). И, как по мне, так оригинал смотреть интереснее, в нем как-то все более естественно.

Дьявол носит Prada 2 (The Devil Wears Prada 2, 2026). Происходящее на экране невозможно воспринимать серьезно.

Вне категории

Ангелы Ладоги (2026). Такое впечатление, что фильм рассчитан совсем на другое поколение. Возможно, на современных детей 8-10 лет. Как мне его оценивать непонятно.

Литвяк (2026). С одной стороны, хороший военный фильм, без пьяных командиров, трусливых комиссаров и неадекватных СМЕРШев с наганами в руках. С другой стороны, рассказанная история нисколько не зацепила, главные герои показаны так, что не успеваешь к ним прикипеть так, чтобы их гибель заставляла тебя переживать именно потому, что ты успел проникнуться к ним какими-то чувствами. Кроме того, напрягает и раздражает то, насколько много сцен в фильме снято на фоне зеленого экрана. В общем, не знаю, как оценивать. За попытку снять хороший фильм о войне -- отлично, за исполнение -- незачет 🙁

понедельник, 29 июня 2026 г.

[prog.thoughts] Начинаю думать, что бесстековые короутины в C++ следовало делать чуть иначе

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

Изначально я планировать только позволить пользователю описывать event-handler-ы, в которых можно было бы делать co_await. Но по мере продумывания способов реализации столкнулся с тем, что не имею на руках никаких механизмов запуска внешних по отношению к SO-5 короутин. А это нужно, чтобы проверить, что из event-handler-а можно дернуть, скажем, короутину из Asio, и когда она завершится, управление должным образом вернется event-handler-у.

И тогда появилась мысль сделать сперва возможность асинхронной работы с mchain-ами. Сейчас в SO-5 есть синхронные версии receive и select, а что, если предоставить их асинхронные версии? Тогда в event-handler-е можно было бы написать что-то вроде:

so_5::event_handler_task_t
some_agent::evt_some_handler( mhood_t<some_msg> cmd )
{
  auto result = co_await so_5::async_receive( so_5::from( test_chain ), ... );
  ...
}

Тогда отправляя (или не отправляя) сообщения в тестовый канал я бы мог тестировать поведение event-handler-ов.

Но стоило погрузиться в задачу создания асинхронных версий async_receive и async_select-а, как стало возникать подспудное подозрение, что с C++ными бесстековыми короутинами что-то не так. И я не говорю про их мудреность (это тема отдельного разговора). Было ощущение, что несмотря на заумность и гибкость C++ных короутин в них все-таки чего-то важного не хватает.

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

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

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

В Capy ее предлагают решать за счет введения специальной сущности -- Executor-а и за счет изменения интерфейса сущности Awaiter-а: метод await_suspend для Awaiter-а вместо одного аргумента получает два:

std::coroutine_handle<> await_suspend(std::coroutine_handle<> h, io_env const* env);

И во втором параметре передаются вещи, которые могут потребоваться короутине (и ее дочерним короутинам): executor, stop_token, allocator.

За счет того, что в env есть ссылка на executor, короутина может сохранить этот executor и, когда возникает возможность возобновить работу, короутина обращается к этому executor-е с просьбой обеспечить это возобновление на должном контексте.

Это как раз то, чего мне не хватало для реализации асинхронных версий receive/select. Я уже сам пришел к мысли о том, что в асинхронный receive нужно передавать какой-то coro_scheduler, который будет отвечать за то, чтобы возобновить приостановленный receive/select именно там, где это разрешено. Например, если receive вызывается из event-handler-а, то это могло бы выглядеть так:

auto result = co_await so_5::receive(
  so_5::from( test_ch ).resume_by( this->so_coro_scheduler_for_this_agent() )...,
  ... );

А если mchain используется вне агентов, то может быть что-то вроде:

so_5::cpp_coro::this_thread_scheduler_t coro_scheduler;
coro_scheduler.sync_wait(
  [&coro_scheduler, &ch]() -> so_5::cpp_coro::async_receive_task_t {
    co_await so_5::receive(
      so_5::from( ch ).resume_by( coro_scheduler )..., ...);
  } );

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

Но, повторюсь, и подход Capy, и мои попытки придумать некий условный coro_scheduler для SObjectizer-а -- это попытки прикрутить решение сбоку посредством синей изоленты.

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

Например, чтобы обращение к co_await можно было параметризовать. Скажем, передавать executor/scheduler непосредственно в co_await:

co_await(executor) some_task();

И чтобы этот executor передавался бы параметром в await_resume. Может быть в виде ссылки на некоторый специальный объект environment, как это делается в Capy.

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

PS. Раз уж заговорил про попытку добавить поддержку короутин в SO-5, то слегка обозначу текущий статус. Пока до чего-то работающего еще далеко. Пытаюсь двигаться от простого к более сложному. Сперва хочу попробовать сделать асинхронную версию receive/select. Чтобы с ее помощью перейти к поддержке event-handler-ов в виде короутин. Затем, возможно, попробую погрузиться еще глубже, чтобы разрешить короутины в роли обработчиков входящих сообщений в receive/select (если это вообще возможно). Работы много, ресурсов мало, движется все очень медленно. Поэтому я сам в течении ближайших пару месяцев никаких значимых результатов не жду.

понедельник, 15 июня 2026 г.

[prog.c++.imho] Не согласен с постулатами пропозала P3097 (контракты для виртуальных методов)

Комитет по стандартизации C++ продолжает творить дичь. Сперва в C++26 были включены кастрированные контракты (нет ключевого слова old в постусловиях, нет контрактов для виртуальных методов, нет инвариантов для экземпляров классов и циклов). Для людей, знакомых с Eiffel, контракты из C++26 выглядят как "мы не осилили тему полностью, поэтому впихнули в стандарт какой-то эрзац с надеждой, что со временем допилим". Не хочу обсуждать зачем нужен эрзац вместо нормального продукта. Просто перейду к следующей дичи.

Далее в C++29 включили предложение P3097, которое описывает контракты для виртуальных методов классов. И авторы этого предложения, как по мне, покусились на святое: на сформулированное много-много лет назад для Design By Contract в Eiffel-е требование о том, что производный класс может только ослабить предусловния и ужесточить постусловия, но не наоборот.

И вот авторы пропозала почему-то пришли к выводу, что C++ настолько особенный, что в нем можно это требование послать в пешее эротическое.

На протяжении нескольких страниц пропозала эти люди пытаются приводить "аргументацию" своей точки зрения. Меня эта аргументация не убеждает от слова совсем. Скорее наводит на мысль о том, что люди толком не понимают тему, о которой пытаются рассуждать и, скорее всего, не имеют опыта разработки на языках с поддержкой Design By Contract (в первую очередь на Eiffel-е, на который в данной теме и следует равняться).

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


В разделе "3.2 Adoptability in legacy code" есть интересный заход:

пятница, 12 июня 2026 г.

[prog.c++] Похоже, что SObjectizer-5 будет использоваться в еще одном проекте

Больше двух лет сотрудничаю с интересным проектом. Занимался в рамках этого проекта разными задачами, начинал вообще с замены C++ REST SDK на RESTinio, а потом пошло поехало по нарастанию сложности 😎. Не все из этого было связано с многопоточностью, но многое.

Многопоточность здесь самая обычная -- std::thread, std::mutex, std::condition_variable, немного std::atomic-ов. Ну и специфика больше про параллельную обработку данных, нежели про событийно-ориентированное программирование.

Местами об отсутствии SO-5 в проекте приходилось жалеть, но, по большому счету, всерьез только один раз. Мне кажется, что та задачка на агентах решалась бы проще, чем на std::thread. Плюс еще пара-тройка мест, где простой агент с периодическим сообщением, на мой взгляд, оказался бы практичнее, чем выделенная нить с ручным циклом вокруг std::this_thread::sleep_for. Т.е. в недавнем прошлом от SO-5 полезный выхлоп вряд ли был заметным.

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

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

Такое распараллеливание средствами Taskflow было сделано, но некоторый осадочек остался:

  • в Taskflow нет встроенной поддержки таймеров. Одна из идей о том как оптимальнее поделить общий объем работы на отдельные кусочки базировалась на том, чтобы контролировать процесс через некоторые интервалы времени. Типа начнем считать на текущем треде, но поставим отложенную на 250ms задачу. Если к моменту ее запуска вычисление не завершится, то задача возьмет на себя часть оставшейся работы. Если же вычисление успеет закончится, то надобность в отложенной задаче отпадет. Только вот провалидировать эту идею из-за отсутствия таймеров в Taskflow сходу не получилось;
  • не был понятно как в Taskflow реализовать квотирование имеющихся ресурсов для того, чтобы одно вычисление не захватило все ресурсы себе, а оставшиеся вычисления сидели бы на "голодном пайке". При этом на горизонте маячит фича по приоритетам вычислений, т.е. каким-то высокоприоритеным вычислениям такая узурпация ресурсов разрешается, а вот низкоприоритетным -- нет.

Возможно, все это можно было бы сделать и средствами Taskflow, если в достаточной степени изучить ее возможности и детали реализации. Но попутно стали всплывать и другие моменты.

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

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

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

Не знаю, приживется ли SO-5 в проекте окончательно. Развитие идет динамично и у команды очень легкое отношение к включению или изъятию тех или иных зависимостей. Это я с большой осторожностью отношусь к тому что включать, а что нет, и могу долго раздумывать над тем, можно ли обойтись без какой-нибудь внешней библиотеки. Но здесь ребята более шустрые и рисковые -- если подвернулось что-то потенциально полезное, то сходу добавили. Если не оправдало себя, то не менее быстро выбросили 🙂

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

Пока же я рад происходящему и есть два воодушевляющих фактора:

Во-первых, появляется дополнительный стимул изыскать время и ресурсы, чтобы возобновить дальнейшую работу над SObjectizer-ом. Признаюсь честно, что в последние 3-4 месяца с этим были проблемы, не хватало мне сил после основной работы находить еще по 2-3 часа в день, чтобы, скажем, вернуться к поддержке короутин в SO-5.

Во-вторых, SO-5 будут изучать и пробовать в работе совершенно новые люди. Наверняка от них последуют какие-то замечания/соображения, которые позволят сделать SObjectizer еще лучше. Да и вообще опыт применения SO-5 в новом проекте лишним точно не будет.

Так что будем пробовать и смотреть что из этого получится.