LINUX.ORG.RU

Обработка сигналов в программе на C

 , , kevent, kqueue


0

2

Всем доброго времени суток.

Имеется программа на Си которая обрабатывает входящие подключения при помощи kqueue и каждое подключение отправляет на обработку в отдельный тред:

int main(int argc, char **argv) {
...

#ifdef __FreeBSD__
    // Create kqueue
    int kq;
    kq = kqueue();
    if (kq < 0) {
        perror("kqueue");
        exit(EXIT_FAILURE);
    }

    // Register server socket with kqueue for read events
    struct kevent ev;
    EV_SET(&ev, listen_sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
    if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) {
        perror(«kevent»);
        exit(EXIT_FAILURE);
    }

    // Loop for handling events
    while (1) {
        struct kevent event;
        int n = kevent(kq, NULL, 0, &event, 1, NULL);

        if (n < 0) {
            perror(«kevent»);
            exit(EXIT_SUCCESS);
        }
        //int *arg;
        if ((int)event.ident == listen_sock) {
            int client_socket = accept(listen_sock, NULL, NULL);
            if (client_socket < 0) {
                perror(«accept»);
                exit(EXIT_FAILURE);
            }
            int *arg = malloc(sizeof(*arg));
            *arg = client_socket;
            pthread_t tid;
            if (pthread_create(&tid, NULL, handle_client, arg) != 0) {
                perror(«pthread_create»);
                free(arg);
                continue;
            }
            pthread_detach(tid);
        }
    }
#endif                                                                                                                         
}

Программа пишет в лог в отдельный файл. Возникла необходимость сделать ротацию логов. Ротация логов осуществляется стандартно: переименовывается текущий лог и отправляется программе SIGUSR1 чтоб она переоткрыла лог файл. Следовтельно, нужно чтоб программа перехватывала SIGUSR1 и переоткрывала лог файл.
Для этого в main() вешаю обработчик SIGUSR1:

signal(SIGUSR1, sigusr1_handler);
... и он вызывается:

void sigusr1_handler(int signal_num) {
    printf("Got SIGUSR1!\n");
}

В итоге получаю ошибку: kevent: Interrupted system call
Вот трейс:

Thread 1 received signal SIGUSR1, User defined signal 1.
Sent by kill() from pid 39787 and user 1001.
0x000000080105341a in _kevent () from /lib/libc.so.7
(gdb) bt
#0  0x000000080105341a in _kevent () from /lib/libc.so.7
#1  0x0000000800f1cb81 in ?? () from /lib/libthr.so.3
#2  0x00000000002088e9 in main (argc=3, argv=0x7fffffffe460) at main.c:387
(gdb) list main.c:387
382	   }
383	
384	   // Loop for handling events
385	   while (1) {
386	       struct kevent event;
387	       int n = kevent(kq, NULL, 0, &event, 1, NULL);
388	
389	       if (n < 0) {
390	           perror("kevent");
391	           exit(EXIT_FAILURE);
Без kqueue & kevent (если убрать все внутри цикла while(1)) все работает отлично: сигнал перехватывается, обрабатывается и программа продолжает работу.

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

★★★★★

void sigusr1_handler(int signal_num) {
    printf("Got SIGUSR1!\n");
}

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

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

есть большие сомнения

А в чем проблема? У меня есть отдельный хендлер для остановки программы который перехватывает SIGINT, SIGKILL и SIGTERM, останавливает все треды, закрывает открытые файлы и печатает статус через printf(). И все работает замечательно.

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

А в чем проблема?

В обработчиках сигналов нельзя вызывать подавляющее большинство функций. Правильный способ это использовать глобальную переменную с типом sig_atomic_t (это такой integer), менять её в обработчике сигнала, а в другом треде периодически проверять и при изменении - осуществлять запрошенную операцию.

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

И все работает замечательно.

маладой человееек! это самый губительный аргумент в софтостроении.

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

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

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

А вообще не очень понятно, в чём у тебя проблема. Тебе kevent возвращает -1, т.к. сигнал его прервал. Зачем ты делаешь exit? перепиши этот код и всё. Насколько я понимаю, errno там будет равен EINTR, просто игнорируй эту ошибку и запускай kevent заново.

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

Зачем ты делаешь exit?

Хороший вопрос... Делал exit так как -1 свидетельствует об ошибке.

перепиши этот код и всё.

Хорошая идея. Вот так заработало:

        if (n < 0 && errno == EINTR) {
            continue;                                                                                                          
        } else {
            perror("kevent");
            exit(EXIT_FAILURE);
        }
br && alysnix — Спасибо!

Остался вопрос — как сделать правильно. Насколько я понял, оптимальный вариант: в sigusr1_handler() переключить булевую атомарную переменную, которая будет свидетельствовать о том, что нужно переоткрыть лог файл. Далее, в функции логгера я проверяю эту переменную и перед записью лога в файл делаю:

    pthread_mutex_lock(&logger_mutex);
    if (log_rotate) {
        fflush(lf);
        fclose(lf);
        lf = fopen(cfg.log_file, "a+");
        atomic_store(log_rotate, false);
    }
    ... // пишем в лог
    pthread_mutex_unlock(&logger_mutex);
Правильный ход мыслей?

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

В обработчике сигнала вроде бы можно открывать и закрывать файлы, но файлы не FILE а как файловые дескрипторы. https://man7.org/linux/man-pages/man7/signal-safety.7.html - функции open и close там есть.

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

Как вариант, можно в обработчике сигнала текущий файл с логами скопировать в другой файл, потом текущий файл очистить через ftruncate() и писать с самого начала, чтобы оригинальный файловый дескриптор не менять даже.

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

Или можно в обработчике старый файл закрыть, открыть новый, через dup2() назначить ему то же значение, что было у старого (если оно не совпало). Хотя сам dup2() старый файл сам закрыть может, см. https://man7.org/linux/man-pages/man2/dup2.2.html

   dup2()
       The dup2() system call performs the same task as dup(), but
       instead of using the lowest-numbered unused file descriptor, it
       uses the file descriptor number specified in newfd.  In other
       words, the file descriptor newfd is adjusted so that it now
       refers to the same open file description as oldfd.

       If the file descriptor newfd was previously open, it is closed
       before being reused; the close is performed silently (i.e., any
       errors during the close are not reported by dup2()).
SZT ★★★★★
()
Ответ на: комментарий от SZT

Как вариант, можно в обработчике сигнала текущий файл с логами скопировать в другой файл

Идея интересная. Но мне кажется, всем что касается ротацией логов пускай занимается newsyslog(5). Дублировать его функционал — не лучшая идея. Прога пускай по сигналу блокирует запись, переоткрывает файл и продолжает работать.

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

Как вариант, можно в обработчике сигнала текущий файл с логами скопировать в другой файл, потом текущий файл очистить через ftruncate() и писать с самого начала, чтобы оригинальный файловый дескриптор не менять даже.

Твою мать, никогда так не делай! Тебя побьют за такой код.

Есть примерно ровно один способ реагировать на сигнал в обработчике: поставить флаг и вернуться. Как вариант, можно заранее создать pipe и в обработчике сигнала в него записать что-то, но это эквивалентно signalfd(), просто с большим количеством телодвижений.

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

Возьми signalfd и используй его в том же kqueue().

Интересно, гляну, спасибо.

P.S. если твоя прога – не лабораторная для универа, то не парься и возьми libuv. Там есть и обработка сигналов, и треды, и всё что ты хочешь. Жить гораздо проще будет.

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

то не парься и возьми libuv

Прога не лаботаторная и работает уже пол года в проде под высокой нагрузкой. Мне было интересно все низкоуровневые вещи реализовать ручками: обработка коннектов через kqueue во фре и epool в линуксе, каждый коннект в отдельном треде с последующей передачей задач в очередь. Потом пул тредов хватает задачу из очереди, обрабатывает и отправляет ответ клиенту. Вот осталось разрулить все моменты с сигналами и считай цель достигнута.

Я в курсе про libev и libevent. Но пока что до них не добрался.

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

Есть примерно ровно один способ реагировать на сигнал в обработчике: поставить флаг и вернуться.

Вот допустим мы атомарно присвоили какой-то глобальной переменной какое-то значение в обработчике, чтобы сигнализировать о том, что надо в основном потоке закрыть текущий файл и открыть новый, но это произошло как раз точно в тот момент, когда этот флаг уже проверился. И после выхода из обработчика сигнала получится так, что данные запишутся еще по старому файловому дескриптору, реакция на флаг и создание нового файла будет только при следующей попытке записи. Может нужен какой-то способ атомарно сделать «проверяем флаг и пишем в файл если флаг не выставлен»?

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

Вот допустим мы атомарно присвоили какой-то глобальной переменной какое-то значение в обработчике, чтобы сигнализировать о том, что надо в основном потоке закрыть текущий файл и открыть новый, но это произошло как раз точно в тот момент, когда этот флаг уже проверился. И после выхода из обработчика сигнала получится так, что данные запишутся еще по старому файловому дескриптору, реакция на флаг и создание нового файла будет только при следующей попытке записи. Может нужен какой-то способ атомарно сделать «проверяем флаг и пишем в файл если флаг не выставлен»?

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

Поэтому никто не делает ротацию файлов, которые открыты программой. Т.е. либо программа сама занимается ротацией логов, и тогда проблемы нет, потому что можно закрыть файл, переименовать его и открыть новый. Либо просто пишут в syslog/journald/loki/что угодно ещё, и эти приблуды сами ротацию делают.

Рекомендую второй способ. Syslog примерно для этого и изобрели.

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

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

Если у тебя по треду на соединение и тебе этого хватает, то вряд ли нагрузка высокая.

Вот осталось разрулить все моменты с сигналами и считай цель достигнута.

Если ты так хочешь сам всё это разруливать, то сделай ротацию иначе: пиши логи в pipe вместо файла и сделай отдельный тред или даже процесс, который будет брать строчки из этого pipe и писать их в файл. Тогда никаких локов тебе не понадобится. Как вариант, если сделаешь отдельный процесс, то можешь ему в stdin свои логи писать. Это ещё проще будет.

Но я бы на твоём месте всё ещё взял syslog.

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

Если у тебя по треду на соединение и тебе этого хватает, то вряд ли нагрузка высокая.

При каждом подключении создается тред (в коде выше видно), который парсит HTTP заголовки вытягивает все нужные данные и кладет задачу в очередь. Пул тредов хватают задачу (в которой присутствует сокет клиента в который нужно слать ответ) и обрабатывают ее и отправляют ответ. Можно было бы клиентский коннект сразу отправлять в пул тредов. Но тогда есть вероятность что много медленных клиентов заблокируют все треды в пуле. Собственно, по этому так и реализовано как описано выше.

Но я бы на твоём месте всё ещё взял syslog.

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

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

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

я намекал на асинхронную обработку, есличо.

Но сейчас хочется решить вопрос с обычным логированием в файл и переоткрытие его по сигналу.

Вынеси запись логов в отдельный тред через схему с pipe, которую я выше описал. В треде открой signalfd (не забудь заблокировать этот сигнал в остальных тредах) и сделай kqueue на эти два файловых дескриптора. Если пришло что-то в pipe, пишешь это в файл. Если пришёл сигнал, делаешь ротацию.

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

Тебе уже конечно ответили про EINTR, но всё-таки добавлю: он может много где появляться, не везде это даже задокументировано, так что лучше всегда проверять на этот счёт. Если сисколл вернул -1 и errno==EINTR то это скорее всего означает что он ничего не сделал и надо его запустить заново.

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

Из accept-а тоже может EINTR прийти, и ещё какие-то мусорные ошибки (не помню список), так что лучше либо вообще ошибки accept-а игнорировать (ну в лог только писать), либо детально разобраться какие из них действительно ошибки (всякие EBADF EINVAL), а всё остальное всё так же игнорировать. «Проигнорировать ошибку accept-а» означает сделать вид что ты его не вызывал и не собирался и уйти в следующий цикл kevent.

Чтобы уменьшить количество EINTR-ов (но не убрать их до конца) ставь обработчик не signal()-ом а через sigaction() и с флагом SA_RESTART.

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

Понятие «можно» весьма расплывчатое. С одной стороны, никто не запретит. С другой, у всего есть последствия и надо оценивать хочешь ты их или нет. Открывание файлов через open() например может испортить errno тому коду, в середине которого сигнал пришёл. Открывание файлов через fopen() может сломать внутреннее состояние stdio если сигнал придёт внутри его кода.

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

Атомик тут не обязателен. Но и не повредит. fflush() перед fclose() имеет смысл только если ты собираешься проверять у него код возврата и что-то предпринимать при ошибках.

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

можно заранее создать pipe и в обработчике сигнала в него записать что-то, но это эквивалентно signalfd(), просто с большим количеством телодвижений.

А ещё может испортить errno, так что не надо.

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

Это означает что мы успели записать лог до того как принялся сигнал. Строгую синхронность тут никто не делает да и невозможно это: сигнал шлёт другой процесс, который может задержаться по рандомным причинам (например попал в свап на лагающем диске и медленно вылазит из него) хоть на 10 секунд.

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

Поэтому никто не делает ротацию файлов, которые открыты программой.

Делают ещё как, и безо всяких проблем. То что SZT описал - не проблема, конечно.

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

пиши логи в pipe вместо файла и сделай отдельный тред или даже процесс, который будет брать строчки из этого pipe и писать их в файл. Тогда никаких локов тебе не понадобится. Как вариант, если сделаешь отдельный процесс, то можешь ему в stdin свои логи писать. Это ещё проще будет.

Локи однозначно проще чем эти нагромождения. А вообще можно и без локов через dup2() делать как кто-то выше уже подсказывал.

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

Локи однозначно проще чем эти нагромождения.

Да не, не проще.

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

#define _GNU_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>

#define UNUSED(x) do{(void)(x);}while(false)

// I don't want to care about message lengths today.
const size_t max_len = 256;

static void die(const char*);
static void logmsg(const char*);
static void* write_logs(void *);

static int log_in = -1,
           log_out = -1;
static sigset_t sigmask;

static void die(const char* fname)
{
    const char* err = strerror(errno);
    fprintf(stderr, "Call to %s failed: %s\n", fname, err);
    exit(EXIT_FAILURE);
}

static void logmsg(const char* msg)
{
    size_t msglen = strlen(msg);
    size_t sz = msglen < max_len ? msglen : max_len;
    write(log_in, msg, sz);
}

int main()
{
    int r;
    pthread_t writer_thread;
    int pipe_fds[2];

    r = pipe2(pipe_fds, O_DIRECT); // O_DIRECT is needed for packet mode and isn't strictly necessary here
    if(r == -1) {
        die("pipe");
    }
    log_in = pipe_fds[1];
    log_out = pipe_fds[0];

    // Set up sigmask
    sigemptyset(&sigmask);
    sigaddset(&sigmask, SIGUSR1);
    // Block SIGUSR1
    // It will be handled separately by signalfd()
    r = sigprocmask(SIG_BLOCK, &sigmask, NULL);
    if(r == -1) {
        die("sigprocmask");
    }

    r = pthread_create(&writer_thread, NULL, write_logs, NULL);
    if(r == -1) {
        die("pthread_create");
    }

    for(unsigned i = 0; ; i++) {
        char str[max_len];
        snprintf(str, max_len, "Message number %u\n", i);
        logmsg(str);
        usleep(1000000);
    }
}

static void* write_logs(void *arg)
{
    const char* filemask = "logfile_%d.log";
    char filename[256] = {};
    int fd = -1;
    int sigfd = -1;
    int log_counter = 0;
    struct pollfd pfds[2];

    UNUSED(arg);

    sigfd = signalfd(-1, &sigmask, 0);
    if (sigfd == -1) {
        die("signalfd");
    }
    snprintf(filename, sizeof(filename), filemask, log_counter);
    const int open_flags = O_WRONLY | O_TRUNC | O_CREAT;
    const int open_mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    fd = open(filename, open_flags, open_mask);
    if(fd == -1) {
        die("open");
    }

    pfds[0].fd = sigfd;
    pfds[0].events = POLLIN;
    pfds[1].fd = log_out;
    pfds[1].events = POLLIN;

    do {
        int r = poll(pfds, 2, -1);
        switch(r) {
            case -1: 
              if(errno == EAGAIN || errno == EINTR)
                continue;
              else
                die("poll");
            case 0:  continue;
        }
        if(pfds[0].revents & POLLIN) {
            // We have a signal!
            struct signalfd_siginfo sig = {};
            r = read(sigfd, &sig, sizeof(sig));
            if(r == -1) {
                die("read");
            }
            // We only have one signal, so there's no need to check it.
            close(fd);
            snprintf(filename, sizeof(filename), filemask, ++log_counter);
            fd = open(filename, open_flags, open_mask);
            if(fd == -1) {
                die("open");
            }
        }
        if(pfds[1].revents & POLLIN) {
            // We have a message to log!
            char buf[max_len];
            r = read(log_out, buf, max_len-1);
            if(r == -1) {
                die("read");
            }
            r = write(fd, buf, r);
            if(r == -1) {
                die("write");
            }
        }
    } while(true);

    return NULL;
}

Компилить через clang -O2 -Wall -Werror -lpthread logpipe.c -o logpipe.

Пишет в текущую директорию логи в файлы по маске «logfile_%u.log». При получении SIGUSR1 переключается на новый файл.

ТС, вот тебе готовый код. poll() на kqueue() можешь сам заменить по вкусу.

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

ТС, вот тебе готовый код. poll() на kqueue() можешь сам заменить по вкусу.

Спасибо за примерчик. Вот накалякал в качестве теста: в main() создаем пайп, пишем в него и в отдельном треде через kqueue читаем из него.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/event.h> // kqueue
#include <pthread.h>
#include <time.h> // for rng

static const size_t max_len = 1024;
static int pipe_fd[2];

static void* logger_thread(void *arg) {
    char buff[max_len];
    int kq, env;
    struct kevent change;
    struct kevent event;

    (void)arg;

    kq = kqueue();

    if (kq == -1) {
        perror("kqueue");
        exit(EXIT_FAILURE);
    }

    EV_SET(&change, pipe_fd[0], EVFILT_READ, EV_ADD, 0, 0, 0);

    while(true) {
        env = kevent(kq, &change, 1, &event, 1, NULL);
        if (env == -1)
            perror("kevent");
        else if (env > 0) {
            if (event.data > 0) {
                printf("Received %ld bytes in pipe to read\n", event.data);
                printf("Reading data from it:\n");
                if ((size_t)event.data < max_len - 1) {
                    read(pipe_fd[0], buff, (size_t)event.data);
                    buff[event.data] = '\0';
                    printf("%s\n", buff);
                } else
                    printf("Buffer overflow!\n");
            }
        }
    }
}

int main(void) {
    int inc = 0;
    int rng, i;
    char data[max_len];
    pthread_t thread;
    
    srand(time(NULL)); // init rng generator

    // bring up logger thread
    if (pthread_create(&thread, NULL, logger_thread, NULL) < 0) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    if (pipe2(pipe_fd, O_NONBLOCK) == -1) {
         perror("pipe2");
         exit(EXIT_FAILURE);
    }

    char * message = "Hello, logger thread!\n";
    while(true) {
        rng = ((rand() % 3) + 1);
        for (i = 0; i < rng; i++) {
            snprintf(data, max_len, "#%d %s", inc++, message);
            write(pipe_fd[1], data, strlen(data));
        }
        sleep(1);
    }
}
Компилять:
cc -Wall pipe_logger.c -o pipe_logger -lpthread
Если есть идеи/поправки/замечания — велкам.

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

pipe2(pipe_fd, O_NONBLOCK)

Не делай так. Если у тебя запись сломается (например, буфер кончился), ты об этом либо не узнаешь совсем, либо узнаешь по кривым логам, потому что сообщение частично записалось.

O_NONBLOCK нужен только если ты готов проверять, что всё записалось, и дозаписывать то, что не успело.

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

у меня хандлеры сигналов на printf не спотыкаются.

Так у меня тоже не спотыкаются. Но в теории споткнуться если случится что два printf будут писать в один поток (судя по докам).

iron ★★★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

не проще ли почитать в инете обсуждалово на эту тему во множестве экземляров?

и список разрешенных к использованию в сигхандлерах функций.

alysnix ★★★
()
Ответ на: комментарий от LINUX-ORG-RU
#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>

FILE * logfile = NULL;

void sigusr1_handler(int s)
{
    printf("Переоткрой\n");
    printf("Дескриптор\n");
    printf("Лога\n");
    printf("ЩАААААААААААААААААААААА\n");
    if(logfile)
    {
        int descriptor = fileno(logfile);
        fsync(descriptor);
        fclose(logfile);
        logfile = fopen("log.txt","w");
    }
    printf("ГААААААААААААААААААААААТОВА\n");
    fprintf(logfile,"Перезапуск лога! Ротатион енабле юхуу\n");
}

int main(int argc, char *argv[])
{

    signal(SIGUSR1,sigusr1_handler);
    printf("%d\n",getpid());
    logfile = fopen("log.txt","w");
    assert(logfile);
    int c = 0;
    int descriptor = fileno(logfile);
    while(true)
    {
        sleep(1);
        fprintf(logfile,"привет я логовая записулька № %d!\n",c++);
        fflush(logfile);
        printf("%d\n",c);
    }
    return 0;
}

kill -s SIGUSR1 `pidof a.out`

Не ломается, как сломать то? :( Или я чего не понял опять? :)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от alysnix
#include <stdio.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>


FILE * logfile = NULL;
FILE * logfile2 = NULL;



void sigusr1_handler(int s)
{
    printf("Переоткрой\n");
    printf("Дескриптор\n");
    printf("Лога\n");
    printf("ЩАААААААААААААААААААААА\n");
    if(logfile)
    {
        int descriptor = fileno(logfile);
        fsync(descriptor);
        fclose(logfile);
        logfile = fopen("log.txt","w");
    }

    if(logfile2)
    {
        int descriptor = fileno(logfile2);
        fsync(descriptor);
        fclose(logfile2);
        logfile2 = fopen("log2.txt","w");
    }

    printf("ГААААААААААААААААААААААТОВА\n");
    fprintf(logfile,"Перезапуск лога! Ротатион енабле юхуу лог 1\n");
    fprintf(logfile2,"Перезапуск лога! Ротатион енабле юхуу лог 2\n");
    fflush(logfile);
    fflush(logfile2);
}

void * function( void *ptr )
{
     int *pid =  (int*)ptr;
     int  x = 0;
     while(true)
     {
         printf("Я тхреаднутый\n");
         sleep(1);
         if(x > 2)
         {
            kill(*pid,SIGUSR1);
         }
         x++;
     }
}

void * function2( void *ptr )
{
     logfile2 = fopen("log2.txt","w");
     assert(logfile2);
     int x = 0;
     while(true)
     {
         fprintf(logfile2,"Я тхреаднутый нумбер 2 и пишу в свой лог %d\n",x);
         fflush(logfile2);
         sleep(1);
         if(x > 2)
         {
            printf("Я тхреаднутый нумбер 2 и пишу в свой лог\n");
         }
         x++;
     }
}


int main(int argc, char *argv[])
{
     signal(SIGUSR1,sigusr1_handler);
     int mypid = getpid();

     pthread_t thr_1, thr_2;
     int thr_1_s = pthread_create( &thr_1, NULL, function, (void*)&mypid);
     int thr_2_s = pthread_create( &thr_2, NULL, function2, NULL);



    printf("%d\n",getpid());
    logfile = fopen("log.txt","w");
    assert(logfile);
    int c = 0;
    int descriptor = fileno(logfile);
    while(true)
    {
        fprintf(logfile,"привет я логовая записулька № %d!\n",c++);
        sleep(1);
        fprintf(logfile,"привет я логовая записулька № %d!\n",c++);
        fflush(logfile);
        printf("%d\n",c);
        fprintf(logfile,"привет я логовая записулька № %d!\n",c++);
    }
    return 0;
}

Не ломается :( Из основной программы которая пишет свой лог пускаю два потока один из потоков пишет свой лог, второй поток шлёт на материнский пид сигнал который переоткрывает логи и основному процессу и птрхреадному в теле обработчика сигнала. Тоесть тут вообще перекрётная перестрелка. И это я ещё сократил, на деле было 4 потока + главный, у каждого свой лог файл все сидят и пишут в свои логи и каждый по random() % 2 условию стреляет по пидам сигналом где обработчик переоткрывает их все для каждого. Короче вакханалия полная, но ничего не ломается.

Всё, стало скучно.

P.S. Спасибо за ссылку.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от hateyoufeel

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

Какой ещё дедлок?

static FILE *log_fp;
static t_lock lock;
static volatile int newlog;
static int usr1_handle(int sig) { newlog = 1; }
extern void init_usr1_handle(void) {
  struct sigaction sa;
  sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); sa.sa_handler = usr1_handle;
  sigaction(SIGUSR1, &sa, NULL);
}
extern void check_newlog(void) {
  LOCK(&lock);
  if(newlog) {
    fd = open("/path/to/log", O_WRONLY|O_CREAT|O_APPEND, 0644);
    if(fd<0) fprintf(log_fp, "can't reopen log!\n");
    else {
      if(dup2(fd, fileno(log_fp))<0) fprintf(log_fp, "can't reopen log!\n");
      close(fd);
    }
    newlog = 0;
  }
  UNLOCK(&lock);
}
extern void write_log(char const *fmt, ...) {
  va_list arg;
  int fd;
  check_newlog(); /* но лучше её делать явно в рабочем цикле */
  va_start(arg, fmt);
  LOCK(&lock);
  vfprintf(log_fp, fmt, arg);
  fputc('\n', log_fp);
  fflush(logfp);
  UNLOCK(&lock);
  va_end(arg);
}

Вместо t_lock подставить мютекс или что-то подобное.

А ещё signalfd в BSD нет, это Linux-specific.

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