Книга: UNIX: взаимодействие процессов

Идемпотентные и неидемпотентные процедуры

Идемпотентные и неидемпотентные процедуры

А что произойдет, если мы перехватим сигнал EINTR и вызовем процедуру сервера еще раз, поскольку мы знаем, что эта ошибка возникла из-за нашего собственного прерывания системного вызова перехваченным сигналом (SIGCHLD)? Это может привести к некоторым проблемам, как мы покажем ниже.

Изменим сервер так, чтобы он выводил идентификатор вызванного потока, делал паузу в 6 секунд и выводил идентификатор потока по завершении его. В листинге 15.23 приведен текст новой процедуры сервера. 

Листинг 15.23. Процедура сервера, выводящая свой идентификатор потока дважды

//doors/serverintr3.c
1  #include "unpipc.h"
2  void
3  servproc(void *cookie, char *dataptr, size_t datasize,
4   door_desc_t *descptr, size_t ndesc)
5  {
6   long arg, result:
7   printf("thread id %ld calledn", pr_thread_id(NULL));
8   sleep(6); /* даем клиенту возможность перехватить SIGCHLD */
9   arg = *((long*)dataptr);
10  result = arg * arg;
11  printf("thread id %ld returningn", pr_thread_id(NULL));
12  Door_return((char *) &result, sizeof(result), NULL, 0);
13 }

В листинге 15.24 приведен текст программы-клиента.

Листинг 15.24. Клиент, вызывающий door_call еще раз, после перехвата EINTR

//doors/clientintr3.c
1  #include "unpipc.h"
2  volatile sig_atomic_t caught_sigchld;
3  void
4  sig_chld(int signo)
5  {
6   caught_sigchld = 1;
7   return; /* прерываем вызов door_call() */
8  }
9  int
10 main(int argc, char **argv)
11 {
12  int fd, rc;
13  long ival, oval;
14  door_arg_t arg;
15  if (argc != 3)
16   err_quit("usage: clientintr3 <server-pathname> <integer-value>");
17  fd = Open(argv[1], O_RDWR); /* открытие двери */
18  /* подготовка аргументов и указателя на результаты */
19  ival = atol(argv[2]);
20  arg.data_ptr = (char*)&ival; /* аргументы */
21  arg.data_size = sizeof(long); /* размер аргументов */
22  arg.desc_ptr = NULL;
23  arg.desc_num = 0;
24  arg.rbuf = (char*)&oval; /* возвращаемые данные */
25  arg.rsize = sizeof(long); /* размер данных */
26  Signal(SIGCHLD, sig_chld);
27  if (Fork() == 0) {
28   sleep(2); /* дочерний процесс */
29   exit(0); /* отправка SIGCHLD */
30  }
31  /* родительский процесс : вызов процедуры сервера и вывод результата */
32  for (;;) {
33   printf("calling door_calln");
34   if ((rc = door_call(fd, &arg)) == 0)
35    break; /* успешное завершение */
36   if (errno == EINTR && caught_sigchld) {
37    caught_sigchld = 0;
38    continue; /* повторный вызов door_call */
39   }
40   err_sys("door_call error");
41  }
42  printf("result: %ldn", oval);
43  exit(0);
44 }

2-8 Объявляем глобальную переменную caught_sigchld, устанавливая ее в единицу при перехвате сигнала SIGCHLD.

31-42 Вызываем door_call в цикле, пока он не завершится успешно.

Глядя на выводимые клиентом результаты, мы можем подумать, что все в порядке:

solaris % clientintr3 /tmp/door3 33
calling door_call
calling door_call
result: 1089

Функция door_call вызывается в первый раз, обработчик сигнала срабатывает через 2 секунды после этого и переменной caught_sigchld присваивается значение 1. door_call при этом возвращает ошибку EINTR и мы вызываем door_call еще раз. Во второй раз процедура завершается успешно.

Посмотрев на выводимый сервером текст, мы увидим, что процедура сервера была вызвана дважды:

solaris % serverintr3 /tmp/door3
thread id 4 called
thread id 4 returning
thread id 5 called
thread id 5 returning

Когда клиент второй раз вызывает door_call, это приводит к запуску нового потока, вызывающего процедуру сервера еще раз. Если процедура сервера идемпотентна, проблем в такой ситуации не возникнет. Однако если она неидемпотентна, это может привести к ошибкам.

Термин «идемпотентность» по отношению к процедуре подразумевает, что процедура может быть вызвана произвольное число раз без возникновения ошибок. Наша процедура сервера, вычисляющая квадрат целого числа, идемпотентна: мы получаем правильный результат вне зависимости от того, сколько раз мы ее вызовем. Другим примером является процедура, возвращающая дату и время. Хотя эта процедура и будет возвращать разную информацию при новых вызовах (поскольку дата и время меняются), это не вызовет проблем. Классическим примером неидемпотентной процедуры является процедура уменьшения банковского счета на некоторую величину. Конечный результат будет неверным, если ее вызвать дважды.

Оглавление книги


Генерация: 1.270. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз