среда, 21 сентября 2022 г.

[prog.c++] Отказался от использования маркера can_throw в arataga

В проекте arataga использовался прием, который на момент активной работы над arataga, существенно повышал мне коэффициент спокойного сна. Подробнее об этом трюке я писал два года назад на Habr-е: can_throw или не can_throw?

В двух словах: в функции/методы, в которых можно было спокойно выбрасывать исключения, передавался специальный маркер типа can_throw_t. Этот маркер указывал, что функция/метод вызывается из контекста, в котором исключения перехватываются и обрабатываются. Соответственно, если такой маркер в функции/методе есть, значит я могу сделать throw или вызвать какую-то бросающую исключение операцию. А если такого маркера нет, то мне нужно обрамить свои действия блоком try-catch.

К появлению can_throw привело то, что в arataga пришлось писать много callback-ов, которые вызывались при выполнении IO-операций через Asio и парсинге HTTP-протокола посредством http_parser. В таких контекстах исключения нельзя было выпускать наружу и требовалось понимать, пишу ли я сейчас код для callback-а или для чего-то другого.

Надо сказать, что прием can_throw мной всегда оценивался как не вполне однозначный.

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

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

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

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

Проект arataga интересен еще и тем, что это один из двух проектов за последние несколько лет, в которых я серьезно задумывался над использованием stackfull-короутин. В arataga в виде короутин можно было бы реализовать обработчики отдельных подключений (т.е. модель coroutine per connection). И, если бы в SObjectizer была поддержка stackfull-короутин "искаропки", то может быть именно с модели coroutine per connection я реализацию arataga и начал бы.

Но в SObjectizer поддержки stackfull-короутин нет. Все еще нет.

Давеча у меня появилось некоторое время чтобы подумать о том, что же в SObjectizer можно добавить. Вариантов для рассмотрения не так, чтобы много, и stackfull-короутины в short-list-е.

Тут-то проект arataga в очередной раз и вспомнился. Если добавлять короутины в SObjectizer, то в arataga их можно будет и сразу же протестировать "в деле".

Дабы освежить в памяти детали работы arataga погрузился в код...

И слегка прифигел от количество can_throw в реализации connection-handler-ов.

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

Обилие can_throw обескуражило настолько, что стало понято, что с этим нужно что-то делать. Только после этого можно было задуматься о попытке переписать arataga на короутинах.

Анализ кода показал, что сейчас в arataga осталось совсем не так много мест, в которых нужно заботиться о выбросе исключения наружу. И эти все места уже старательно обернуты в try-catch. Так что can_throw реально стал выглядеть избыточным рудиментом.

Поэтому в итоге can_throw из кода arataga был полностью изъят.

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

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

В самом начале разработки arataga, когда еще не было тех самых оберток над callback-ами и не было полного понимания того, в каких местах и какие callback-и потребуются, can_throw сыграл свою положительную роль. Но затем, по мере накопления опыта и структуризации использованных подходов, can_throw стал больше мешать, чем помогать. И пришло время от can_throw избавиться. Мавр сделал свое дело, мавр может уходить...


История с can_throw -- это еще одна иллюстрация того, что когда ты занимаешься проектом достаточно долгое время, то тебе доводится увидеть как обесцениваются со временем твои старые идеи, казавшиеся когда-то удачными.

PS. Я заикнулся о том, что рассматриваю возможность добавления stackfull-короутин в SObjectizer. Но тут все очень не просто. Погружение в arataga наводит на мысль о том, что stackfull-короутины здесь могут и не помочь. Так что еще рано говорить о том, что решение о добавлении stackfull-короутин в SObjectizer принято. Тут еще нужно думать и думать, да и вообще это уже совсем другая история.

1 комментарий:

Grigory Demchenko комментирует...

Корутины всегда помогают в асинхронном коде. Можно использовать stackless, можно stackful.