一次使用命名管道与epoll的踩坑记录

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

    # 客户端

    #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

    我来整体描述一下这个demo做的事情:

    1. 服务端会在一开始打开一个非阻塞的FIFO,将fd塞到一个epoll event中,后面就是正常读取消息,这里我强制服务端每次读一整个message,长度是固定的(strlen(MESSAGE)+1)。
    2. 客户端就正常得打开命名管道,然后while 10000次向server发送Hello, world!

    当这个demo跑起来后很容易就能复现前面说的那个bug。如下图:

    image-20231207175429533

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

    image-20231207174744195

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

    嘉宾
    路文飞