Огромной ложкой дегтя остается лицензия – GPLv2, а ценник на коммерческую лицензию для небольших проектов кусается.
Вроде как для тех, кого это не устраивает, есть civetweb, который является форком Mongoose от 2013-го года (когда Mongoose сменил лицензию).
Но меня вот какой вопрос интересует: если при обработке события MG_EV_HTTP_REQUEST не выставить флаг MG_F_SEND_AND_CLOSE, а передать запрос на обработку куда-то еще, то как отдать результат обработки в соединение когда этот результат будет таки получен (например, спустя 10 секунд после вызова обработчика)?
Со временем подобный код превращается во что-то такое:
Gdiplus::Bitmap* bitmap; // (1)
// Инициализация GDI+.
Gdiplus::GdiplusStartup(&token, &input, &output);
// Еще какие-то действия, которые появились в последствии.
...
bitmap->SetSomeProperty(...); // Упс №1: bitmap еще не создан.
// Из-за того, что в (1) переменная bitmap даже не получила
// нулевого значения может произойти все, что угодно.
// Еще какие-то действия, которые добавились позже.
...
bitmap = new Gdiplus::Bitmap(filename); // Вот и создали объект.
int w = bitmap->GetWidth();
int h = bitmap->GetHeight();
// Тут со временем появились какие-то проверки.
if( w > SOME_LIMIT || h > SOME_ANOTHER_LIMIT )
return; // Упс №2: утекла память, т.к. delete сделать забыли.
for (int i = 0; i < w; i++)
for (int j = 0; j < h; j++)
{
...
}
delete bitmap;
Ну и работа с динамической памятью всегда дороже размещения объекта на стеке.
Ничего бы этого не было, если бы вы просто написали:
Gdiplus::Bitmap bitmap(filename);
Если вам кажется, что показанные выше потенциальные проблемы — это плод моей паранойи, то поспрашивайте разработчиков статических анализаторов или тех, кому приходится древний легаси код поддерживать. Там еще и не такие чудеса случаются.
> позволяющий выявлять ещё на этапе кодирования многие ошибки в программах на языке С, С++ и С#
Можно узнать, как инструмент статанализа, не имея привязок к конкретной предметной области, сможет выловить вот эту ошибку с округлением в системе Patriot?
Спасибо вам за проявленный интерес.
Еще мы подготовили ряд презентаций с рассказом об отдельных возможностях SO-5 в виде серии Deep Dive into SObjectizer-5.5. Некоторым такая форма подачи больше нравится, проще заходит. Найти эти презентации можно здесь.
Зависит, прежде всего, от двух вещей:
— уровня знания C++ (использование шаблонов и лябда-функций не должно вызывать проблем);
— от сложности предметной области. Иногда агенты могут быть сложными с запутанной логикой, но это не столько проблема SObjectizer, сколько сложность самой задачи, для которой агент написан.
Очень сильно облегчает погружение в чужой код то, что агенты в SO-5 — это классы со специальными методами (so_define_agent, so_evt_start, so_evt_finish). Нужно посмотреть, как и на что агент подписывается — смотришь в so_define_agent. Нужно узнать, что агент делает на старте — смотришь в so_evt_start и т.д. В эту же тему и то, что состояния агента описываются отдельными атрибутами. В эту же тему и то, что событие — это, обычно, отдельный метод.
Пример того, как может выглядеть реальный агент (не самый простой при этом), можно увидеть здесь.
Нет, в штатных очередях такой возможности нет.
В этом случае отлично работает схема collector-performer. Collector сначала складирует запросы у себе. Поскольку он знает структуру запроса, он знает, по какому критерию проверять сообщения на уникальность (т.е. что в сообщении является уникальным ключом). Плюс, Collector может использовать структуры данных, которые поддерживают возможность удаления элементов по ключу (например, это может быть Boost.Multindex, первым ключом может быть имя картинки, вторым ключом — порядковый номер).
Мы несколько раз использовали такую схему. И одной из задач Collector-а была как раз отбрасывание запросов-дубликатов.
Обычный send в SObjectizer асинхронный — он не блокируется. Сделано это для того, чтобы не заблокировать самого себя, если и получатель, и отправитель работают на одном и том же рабочем контексте. Это запросто возможно, т.к. агенты могут быть привязаны к одному и тому же диспетчеру.
Приостанавливает отправителя синхронный запрос (т.е. request_value вместо send). Но он приостанавливает в любом случае. Даже если нагрузка на получателя находится в разумных пределах.
А вот если send выполняется для message chain с ограничением на размер, то send может быть приостановлен на некоторый тайм-аут. Если в течении этого времени места для сообщения не нашлось, то SO-5 предпринимает одно из следующих действий: бросает исключение (т.е. игнорирует новое сообщение, но с большим треском), тихо выбрасывает новое сообщение, тихо выбрасывает самое старое сообщение или же вызывает std::abort. Так что на message chain можно делать backpressure, но здесь будут свои сложности. В том числе и в случае, когда отправитель и получатель работают на одном и том же контексте.
Это как раз то, о чем речь шла в статье: хороший механизм overload control затачивается под определенные условия. Тут пространство для вариантов весьма большое. Например, можно настроить несколько уведомлений для очереди: когда ее объем превышает 1000, 10000 и 100000 экземпляров. Причем уведомления могут быть как о превышении (т.е. когда объем превышает 10000), так и о падении ниже порога (т.е. когда объем опускается ниже 1000). Можно много чего придумать.
Поинт был несколько в другом: если фреймворк претендует на универсальность, то он вряд ли сможет предложить большое количество готовых вариантов, доступных из коробки. Посему хорошее решение придется программировать пользователю фреймворка. А если такой возможности нет, то придется пользоваться чем-то готовым, но не факт, что это будет удобно и оптимально.
Как видно, у стандартных массивов в C++ много проблем. Поэтому в современном C++ стоит использовать std::array: его API схож с std::vector и другими контейнерами и ошибиться при его использовании труднее.
Замена голых сишных массивов на std::array — это разумный совет, но вот к такому коду:
void f(std::array<T, N> v) {...}
нужно относиться осторожно, т.к. при вызове f в стек запихивается все содержимое v. Чем больше значение N, тем «тяжелее» окажется вызов f. А если f еще и рекурсивная…
Если реализация стандартной библиотеки не кривая, то сделает. Можете погуглить описание std::make_unique (например, тут или тут), или просто загляните заголовочные файлы, которые идут с вашим компилятором.
Пикантность, однако, в том, что авторы статьи предупреждают и об опасностях auto, и об неоднозначностях unique_ptr для массивов, но не могут показать простой пример того, как auto и make_unique не дают стрельнуть в ногу как раз в случае unique_ptr для массивов.
Да и вообще ваши рассуждения о современном C++ наводят на мысль, что для вас «современный» С++ начался в 2011-ом. А до этого был лишь «С с классами». Тогда как рекомендации использовать алгоритмы из STL вместо написанных вручную циклов, начали появляться еще на рубеже 2000-х. А тот же Boost.Ranges, который все это дело сильно упростил, вошел в Boost-1.33 еще в 2005-ом году. Такое ощущение, что никого из вас в свое время Александреску не покусал.
Не уверен, что понял вопрос. Если вы про классы-performer-ы, но наружу они не выставляют ничего похожего на work_started/work_finished. Там скорее что-то вроде start()/shutdown(), внутри которых может быть много задач обработано. А статистику нужно замерять как раз по задачам, которые внутри выполняются. Так что замерять время снаружи performer-ов нет смысла.
Вроде как для тех, кого это не устраивает, есть civetweb, который является форком Mongoose от 2013-го года (когда Mongoose сменил лицензию).
Но меня вот какой вопрос интересует: если при обработке события MG_EV_HTTP_REQUEST не выставить флаг MG_F_SEND_AND_CLOSE, а передать запрос на обработку куда-то еще, то как отдать результат обработки в соединение когда этот результат будет таки получен (например, спустя 10 секунд после вызова обработчика)?
Со временем подобный код превращается во что-то такое:
Ну и работа с динамической памятью всегда дороже размещения объекта на стеке.
Ничего бы этого не было, если бы вы просто написали:
Если вам кажется, что показанные выше потенциальные проблемы — это плод моей паранойи, то поспрашивайте разработчиков статических анализаторов или тех, кому приходится древний легаси код поддерживать. Там еще и не такие чудеса случаются.
Ну вот так в последствии C++ный код начинает и тормозить, и течь, и падать.
Зато разработчики PVS-Studio без работы и клиентов не останутся :)
Скажите, а зачем вам вообще потребовалось Bitmap создавать динамически? Почему нельзя было объявить bitmap как автоматическую переменную на стеке?
Но у вас здесь получилось как в анекдоте «у рыбы нет шерсти, поэтому нет блох, а вот кстати о блохах».
Можно узнать, как инструмент статанализа, не имея привязок к конкретной предметной области, сможет выловить вот эту ошибку с округлением в системе Patriot?
Еще мы подготовили ряд презентаций с рассказом об отдельных возможностях SO-5 в виде серии Deep Dive into SObjectizer-5.5. Некоторым такая форма подачи больше нравится, проще заходит. Найти эти презентации можно здесь.
— уровня знания C++ (использование шаблонов и лябда-функций не должно вызывать проблем);
— от сложности предметной области. Иногда агенты могут быть сложными с запутанной логикой, но это не столько проблема SObjectizer, сколько сложность самой задачи, для которой агент написан.
Очень сильно облегчает погружение в чужой код то, что агенты в SO-5 — это классы со специальными методами (so_define_agent, so_evt_start, so_evt_finish). Нужно посмотреть, как и на что агент подписывается — смотришь в so_define_agent. Нужно узнать, что агент делает на старте — смотришь в so_evt_start и т.д. В эту же тему и то, что состояния агента описываются отдельными атрибутами. В эту же тему и то, что событие — это, обычно, отдельный метод.
Пример того, как может выглядеть реальный агент (не самый простой при этом), можно увидеть здесь.
В этом случае отлично работает схема collector-performer. Collector сначала складирует запросы у себе. Поскольку он знает структуру запроса, он знает, по какому критерию проверять сообщения на уникальность (т.е. что в сообщении является уникальным ключом). Плюс, Collector может использовать структуры данных, которые поддерживают возможность удаления элементов по ключу (например, это может быть Boost.Multindex, первым ключом может быть имя картинки, вторым ключом — порядковый номер).
Мы несколько раз использовали такую схему. И одной из задач Collector-а была как раз отбрасывание запросов-дубликатов.
Приостанавливает отправителя синхронный запрос (т.е. request_value вместо send). Но он приостанавливает в любом случае. Даже если нагрузка на получателя находится в разумных пределах.
А вот если send выполняется для message chain с ограничением на размер, то send может быть приостановлен на некоторый тайм-аут. Если в течении этого времени места для сообщения не нашлось, то SO-5 предпринимает одно из следующих действий: бросает исключение (т.е. игнорирует новое сообщение, но с большим треском), тихо выбрасывает новое сообщение, тихо выбрасывает самое старое сообщение или же вызывает std::abort. Так что на message chain можно делать backpressure, но здесь будут свои сложности. В том числе и в случае, когда отправитель и получатель работают на одном и том же контексте.
Поинт был несколько в другом: если фреймворк претендует на универсальность, то он вряд ли сможет предложить большое количество готовых вариантов, доступных из коробки. Посему хорошее решение придется программировать пользователю фреймворка. А если такой возможности нет, то придется пользоваться чем-то готовым, но не факт, что это будет удобно и оптимально.
Замена голых сишных массивов на std::array — это разумный совет, но вот к такому коду:
нужно относиться осторожно, т.к. при вызове f в стек запихивается все содержимое v. Чем больше значение N, тем «тяжелее» окажется вызов f. А если f еще и рекурсивная…
Поэтому при рефакторинге кода вида
нельзя бездумно менять его на
может быть лучше сделать так:
или так:
или через какой-нибудь самописный array_slice:
Пикантность, однако, в том, что авторы статьи предупреждают и об опасностях auto, и об неоднозначностях unique_ptr для массивов, но не могут показать простой пример того, как auto и make_unique не дают стрельнуть в ногу как раз в случае unique_ptr для массивов.
вместо
Пример с использованием unique_ptr для хранения FILE* выглядит как-то странно. Лямбда зачем-то понадобилась. Почему нельзя было без нее?
Да и вообще ваши рассуждения о современном C++ наводят на мысль, что для вас «современный» С++ начался в 2011-ом. А до этого был лишь «С с классами». Тогда как рекомендации использовать алгоритмы из STL вместо написанных вручную циклов, начали появляться еще на рубеже 2000-х. А тот же Boost.Ranges, который все это дело сильно упростил, вошел в Boost-1.33 еще в 2005-ом году. Такое ощущение, что никого из вас в свое время Александреску не покусал.