inotify句柄在fork后被子进程继承,导致事件冲突和崩溃;应fork前关闭句柄或fork后子进程重建独立句柄,并确保worker不调用inotify函数。

inotify 句柄在 fork 后被子进程继承的问题
PHP 中使用 inotify 监控文件时,若主进程调用 inotify_init() 创建监听句柄后执行 pcntl_fork(),子进程会**完整继承该 inotify 文件描述符**。这意味着父子进程共用同一个 inotify 实例,后续对同一监控路径调用 inotify_add_watch() 会重复注册,而 inotify_read() 在任一进程读取事件后,另一进程再读将返回空或阻塞异常——更严重的是,当父子进程各自尝试 fclose() 或退出时,可能引发资源双重释放、事件丢失或 PHP 崩溃。
关键修复原则:fork 前关闭 or fork 后重置
根本思路是避免多个进程共享同一 inotify 句柄。不能依赖“只在父进程监听”,因为子进程继承后仍可操作;也不能靠运气让子进程不读——它只要调用一次 inotify_read() 就可能干扰主流程。必须显式隔离:
-
方案一(推荐):fork 前销毁句柄 —— 若子进程无需监听,父进程应在
pcntl_fork()前主动fclose($inotify_fd),并在子进程中不再初始化 inotify -
方案二:fork 后子进程重建句柄 —— 父子进程各自独立调用
inotify_init(),子进程重新添加所需 watch。注意:子进程需自行管理其 inotify 生命周期,不可复用父进程句柄 -
方案三:设置 FD_CLOEXEC 标志(需扩展支持) —— 若使用较新 inotify 扩展(≥1.0.2)且内核支持,可在
inotify_init()后立即调用:fstat($fd, 'flags') & STREAM_META_NO_CHECK不适用;应改用:stream_set_option($fd, STREAM_OPTION_BLOCKING, 0);配合stream_set_option($fd, STREAM_OPTION_WRITE_BUFFER, 0);并手动设置 close-on-exec:posix_setfd($fd, POSIX_FD_CLOEXEC);(需启用 posix 扩展)
配置文件监听场景下的典型修复结构
假设你用 inotify 监控 /etc/myapp/config.php,并在守护进程中 fork 出 worker 子进程处理业务:
- 主进程(master)负责监听,收到
IN_MODIFY后 reload 配置,不 fork 之前不创建 inotify 句柄 - 所有 inotify 初始化(
inotify_init())、watch 添加(inotify_add_watch())、事件循环(inotify_read())必须限定在 master 进程内 - fork 出的每个 worker 进程启动时,先检查并关闭任何可能继承的 inotify 句柄:
if (is_resource($inotify_fd) && get_resource_type($inotify_fd) === 'stream') { fclose($inotify_fd); $inotify_fd = null; } - worker 进程绝不调用
inotify_*()函数,避免意外触发
验证是否修复成功的方法
运行时确认无句柄泄漏和重复监听:
- 在 fork 前后分别执行:
echo "Inotify fd: ", (int)$inotify_fd;—— 子进程输出应为0或NULL(非有效资源) - 用系统命令观察 inotify 实例数:
ls -l /proc/$(pidof php)/fd/ | grep inotify,master 进程应有 1 个,worker 进程应为 0 个 - 修改被监听文件,仅 master 日志输出事件,worker 日志不应出现 inotify_read 调用或警告
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











