LINUX.ORG.RU

Как выйти из системного вызова внутри потока?

 , , ,


2

3

Сабж. Основной процесс получает сигнал.

Однако потоки находятся в системных вызовах (accept, read, и т.д.). Собственно, как из них выйти?

Почитав документацию и stackoverflow я понимаю, что:

Сигналы - прерогатива процессов. И потому лишь главный поток (тот который в main()) чаще всего получает сигнал и его обрабатывает. Остальные потоки сигналы не получают

Собственно, мне нужно чтобы ВСЕ потоки получили сигнал и выполнили хендлер для того чтобы их «выбросило» из системных вызовов.

Демонстрационный код:

#define _BSD_SOURCE
#define _DEFAULT_SOURCE

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <iso646.h>

#define n_threads 4
pthread_t threads[n_threads];
pthread_t main_thread;

static void signal_handler(int signo) {
	pthread_t mypid = pthread_self();
	if (mypid != main_thread) return;

	for (unsigned i = 0; i < n_threads; i++) {
		pthread_kill(threads[i], SIGINT);
	}
}

void *worker() {
	//sigset_t set;
	//sigemptyset(&set);
	//sigaddset(&set, SIGTERM);
	//sigaddset(&set, SIGINT);
	//pthread_sigmask(SIG_UNBLOCK, &set, NULL);

	char buffer;
	while(1) {
		if (read(STDIN_FILENO, &buffer, 1) < 0 and errno == EINTR) return NULL;
	}
	return NULL;
}

int main() {
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	main_thread = pthread_self();

	for (unsigned i = 0; i < n_threads; i++ ) {
		pthread_create(&threads[i], NULL, worker, NULL);
	}

	for (unsigned i = 0; i < n_threads; i++ ) {
		pthread_join(threads[i], NULL);
	}

	return EXIT_SUCCESS;
}

c99 test.c -lpthread
./a.out

После запуска пожмакайте Ctrl+C, результата не будет.

Убить процесс:
killall -SIGKILL a.out


P.S. Закомментированный код не работает.

РЕШЕНИЕ: Я лично выбрал вот это: Как выйти из системного вызова внутри потока? (комментарий)

★★★★★

Последнее исправление: reprimand (всего исправлений: 2)

Собственно, как из них выйти?

Никак. Выйдет, когда так решит ядро.

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

Harald ★★★★★
()

Читать man 7 signal до просветления. Особо обращаю внимание на то, что

  1. По умолчанию сигналы вызывают перезапуск системных вызовов (или это только в glibc так?), так что твой кейс с EINTR никогда не сработает. Кстати, ты удивишься, сколько всего может сломаться, если вдруг начнет получать EINTR. Больше узнать об этом можно по ключевом слову SA_RESTART в man 2 sigaction
  2. Вешаться на SIGINT, а потом рассылать его же кажется не самой хорошей идеей, хотя бы потому что pthread_kill(SIGINT) в теории должен позвать обработчик этого сигнала в «убиваемом» треде и, кажется, тогда наступит неловкая рекурсивная ситуация
kawaii_neko ★★★★
()
Последнее исправление: kawaii_neko (всего исправлений: 1)
Ответ на: комментарий от kawaii_neko

По умолчанию сигналы вызывают перезапуск системных вызовов (или это только в glibc так?)

Не вызывают. И даже в glibc не вызывают.

Пруфцы:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <iso646.h>
#include <errno.h>

static void signal_handler(int signo) {
}

int main() {
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	while(1) {
		char buf;
		ssize_t got = read(STDIN_FILENO, &buf, 1);
		if (got < 0 and errno == EINTR) {
			puts("I GOT EINTR!\n");
			break;
		}
	}
	return EXIT_SUCCESS;
}


Кстати, ты удивишься, сколько всего может сломаться, если вдруг начнет получать EINTR. Больше узнать об этом можно по ключевом слову SA_RESTART в man 2 sigaction

Я не удивляюсь этому ничего, мало того, я еще N лет назад писал коммерческий код который предполагает EINTR и перезапускает системные вызовы. Собственно, так же и делают многие библиотеки, так же и делает библиотека с которой я сейчас работаю. И это _норма_.

ЕМНИП SA_RESTART просто позволяет не перезапускать системные вызовы руками - это происходит автоматически.

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

Нет, потому что у меня в коде я эту ситуацию избегаю:

if (mypid != main_thread) return;

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

Никак. Выйдет, когда так решит ядро.

Ну то есть никогда :)

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

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

То всё, капец?

Создается впечатление что проще «уйти» с потоков на процессы. Меньше тупняка и гемора.

reprimand ★★★★★
() автор топика

Подозреваю, что ТС решит принципиально свои проблемы переходом на epoll и асинхронный ввод/вывод. Так же подозреваю, что ТС не будет этого делать, так-как слишком много переписывать.

ИМХО работа с сигналами - хождение по минному полю. С сигналами должны работать системные библиотеки, типа glibc. Те, кто пишет пользовательские программы должны либо вообще не трогать их, либо касаться по минимуму.

Это коллективный сверх-разум ЛОРа всё знает точно, а живой человек всегда что-то понимает не до конца. Можно не знать некоторых важных нюансов, которые могут неприятно выстрелить.

Я бы избегал использования в логике программы сигналов.

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

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

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

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

За шкафом. Ты можешь установить разные обработчики сигналов для разных тредов? Я вот пытался - не получилось. Либо я ошибся.

А для того чтобы избежать рекурсии, обработчики сигналов в основном треде и в остальных выполняются по-разному, см внимательнее код

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

Подозреваю, что ТС решит принципиально свои проблемы переходом на epoll и асинхронный ввод/вывод.

Не могу, ибо весь этот код выполняется внутри либы: https://github.com/FastCGI-Archives/fcgi2/blob/master/libfcgi/os_unix.c

ИМХО работа с сигналами - хождение по минному полю. С сигналами должны работать системные библиотеки, типа glibc. Те, кто пишет пользовательские программы должны либо вообще не трогать их, либо касаться по минимуму.

Скорее квест на «кто более внимательно прочтет мануалы» с негативными концовками

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

Выходит, что это чуть ли не единственно-адекватное решение...
Костылировать, правда, придется страшно.

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

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

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

у тебя обработчик ловит в том числе SIGINT и посылает потом в поток каждый этот же сигнал

Я это и хочу.

То есть ты просто остальные события превращаешь в SIGINT, и потом по циклу начинаешь уже обрабатывать свои же сигналы SIGINT

Я это и хочу.

Перефразируя, я пытался нажав на Ctrl+C отправить соответствующий сигнал _всем_ потокам.

Но это не работает.

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

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

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

или сделай после того цикла сброс
signal(SIGINT, SIG_DFL);

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

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

Уже, и эта проверка находится внутри самого обработчика:

if (mypid != main_thread) return;


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

Судя по треду - это невозможно.

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

Это у тебя проверка не того совершенно. Ты проверяешь был ли сигнал получен в основном треде, а не был ли получен сигнал, который сам обработчик сигналов и послал. У тебя дефолтный обработчик же перехвачен, поэтому никак твои потоки с read и не реагируют на посылаемый сигнал - он опять уходит обратно в твой обработчик.

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

Не работает :(

[pid 17327] <... rt_sigaction resumed>NULL, 8) = 0
[pid 17326] <... rt_sigaction resumed>{sa_handler=0x7f5e5b740240, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5e5b75b1f0}, 8) = 0
[pid 17328] <... mmap resumed>)         = 0x7f5e48000000
[pid 17327] accept(3,  <unfinished ...>
[pid 17325] accept(3,  <unfinished ...>
[pid 17328] mprotect(0x7f5e48000000, 135168, PROT_READ|PROT_WRITE <unfinished ...>
[pid 17326] accept(3,  <unfinished ...>
[pid 17328] <... mprotect resumed>)     = 0
[pid 17328] rt_sigaction(SIGPIPE, NULL, {sa_handler=0x7f5e5b740070, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5e5b75b1f0}, 8) = 0
[pid 17328] rt_sigaction(SIGUSR1, NULL, {sa_handler=0x7f5e5b740240, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5e5b75b1f0}, 8) = 0
[pid 17328] accept(3, ^C <unfinished ...>
[pid 17324] <... futex resumed>)        = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 17324] --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
[pid 17324] close(3)                    = 0
[pid 17324] rt_sigreturn({mask=[]})     = 202
[pid 17324] futex(0x7f5e5b549910, FUTEX_WAIT_BITSET|FUTEX_CLOCK_REALTIME, 17325, NULL, FUTEX_BITSET_MATCH_ANY^C) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid 17324] --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
[pid 17324] close(3)                    = -1 EBADF (Bad file descriptor)

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

У тебя дефолтный обработчик же перехвачен, поэтому никак твои потоки с read и не реагируют на посылаемый сигнал - он опять уходит обратно в твой обработчик

И что с того что дефолтный обработчик перехвачен?
Пусть он был бы другой либо тот же - это должно выбрасывать из системного вызова.

Ты можешь попробовать отредактировать код так как ты предлагаешь? Потому что мне кажется в любом случае работать не будет :(

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

Да. Потому что иначе будет рекурсия.

reprimand ★★★★★
() автор топика

Уже точно не помню но вроде как послание сигнала на PID процесса тригерит обработчик в КАЖДОМ треде процесса (так написано в спекая ядра), что несколько дибильно на прикладном уровне и поэтому на уровне libc по дефолту сигналы блокируются на всех тредах кроме главного (pthread_sigmask) если есть желание получать сигнал не в главном потоку нужно маски всем тредам заставить

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

Это не работает, см закомментированный код:

	//sigset_t set;
	//sigemptyset(&set);
	//sigaddset(&set, SIGTERM);
	//sigaddset(&set, SIGINT);
	//pthread_sigmask(SIG_UNBLOCK, &set, NULL);

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

pthread_cancel && cancellation points

Спасибо, интересная штука, однако требует запихивания/переписывания кода потока чтобы он был в «Cancellation clean-up handlers»

Если ничего другого не придумаю, попробую воспользоваться

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

да внутри обработчика, после цикла рассылки через sigkill.

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

Да, после Ctrl+C выходит из программы, однако как таковой EINTR внутри потоков не происходит. Я не вижу результатов работы этого:

puts("I GOT EINTR!\n");

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

однако требует запихивания/переписывания кода потока чтобы он был в «Cancellation clean-up handlers»

это нормальная практика.

вообще варианты есть. например, можно перевести дескрипторы в неблокируемый режим, либо использовать poll/select вкупе с eventfd.

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

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

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

вообще варианты есть. например, можно перевести дескрипторы в неблокируемый режим, либо использовать poll/select вкупе с eventfd.

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

Не, ну вообще-то есть, но как я понимаю пользователю лучше сделать sudo apt-get install libfcgi-dev чем клонировать к себе весь репозиторий исходников fastcgi и билдить целую либу

это нормальная практика

:(

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

Попробуй убрать из начала исходников свои надписи.

Если Вы определяете _BSD_SOURCE или _GNU_SOURCE перед вызовом сигнала, значение по умолчанию - продолжить примитивы; иначе, значение по умолчанию должно делать сбой с EINTR.

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

siginterrupt(SIGINT, 1); вроде как решает проблему только выставлять его нужно до установки сигналов ну или ставить обработчики через sigaction

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

Вот что лежит внутри libc signal

__bsd_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;

  /* Check signal extents to protect __sigismember.  */
  if (handler == SIG_ERR || sig < 1 || sig >= NSIG
      || __is_internal_signal (sig))
    {
      __set_errno (EINVAL);
      return SIG_ERR;
    }

  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  __sigaddset (&act.sa_mask, sig);
  act.sa_flags = __sigismember (&_sigintr, sig) ? 0 : SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;

  return oact.sa_handler;
}

По умолчанию все обработчики заводятся с SA_RESTART флагом через него

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

ВАУ!!!!!
РАБОТАЕТ!!!!!!!!!

❤❤❤❤❤❤❤❤❤❤❤❤❤❤

ЛУЧИ РАДОСТИ-СЧАСТЬЯ

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

Не могу, ибо весь этот код выполняется внутри либы: https://github.com/FastCGI-Archives/fcgi2/blob/master/libfcgi/os_unix.c

Кхм, забавно. Я решал похожую задачу. Была программа, которая использовала libfcgi. Программа страдала плохой отзывчивостью. Я всё переделал на асинхронный ввод/вывод. Но я отказался от библиотеки и сам реализовал протокол FastCGI.

У ТС будет свой путь через многопоточку и сигналы.

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

Заведи функцию со статик переменной. Ну типа вот образно.

enum sg
{
   SET,
   GET,
}
int mylib_setget_signal(enum sg,int signal, void * sender, void * current)
{
  

  static int local = NO_SIGNAL;
  switch(sg)
  {
    case SET: local = signal; return local;
    case GET: if(sender == current){ return NO_SIGNAL;} return local;
  }
  return ERROR;
}

Пусть в главном потоке получаются и обрабатываются сигналы и делают SET. Пусть все остальные потоки при работе постоянно делают GET проверяя текущий статус «сигнала». На всякий случай «броадкаста» при установке значения сигнала ещё указывается указатель на что-то специфичное потоку и если отправитель == приёмник то игнорируем, что-бы не слать «сигналы» самому себе.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)

Зайчем 2 раза:
#include <stdlib.h>

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

Это для меня загадка, при том что у меня этот код не работает без siginterrupt/sigaction

Но возможно различие в libc или ключах сборки

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

Эммм... Я бы добавил.

Не только pthread_cancel(), а ещё и pthread_setcancelstate() и в особенности pthread_setcanceltype().

Если сам по себе pthread_cancel() посылает запрос на выход потоку, то отработает поток или нет зависит от двух последующих вызовов. Первый (state) по дефолту как правило enabled для новых потоков, здесь всё нормально, волноваться не о чем, а вот второй (type) как правило для новых потоков deferred. И здесь лучше бы type поменять на asynchronius, тогда по идее, выход из потока должен быть совершён немедленно, но на самом деле, система этого не гарантирует, тут может быть небольшая задержка по независящим от потока причинам. В случае с deferred выход из потока может быть задержан до тех пор, пока поток не вызовет функцию, которая является окончательной точкой выхода из потока. Т.е., может быть что и никогда. Т.е., поток якобы отменён, а работать продолжает как и работал.

И да, я бы убрал вот это вот #define _BSD_SOURCE. Там по идее должна ещё требоваться -lbsd_compat тогда, которая реализует слой совместимости кроме нормальной glibc.

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

Ты полностью проигнорировал мои слова про SA_RESTART.

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

static void signal_handler(int signo) { }

int main() {
	struct sigaction sa;
	sigaction(SIGINT, NULL, &sa);
	sa.sa_flags &= ~(SA_RESTART | SA_SIGINFO);
	sa.sa_handler = signal_handler;
	sigaction(SIGINT, &sa, NULL);
	while(1) {
		char buf;
		ssize_t got = read(STDIN_FILENO, &buf, 1);
		if (got < 0 && errno == EINTR) {
			puts("I GOT EINTR!\n");
			break;
		}
	}
	return EXIT_SUCCESS;
}

SA_RESTART просто позволяет не перезапускать системные вызовы руками - это происходит автоматически

SA_RESTART как раз и определяет, что произойдет при получении сигнал с системными вызовами: будут ли они перезапущены автоматически, или же придется явно обработать EINTR. Покури-ка man 7 signal до просветления.

kawaii_neko ★★★★
()
Ответ на: Эммм... Я бы добавил. от Moisha_Liberman

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

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

На чтение и запись ставь таймауты и по таймауту проверяй флаг (за мютексом, или атомик). accept и connect нужно сделать неблокирующими и использовать poll или epoll с таймаутом.

Гугли setsockopt (SO_SNDTIMEO, SO_RCVTIMEO) и fcntl.

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

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

Вот из-за таких как ты - нажимаешь в программе крестик, а она ещё 10 минут на таймаутах висит.

Оно то верно, но прибивать нити безопасно тоже весьма не простое дело. Существует идея, что нити должны завершать свою работу сами. Так что в чём-то vlad9486 прав.

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

Таймаут может быть 1 секунду, или еще меньше. Если сделать меньше, то будет больше оверхед. Все треды будут останавливаться за это время, оно не суммируется. Программы наверное висят не на таймаутах, а сохраняют информацию, делают flush базы данных, шуршат диском.

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