一次使用命名管道与epoll的踩坑记录
Ryan 12/8/2023
笔者最近在写一个视频转码和压缩的服务,其中还包含了对于错误的文件进行修复的模块,其中包含一个服务端,它包含了几乎所有这个服务的功能。还包含了一个客户端工具,用来向服务端提交数据。因为这个服务的客户端和服务端是部署在同一台机器上的,所有这里我采用了命名管道来进行通信。
一开始我自测还是很顺利的,但是当我写了一个脚本提交了10000的任务后,整个服务就卡住了。这里我来写一个案例来复线这个Bug
# 服务端
#include <asm-generic/errno-base.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO_PATH "/tmp/mytest_fifo"
#define MESSAGE "Hello, world!"
#define MAX_EVENTS 10
static int volatile count = 0;
int main()
{
int fd, epoll_fd, events_count, read_bytes;
struct epoll_event event, events[MAX_EVENTS];
int packet_len = strlen(MESSAGE) + 1;
char buffer[64] = {0};
mkfifo(FIFO_PATH, 0666);
fd = open(FIFO_PATH, O_RDWR | O_NONBLOCK);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
epoll_fd = epoll_create1(0);
if (epoll_fd == -1)
{
perror("epoll_create1");
exit(EXIT_FAILURE);
}
event.events = EPOLLIN | EPOLLET;
event.data.fd = fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1)
{
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
printf("Server waiting for clients...\n");
ssize_t bytes_read = 0;
size_t total_bytes_read = 0;
while (1)
{
events_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (events_count == -1)
{
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < events_count; ++i)
{
if (events[i].events & EPOLLIN)
{
while (total_bytes_read < packet_len)
{
bytes_read =
read(events[i].data.fd, buffer + total_bytes_read,
packet_len - total_bytes_read);
if (bytes_read <= 0)
{
if (errno == EAGAIN || errno == EINTR ||
errno == EWOULDBLOCK)
{
continue;
}
printf("error read from fifo deu to:%s\n",
strerror(errno));
break;
}
total_bytes_read += bytes_read;
}
printf("read from client :%s %d\n", buffer, count++);
total_bytes_read = 0;
}
}
}
close(fd);
close(epoll_fd);
unlink(FIFO_PATH);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 客户端
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#define FIFO_PATH "/tmp/mytest_fifo"
#define MESSAGE "Hello, world!"
static int volatile count = 100000;
int main()
{
int fd;
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
while (count > 0)
{
ssize_t bytesWritten = write(fd, MESSAGE, strlen(MESSAGE) + 1);
if (bytesWritten == -1)
{
printf("write error:%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("write %d hello msg to server\n", 100000 - count);
count--;
}
close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
我来整体描述一下这个demo做的事情:
- 服务端会在一开始打开一个非阻塞的FIFO,将fd塞到一个epoll event中,后面就是正常读取消息,这里我强制服务端每次读一整个message,长度是固定的(
strlen(MESSAGE)+1)。 - 客户端就正常得打开命名管道,然后while
10000次向server发送Hello, world!。
当这个demo跑起来后很容易就能复现前面说的那个bug。如下图:

服务端只读了四个消息后就stack住了,反观客户端,发送了上千条消息后也同样卡住了。

我们用Strace来去启动这两个服务来看一下、他俩hang在了什么地方:
