使用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 }
来源:oschina
链接:https://my.oschina.net/u/4402150/blog/3905781