Inotify:目录创建的奇怪行为

Posted

技术标签:

【中文标题】Inotify:目录创建的奇怪行为【英文标题】:Inotify: Odd behavior with directory creations 【发布时间】:2017-12-05 07:31:41 【问题描述】:

我有一个 inotify/内核问题。我正在使用“inotify”Python 项目进行观察,但我的问题本质上仍然是关于核心 inotify 内核实现

Python inotify 项目处理递归 inotify 监视。它提供了一个很好的生成器,允许您循环事件。它通过识别目录创建事件并在产生事件之前自动添加这些监视来实现递归监视。

我注意到“mkdir -p”调用有一些奇怪的行为。虽然我可以快速、增量地创建单个目录并从事件循环中查看它们,但“mkdir -p”永远不会为子目录的子目录或在该子目录中创建的文件生成事件。

有人有什么想法吗?

作品:“mkdir aa && mkdir aa/bb && touch aa/bb/文件名”:

(_INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], '/tmp/tmpt3MlIQ', u'aa')
(_INOTIFY_EVENT(wd=2, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], u'/tmp/tmpt3MlIQ/aa', u'bb')
(_INOTIFY_EVENT(wd=3, mask=256, cookie=0, len=16), ['IN_CREATE'], u'/tmp/tmpt3MlIQ/aa/bb', u'filename')
(_INOTIFY_EVENT(wd=3, mask=32, cookie=0, len=16), ['IN_OPEN'], u'/tmp/tmpt3MlIQ/aa/bb', u'filename')
(_INOTIFY_EVENT(wd=3, mask=4, cookie=0, len=16), ['IN_ATTRIB'], u'/tmp/tmpt3MlIQ/aa/bb', u'filename')
(_INOTIFY_EVENT(wd=3, mask=8, cookie=0, len=16), ['IN_CLOSE_WRITE'], u'/tmp/tmpt3MlIQ/aa/bb', u'filename')

不起作用:“mkdir -p aa/bb && touch aa/bb/filename”:

(_INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], '/tmp/tmpuTSxYl', u'aa')
(_INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], '/tmp/tmpuTSxYl', u'aa')
(_INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], '/tmp/tmpuTSxYl', u'aa')

当然,我做了下一个我能想到的明显的、无脑的事情,并将“-p”标志添加到“mkdir aa && mkdir aa/bb”,只是为了确保没有任何“-p” - 特定的异常,但没有任何区别。

“mkdir -p”的 GNU 实现只是在路径中从分隔符迭代到分隔符。没有魔法。 os.makedirs(相同功能)的 Python 实现也只是拆分路径并枚举部分。然而,GNU 不工作,但 Python 工作。这似乎暗示了一种竞争条件,只是无论我如何操纵这些条件,结果都是相同的。我什至开始在我们正在执行的 epoll 上使用微不足道的超时来读取事件(阅读:如果原始超时值有任何延迟,则不再是工厂)。就好像内核中的inotify似乎完全错过了“mkdir -p”中的后续创建。

我确定我只是错过了一些东西。

作为参考,GNU 实现中涉及的调用:

    http://code.metager.de/source/xref/gnu/coreutils/src/mkdir.c

    http://code.metager.de/source/xref/gnu/octave/gnulib-hg/lib/mkdir-p.c#85

    http://code.metager.de/source/xref/gnu/octave/gnulib-hg/lib/mkancesdirs.c#67

请注意,我们从 GNU 的“coreutils”开始,显然进入 GNU Octave 以实现“mkdir -p”。这是 OpenGrok 提供的唯一参考。我无法解释这一点,而且我处于陌生的领域。

Python 的实现:

https://github.com/python/cpython/blob/master/Lib/os.py#L196

我是否忽略了 inotify 行为的一些细节?

【问题讨论】:

【参考方案1】:

你有一个非常有趣的收获!

不,本机内核 inotify 库完全按照 documentation 所说的那样做。 GNU mkdir -p 也完全没问题。

我注意到“mkdir -p”调用有一些奇怪的行为。而我可以 快速、增量地创建单个目录并从 事件循环“mkdir -p”从不为子目录生成事件 子目录或在该子目录中创建的文件。

您必须尝试使用​​其他 inotify 实现来断言 PyInotify 的递归监视的可信度。仅仅它是一个流行的 Python 实现这一事实并不能赢得我的信任!

假设您没有弄乱您为参考而生成的示例 Python 输出,我做出以下声明。 PyInotify 的实现有点松懈,我想说,不太擅长它的功能

对于我们这里的推测,让我们拆分流程并从创建目录开始,然后再考虑创建文件。

您是否注意到,即使在您的第一个场景中,IN_CREATE 用于目录 aa

(_INOTIFY_EVENT(wd=1, mask=1073742080, cookie=0, len=16), ['IN_ISDIR', 'IN_CREATE'], '/tmp/tmpt3MlIQ', u'aa')

没有相应的 IN_OPENIN_CLOSE_NOWRITE 事件,尽管操作只是 mkdir?

(_INOTIFY_EVENT(wd=1, mask=1073741856, cookie=0, len=16), ['IN_ISDIR', 'IN_OPEN'], '/tmp/tmpuTSxYl', u'aa')
(_INOTIFY_EVENT(wd=1, mask=1073741840, cookie=0, len=16), ['IN_ISDIR', 'IN_CLOSE_NOWRITE'], '/tmp/tmpuTSxYl', u'aa')

这显然有些可疑。绝对不一致。

我还没有看 PyInotify 的实现,我不打算浪费我的时间。但是,我已经与本地 inotify 界面密切合作,以保证其准确性!它从未错过报告单个事件,也就是说,如果它发生了。

现在让我们进入文件创建部分,这似乎是您最关心的问题 - mkdir -p 从不为子目录的子目录或在该子目录中创建的文件生成事件. 并非总是如此;取决于实施情况有多差。

我已经用a better inotify implementation 重现了您执行的同一组操作。是的,只是为了证明我的主张。

注意到比 PyInotify 的报告更准确的事件流了吗?

Reproduction:

案例1: mkdir aa && mkdir aa/bb && touch aa/bb/filename

root@six-k:/opt/test# ls -la
total 8
drwxr-xr-x  2 root root 4096 Mar 18 13:55 .
drwxr-xr-x 20 root root 4096 Mar 18 13:53 ..
root@six-k:/opt/test# fluffyctl -w ./
root@six-k:/opt/test# mkdir aa && mkdir aa/bb && touch aa/bb/filename

捕获的事件:

root@six-k:/home/lab/fluffy# fluffy
event:  CREATE, ISDIR, 
path:   /opt/test/aa

event:  ACCESS, ISDIR, 
path:   /opt/test/aa

event:  ACCESS, ISDIR, 
path:   /opt/test/aa

event:  CLOSE_NOWRITE, ISDIR, 
path:   /opt/test/aa

event:  CREATE, ISDIR, 
path:   /opt/test/aa/bb

event:  ACCESS, ISDIR, 
path:   /opt/test/aa/bb

event:  ACCESS, ISDIR, 
path:   /opt/test/aa/bb

event:  CLOSE_NOWRITE, ISDIR, 
path:   /opt/test/aa/bb

event:  CREATE, 
path:   /opt/test/aa/bb/filename

event:  OPEN, 
path:   /opt/test/aa/bb/filename

event:  ATTRIB, 
path:   /opt/test/aa/bb/filename

event:  CLOSE_WRITE, 
path:   /opt/test/aa/bb/filename

案例 2: mkdir -p aa/bb && touch aa/bb/filename

root@six-k:/opt/test# cd ../
root@six-k:/opt# mkdir test2
root@six-k:/opt# cd test2/
root@six-k:/opt/test2# fluffyctl -w ./
root@six-k:/opt/test2# mkdir -p aa/bb && touch aa/bb/filename
root@six-k:/opt/test2#

捕获的事件:

root@six-k:/home/lab/fluffy# fluffy
event:  CREATE, ISDIR, 
path:   /opt/test2/aa

event:  ACCESS, ISDIR, 
path:   /opt/test2/aa

event:  ACCESS, ISDIR, 
path:   /opt/test2/aa/bb

event:  ACCESS, ISDIR, 
path:   /opt/test2/aa/bb

event:  CLOSE_NOWRITE, ISDIR, 
path:   /opt/test2/aa/bb

event:  ACCESS, ISDIR, 
path:   /opt/test2/aa

event:  CLOSE_NOWRITE, ISDIR, 
path:   /opt/test2/aa

event:  CREATE, 
path:   /opt/test2/aa/bb/filename

event:  OPEN, 
path:   /opt/test2/aa/bb/filename

event:  ATTRIB, 
path:   /opt/test2/aa/bb/filename

event:  CLOSE_WRITE, 
path:   /opt/test2/aa/bb/filename

到了,子目录上的事件和其中的文件。


答案越来越长了!

Neverthless, no recursive 构建在原生 inotify 库之上的实现可以保证所有事件。 这是不可行的!如果是这样,那would have been rather simple 为编写 inotify 的内核人员在本机引入了递归手表。

陷阱:

请注意,create 事件与我的复制 sn-p 有确实不同吗?第二种情况下的子目录没有报告(mkdir -p)。 为什么?虽然一切都发生得很快,但递归设置还不够快。当 dir aa 上的第一个 create 事件被捕获时,mkdir -p aa/bb 也完成了创建 dir bb 的工作。因此,对于 dir bb,没有 create 事件。再次提醒,提醒,这不是原生 inotify 库的错;这是因为我们还没有在 dir aa 上设置手表事件,我们到底要如何接收它的事件?

我希望一切都解决了!

等一下,如果 dir bb 的创建事件甚至没有被捕获,fluffy 似乎已经设置了监视,并且报告了 dir 'bb` 上的后续事件?

很好,你正在关注!你猜对了,但不完全是。 fluffy 收到 dir aa 的第一个 create 事件。当它处理这个事件时,mkdir -p 完成了它的工作,所以没有目录bb 创建事件。对。但是,虽然fluffy 在目录aa 上设置了手表,但目录bb 已经存在。所以,毛茸茸的,把bb 拉到它的手表里,因为它确实是dir aa 的后代。其余的你已经知道了。由于它正在被监视,它会报告后续事件,其中包括文件创建。


如果您(任何阅读本文的人)打算在 PyInotify GitHub 项目页面上提出有关此问题的票证/问题,请随时引用此答案或指向 fluffy。如果您需要更多信息,我很乐意提供。您也可以在 fluffy 的 GH 页面上打开一个问题以进行一般性讨论/建议/意见。 fluffy 可以使用您的帮助来改进它。

【讨论】:

【参考方案2】:

在我看来,捕捉所有您希望收到通知的事件是很棘手的。我的经验是 inotify 在 C 中。我确信 python 包为了方便添加了一些东西。所以,我在这里调整了我的答案,以便用相当笼统的术语来表达 inotify。

无法保证事情发生的顺序。创建的第一个目录将在父目录中触发通知。稍后,创建下一个目录,并为事件通知注册第一个目录。这些操作可能发生的顺序有两种。

如果 inotify 先更新,那么当第二个目录创建时你会收到通知。

当另一个订单发生时,通知似乎不太可能。但是,可能会打开目录并检查条目。对于每个目录,更新 inotify 以同时查看。

Inotify 是获取初始信号以进行工作的绝佳工具。但是,它确实有其局限性。尤其是在创建新目录时,您需要检查差距和竞争。

【讨论】:

以上是关于Inotify:目录创建的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Inotify 具有一定深度的监控子目录?

带有 NFS 的 inotify/工具

c++ inotify - 监视多个目录/子目录

Python:iNotify_Simple 从其他目录获取文件

inotify - 最好的方法是啥?

inotify_add_watch 失败,没有这样的文件或目录