【问题标题】:Timer and syslog combination will lead my code to hang situationTimer 和 syslog 组合会导致我的代码挂起的情况
【发布时间】:2015-01-21 05:44:49
【问题描述】:

我尝试调试由于在我的程序中使用计时器和系统日志功能而导致的问题。这里我附上示例程序代码和终端和系统日志的日志以进行适当的调试。

我不明白为什么这个程序会在一段时间后挂起。 所以在这里我有2个问题, 1. 当定时器到期时,睡眠正在中断,因为它生成 SIGPROF 2. syslog在第一次试用一段时间或一段时间后被挂起

代码:

#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <syslog.h>

#define get_curr_date_time(date_time) \
{ \
   time_t t; \
   time(&t); \
   char *strtime = ctime(&t); \
   strncpy(date_time, strtime, strlen(strtime) - 1); \
}

#define DEBUG_INFO(p,x,arg...) \
{\
   printf("%s:%d,1\n", __func__, __LINE__);\
   char current_time[32] = {0}; \
   printf("%s:%d,2\n", __func__, __LINE__);\
   get_curr_date_time(current_time); \
   printf("%s:%d,3\n", __func__, __LINE__);\
   syslog(LOG_INFO,"[%s] : " p " : "#x"\n", current_time, ##arg);\
   printf("%s:%d,4\n", __func__, __LINE__);\
}

char exit_flag = 0;

typedef struct _test_ctx_
{
   char     timer_init;
   timer_t  timerid;

}test_ctx;

void Timer_Handler(int sig, siginfo_t *si, void *uc)
{
   printf("Timer handler is start\n");
   DEBUG_INFO("timer_hang", "Timer handler is running\n");
   printf("Timer handler is stop\n");
}

int InitTimer(test_ctx *tst_ctx)
{
   int                  status   = 0;
   struct sigaction     sa;
   struct sigevent      sig;

   memset(&sig, 0x00, sizeof(struct sigevent));
   memset(&sa, 0x00, sizeof(struct sigaction));

   do
   {
      sa.sa_flags = SA_SIGINFO;
      sa.sa_sigaction = Timer_Handler;
      sigemptyset(&sa.sa_mask);
      if (0 != (status = sigaction(SIGPROF, &sa, NULL)))
      {
         printf("Fail to register SIGPROF signal for timer, ret: %d\n", status);
         break;
      }

      sig.sigev_notify = SIGEV_SIGNAL;
      sig.sigev_signo = SIGPROF;
      sig.sigev_value.sival_ptr = &tst_ctx->timerid;
      if (0 != (status = timer_create(CLOCK_REALTIME, &sig, &(tst_ctx->timerid))))
      {
         printf("Failed to create timer, ret: %d\n", status);
         break;
      }

      //Timer inited sucessfully
      tst_ctx->timer_init = 1;

   }while(0);

   return status;
}

void DeInitTimer(test_ctx *tst_ctx)
{
   int   status   = 0;

   if (0 != tst_ctx->timer_init)
   {
      //Delete timer
      if (0 != (status = timer_delete(tst_ctx->timerid)))
      {
         printf("Fail to delete timer, ret: %d\n", status);
      }
      tst_ctx->timer_init = 0;
   }
}

int SetTimer(test_ctx *tst_ctx)
{
   int   status   = 0;
   struct itimerspec in;
   memset(&in, 0x00, sizeof(struct itimerspec));

   do
   {
      in.it_value.tv_sec = 0;
      in.it_value.tv_nsec = 1;
      in.it_interval.tv_sec = 0;
      in.it_interval.tv_nsec = 0;

      if (0 != (status = timer_settime(tst_ctx->timerid, 0, &in, NULL)))
      {
         printf("Fail to set timer, ret: %d\n", status);
         break;
      }

   }while(0);

   return status;
}

void terminate_app(int sig)
{
   exit_flag = 1;
   printf("signal %d received exiting application\n", sig);
   DEBUG_INFO("timer_hang", "signal %d received exiting application\n", sig);
}

int main(int argc, char *argv[])
{
   int            status   = 0;
   test_ctx       tst_ctx;
   memset(&tst_ctx, 0x00, sizeof(tst_ctx));

   do
   {
      //Register signal handler
      signal(SIGTERM, terminate_app);
      signal(SIGINT,  terminate_app);

      //Init timer
      status = InitTimer(&tst_ctx);
      if (0 != status)
      {
         break;
      }

      while(0 == exit_flag)
      {
         printf("Setting timer\n");
         DEBUG_INFO("timer_hang", "Setting timer");

         //Set Timer
         SetTimer(&tst_ctx);

         printf("Hello!!!\n");
         DEBUG_INFO("timer_hang", "Hello!!!");
         printf("Say!!!\n");

         sleep(5);
      }

   }while(0);

   //De init timer
   DeInitTimer(&tst_ctx);

   return status;
}

终端日志:

root@AHMCPU0085:/home/ravi/work/test_app/timer_hang# ./hang_issue
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!



Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,3
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
main:154,4
Say!!!




Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
main:154,4
Say!!!
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3
Timer_Handler:39,4
Timer handler is stop
Setting timer
main:148,1
main:148,2
main:148,3
main:148,4
Hello!!!
main:154,1
main:154,2
main:154,3
Timer handler is start
Timer_Handler:39,1
Timer_Handler:39,2
Timer_Handler:39,3



^Csignal 2 received exiting application
terminate_app:123,1
terminate_app:123,2
terminate_app:123,3

^Z
[1]+  Stopped                 ./hang_issue
root@AHMCPU0085:/home/ravi/work/test_app/timer_hang#
root@AHMCPU0085:/home/ravi/work/test_app/timer_hang# killall -s 9 hang_issue
[1]+  Killed                  ./hang_issue
root@AHMCPU0085:/home/ravi/work/test_app/timer_hang#
root@AHMCPU0085:/home/ravi/work/test_app/timer_hang#

系统日志消息:

root@AHMCPU0085:/home/ravi/work/test_app/timer_hang# tail -f /var/log/messages
...

Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Setting timer"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:15 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:15 2015] : timer_hang : "Hello!!!"



Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Setting timer"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Setting timer"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Setting timer"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:20 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:20 2015] : timer_hang : "Hello!!!"



Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Hello!!!"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Timer handler is running\n"
Jan 21 10:52:25 AHMCPU0085 hang_issue: [Wed Jan 21 10:52:25 2015] : timer_hang : "Setting timer"


^C
root@AHMCPU0085:/home/ravi/work/test_app/timer_hang#

您将通过以下步骤在您的 linux 机器上运行此测试应用程序:

# gcc -o hang_issue timer_hang.c -Wall -lrt
# ./hang_issue
... <You will get logs> ...

任何帮助将不胜感激。

问候, 拉维

【问题讨论】:

  • printf() 不是异步信号保存。所以不要从信号处理程序......和计时器处理程序调用它?
  • 谢谢,我很感激。即使我从信号处理程序和计时器处理程序中删除所有 printf,它也会被阻塞。那么你能建议我如何解决挂起问题,或者你能给我一些点来调试它吗?
  • 它挂起是因为exit_flag 不是volatile,允许编译器在不实际检查的情况下对其值进行假设。此外,sleep() 与信号的交互非常糟糕。您应该改用nanosleep();它将被信号传递中断,但随后会返回 -1,并更新 errno == EINTR 和 *rem 以指示未睡眠时间。
  • exit_flag 不是易失性的:当在同一个文件中定义标志时,编译器非常智能地检测标志是可变的。所以挂起问题不是由于exit_flag。感谢您提供 nanosleep 而不是 sleep 的想法,我一定会寻找这个选项。
  • (我对exit_flag 不太确定,因为它取决于所使用的编译器、编译器版本和编译器选项。不过,我会接受你的话。)syslog()也不是异步信号安全的,而且它的功能相当复杂;如果它在信号处理程序中阻塞(很长时间)也就不足为奇了。看,这很简单:如果你在信号处理程序中使用非async-signal safe functions,而你的程序出现问题,这是你自己的错。只需修复您的代码,挂起/阻塞就会消失。

标签: c linux timer signals syslog


【解决方案1】:

如 cmets 中所述,您不能在信号处理程序中使用 syslog()。它不是async-signal safe,并且作为一个复杂的函数(根据需要打开和关闭到 syslog 的连接),难怪使用它会导致您的程序以奇怪的方式失败。

改为使用write() 以及标准输出和标准错误流。例如,

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

static void wr(int fd, const void *const ptr, const size_t len)
{
    const char       *p = (const char *)ptr;
    const char *const q = (const char *)ptr + len;
    ssize_t           n;

    while (p < q) {
        n = write(fd, p, (size_t)(q - p));
        if (n > (ssize_t)0)
            p += n;
        else
        if (n != (ssize_t)-1 || errno != EINTR)
            return;
    }
}

static void wrerr(const char *const string)
{
    if (string != NULL)
        wr(STDERR_FILENO, string, strlen(string));
}

static void wrout(const char *const string)
{
    if (string != NULL)
        wr(STDOUT_FILENO, string, strlen(string));
}

在上面,wrout() 和 wrerr() 只接受一个字符串(如 puts(),除了它们不会自动附加换行符),并且不如 printf() 有用,但它们是异步信号安全的,并且可以安全地从信号处理程序中使用。

请记住,在使用 Bash 时,您可以使用 &gt;out 将标准输出重定向到文件 out,并使用 2&gt;err 将标准错误重定向到文件 err。输出到终端出奇地慢,所以对于任何时间,都重定向到文件(或者更好的是,根本不输出额外的调试信息)。

如果您确实需要将信号处理程序的一些输出发送到 syslog,则需要使用管道或套接字对,并从另一端读取线程或子进程,将数据发送到 syslog .然后,您可以只使用异步信号安全函数从信号处理程序写入管道或套接字对(使用write())。 (显然,读取和 syslog 数据的线程或子进程不需要使用异步信号安全函数——毕竟它不是信号处理程序。)


让我们来讨论一下超时这个话题。

我建议不要使用正常信号进行超时。如果您需要信号,例如中断特定线程中的阻塞系统调用,请改用实时信号(SIGRTMIN+0 到 SIGRTMAX-0)。

在实践中,使用单独的线程来处理一组超时效果要好得多。这是一个示例,使用少于 400 行代码,它允许您使用任意数量的并发超时,并为每个超时提供一个 volatile 标志和一个信号量以方便使用。它使用CLOCK_MONOTONIC 时钟,该时钟不易受到跳跃(UTC 秒、夏令时等)的影响,但除此之外尝试保持实时时钟(挂钟)的滴答率:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>

/* Number of timeouts to test. */
#ifndef TIMEOUT_TESTS
#define TIMEOUT_TESTS 100000
#endif

/* Seconds per timeout. */
#ifndef TIMEOUT_SECONDS
#define TIMEOUT_SECONDS 0.000000001
#endif

/* Timeout thread worker stack size; uses very few local variables. */
#define TIMEOUT_STACK_SIZE    65536

typedef struct timeout  timeout;
struct timeout {
    struct timeout      *next;
    struct timespec      abstime;   /* Using CLOCK_MONOTONIC clock */
    sem_t                elapsed;   /* sem_post()ed when elapsed */
    volatile int         pending;   /* Cleared to zero when expires */
};

static volatile int      timeouts_error = -1;
static pthread_t         timeouts_thread;
static pthread_mutex_t   timeout_lock;
static pthread_cond_t    timeout_cond; /* Uses CLOCK_MONOTONIC clock */
static timeout *volatile timeout_pending = NULL;

static void *timeouts_worker(void *unused __attribute__((unused)))
{
    struct timespec now;
    timeout *curr;
    int err;

    err = pthread_mutex_lock(&timeout_lock);
    if (err) {
        timeouts_error = err;
        pthread_cond_signal(&timeout_cond);
        return (void *)(long)err;
    }

    timeouts_error = 0;
    pthread_cond_signal(&timeout_cond);

    while (!timeouts_error) {

        /* If there are no pending timeouts,
         * all we need to do is wait for a condition. */
        if (timeout_pending == NULL) {
            pthread_cond_wait(&timeout_cond, &timeout_lock);
            continue;
        }

        /* CLOCK_MONOTONIC is used for the timeouts. */
        if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
            timeouts_error = errno;
            pthread_mutex_unlock(&timeout_lock);
            return (void *)(long)timeouts_error;
        }

        /* Trigger and remove all timeouts that have elapsed thus far. */
        curr = timeout_pending;
        while (curr != NULL &&
               (curr->abstime.tv_sec < now.tv_sec ||
                (curr->abstime.tv_sec == now.tv_sec &&
                 curr->abstime.tv_nsec <= now.tv_nsec))) {
            timeout *const prev = curr;
            curr = prev->next;
            prev->next = NULL;

            /* Mark 'prev' timeout elapsed. */
            prev->pending = 0;
            sem_post(&(prev->elapsed));
        }

        /* No more timeouts? */
        if (timeout_pending == NULL)
            continue;

        /* Wait for the next one to elapse.
         * TODO: Adjust 'now' according to previous over/undershoots,
         *       Say, by one tenth of previous wakeup error
         *       (i.e. dynamically estimating the _timedwait latency).
         *       This would result in much more accurate timeouts.
        */
        now = timeout_pending->abstime;
        pthread_cond_timedwait(&timeout_cond, &timeout_lock, &now);
    }

    return (void *)0L;
}

static int timeout_free(timeout *const old_timeout)
{
    int err;

    if (old_timeout == NULL)
        return 0;

    err = pthread_mutex_lock(&timeout_lock);
    if (err)
        return errno = err; /* We also leak memory here. */

    /* Remove from timeout_pending chain. */
    if (timeout_pending == old_timeout)
        timeout_pending = old_timeout->next;
    else {
        timeout *temp = timeout_pending;
        if (temp != NULL)
            while (temp->next != NULL)
                if (temp->next == old_timeout) {
                    temp->next = old_timeout->next;
                    break;
                } else
                    temp = temp->next;
    }

    /* Unlock mutex; we no longer need to access the chain. */
    pthread_mutex_unlock(&timeout_lock);

    /* Poison and free the timeout structure. */
    sem_destroy(&(old_timeout->elapsed));
    old_timeout->next = NULL;
    old_timeout->abstime.tv_sec = 0;
    old_timeout->abstime.tv_nsec = 0;
    old_timeout->pending = 0;

    free(old_timeout);

    return 0;
}


static timeout *timeout_after(const double seconds)
{
    const long sec = (long)seconds;
    const long nsec = (long)(1000000000.0 * (seconds - (double)sec));
    struct timespec now;
    timeout *new_timeout;
    int err;

    /* Negative time is not allowed. */
    if (seconds < 0.0) {
        errno = EINVAL;
        return NULL;
    }

    /* Get current monotonic time. */ 
    if (clock_gettime(CLOCK_MONOTONIC, &now))
        return NULL;

    new_timeout = malloc(sizeof *new_timeout);
    if (new_timeout == NULL) {
        errno = ENOMEM;
        return NULL;
    }

    if (sem_init(&(new_timeout->elapsed), 0, 0) == -1) {
        err = errno;
        free(new_timeout);
        errno = err;
        return NULL;
    }

    new_timeout->next = NULL;
    new_timeout->abstime.tv_sec = now.tv_sec + sec + (now.tv_nsec + nsec) / 1000000000L;
    new_timeout->abstime.tv_nsec = (now.tv_nsec + nsec) % 1000000000L;
    new_timeout->pending = 1;

    /* Already elapsed? */
    if (new_timeout->abstime.tv_sec < now.tv_sec ||
        (new_timeout->abstime.tv_sec == now.tv_sec &&
         new_timeout->abstime.tv_nsec <= now.tv_nsec)) {
        new_timeout->next = NULL;
        new_timeout->pending = 0;
        sem_post(&(new_timeout->elapsed));
        return new_timeout;
    }

    /* Get timeout lock, to add to chain. */
    err = pthread_mutex_lock(&timeout_lock);
    if (err) {
        sem_destroy(&(new_timeout->elapsed));
        free(new_timeout);
        errno = err;
        return NULL;
    }


    if (timeout_pending == NULL)
        timeout_pending = new_timeout;
    else
    if (timeout_pending->abstime.tv_sec > new_timeout->abstime.tv_sec ||
        (timeout_pending->abstime.tv_sec == new_timeout->abstime.tv_sec &&
         timeout_pending->abstime.tv_nsec >= new_timeout->abstime.tv_nsec)) {
        new_timeout->next = timeout_pending;
        timeout_pending = new_timeout;
    } else {
        timeout *temp = timeout_pending;

        while (temp->next != NULL &&
               (temp->next->abstime.tv_sec > new_timeout->abstime.tv_sec ||
                (temp->next->abstime.tv_sec == new_timeout->abstime.tv_sec &&
                 temp->next->abstime.tv_nsec >= new_timeout->abstime.tv_nsec)))
            temp = temp->next;

        new_timeout->next = temp->next;
        temp->next = new_timeout;
    }

    /* Timeout chain manipulated; notify and unlock. */
    pthread_cond_signal(&timeout_cond);
    pthread_mutex_unlock(&timeout_lock);

    errno = 0;
    return new_timeout;
}

static int timeouts_end(void)
{
    if (timeouts_error == 0) {
        int err;
        void *errptr;

        pthread_mutex_lock(&timeout_lock);
        timeouts_error = -1;
        pthread_cond_signal(&timeout_cond);
        pthread_mutex_unlock(&timeout_lock);

        err = pthread_join(timeouts_thread, &errptr);
        if (err == 0)
            err = (long)errptr;

        return errno = err;

    } else
    if (timeouts_error != -1) {
        int err;
        void *errptr;

        err = pthread_join(timeouts_thread, &errptr);
        if (err == 0)
            err = (long)errptr;
        else
            err = timeouts_error;

        return errno = err;

    } else
        return errno = ENOENT;
}

static int timeouts_init(void)
{
    pthread_mutexattr_t lock_attrs;
    pthread_condattr_t cond_attrs;
    pthread_attr_t attrs;
    void *errptr;
    int err;

    if (timeouts_error != -1)
        return errno = EEXIST;

    /* Initialize timeout_lock as an adaptive mutex. */
    err = pthread_mutexattr_init(&lock_attrs);
    if (err)
        return errno = err;

    err = pthread_mutexattr_settype(&lock_attrs, PTHREAD_MUTEX_ADAPTIVE_NP);
    if (err)
        return errno = err;

    err = pthread_mutex_init(&timeout_lock, &lock_attrs);
    if (err)
        return errno = err;

    err = pthread_mutexattr_destroy(&lock_attrs);
    if (err)
        return errno = err;

    /* Initialize timeout_cond as a process-private monotonic clock condition variable. */
    err = pthread_condattr_init(&cond_attrs);
    if (err)
        return errno = err;

    err = pthread_condattr_setpshared(&cond_attrs, PTHREAD_PROCESS_PRIVATE);
    if (err)
        return errno = err;

    err = pthread_condattr_setclock(&cond_attrs, CLOCK_MONOTONIC);
    if (err)
        return errno = err;

    err = pthread_cond_init(&timeout_cond, &cond_attrs);
    if (err)
        return errno = err;

    err = pthread_condattr_destroy(&cond_attrs);
    if (err)
        return errno = err;

    /* Initialize the thread attributes to a 64k stack. */
    err = pthread_attr_init(&attrs);
    if (err)
        return errno = err;

    err = pthread_attr_setstacksize(&attrs, TIMEOUT_STACK_SIZE);
    if (err)
        return errno = err;

    /* Grab the timeout lock; we'll wait on the cond later. */
    err = pthread_mutex_lock(&timeout_lock);
    if (err)
        return errno = err;

    /* Start the timeout worker thread. */
    err = pthread_create(&timeouts_thread, &attrs, timeouts_worker, NULL);
    if (err)
        return errno = err;

    pthread_attr_destroy(&attrs);

    /* Wait for the worker to be ready. */
    pthread_cond_wait(&timeout_cond, &timeout_lock);

    /* Failed? */
    if (timeouts_error != 0) {
        timeouts_error = -1;
        pthread_mutex_unlock(&timeout_lock);
        err = pthread_join(timeouts_thread, &errptr);
        if (err == 0)
            err = (long)errptr;
        return errno = err;
    }

    /* Unlock. */
    pthread_mutex_unlock(&timeout_lock);

    /* Success. */
    return 0;
}

int main(void)
{
    long i;
    timeout *t;

    if (timeouts_init()) {
        fprintf(stderr, "Cannot initialize timeouts: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Testing:\n");
    fflush(stderr);

    for (i = 1L; i <= TIMEOUT_TESTS; i++) {

        t = timeout_after(0.000000001);
        if (t == NULL) {
            fprintf(stderr, "Test %ld of %ld failed: Cannot obtain a timeout: %s.\n", i, (long)TIMEOUT_TESTS, strerror(errno));
            timeouts_end();
            return EXIT_FAILURE;
        }

        printf("Timeout %ld of %ld: ", i, (long)TIMEOUT_TESTS);
        fflush(stdout);

        /* Wait for timeout to elapse. */
        sem_wait(&(t->elapsed));

        printf("Elapsed.\n");
        fflush(stdout);

        timeout_free(t);
    }

    if (timeouts_end()) {
        fprintf(stderr, "Error in disarming timeouts: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(stderr, "No errors.\n");
    return EXIT_SUCCESS;
}

由于我们有一个专用的超时工作线程,上面使用pthread_cond_timedwait() 等待下一次超时发生(条件变量设置为使用CLOCK_MONOTONIC 时钟源),或者一个信号(由另一个线程插入了新的超时)。

要中断阻塞系统调用,将pthread_t thread 添加到timeout 结构,将timeout_after() 设置为pthread_self()。安装一个实时信号(例如SIGRTMIN+0)处理程序(带有一个空的主体——重要的是交付,而不是处理程序的作用)。最后,将pthread_kill(curr-&gt;thread, SIGRTMIN+0) 添加到timeouts_worker() 以在目标线程中引发信号。

上面的实现使用一个简单的排序链表timeout_pending,来保存当前未决的超时。这会在添加、删除和触发超时方面产生 O(N) 行为,如果您有数百或数千个并发超时,这将是非常不理想的。用二进制最小堆替换列表处理,以获得更好的性能,并具有大量并发超时。

此外,没有必要总是通知超时工作人员添加了新的超时。由于加法器持有互斥体,因此仅在将新超时添加为下一个超时时发出信号就足够了。

代码可以编译,但我没有彻底检查逻辑,所以可能有一些错误潜伏。如果您发现任何问题,请告诉我,我会尽力修复它们。 (不过,我很确定算法和方法本身是有效的。)

我不认为上述代码具有版权,因为它是如此简单,但如果有人这样做,我认为它属于公共领域,并且在那些没有法律概念的司法管辖区,根据知识共享零许可执照。简而言之:随心所欲,但不能保证,任何破损都不能怪我。

问题?评论?错误修复?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多