诊断
大多数时候,当我在运行 Big Sur 11.6.3 的 MacBook Pro 上进行测试时,这些命令都会运行完成。我正在使用一个程序tester
来运行服务器,然后是客户端——该程序的优点是我可以准确地报告客户端和服务器程序的退出状态。我一直在使用越来越复杂的测试设备来捕获信息。
每隔一段时间,我似乎让服务器立即死机。我相信这是由于 o/s 调度程序造成的时间问题。启动代码在启动服务器后运行客户端,但碰巧系统调度程序在服务器设置其信号处理程序之前运行客户端,因此服务器被客户端的初始信号杀死。
支持证据
我修改了客户端和服务器程序以包含alarm(15);
,以便进程在 15 秒后超时。大多数情况下,这对完成不到一秒钟。在那些事情失败的情况下,我让服务器以状态 0x001E 退出(这表明它死于 SIGUSR1 信号)并且在相同的运行中,客户端在 15 秒后以状态 0x000E 退出(这表明它死于 SIGALRM 信号) . 日志文件不包含“ received
”消息。
$ rmk && timecmd -m -- ./tester | tpipe -sx "grep -c -e '^C received'" "grep -c -e '^S received'" "grep -E -e 'PID =|^Child|(Server|Client) PID'" "cat > log.$(isodate -c)"
2022-02-15 14:25:07.172 [PID 10210] ./tester
tpipe: + grep -c -e '^C received'
tpipe: + grep -c -e '^S received'
tpipe: + grep -E -e 'PID =|^Child|(Server|Client) PID'
tpipe: + cat > log.20220215.142507
Server PID: 10211
Client PID: 10212
10212: sent signal to PID = 10211
Child 10211 exited with status 0x001E
Child 10212 exited with status 0x000E
2022-02-15 14:25:22.193 [PID 10210; status 0x0000] - 15.021s
0
0
$
10211 的状态消息几乎立即出现;10212 等待了 15 秒多一点。两个零来自grep -c
命令——没有有趣的消息。
相比之下,之前的运行显示:
$ rmk && timecmd -m -- ./tester | tpipe -sx "grep -c -e '^C received'" "grep -c -e '^S received'" "grep -E -e 'PID =|^Child|(Server|Client) PID'" "cat > log.$(isodate -c)"
2022-02-15 14:25:05.965 [PID 10196] ./tester
tpipe: + grep -c -e '^C received'
tpipe: + grep -c -e '^S received'
tpipe: + grep -E -e 'PID =|^Child|(Server|Client) PID'
tpipe: + cat > log.20220215.142505
Server PID: 10197
Client PID: 10198
PID = 10197
10198: sent signal to PID = 10197
Child 10197 exited with status 0x0000
Child 10198 exited with status 0x0000
2022-02-15 14:25:06.481 [PID 10196; status 0x0000] - 0.515s
5000
5000
$
这里的 5000 个条目是通过程序grep -c
运行的命令的计数。tpipe
(它是;rmk
的变体,是一个有点像的程序,只是它写入进程而不是文件(另请参阅不幸命名的程序);以压缩的 ISO 8601 格式打印日期,例如;执行命令并将其计时到毫秒,报告命令和状态等)make
tpipe
tee
pee
isodate
20220215.142505
timecmd -m
我没有记录过 where 的情况info->si_pid == 0
,也没有记录过在一些中间数量的信号交换后出现问题的情况——它是 0 或 5000,没有其他值。因此,我可能没有准确地重现您的场景。
使用 shell 脚本启动服务器,然后客户端没有重现过早信号——处理 shell 脚本所固有的延迟似乎足以让服务器在客户端发送初始信号之前设置其信号处理.
脚本测试:
time=$(isodate -c)
server > server.$time.log &
client $! > client.$time.log
修改后的代码
JFTR,这是我修改后的代码。它使用我在 GitHub 上的SOQ(堆栈溢出问题)存储库中提供的一些代码作为文件stderr.c
和src/libsoq子目录中的stderr.h
代码。
client.c
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "stderr.h"
static void ft_send_signal(int pid)
{
if (kill(pid, SIGUSR1) != 0)
err_syserr("failed to send initial signal to PID %d: ", pid);
printf("%d: sent signal to PID = %d\n", getpid(), pid);
fflush(stdout);
}
static void ft_signal_handler(int sig, siginfo_t *info, void *context)
{
static int i = 0;
(void)context;
if (sig == SIGUSR1)
{
printf("C received - %d PID: %d\n", i, info->si_pid);
fflush(stdout);
i++;
if (info->si_pid != 0)
{
if (kill(info->si_pid, SIGUSR1) != 0)
err_syserr("failed to send signal to PID %d: ", info->si_pid);
}
else
err_error("info->si_pid == 0 at iteration %d\n", i);
if (i == 5000)
exit(EXIT_SUCCESS);
}
}
int main(int ac, char **av)
{
err_setarg0("client");
struct sigaction action;
sigset_t set;
if (ac != 2)
err_usage("PID");
alarm(15);
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
action.sa_flags = SA_SIGINFO;
action.sa_sigaction = ft_signal_handler;
action.sa_mask = set;
sigaction(SIGUSR1, &action, NULL);
ft_send_signal(atoi(av[1]));
while(1)
pause();
return (0);
}
server.c
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "stderr.h"
static void ft_respond(int sig, siginfo_t *info, void *context)
{
static int i = 0;
(void)context;
if (sig == SIGUSR1)
{
i++;
printf("S received - %d PID: %d\n", i, info->si_pid);
fflush(stdout);
if (info->si_pid != 0)
{
if (kill(info->si_pid, SIGUSR1) != 0)
err_syserr("failed to send signal to PID %d: ", info->si_pid);
}
else
err_error("info->si_pid == 0 at iteration %d\n", i);
if (i == 5000)
exit(EXIT_SUCCESS);
}
}
int main(void)
{
err_setarg0("server");
struct sigaction reaction;
sigemptyset(&reaction.sa_mask);
alarm(15);
reaction.sa_flags = SA_SIGINFO;
reaction.sa_sigaction = ft_respond;
sigaction(SIGUSR1, &reaction, NULL);
printf("PID = %d\n", getpid());
fflush(stdout);
while (1)
pause();
return(0);
}
tester.c
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include "stderr.h"
int main(void)
{
err_setarg0("tester");
alarm(20);
pid_t server = fork();
if (server < 0)
err_syserr("failed to fork for server: ");
if (server == 0)
{
char *args[] = { "./server", 0 };
execv(args[0], args);
err_syserr("failed to exec server: ");
}
printf("Server PID: %d\n", server);
fflush(stdout);
pid_t client = fork();
if (client < 0)
err_syserr("failed to fork for client: ");
if (client == 0)
{
char buffer[20];
snprintf(buffer, sizeof(buffer), "%d", server);
char *argc[] = { "./client", buffer, 0 };
execv(argc[0], argc);
err_syserr("failed to exec client: ");
}
printf("Client PID: %d\n", client);
fflush(stdout);
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
printf("Child %d exited with status 0x%.4X\n", corpse, status);
fflush(stdout);
}
return 0;
}
处方
我不确定是否有解决此问题的好方法,除了添加对客户端代码的调用以延迟它发送初始信号一段时间——一两毫秒可能就足够了。这种延迟意味着服务器有时间设置其信号处理。等效地,该tester
程序可以在启动服务器和客户端之间添加延迟。
为什么在 Linux 上没有问题?运气?或者 o/s 调度程序不会tester
在第一个孩子之前运行第二个孩子,因此服务器总是在客户端发送第一个信号之前设置其信号处理。