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

О блоге

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

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

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

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

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

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

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

[prog.c++.multithreading] Интересно, это false positive от thread sanitizer-а или нет?

Thread sanitizer выдал предупреждение на код, который я много лет считал корректным.

Для нетерпеливых вот самодостаточный пример на godbolt: https://godbolt.org/z/3xPadcnva.

Для всех остальных пояснение:

  • на главной нити создается объект actual_repo. В этом объекте живут и std::mutex, и condition_variable (на котором будет осуществляться ожидание);
  • ссылка на actual_repo передается в дочернюю нить. Через какое-то время дочерняя нить вызывает для actual_repo метод stop;
  • главная же нить засыпает на вызове wait_for_stop у объекта actual_repo. Этот метод вернет управление только после того, как дочерняя нить вызовет stop;
  • когда дочерняя нить вызывает stop, то главная нить просыпается, выходит из wait_for_stop, после чего разрушается объект actual_repo;
  • после чего дожидаемся завершения дочерней нити и прекращаем работу.

Фокус здесь в том, что внутри stop условная переменная взводится (вызов notify_one()) без захвата мутекса.

А это ведет к тому, что главная нить может проснуться и уничтожить объект actual_repo еще до того, как дочерняя нить завершит вызов stop.

Т.е. деструктор для repo_basic::m_stop_initiated_cv может отработать еще до того, как на дочерней нити завершится вызов m_stop_initiated_cv.notify_one().

И как раз thread sanitizer и ругается на то, что в главной нити происходит модификация содержимого repo_basic::m_stop_initiated_cv тогда как на дочерней нити мы это содержимое только только прочитали.

Полный код примера под катом. Если раскомментировать строчку в repo_basic::try_initiate_stop, то thread sanitizer будет доволен и ничего не диагностирует.

Собственно, у меня два (возможно, риторических) вопроса к аудитории:

  • это ложное срабатывание thread sanitizer-а или же в таком коде реально есть проблема гонок? Я лично всегда думал, что здесь гонок нет. Но есть вероятность, что ошибаюсь. А так как ошибаюсь часто, то уже сомневаюсь даже в том, в чем раньше был уверен;
  • если это таки ложное срабатывание, то как тогда быть с кодом, чтобы его можно было проверять thread sanitizer-ом, но не натыкаться на это ложное срабатывание снова и снова?

В качестве дополнения: в примере максимально выхолощенная версия, выделенная из реального кода. Поэтому там есть и repo_basic, и другие усложнения. Полагаю, что для демонстрации данного случая можно было и более простой код написать. Однако, оригинальный код специально написан с учетом того, что после метода stop к actual_repo обращаться нельзя (т.е. уже учтено, что actual_repo может быть разрушен параллельно работающей нитью). Да и сам метод stop в actual_repo специально написан так, что последнее, что он делает -- это обращается к m_stop_initiated_cv.notify_one().

пятница, 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" есть интересный заход: