skynet源码分析之skynet_monitor(转)

使用skynet框架中,偶尔会遇到A message from [ :0000000b ] to [ :0000000c ] maybe in an endless loop (version = 13187)类似的error,意思是0000000c服务处理0000000b服务发过来的某条消息时可能陷入死循环。出现这种error的原因:业务层发生死循环或者比较耗时(超过5s)。这就是skyent_monitor的作用。

1. skynet启动时会启动一个monitor线程,用来监测各个工作线程。5s循环一次,调用skynet_monitor_check()检测工作线程,稍后说明。

复制代码
 1 // skynet-src/skynet_start.c
 2 static void *
 3 thread_monitor(void *p) {
 4         struct monitor * m = p;
 5         int i;
 6         int n = m->count;
 7         skynet_initthread(THREAD_MONITOR);
 8         for (;;) {
 9                 CHECK_ABORT
10                 for (i=0;i<n;i++) {
11                         skynet_monitor_check(m->m[i]);
12                 }
13                 for (i=0;i<5;i++) {
14                         CHECK_ABORT
15                         sleep(1);
16                 }
17         }
18 
19         return NULL;
20 }
复制代码

每个工作线程指定一个skynet_monitor c结构的变量,处理消息前,会记录消息的源地址和目的地址(第5行);处理完消息,清空记录(第7行)。并且会累加version(15行)。

复制代码
 1 // skynet-src/skynet_server.c
 2 struct message_queue * 
 3 skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight){
 4     ...
 5     skynet_monitor_trigger(sm, msg.source , handle);
 6     dispatch_message(ctx, &msg); //处理消息
 7     skynet_monitor_trigger(sm, 0,0);
 8 }
 9 
10 // skynet-src/skynet_monitor.c
11 void 
12 skynet_monitor_trigger(struct skynet_monitor *sm, uint32_t source, uint32_t destination) {
13         sm->source = source;
14         sm->destination = destination;
15         ATOM_INC(&sm->version);
16 }
复制代码

monitor线程每5s调用一次skynet_monitor_check()检测工作线程:

没有死循环或者很耗时的操作,version在不断累加,设置check_version等于version(第10行)。

如果version等于check_version,有两种情况

1. 这5s期间工作线程没有消息可处理,此时,destination为0

2. 在处理某条消息时,耗时超过5s,说明有可能死循环了,于是就有了最初的错误日志(第7行)。

复制代码
 1 // skynet-src/skynet_monitor.c
 2 void 
 3 skynet_monitor_check(struct skynet_monitor *sm) {
 4         if (sm->version == sm->check_version) {
 5                 if (sm->destination) {
 6                         skynet_context_endless(sm->destination);
 7                         skynet_error(NULL, "A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)", sm->source , sm->destination, sm->version);
 8                 }
 9         } else {
10                 sm->check_version = sm->version;
11         }
12 }
复制代码

2. skynet定义了一个monitor结构

复制代码
// skynet-src/skynet_start.c
struct monitor {
        int count; //工作线程总数
        struct skynet_monitor ** m; //监测各个工作线程
        pthread_cond_t cond; //条件变量
        pthread_mutex_t mutex; //锁
        int sleep; //休眠的工作线程数
        int quit;
};
复制代码

当没有消息处理(全局消息队列为空)时,工作线程进入休眠(第14行),并且累加m->sleep变量(第10行)。等待socket线程,timer线程唤醒。

复制代码
 1 // skynet-src/skynet_start.c
 2 static void *
 3 thread_worker(void *p) {
 4         ...
 5         struct message_queue * q = NULL;
 6         while (!m->quit) {
 7                 q = skynet_context_message_dispatch(sm, q, weight);
 8                 if (q == NULL) {
 9                         if (pthread_mutex_lock(&m->mutex) == 0) {
10                                 ++ m->sleep;
11                                 // "spurious wakeup" is harmless,
12                                 // because skynet_context_message_dispatch() can be call at any time.
13                                 if (!m->quit)
14                                         pthread_cond_wait(&m->cond, &m->mutex);
15                                 -- m->sleep;
16                                 if (pthread_mutex_unlock(&m->mutex)) {
17                                         fprintf(stderr, "unlock mutex error");
18                                         exit(1);
19                                 }
20                         }
21                 }
22         }
23         return NULL;
24 }
复制代码

第6行,调用pthread_cond_signal唤醒阻塞的工作线程。

当socket线程接收到数据时,只有当所有工作线程都休眠时才会去唤醒wakeup(m,0)

当timer定时器线程到达时,只要有工作线程休眠,都会去唤醒wakeup(m,m->count-1),是因为定时器事件需要及时处理。

复制代码
 1 // skynet-src/skynet_start.c
 2 static void
 3 wakeup(struct monitor *m, int busy) {
 4         if (m->sleep >= m->count - busy) {
 5                 // signal sleep worker, "spurious wakeup" is harmless
 6                 pthread_cond_signal(&m->cond);
 7         }
 8 }
 9 
10 static void *
11 thread_socket(void *p) {
12         struct monitor * m = p;
13         ...
14         wakeup(m,0);
15         return NULL;
16 }
17 
18 static void *
19 thread_timer(void *p) {
20         ...
21         wakeup(m,m->count-1);
22         ...
23         return NULL;
24 }
复制代码

 

 
 

posted on 2022-02-28 14:08  &大飞  阅读(19)  评论(0编辑  收藏  举报

导航