Показаны сообщения с ярлыком timertt. Показать все сообщения
Показаны сообщения с ярлыком timertt. Показать все сообщения

четверг, 21 мая 2026 г.

[prog.c++] Обнаружился баг в timertt возрастом более 10 лет

Пользователи обнаружили в SObjectizer проблему, которая была вызвана неправильной работой механизма timer_heap в библиотеке timertt.

Эта библиотека написана мной осенью 2014-го года для того, чтобы можно было окончательно отвязать SObjectizer от ACE. И как раз тогда, чуть ли не в самой первой версии, допущена ошибка в операции удаления таймерной заявки в механизме timer_heap. Этот timer_heap реализован в виде binary heap на базе вектора. И как раз удаление из вектора и содержало проблему.

То, что я допустил достаточно дурацкую ошибку совсем не удивительно. Я вообще умудряюсь делать на удивление много ошибок при реализации простых структур данных (скажем, если приходится вручную программировать интрузивный двусвязный список, то я там обязательно в паре мест накосячу). Дополнительным отягчающим фактором стало то, что специфическое для timer_heap тестирование было проведено "по верхам". Думаю, что если бы в 2014-ом не поленился составить тест на базе примитивного fuzzing-а, то эта проблема вскрылась бы уже тогда. Но невнимательность + разгильдяйство сделали свое темное дело.

Более удивительно то, что этот баг проявился в полный рост только сейчас, в 2026-ом. Вот это внушаить 🤔

Какие выводы можно сделать?

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

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

PPS. Видимо, нужно найти время и вытащить timertt из старого svn-репозитория на SourceForge чтобы он продолжил жить на GitHub-е. Плюс выбросить оттуда MxxRu и перевести все на CMake (собственно, необходимость бодаться с CMake и является основным стоп-фактором). Нужно как-то себя заставить сделать это. Жаль только, что история коммитов при переносе в git потеряется 🙁

PPPS. Обновление для SObjectizer-а уже опубликовано в виде версии 5.8.5.1.

понедельник, 23 сентября 2024 г.

[prog.c++] Десять лет библиотеке timertt

Десять лет назад в свет вышла небольшая библиотека timer thread template, она же timertt. Делалась она чтобы иметь возможность создавать в приложении большое количество одноразовых и/или периодических таймеров. Когда я говорю про "большое", то речь идет о сотнях тысяч, как минимум.

Сделать собственные средства для поддержки таймеров заставила жизнь. Долгое время в SObjectizer-е применялись таймеры из замечательной библиотеки ACE. И не только таймеры. Много лет ACE для нас служила базовым слоем, мы брали оттуда и мутексы, и сокеты, и даже хэш-таблицы, поскольку ничего из этого в C++98 не было.

Однако, в SObjectizer-5 мы заложились сразу на C++11, в котором многое нашлось в стандартной библиотеке. Кроме того, после выхода SObjectizer-а в "свободное плавание" нам пришлось отказаться от развития ряда построенных над SObjectizer-ом библиотек, так что нам больше не нужны были сокеты и инструменты для ручной загрузки-выгрузки динамических библиотек.

В итоге, к середине 2014-го года единственное, что нас привязывало к ACE, -- это таймеры. Которые, как по мне, в ACE были сделаны очень здорово. И мы оказались в ситуации, когда небольшой SObjectizer, архив которого "весили" ~600Kb, требовал внешней зависимости размером порядка 6Mb в архиве. И нужна нам ACE была только для таймеров 😣

В общем, решили от ACE отказаться полностью для чего и пришлось написать свой timertt, т.к. ничего готового на просторах тогдашнего Интернета не нашлось. А для этого потребовалось погрузиться в тему различных механизмов таймеров и реализовать пару-тройку оных самостоятельно. На C++ных шаблонах, понятное дело, чтобы было хардкорнее...

Если мне не отшибает склероз, на все про все ушло порядка месяца. В конце августа 2014-го работа началась, 4-го сентября 2014-го был сделан первый коммит, 18-го сентября 2014-го был зафиксирован первый стабильный тег.

timertt изначально создавалась под SObjectizer, хотя к самому SObjectizer-у она и не привязана, тут полностью обратная ситуация, можно сказать, что без timertt не было бы того SObjectizer-5, каким мы его знаем сегодня. Но т.к. timertt писалась под SObjectizer, то отдельно мы ее мало пиарили. Было несколько анонсов то здесь, то там, но не более того. Самый эпичный анонс случился на LOR-е, сейчас перечитываешь и остатки волос непроизвольно шевелятся... 😂

Что меня до сих пор удивляет в timertt, так это тот факт, что она очень медленно обрастает какой-то новой функциональностью. А с 2019-го туда вообще ничего не добавляется. Работает себе и работает. Редкий в моей практике случай, обычно если что-то пошло в дело, то новые хотелки возникают регулярно, да и какие-то прятавшиеся баги время от времени вылезают наружу. Тот же SObjectizer постоянно расширяется и для RESTinio есть ряд нереализованных хотелок (но тупо не хватает времени на эти работы). А вот timertt просто работает и каких-то новых пожеланий к ней нет. Это-то как раз и удивляет больше всего. Никогда прежде такого не было, а вот поди ж ты.

В завершение хочется пожелать и самому себе, и библиотеке timertt, и читателям блога вернуться к продолжению этой истории в этом же блоге в сентябре 2034-го 😁


Что-то круглые даты как-то кучно пошли. И это еще не последняя, в начале октября ожидается еще одна ;)

пятница, 3 ноября 2017 г.

[prog.c++] timertt-1.2.1

Сегодня мы выкатили небольшое обновление для нашей библиотеки для работы с таймерами -- timertt. Изменений в ней совсем мало, поэтому никаких громких анонсов мы не делаем.

Во-первых, мы добавили макрос TIMERTT_VERSION, которых хранит в себе версию библиотеки. В десятичном формате YXXXZZZ, где Y -- это мажорный номер версии, X и Z -- минорный номер и номер патча с лидирующими нулями. Т.е. версия 1.2.1 кодируется как 1002001, а версия 1.3.14 будет кодироваться как 1003014. Каюсь, такой макрос нужно было ввести еще в 1.2.0, но хорошая мысля, как говорится, приходит опосля...

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

auto t = tt.allocate();
tt.activate(t, std::chrono::seconds(30), []{...});
...
// Timer needs to be rescheduled.
// Deactivate it first.
tt.deactivate(t);
// Reactivate it.
tt.activate(t, std::chrono::seconds(45), []{...});

В случае, если использовался thread-safe механизм (например, такой, как timer_thread), то эта двойная операция обрабатывалась не очень эффективно -- требовалось два раза захватывать mutex.

Теперь же reschedule выполняет это все внутри себя захватив mutex всего один раз:

auto t = tt.allocate();
tt.activate(t, std::chrono::seconds(30), []{...});
...
// Timer needs to be rescheduled.
tt.reschedule(t, std::chrono::seconds(45), []{...});

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

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

Поэтому reschedule следует использовать с большой осторожностью. Например, достаточно безопасно вызывать reschedule при работе с однопоточными timer_manager-ами. Там вы сами определяете, когда вы вызываете process_expired_timers. Соответственно, если вы находитесь вне process_expired_timers, то reschedule вызывать безопасно. А вот внутри process_expired_timers, т.е. внутри таймерного события, reschedule вызывать нельзя.

Так что еще раз: при работе с reschedule нужно проявлять двойную осмотрительность.

Вообще, в timertt с самого начала существует рекомендация: вместо реактивации таймеров лучше создавать таймеры заново. Т.е. сперва вы вызваете deactivate для старого таймера, затем создаете новый таймер и вызываете для него activate. С учетом того, что timertt может поддерживать темп активации в несколько миллионов таймеров в секунду, мы считаем, что это приемлемый подход. Однако, если кому-то приходится сталкиваться с ситуациями, когда прямо внутри deactivate нужно точно знать, что таймер полностью деактивирован (может быть даже он успел сработать прямо внутри deactivate), то дайте знать. Будем думать, как это побороть. Но не бесплатно. Ну, в том смысле, что это не сможет не сказаться на скорости работы timertt. Хотя, если кто-то возьмется проспонсировать такую доработку, мы тоже не откажемся ;)

пятница, 27 октября 2017 г.

[prog.c++] Библиотека timertt обновилась до версии 1.2.0

Мы обновили свою легковесную библиотеку для работы с отложенными и периодическими таймерами (wallclock-таймеры не поддерживаются в принципе). В этой версии добавлены две важные фичи:

1. Раньше действие для таймера всегда имело тип std::function<void()>, что было гибко и удобно, но имело скрытые накладные расходы, связанные с std::function (по сути, std::function тут выступал как умный указатель для лямбд и функторов). Если от этих скрытых расходов хочется избавиться, то можно задать свой собственный тип. Например:

class operation_canceler {
    operation_manager & manager_;
    operation_id id_;
public:
    operation_canceler(operation_manager & manager, operation_id id)
        : manager_{manager}, id_{id}
    {}
    void operator()() const noexcept
    {
        manager_.cancel(id_);
    }
};
...
// Define type of timer thread which was use operation_canceler as
// a timer action type.
using my_timer_wheel_thread = timertt::timer_wheel_thread_template<
    operation_canceler,
    timertt::default_error_logger,
    timertt::default_actor_exception_handler >;
...
// Create and use this timer thread.
my_timer_wheel_thread tt;
tt.start();
...
tt.activate( std::chrono::milliseconds(750), operation_canceler{manager, current_id});

Тип должен быть Moveable и MoveConstructible. Соответственно, теперь когда создается объект-таймер, там резервируется место для пользовательского объекта-функтора. А при активации таймера пользовательский функтор мувится в этот зарезервированный кусок памяти. Тем самым не происходит дополнительных аллокаций памяти (как это временами может происходить с std::function).

2. Теперь пользователь сам может создавать объекты-таймеры на стеке или внутри других своих объектов. Ранее объекты-таймеры создавались исключительно динамически. Теперь можно избежать дополнительных аллокаций, но зато пользователь сейчас сам должен следить за тем, чтобы таймер не закончил свою жизнь раньше времени. Например:

void do_something_complex() {
    timertt::default_timer_wheel_thread tt;
    tt.start();
    ...
    timertt::default_timer_wheel_thread::scoped_timer_object timer;
    // Activate
    tt.activate(timer, std::chrono::milliseconds(250), ...);
    ...
    // Timer can be deactivated in usual way.
    tt.deactivate(timer);
    ...
    tt.shutdown_and_join();
}

Правда, в этой версии мы несколько сломали совместимость на уровне исходного кода. Поэтому номер версии 1.2.0, а не 1.1.4.

Пару слов о происхождении и назначении библиотеки. Когда-то мы долго и с удовольствием использовали большую библиотеку ACE. В том числе и тамошние таймеры (реализация которых была добротной и продвинутой). Но по мере перехода на C++11 мы постепенно отказывались от ACE и в один прекрасный момент оказалось, что из ACE нам нужны только таймеры. Чтобы не таскать дистрибутив ACE только ради таймеров, мы сделали свою легковесную header-only либу, которая базируется только на штатных возможностях C++11.

У нас timertt в работе уже года три. Проблем не замечено. Работает стабильно, может поддерживать изрядное количество таймеров (десятки и сотни миллионов). Реализует три разных таймерных механизма: wheel, heap и list, каждый из которых хорош в своей ситуации.

Предыдущие версии timertt могли работать и с компиляторами, которые не очень хорошо поддерживали C++11 (в частности, VS2013). Начиная с 1.2.0 мы на такие компиляторы уже не оглядываемся. Нужно что-то более-менее нормальное (gcc 4.8-7.2, clang 3.5-5.0, vs2015/2017). Однако, основная часть кода пока еще под все возможности C++ (вроде noexcept и constexpr там, где это разумно) еще не адаптирована. Сделаем это со временем.

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

Архивы с исходниками библиотеки можно найти здесь. Сами исходники живут здесь. Документация здесь.

вторник, 28 марта 2017 г.

[prog.c++] Библиотека timertt обновилась до версии 1.1.3

Вышла обновленная версия библиотеки timertt для работы с таймерами в C++ -- 1.1.3. В этой версии в публичный интерфейс timer_manager и timer_thread добавлена функция empty(), которая проверяет, пуст ли список таймеров или нет.

Признаюсь, это глупый косяк, который был допущен пару лет назад. В потрохах библиотеки empty() был реализован для всех timer_engine, но вот наружу я его тупо забыл вытащить (объект timer_engine инкапсулирован внутри timer_manager/timer_thread и просто так его методы недоступны).

Библиотека разрабатывалась для замены ACE в проекте SObjectizer, поэтому все, что связано с timertt, находится на SourceForge:

  • архивы с исходными текстами доступны в секции Files. Архив timertt-1.1.3-headeronly.7z содержит только основной заголовочный файл со всей функциональностью timertt. Архив timertt-1.1.3-full.7z содержит так же тесты, примеры и сгенерированный посредством Doxygen API Reference Manual;
  • основная документация для проекта собрана в Wiki;
  • исходники лежат в Subversion-репозитории на SourceForge. Релизные версии в tags/timertt, находящиеся в разработке версии в branches/timertt.

пятница, 17 марта 2017 г.

[prog.c++] Библиотека timertt обновилась до версии 1.1.2

Вышла обновленная версия библиотеки timertt для работы с таймерами в C++ -- 1.1.2. В этой версии добавлена одна малюсенькая, но очень важная деталь: теперь в классах таймерных нитей и таймерных менеджеров есть публичное имя thread_safety. С его помощью стало гораздо проще писать шаблонный код, который может работать с разными типами таймерных нитей/менеджеров. Например:

// Класс для управления таймерами в прикладной программе.
// Может работать с разными типами таймерных нитей или менеджеров.
templatetypename TIMER_CONTROLLER >
class timer_client {
   TIMER_CONTROLLER & controller_;

public :
   // Определяем тип идентификатора с учетом того, какой thread_safety
   // используется у менеджера таймеров.
   using timer_holder = timertt::timer_holder_object< typename TIMER_CONTROLLER::thread_safety >;

   // Создать новый таймер, при срабатывании которого нужно что-то выполнить.
   templatetypename DURATION, typename ACT >
   auto schedule_action( DURATION timeout, ACT && action ) {
      timer_holder id = controller_.allocate();
      controller_.activate( id, timeout, [act = std::move(action)] {
            act();
         } );
      return id;
   }

   // Отменить действие, которое было запланировано.
   void cancel_action( timer_holder id ) {
      controller_.deactivate( id );
   }
   ...
};

using mtsafe_wheel_manager = timertt::timer_wheel_manager< timertt::thread_safety::safe >;

timer_client< mtsafe_wheel_manager > client1(...);
auto id1 = client1.schedule_action( 250ms, []{ ... } );
...
client1.cancel_action( id1 );

using mtunsafe_wheel_manager = timertt::timer_wheel_manager< timertt::thread_safety::unsafe >;

timer_client< mtunsafe_wheel_manager > client2(...);
auto id2 = client2.schedule_action( 500ms, []{ ... } );
...
client2.cancel_action( id2 );

Без этого маленького дополнения класс timer_client пришлось бы делать зависящим от двух параметров шаблонов: от самого таймерного механизма и от признака thread_safety. Начиная с версии 1.1.2 достаточно всего одного шаблонного параметра.

Если вдруг кто-то не в курсе, что такое timertt, то в нескольких словах это:

Header-only библиотека без внешних зависимостей, базирующаяся на возможностях стандартной библиотеки C++11. Реализует таймеры на основе тайм-аутов, т.е. таймеры, которые должны сработать через сколько-то миллисекунд (секунд, минут и т.д.) после момента активации таймера. wallclock-таймеры не поддерживаются.

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

Библиотека поддерживает три таймерных механизма: timer_wheel, timer_heap и timer_list, у каждого из которых есть свои преимущества и недостатки. Может поддерживаться большое количество таймеров (десятки и сотни миллионов) и обеспечивается высокая скорость обработки таймеров (до нескольких миллионов в секунду).

Кросс-платформенная, проверялась посредством MSVS2013, 2015, 2017 (Windows), GCC 4.9-6.3 (Windows, Linux), Clang 3.5-3.9 (Linux, FreeBSD).

Распространяется под 3-х секционной BSD-лицензией, может свободно использоваться как в открытых, так и в закрытых коммерческих проектах.

Мы сделали timertt где-то 2.5 года назад, с тех пор она верой и правдой служит нам в SObjectizer-е. Последние правки вносились почти два года назад, когда вышла версия 1.1.1. За все время каких-то проблем с timertt не замечено.

Библиотека разрабатывалась для замены ACE в проекте SObjectizer, поэтому все, что связано с timertt, находится на SourceForge:

  • архивы с исходными текстами доступны в секции Files. Архив timertt-1.1.2-headeronly.7z содержит только основной заголовочный файл со всей функциональностью timertt. Архив timertt-1.1.2-full.7z содержит так же тесты, примеры и сгенерированный посредством Doxygen API Reference Manual;
  • основная документация для проекта собрана в Wiki;
  • исходники лежат в Subversion-репозитории на SourceForge. Релизные версии в tags/timertt, находящиеся в разработке версии в branches/timertt.

вторник, 25 ноября 2014 г.

[prog.c++] Версия 1.1.0 библиотеки timertt

Библиотека timertt обновилась до версии 1.1.0.

В двух словах, timertt -- это:

Header-only библиотека без внешних зависимостей, базирующаяся на возможностях стандартной библиотеки C++11. Реализует таймеры на основе тайм-аутов, т.е. таймеры, которые должны сработать через сколько-то миллисекунд (секунд, минут и т.д.) после момента активации таймера. wallclock-таймеры не поддерживаются.

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

Библиотека поддерживает три таймерных механизма: timer_wheel, timer_heap и timer_list, у каждого из которых есть свои преимущества и недостатки. Может поддерживаться большое количество таймеров (десятки и сотни миллионов) и обеспечивается высокая скорость обработки таймеров (до нескольких миллионов в секунду).

Кросс-платформенная, проверялась посредством MSVS2013 (Windows), GCC 4.9 (Windows, Linux), Clang 3.5 (Linux).

Распространяется под 3-х секционной BSD-лицензией, может свободно использоваться как в открытых, так и в закрытых коммерческих проектах.

Версия 1.1.0 добавляет новый тип сущностей -- timer_manager. В отличии от timer_thread, timer_manager не создает отдельной рабочей нити для отслеживания времени срабатывания таймеров и запуска обработчиков таймерных событий. Все эти действия должен инициировать пользователь, вызывая методы timer_manager в своем собственном цикле обработки событий. Что позволяет пользователю самому выбирать рабочий поток на котором будут осуществляться операции с таймерами.

Вот так может выглядеть работа с таймерами посредством timer_manager:

using namespace std;
using namespace chrono;
using namespace timertt;

timer_heap_manager_template< thread_safety::unsafe > tm;

tm.activate( milliseconds(100), []{ cout << "hello!" << endl; } );
tm.activate( milliseconds(200), []{ cout << "hello!" << endl; } );

auto stop_point = system_clock::now() + milliseconds(300);
while( stop_point > system_clock::now() )
{
  tm.process_expired_timers();
  this_thread::sleep_for(
        tm.timeout_before_nearest_timer( milliseconds(10) ) );
}

А вот, для сравнения, аналогичный код, использующий timer_thread:

using namespace std;
using namespace chrono;
using namespace timertt;

timer_heap_thread_t tt;

tt.start();

tt.activate( milliseconds(100), []{ cout << "hello!" << endl; } );
tt.activate( milliseconds(200), []{ cout << "hello!" << endl; } );

this_thread::sleep_for( milliseconds(300) );

tt.shutdown_and_join();

Новые timer_manager-ы могут иметь или не иметь средств защиты от многопоточности. Не защищенные от многопоточности timer_manager-ы предназначены для использования в однопоточных приложениях, где вообще все операции выполняются на контексте одной нити. Тогда как защищенные от многопоточности thread_manager-ы позволяют создавать и удалять таймеры из разных нитей. Но обработка таймеров (т.е. вызовы методов process_expired_timers и nearest_time_point/timeout_before_nearest_timer) должна выполняться всего одной нитью.

Новые timer_manager-ы предназначены для случаев, когда обработку таймеров нужно объединить с обработкой других событий в приложении, без выделения под обработку таймеров отдельной нити. Например, если приложение является MQTT-клиентом, то его цикл обработки событий, при использовании библиотеки libmosquittopp, схематично может выглядеть следующим образом:

timer_heap_manager_template< thread_safety::unsafe > tm(...);
app_event_queue eq(...);
mosqpp::mosquittopp mosq(...);
...
while(true)
{
  // Process application events while they exist...
  while( !eq.empty() )
  {
    process_app_event( eq.pop() );
    // All expired timers can be handled.
    tm.process_expired_timers();
  }

  // Process any MQTT-related events.
  auto r = mosq.loop(
    // Wait no more time than next timer event.
    to_milliseconds( tm.timeout_before_nearest_timer( millisecons(1000) ) ) );
  if( r != MOSQ_ERR_SUCCESS )
    ... // Some error handling.
}

Библиотека разрабатывалась для замены ACE в проекте SObjectizer, поэтому все, что связано с timertt, находится на SourceForge:

  • архивы с исходными текстами доступны в секции Files. Архив timertt-1.1.0-headeronly.7z содержит только основной заголовочный файл со всей функциональностью timertt. Архив timertt-1.1.0-full.7z содержит так же тесты, примеры и сгенерированный посредством Doxygen API Reference Manual;
  • основная документация для проекта собрана в Wiki;
  • исходники лежат в Subversion-репозитории на SourceForge. Релизные версии в tags/timertt, находящиеся в разработке версии в branches/timertt.

пятница, 21 ноября 2014 г.

[prog.c++] Про использование C++ных шаблонов при разработке timertt-1.1

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

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

Полагаю, что процесс преобразования timertt к поддержке как однопоточного, так и многопоточного варианта еще не закончен. Но какие-то работающие куски уже получены. Данный пост преследует цель попытаться лучше понять, что же именно было сделано, устраивает ли этот вариант и куда/как следует двигаться дальше. Ну и может быть кому-то будет интересно посмотреть на фокусы с шаблонами, на которые пришлось пойти, дабы минимизировать объем дублирующегося кода и избежать тупой копи-пасты.

четверг, 18 сентября 2014 г.

[prog.c++] Версия 1.0.0 библиотеки timertt

Теоретические изыскания, начатые в двух заметках про таймеры (#1, #2) завершились вот таким практическим результатом -- на SourceForge выложен первый релиз библиотеки timertt.

Библиотека timertt -- это header-only библиотека, не имеющая внешних зависимостей, базирующаяся только на стандартной библиотеке C++11 (правда, нужно, чтобы там была корректная реализация std::chrono::steady_clock, иначе будут глюки при коррекции локального времени). Должна быть кросс-платформенной, т.к. никаких системно-зависимых вещей в ней нет. Я ее проверял под Visual C++ 2013 (Windows) и GCC 4.9.1 (Windows, Linux).

Лицензия: 3-х секционная BSD. Т.е. использоваться может без проблем как в открытых, так и в закрытых проектах.

Библиотека поддерживает только таймеры на основе тайм-аутов, т.е. таймеры, которые должны сработать через сколько-то миллисекунд (секунд, минут и т.д.) после момента активации таймера. wallclock-таймеры не поддерживаются.

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

Библиотека поддерживает три таймерных механизма: timer_wheel, timer_heap и timer_list, у каждого из которых есть свои преимущества и недостатки. Может поддерживаться большое количество таймеров (сотни тысяч, миллионы или даже десятки миллионов) и обеспечивается высокая скорость обработки таймеров (до нескольких миллионов в секунду, но это зависит от времени работы связанных с таймером пользовательских событий).

В коде все это выглядит приблизительно следующим образом:

#include <iostream>
#include <cstdlib>

#include <timertt/all.hpp>

using namespace std;
using namespace std::chrono;
using namespace timertt;

int main()
{
   timer_wheel_thread_t tt;

   // Timer thread must be started before activation of timers.
   tt.start();

   // The simple single-shot timer.
   tt.activate( milliseconds( 20 ),
         []() { cout << "Simple one-shot" << endl; } );

   // The simple periodic timer.
   // Will work until timer thread finished.
   tt.activate( milliseconds( 20 ), milliseconds( 20 ),
         []() {
            static int i = 0;
            cout << "Simple periodic (" << i << ")" << endl;
            ++i;
         } );

   // Allocation of timer and explicit activation.
   auto id1 = tt.allocate();
   tt.activate( id1, milliseconds( 30 ),
         []() {
            cout << "Preallocated single-shot timer" << endl;
         } );

   // Periodic timer with timer preallocation, explicit activation
   // and deactivation from the timer action.
   auto id2 = tt.allocate();
   tt.activate( id2, milliseconds( 40 ), milliseconds( 15 ),
         [id2, &tt]() {
            static int i = 0;
            cout << "Preallicated periodic (" << i << ")" << endl;
            ++i;
            if( i > 2 )
               tt.deactivate( id2 );
         } );

   // Single-shot timer with explicit activation and deactivation
   // before timer event.
   auto id3 = tt.allocate();
   tt.activate( id3, milliseconds( 50 ),
         []() {
            cerr << "This timer must not be called!" << endl;
            std::abort();
         } );
   tt.deactivate( id3 );

   // Wait for some time.
   this_thread::sleep_for( milliseconds( 200 ) );

   // Finish the timer thread.
   tt.shutdown_and_join();
}

Библиотека разрабатывалась для замены ACE в проекте SObjectizer, поэтому все, что связано с timertt, находится на SourceForge:

  • архивы с исходными текстами доступны в секции Files. Архив timertt-1.0.0-headeronly.7z содержит только основной заголовочный файл со всей функциональностью timertt. Архив timertt-1.0.0-full.7z содержит так же тесты, примеры и сгенерированный посредством Doxygen API Reference Manual;
  • основная документация для проекта собрана в Wiki. На данный момент она на русском языке, поскольку так было быстрее. Но потихонечку документацию буду переводить на английский. Если кто-то вызовется помочь с переводом, буду очень признателен, т.к. у меня это займет много времени, а результат будет весьма так себе;
  • исходники лежат в Subversion-репозитории на SourceForge. Релизные версии в tags/timertt, находящиеся в разработке версии в branches/timertt. В Svn на SF, а не в git-е на GitHub-е потому, что мне timertt нужна в SObjectizer-е, и в SObjectizer уже давно используется подключение зависимостей через svn:externals.