Bash脚本在退出后仍然存在(命名管道I/O存在问题) 总结

Bash脚本在退出后仍然存在(命名管道I/O存在问题) 总结,bash,memory-leaks,Bash,Memory Leaks,我已经想出了解决这个问题的办法 基本上,被调用方(wallpaper)本身并没有退出,因为它正在等待另一个进程完成 在52天的过程中,这个有问题的副作用像滚雪球一样不断扩大,直到10000多个延迟进程消耗了超过10 GB的RAM,几乎使我的系统崩溃 令人不快的过程是从一个名为log的函数调用printf,我把它发送到后台并忘记了,因为它正在写入管道并挂起 事实证明,写入命名管道的进程将阻塞,直到出现另一个进程并从中读取 这反过来又将问题的要求从“我需要一种方法来阻止这些进程的累积”改为“我需要一

我已经想出了解决这个问题的办法

基本上,被调用方(
wallpaper
)本身并没有退出,因为它正在等待另一个进程完成

在52天的过程中,这个有问题的副作用像滚雪球一样不断扩大,直到10000多个延迟进程消耗了超过10 GB的RAM,几乎使我的系统崩溃

令人不快的过程是从一个名为
log
的函数调用printf,我把它发送到后台并忘记了,因为它正在写入管道并挂起

事实证明,写入命名管道的进程将阻塞,直到出现另一个进程并从中读取

这反过来又将问题的要求从“我需要一种方法来阻止这些进程的累积”改为“我需要一种更好的方法来绕过FIFO I/O,而不是将其抛到后台”


请注意,虽然问题已经解决,但我非常乐意接受一个在技术层面上详细阐述的答案。例如,为什么调用方脚本的(
墙纸运行
)进程也会被复制,即使它只被调用一次,或者如何正确读取管道的状态信息,而不是依赖于
打开
,在使用
O_NONBLOCK
调用时失败

原来的问题如下


问题 我有两个bash脚本要在循环中运行。第一个
墙纸运行
,以无限循环运行,并调用第二个
墙纸

它们是我的“桌面”的一部分,这是一堆拼凑在一起的shell脚本,用于增强
dwm
窗口管理器

墙纸运行:

log "starting wallpaper runner"

while true; do
    log "..."
    $scr/wallpaper
    sleep 900 # 15 minutes
done &
壁纸:

log "changing wallpaper"

# several utility functions ...

if [[ $1 ]]; then
    parse_arg $1
else
    load_random
fi
一些注意事项:

  • log
    是从
    init
    导出的函数,顾名思义,它记录消息

  • init
    调用
    wallpaper在其前台运行(除其他外)(因此while循环位于后台)

  • $scr
    也由init定义;它是所谓的“init脚本”所在的目录

  • parse_arg
    load_random
    是本地的
    wallpaper

  • 特别是,图像通过程序
    feh

  • 墙纸运行的加载方式如下:
    $mod/wallpaper run

  • init由
    startx
    直接调用,并在运行wallpaper run(和其他“模块”)之前启动dwm

现在,问题是,由于某种原因,墙纸会在内存中运行,而墙纸会在内存中“徘徊”。也就是说,在循环的每次迭代之后,会创建两个新的wallpaper和wallpaper run实例,而“旧”实例不会得到清理,并且会陷入睡眠状态。这就像一个内存泄漏,但进程延迟,而不是糟糕的内存管理

我发现这个“进程泄漏”是在我的系统运行了52天之后发生的,当时所有东西都坏了(比如
bash:cannot fork:resource temporary unavailable
每当我试图运行命令时都会向终端发送垃圾邮件),因为系统内存不足。为了使我的系统恢复工作状态,我不得不杀死10000多个壁纸/运行实例

我完全不知道为什么会这样。我认为这些脚本没有理由在内存中逗留,因为脚本退出应该意味着其进程被清理

他们为什么要拖延时间,消耗资源


更新1 在评论的帮助下(非常感谢I'L'I),我将问题追溯到函数
log
,该函数对printf进行后台调用(尽管我不记得为什么选择这样做)。以下是在init中显示的函数:

log(){
    local pipe=$pipe_front
    if ! [[ -p $pipe ]]; then
        mkfifo $pipe
    fi
    printf ... >> $initlog
    printf ... > $pipe &
    printf ... &
    [[ $2 == "-g" ]] &&  notify-send "[DWM Init] $1"
    sleep 0.001
}
如您所见,函数编写得非常糟糕。我把它拼凑在一起是为了让它工作,而不是让它变得健壮

第二个和第三个printf被发送到后台。我不记得我为什么这样做,但这大概是因为第一个printf一定是让log挂起了

printf行已被删节为“…”,因为它们相当复杂,与手头的问题无关(而且我有40分钟的时间做比使用Android的垃圾文本输入界面更好的事情)。特别是,根据我们讨论的printf,打印诸如当前时间、调用进程的名称和传递的消息等内容。第一行的详细信息最多,因为它被保存到一个文件中,其中立即上下文丢失,而通知发送行的详细信息最少,因为它将显示在桌面上

整个管道崩溃是为了通过我为它编写的基本shell直接与init接口

第三个printf是故意的;它打印到我在会话开始时登录的tty。这样,如果init突然崩溃在我身上,我可以看到出错的日志。或者至少在它崩溃之前发生了什么

我把这个问题包括在内,因为这是“泄漏”的根本原因。如果我能修复这个函数,这个问题就会得到解决

函数需要将消息记录到各自的源并暂停,直到每次对printf的调用完成,但它也必须及时完成;无限期挂起和/或未能记录消息是不可接受的行为


更新2 在将
log
函数(参见更新1)隔离到测试脚本中并设置模拟环境之后,我将其归结为printf

被重定向到管道中的printf调用

printf "..." > $pipe
如果没有任何内容在侦听它,则挂起,因为它正在等待第二个进程拾取管道的读取端并使用数据。
#include <fcntl.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
    int fd = open(argv[1], O_WRONLY | O_NONBLOCK);

    if(fd == -1)
        return 1;

    close(fd);
    return 0;
}
$ gcc pipe-open.c
$ ./a.out ./pipe && echo "pipe has a reader" || echo "pipe has no reader"
$ ./a.out ./pipe && echo "pipe has a reader" || echo "pipe has no reader"
#include <fcntl.h>
#include <unistd.h>

int
write_to_pipe(int fd)
{
    char buf[1024];
    ssize_t nread;
    int nsuccess = 0;

    while((nread = read(0, buf, 1024)) > 0 && ++nsuccess)
        write(fd, buf, nread);

    close(fd);
    return nsuccess > 0 ? 0 : 2;
}

int
main(int argc, char **argv)
{
    int fd = open(argv[1], O_WRONLY | O_NONBLOCK);

    if(fd == -1)
        return 1;

    return write_to_pipe(fd);
}
$ echo hello world | ./a.out pipe
$ ret=$?
$ if [[ $ret == 1 ]]; then echo no reader
> elif [[ $ret == 2 ]]; then echo an error occurred trying to write to the pipe
> else echo success
> fi