LINUX.ORG.RU

C++ signal handler

 ,


0

1

А как это можно без boost asio? А то жирновато как-то для такой простой задачи.

https://stackoverflow.com/a/48164204

Как не смотришь, всё криво и косо. Вот есть экземпляр серверного класса в main, а как его загасить красиво?

UPDATE: вопрос про верхний уровень. Низ не интересует. Как пробросить контекст в обработчик, устанавливаемый с std::signal. Чтобы красиво было на C++, а не как всегда (рабочий код):

unique_ptr<StateChecker> myServer;
unique_ptr<thread> server_thread;
// DO NOT WANT GLOBAL VARIABLES!!!

void signal_handler(int signal_num) {
	if(myServer!=nullptr) {
		myServer->stop(); server_thread.join(); }
	exit(signal_num);
}


int main() {
// bla bla
	return 0;
}
★★★★★

Последнее исправление: fornlr (всего исправлений: 7)
Ответ на: комментарий от fornlr

Ты спросил:

А как это можно без boost asio? А то жирновато как-то для такой простой задачи.

По ссылке нет asio. Ответ на твой вопрос: как по ссылке.

intelfx ★★★★★
()
Ответ на: комментарий от intelfx

А вот если был бы сервер с ним. То это просто делается и красиво. Но раз нет, то нет. Поэтому и вопрос.

Хватит глупости писать.

А по ответу там правильно написано

This will kill the program with an uncaught exception of type bad_function_call if a SIGINT happens (Ctrl+C) before the lambda is assigned to shutdown_handler.

fornlr ★★★★★
() автор топика
Последнее исправление: fornlr (всего исправлений: 1)

Красиво – никак, сигналы не для красивостей делали. По ссылке чушь, там не учитывается налагаемое на обработчик сигнала условие async-signal-safe, и я что-то не верю, что server.shutdown() будет удовлетворять ему.

В Glib делают так: создается pipe, задаются обработчики, которые делают write(pipe, sig). С другого конца в рамках евент лупа делается read и сигнал обрабатывается уже там, без наложенных ограничений и более-менее красиво.

Собственно, если glib уже используется в проекте, есть смысл воспользоваться функциями оттуда.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)

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

kvpfs ★★
()
Ответ на: комментарий от kvpfs
void singal_handler() {
    // I dont want global wariables! where is my closures and etcetra?
	myServer->stop();
	connection->stop();
}


int main() {
    std::set_global_signal_handler(SIG_CODE, signal_handler);

	auto connection = sdbus::createSystemBusConnection(DBUS_SERVICE_NAME);
	DbusServer myDbusServer(*connection, DBUS_OBJECT_PATH);

	Server myServer(myDbusServer);
	thread main_thread(&Server::run, &myServer);

	connection->enterProcessingLoop(); // sync call - block
	return 0;
}

Вот как-то так понятнее?

anonymous
()
Ответ на: комментарий от anonymous

Сперва делаешь так, чтобы можно было гасить сервер сообщением от клиента. В обработчике сигналов посылаешь это сообщение на сервер.

anonymous
()
Ответ на: комментарий от anonymous

В обработчике сигналов посылаешь это сообщение на сервер.

Это и есть вопрос. Выше код, понятно не скомпилируется, потому что myServer и connection не определены в функции обработчике.

fornlr ★★★★★
() автор топика
Последнее исправление: fornlr (всего исправлений: 1)
Ответ на: комментарий от fornlr

Как ты пишешь клиент-серверную систему, если не знаешь как писать клиент-серверную систему?

anonymous
()
Ответ на: комментарий от fornlr

Обработчик сигнала - это клиент твоего сервера с особыми привилегиями, клиент-админ. Вот тебе верхушка айсберга, действуй. Если не умеешь, уже подсказали std::exit

anonymous
()
Ответ на: комментарий от anonymous

И что? Я это всё знаю. Ну вот допустим так

unique_ptr<StateChecker> myServer; // DO NOT WANT GLOBAL VARIABLES!!!

void signal_handler(int signal_num) {
	if(myServer!=nullptr)
		myServer->stop();
	exit(signal_num);
}


int main() {
    std::set_global_signal_handler(SIG_CODE, signal_handler);

	auto connection = sdbus::createSystemBusConnection(DBUS_SERVICE_NAME);
	DbusServer myDbusServer(*connection, DBUS_OBJECT_PATH);

	Server myServer(myDbusServer);
	thread main_thread(&Server::run, &(*myServer);

	connection->enterProcessingLoop(); // sync call - block
	return 0;
}
anonymous
()
Ответ на: комментарий от anonymous

твой уровень:

void signal_handler(int signal_num) {
	exit(signal_num);
	if(myServer!=nullptr)
		myServer->stop();
}

Но ты даже это не можешь написать!

anonymous
()

Можно в обработчике сигнала взвести atomic-флажок, а сервером его проверять почаще и завершаться. ЕМНИП работало.

dimgel ★★★★★
()
Ответ на: комментарий от Siborgium

В Glib делают так: создается pipe, задаются обработчики, которые делают write(pipe, sig). С другого конца в рамках евент лупа делается read и сигнал обрабатывается уже там, без наложенных ограничений и более-менее красиво.

Нынче модно главный message processing thread будить через eventfd, а не пайпы (если реально только разбудить надо, а не пробросить какие-то результаты из background thread). Для флажка «продолжаем крутиться» смотрим в сторону std::sig_atomic_t.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)

Не надо никакой нетривиальной работы делать в обработчике, не надо вообще вспоминать exit в C++. Плюсую взведение флага и/или побудку евентлупа спец. событием.

anonymous
()

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

тогда хандлер сигнала вызывает этот метод, а мейн ждет на событии, что сервер завершил работу. затем, ну например, пишем - «server terminated» и выходим из мейна.

остановить сервер(я говорю о «сервере» в общем смысле), и как и любой многотред, это в общем случае, сложная операция, когда треды останавливают в определенном порядке… ну например выполняющие логгирование или сборку мусора, ну типа того, должны остановиться позже всех.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

Не слушай тех, кто говорит про всякие ->stop() и прочие. Этого нельзя делать в обработчиках сигналов. Они выполняются через longjmp, нарушая состояние вложенности. Можно даже словить вызов обработчика внутри обработчика. Посмотри еще раз код, что тебе предлагают другие и скажи, безопасен ли их код в таком случае. Максимум, что тебе разрешено делать в обработчиках, это вот так:

/*! \brief Флаг работы демона */
static volatile bool g_daemon_run = true;
/*! \brief Индекс сигнала завершения */
static volatile i32 g_signal = 0;

/*! \brief Обработчик сигналов
 * \param[in] sig Сигнал по которому произошло прерывание */
static void _signal_handler(i32 sig) {
    g_daemon_run = false;
    g_signal     = sig;
}

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

В том же модуле добавляешь что-то вроде (понятно, что для своего языка выбираешь соответствующие конструкции):

/*! \brief Описывает обрабатываемые сигналы */
static void _daemon_signal(void) {
#if defined(BXI_OS_GLX)
    struct sigaction action = {
        .sa_handler = _signal_handler
    };
    sigfillset(&action.sa_mask);

    sigaction(SIGHUP,  &action, NULL);
    sigaction(SIGINT,  &action, NULL);
    sigaction(SIGTERM, &action, NULL);
#elif defined(BXI_OS_WIN)
    signal(SIGINT,  _signal_handler);
    signal(SIGTERM, _signal_handler);
#endif
}

/*! \brief Цикл ожидания сигнала */
static void _daemon_loop(void) {
    static const u32 daemon_period = 1000;
    while (g_daemon_run) {
        bxi_msleep(daemon_period);
    }
}

/*! \brief Действия после завершения демона */
static void _daemon_stop(void) {
#if defined(BXI_OS_GLX)
    frs_inf(
        "Получен сигнал %s, завершение процесса",
        strsignal(g_signal)
    );
#elif defined(BXI_OS_WIN)
    frs_inf("Получен сигнал, завершение процесса");
#endif
}

/*! \brief Сообщает, если пользователь требует остановки
 * \return Истина, если запрошена остановка */
bool frl_signal_isstopped(void) {
    return !g_daemon_run;
}

/*! \brief Запускает цикл ожидания сигналов */
void frl_signal_loop(void) {
    _daemon_signal();
    _daemon_loop();
    _daemon_stop();
}

Ты не обязан прерывать приложение сразу после ^C, поэтому в других нитях ты проверяешь результат функции frl_signal_isstopped и реагируешь соответственно (собираешь манатки и сваливаешь).

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

[2021-08-27 00:03:30] _frs_mutex_lock Вызов pthread_mutex_lock(&mutex->mutex) завершился с ошибкой: 22 ("Interrupted system call" (4))

, что целиком нарушит твой воркфлоу. Поэтому правила работы с сигналами просты - во всех нитях кроме одной ставь pthread_sigmask(SIG_BLOCK, &mask, NULL);, иначе получишь сюрпризы. Если что-то создает нити за тебя и ты не можешь гарантировать вызов этой функции внутри - создай отдельную нить, выставь в нее запрет на сигналы и наследуй все остальные нити от нее.

Сигналы - это вообще «вещь в себе», да, костыльно, да, некрасиво, да, миллион подводных камней, но это лучше чем ничего.

PPP328 ★★★★★
()

у плюсовиков всегда всё так сложно?
а у плюсовкиов толстых яблочников вдвойне сложнее?

reprimand ★★★★★
()
Ответ на: комментарий от PPP328

Сигналы - это вообще «вещь в себе», да, костыльно, да, некрасиво, да, миллион подводных камней, но это лучше чем ничего.

Да нет там ничего сложного. Главное «понимать что делаешь» (c) и свести логику обработчика к минимуму, максимум - взводим флажок и будим главный тред.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Да нет там ничего сложного. Главное «понимать что делаешь» (c)

Скажите это ребятам выше по треду которые предлагают в обработчике посылать какие-то сигналы серверу.

PPP328 ★★★★★
()
Ответ на: комментарий от PPP328

Скажите это ребятам выше по треду которые предлагают в обработчике посылать какие-то сигналы серверу.

Как говориться - каждый ССЗБ. Рано или поздно научаться.

bugfixer ★★★★★
()
Ответ на: комментарий от PPP328

которые предлагают в обработчике посылать какие-то сигналы серверу.

Да, должен признать присутствуют здесь, назовём это так, «альтернативно одаренные» персонажи. С некоторыми даже пытаюсь бороться в соседних ветках (и на хрена спрашивается?). Whatever…

bugfixer ★★★★★
()

не вижу тег "я познаю мир"

int main() {
    while (!server->ShouldStop()) {
        server->ProcessRequest();
    }
    server->Stop();
    return 0;
}

деды воевали так писали, и мы так пишем.

anonymous
()
Ответ на: комментарий от bugfixer

будим главный тред

Как разбудить главный тред, не посылая сигнал?

А пока от «экспертов» вижу костыли сигналов с посекундным опросом переменных.

anonymous
()

в signal_handler вообще не рекомендуется выполнять контекстные задачи, потому что главный поток прерывается неожиданно, и нужно waitpid делать и т.п.
либо кидаете сигнал дальше дешевыми способами, либо ловите траблы… что юзать? pipe, socket, dbus и.т.п. на крайняк в файл пиши + inotify лови в нитке

c++ здесь вообще не причем даже

anonymous2 ★★★★★
()
Последнее исправление: anonymous2 (всего исправлений: 5)
Ответ на: комментарий от anonymous

Как разбудить главный тред, не посылая сигнал?

Зависит от того где он спит и кто поймал сигнал. Если это что-то сетевое спящее в select/epoll то через eventfd или pipe. А если сигнал поймался главным тредом то select/epoll и так проснётся. Читаем про «The self-pipe trick» в man 2 select.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Получается, уже есть рабочий механизм через отправку сообщений «серверу» через файловый дескриптор. Но «эксперты» придумывают свой костыль через атомики и ежесекундный опрос.

anonymous
()
Ответ на: комментарий от anonymous

Ты с таким согласился

Всё правильно человек написал. И даже больше чем пионерам нужно. Он маскирует сигналы во всех тредах кроме главного, у него задача будить главный из других тредов вообще не стоит, о чём там собственно ни слова и не написано. Как и про ежесекундные wake-ups для проверки флага.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

правильно написал только вот это.

Сигналы - это вообще «вещь в себе»

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

Если надо более-менее некостыльно и не сильно вылезая за рамки c++.

anonymous
()
Ответ на: комментарий от anonymous

Обработчик сигналов надо делать в отдельном процессе-мониторе. А сервер с многопотоком - в отдельном, который не знает про существование сигналов.

а если надо написать простейший серверок, что слушает порт и отдает пакет, по приходу пакета. строк в 100… тоже городить огород из двух процессов - монитора и сервера?

alysnix ★★★
()
Ответ на: комментарий от alysnix

а если надо написать простейший серверок

строк в 100

То там обработчик сигналов не нужен.

anonymous
()
Ответ на: комментарий от filosofia

Если event loop: отправка приоритетного сообщения в очередь.

что в лоб, что по лбу.

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

а это тоже самое, что после обработки текущего, вызывать функцию типа stop_requested(что скрывает булеву переменную - stop_request). это даже сложней, поскольку нужно такую очередь написать, и работает она слегка медленней, за счет сортировки. и держать ее только для прокидывания мессаги о терминировании - перебор.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Я не имел в виду чего-то конкретного. Боюсь, ты додумал что-то, и ответил на это.

В зависимости от реализации event loop, отправка приоритетного сообщения в него может быть очень разной. Для большинства IO event loop можно сделать нотификацию через файловый дескриптор или аналог. В таком случае «приоритетность» определяется просто тем, что ты в первую очередь обрабатываешь сообщения с этого дескриптора, потом смотришь на остальные.

Да, в терминальном случае это может быть и priority queue. В плюсах она уже есть, ничего выдумывать не надо. Как минимум в двух вариантах: std::priority_queue и std::[multi]set. Но повторюсь, это крайний случай.

Когда вы предлагаете играться со всякими булевыми флагами, это часто заканчивается грязными хаками, чтобы разбудить event loop. И тут вы либо фактически копируете код отправки сообщения в него; либо код ожидания в event loop становится бесконечным циклом проверки флага, конечно же с таймаутом. Первый вариант в итоге и есть пресловутая отправка сообщения, с изюминкой. Второй вариант — это latency и говнокод.

Да, убивать поток или even loop во время выполнения задачи можно, для этого в ней должны быть контрольные точки. Если хочется прямо зарезать поток посреди выполнения и ни о чем не думать, обработчик сигнала вообще не нужен.

filosofia
()
Последнее исправление: filosofia (всего исправлений: 2)
Ответ на: комментарий от anonymous

То там обработчик сигналов не нужен.

обработчик все равно нужен.

вопрос в процессе - мониторе. как определить границу между - процесс-монитор «не нужен» и «нужен»?

на самом деле ответ тут только в степени буйности фантазии.

alysnix ★★★
()
Ответ на: комментарий от filosofia

Как минимум в двух вариантах: std::priority_queue и std::multiset. Но повторюсь, это крайний случай.

она не тредсейф, она просто контейнер и для межтредового сообщения ее пользовать нельзя.

Когда вы предлагаете играться со всякими булевыми флагами, это часто заканчивается грязными хаками, чтобы разбудить event loop.

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

вот именно когда вы выскакиваете по таймауту, вы и можете проверить флаг или сделать еще что-то низкоприоритетное.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Таймаут — это latency и говнокод

Надеюсь, так стало понятнее.

filosofia
()
Ответ на: комментарий от alysnix

она не тредсейф, она просто контейнер и для межтредового сообщения ее пользовать нельзя.

a) Мы не говорили о междтредовых сообщениях, ты опять разговариваешь с самим собой.

b) Ее легко сделать thread-safe.

filosofia
()
Ответ на: комментарий от alysnix

на самом деле ответ

100 строк, из которых 20 займет обработка сигналов.

Так что придется определится, что пишем: простой обработчик сообщений MSG1 и MSG2, или простой обработчик сигналов USR1 и USR2

anonymous
()

Заведи глобальную переменную и в обработчике сигнала присваивай ей единицу. В основном потоке проверяй её в цикле и если она стала равна единице то стопай сервер. Там ещё надо как-то прописать чтобы компилятор не оптимизировал, вроде volatile.

Legioner ★★★★★
()
Ответ на: комментарий от filosofia

И тут вы либо фактически копируете код отправки сообщения в него; либо код ожидания в event loop становится бесконечным циклом проверки флага, конечно же с таймаутом. Первый вариант в итоге и есть пресловутая отправка сообщения, с изюминкой. Второй вариант — это latency и говнокод.

не понял. вот так пишется по простому, тут примерно так уже писали

message lm;
while(!stop_request()) {
  if (not queue.get(lm, 1000)) //пытаемся взять мессагу с таймаутом в секунду
  {
    // не взяли. нет мессаги. тут можно еще озаботиться вопросом
    //  - а почему мессаги нет?
    // например продюсер отдал концы и это нехорошо. тогда можно
    // ругнуться куда-то там и отправить кому-то ругательную
    // мессагу, типа какому-то треду- менеджеру.
    manager.put("Усьо пропало, мессаги не идут! поможите");
    continue;
  }
  handleMessage(lm) // тут мессагу взяли и обрабатываем ее.
}


alysnix ★★★
()
Ответ на: комментарий от alysnix

Я правильно понимаю этот псевдокод, что, если в очереди нет сообщения, то мы будем ждать до секунды прежде чем выйдем из цикла? А если за это время сообщение таки появится, мы его еще и обработаем прежде чем выйти?

filosofia
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.